diff --git a/.github/workflows/create-zip.yml b/.github/workflows/create-zip.yml index 22074b4a..51b98fec 100644 --- a/.github/workflows/create-zip.yml +++ b/.github/workflows/create-zip.yml @@ -14,8 +14,8 @@ jobs: fetch-depth: 0 - name: build run: | - composer install --no-dev --optimize-autoloader --classmap-authoritative - composer dump-autoload --no-dev --optimize --classmap-authoritative + composer install --no-dev --optimize-autoloader --classmap-authoritative --ignore-platform-reqs + composer dump-autoload --no-dev --optimize --classmap-authoritative --ignore-platform-reqs rm -rf .git .github tests vendor.zip .gitignore mkdir dpdbaltics rsync -Rr ./ ./dpdbaltics diff --git a/.github/workflows/index.yml b/.github/workflows/index.yml index 1b1a182f..206ea688 100644 --- a/.github/workflows/index.yml +++ b/.github/workflows/index.yml @@ -7,26 +7,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Cache vendor folder - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: vendor - key: php-${{ hashFiles('composer.lock') }} + key: php-${{ hashFiles('composer.json') }} - name: Cache composer folder - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.composer/cache key: php-composer-cache - - run: composer install + - run: composer install --ignore-platform-reqs - name: Run auto indexing run: php vendor/bin/autoindex - - uses: stefanzweifel/git-auto-commit-action@v4 + - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Adding auto indexes @@ -36,26 +36,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Cache vendor folder - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: vendor - key: php-${{ hashFiles('composer.lock') }} + key: php-${{ hashFiles('composer.json') }} - name: Cache composer folder - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.composer/cache key: php-composer-cache - - run: composer install + - run: composer install --ignore-platform-reqs - name: Adding licenses run: vendor/bin/header-stamp --license=assets/afl.txt --exclude=vendor,node_modules - - uses: stefanzweifel/git-auto-commit-action@v4 + - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Automatic license addition applying diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 222bc07d..03567f0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,8 @@ jobs: fetch-depth: 0 - name: build run: | - composer install --no-dev --optimize-autoloader --classmap-authoritative - composer dump-autoload --no-dev --optimize --classmap-authoritative + composer install --no-dev --optimize-autoloader --classmap-authoritative --ignore-platform-reqs + composer dump-autoload --no-dev --optimize --classmap-authoritative --ignore-platform-reqs rm -rf .git rm -rf .github rm -rf tests diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a49027..d64c9d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,4 +185,22 @@ ## [3.3.0] - Added PrestaShop 9 compatibility - Fixed issue with price rule "All" -- Fixed other minor issues \ No newline at end of file +- Fixed other minor issues +- Improved Logs section by collapsing request/response into a modal viewer to fix table overflow on long URLs + +## [3.3.1] +- Fixed sorting on Shipment list columns from joined tables (Tracking number, Receiver, Address, City) +- Changed default sort on Shipment list to show latest shipments first +- Fixed incorrect COD label amount when discount code is applied +- Fixed parcelshop info not displaying in backoffice order view when customer city differs from parcelshop city +- Fixed shipment creation overwriting parcelshop address data with customer address data +- Fixed Lithuanian and Latvian translations for shipping label in admin order panel +- Added Poland parcel shop import support with batch processing +- Fixed null parameter deprecation in AddressAdapter preg_replace calls +- Fixed shipment creation failing for accounts requiring Predict SMS service +- Fixed additional validation for supercheckout module using currentcontroller variable +- Fixed automatic PUDO point pre-selection in LIST mode +- Fixed PUDO shipment CSS styling +- Added timeframes to shipment request +- Fixed phone area code, city and street select width rendering as 0px when switching carriers in checkout +- Fixed DPDBaltics menu item disappearing from sidebar when navigating to module pages diff --git a/composer.lock b/composer.lock index 67c4085f..4bb346d7 100644 --- a/composer.lock +++ b/composer.lock @@ -120,6 +120,54 @@ }, "time": "2022-06-15T08:29:49+00:00" }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, { "name": "invertus/dpdbaltics-api", "version": "dev-developer", @@ -271,91 +319,32 @@ ], "time": "2022-06-09T08:53:42+00:00" }, - { - "name": "paragonie/random_compat", - "version": "v2.0.21", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/96c132c7f2f7bc3230723b66e89f8f150b29d5ae", - "reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2022-02-16T17:07:03+00:00" - }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -379,35 +368,42 @@ ], "support": { "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2017-09-11T18:02:19+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.3.2", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": [ @@ -428,43 +424,49 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/3.x" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x" }, - "time": "2017-11-10T14:09:06+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -477,37 +479,42 @@ "email": "me@mikevanriel.com" } ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/master" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2017-07-14T14:27:02+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpoption/phpoption", - "version": "1.7.5", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -522,11 +529,13 @@ "authors": [ { "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "Option Type for PHP", @@ -538,7 +547,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -550,28 +559,76 @@ "type": "tidelift" } ], - "time": "2020-07-20T17:29:33+00:00" + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" }, { "name": "prestashop/decimal", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/PrestaShop/decimal.git", - "reference": "188028580f4b551c126d1d723578f3ee88008e95" + "reference": "b5afdcc4b03140f838bb7b256aec6c21fd83951b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShop/decimal/zipball/188028580f4b551c126d1d723578f3ee88008e95", - "reference": "188028580f4b551c126d1d723578f3ee88008e95", + "url": "https://api.github.com/repos/PrestaShop/decimal/zipball/b5afdcc4b03140f838bb7b256aec6c21fd83951b", + "reference": "b5afdcc4b03140f838bb7b256aec6c21fd83951b", "shasum": "" }, "require": { - "php": ">=5.4" + "php": ">=7.2" }, "require-dev": { - "codacy/coverage": "dev-master", - "phpunit/phpunit": "4.*" + "phpstan/phpstan": "^0.12.99", + "phpunit/phpunit": "8.*", + "prestashop/php-dev-tools": "^4.1" }, "type": "library", "autoload": { @@ -587,10 +644,6 @@ { "name": "PrestaShop SA", "email": "contact@prestashop.com" - }, - { - "name": "Pablo Borowicz", - "email": "pablo.borowicz@prestashop.com" } ], "description": "Object-oriented wrapper/shim for BC Math PHP extension. Allows for arbitrary-precision math operations.", @@ -604,9 +657,9 @@ ], "support": { "issues": "https://github.com/PrestaShop/decimal/issues", - "source": "https://github.com/PrestaShop/decimal/tree/1.4.0" + "source": "https://github.com/PrestaShop/decimal/tree/1.5.0" }, - "time": "2020-10-08T07:14:07+00:00" + "time": "2022-02-02T09:04:37+00:00" }, { "name": "psr/log", @@ -744,27 +797,27 @@ }, { "name": "symfony/debug", - "version": "v3.4.47", + "version": "v4.4.44", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae" + "reference": "1a692492190773c5310bc7877cb590c04c2f05be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", + "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" + "php": ">=7.1.3", + "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" + "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "type": "library", "autoload": { @@ -789,10 +842,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "Provides tools to ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug/tree/v3.4.47" + "source": "https://github.com/symfony/debug/tree/v4.4.44" }, "funding": [ { @@ -809,33 +862,38 @@ } ], "abandoned": "symfony/error-handler", - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-28T16:29:46+00:00" }, { - "name": "symfony/inflector", - "version": "v3.4.47", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/inflector.git", - "reference": "b557c5d061b72cadf454dd87cd1308d0710c8021" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/inflector/zipball/b557c5d061b72cadf454dd87cd1308d0710c8021", - "reference": "b557c5d061b72cadf454dd87cd1308d0710c8021", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.1" }, "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Inflector\\": "" + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" }, - "exclude-from-classmap": [ - "/Tests/" + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -844,26 +902,18 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Inflector Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "keywords": [ - "inflection", - "pluralize", - "singularize", - "string", - "symfony", - "words" - ], "support": { - "source": "https://github.com/symfony/inflector/tree/v3.4.47" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -879,37 +929,36 @@ "type": "tidelift" } ], - "abandoned": "EnglishInflector from the String component", - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.19.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -943,7 +992,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -954,41 +1003,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.19.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b5f7b932ee6fa802fc792eabd77c4c88084517ce", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -996,7 +1046,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1013,17 +1063,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "grapheme", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -1034,39 +1085,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.19.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3fe414077251a81a1b15b1c709faf5c2fbae3d4e", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1074,7 +1128,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "classmap": [ "Resources/stubs" @@ -1094,16 +1148,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -1114,46 +1170,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/property-access", - "version": "v3.4.47", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "f1dc91d0c987f3ba95be1d7874527d11477b25ff" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/f1dc91d0c987f3ba95be1d7874527d11477b25ff", - "reference": "f1dc91d0c987f3ba95be1d7874527d11477b25ff", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/inflector": "~3.1|~4.0", - "symfony/polyfill-php70": "~1.0" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "symfony/cache": "~3.1|~4.0" + "provide": { + "ext-mbstring": "*" }, "suggest": { - "psr/cache-implementation": "To cache access methods." + "ext-mbstring": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1161,29 +1226,25 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony PropertyAccess Component", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", - "property", - "property path", - "reflection" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v3.4.47" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -1194,56 +1255,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "symfony/property-info", - "version": "v3.4.47", + "name": "symfony/polyfill-php80", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "a5f1e77c881342a5b1e05fdc12642650853bd112" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/a5f1e77c881342a5b1e05fdc12642650853bd112", - "reference": "a5f1e77c881342a5b1e05fdc12642650853bd112", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/inflector": "~3.1|~4.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", - "phpdocumentor/type-resolver": "<0.3.0", - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "doctrine/annotations": "~1.7", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/cache": "~3.1|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/serializer": "~2.8|~3.0|~4.0" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1252,26 +1307,28 @@ ], "authors": [ { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Property Info Component", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v3.4.47" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -1283,58 +1340,234 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "symfony/serializer", - "version": "v3.4.47", + "name": "symfony/property-access", + "version": "v6.4.25", "source": { "type": "git", - "url": "https://github.com/symfony/serializer.git", - "reference": "6d69ccc1dcfb64c1e9c9444588643e98718d1849" + "url": "https://github.com/symfony/property-access.git", + "reference": "fedc771326d4978a7d3167fa009a509b06a2e168" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/6d69ccc1dcfb64c1e9c9444588643e98718d1849", - "reference": "6d69ccc1dcfb64c1e9c9444588643e98718d1849", + "url": "https://api.github.com/repos/symfony/property-access/zipball/fedc771326d4978a7d3167fa009a509b06a2e168", + "reference": "fedc771326d4978a7d3167fa009a509b06a2e168", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/property-info": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "symfony/cache": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v6.4.25" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-12T15:42:57+00:00" + }, + { + "name": "symfony/property-info", + "version": "v5.4.48", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "a0396295ad585f95fccd690bc6a281e5bd303902" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/a0396295ad585f95fccd690bc6a281e5bd303902", + "reference": "a0396295ad585f95fccd690bc6a281e5bd303902", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "phpdocumentor/type-resolver": "<0.2.1", - "symfony/dependency-injection": "<3.2", - "symfony/property-access": ">=3.0,<3.0.4|>=2.8,<2.8.4", - "symfony/property-info": "<3.1", - "symfony/yaml": "<3.4" + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<4.4" }, "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/cache": "~3.1|~4.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.2|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/property-access": "~2.8|~3.0|~4.0", - "symfony/property-info": "^3.4.13|~4.0", - "symfony/yaml": "~3.4|~4.0" + "doctrine/annotations": "^1.10.4|^2", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" }, "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader and metadata cache.", - "psr/cache-implementation": "For using the metadata cache.", - "symfony/config": "For using the XML mapping loader.", - "symfony/http-foundation": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/property-info": "To deserialize relations.", - "symfony/yaml": "For using the default YAML mapping loader." + "phpdocumentor/reflection-docblock": "To use the PHPDoc", + "psr/cache-implementation": "To cache results", + "symfony/doctrine-bridge": "To use Doctrine metadata", + "symfony/serializer": "To use Serializer metadata" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v5.4.48" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T16:14:41+00:00" + }, + { + "name": "symfony/serializer", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a", + "reference": "66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1359,10 +1592,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Serializer Component", + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v3.4.47" + "source": "https://github.com/symfony/serializer/tree/v8.0.3" }, "funding": [ { @@ -1373,12 +1606,105 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2025-12-23T14:52:06+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-08T20:44:54+00:00" }, { "name": "vlucas/phpdotenv", @@ -1459,30 +1785,35 @@ }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" }, - "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -1506,9 +1837,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2020-07-08T17:02:28+00:00" + "time": "2025-10-29T15:56:20+00:00" } ], "packages-dev": [ @@ -1585,24 +1916,24 @@ }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -1646,7 +1977,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -1656,13 +1987,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", @@ -1732,32 +2059,36 @@ }, { "name": "doctrine/annotations", - "version": "v1.4.0", + "version": "1.14.4", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/253dca476f70808a5aeed3a47cc2cc88c5cab915", + "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "~1.4.10 || ^1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" }, + "type": "library", "autoload": { "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" @@ -1768,6 +2099,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1776,10 +2111,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1790,7 +2121,7 @@ } ], "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", "keywords": [ "annotations", "docblock", @@ -1798,40 +2129,39 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/v1.4.0" + "source": "https://github.com/doctrine/annotations/tree/1.14.4" }, - "time": "2017-02-24T16:22:25+00:00" + "abandoned": true, + "time": "2024-09-05T10:15:52+00:00" }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -1845,18 +2175,18 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.0.5" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -1872,37 +2202,37 @@ "type": "tidelift" } ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "doctrine/lexer", - "version": "1.0.2", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", "shasum": "" }, "require": { - "php": ">=5.3.2" + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.5" + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.21" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1910,14 +2240,14 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" @@ -1934,9 +2264,23 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.0.2" + "source": "https://github.com/doctrine/lexer/tree/2.1.1" }, - "time": "2019-06-08T11:03:04+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:35:39+00:00" }, { "name": "facebook/webdriver", @@ -2096,25 +2440,30 @@ }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { @@ -2139,9 +2488,15 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.x" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, - "time": "2017-10-19T19:58:43+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", @@ -2199,27 +2554,33 @@ "time": "2018-02-28T20:30:58+00:00" }, { - "name": "php-cs-fixer/diff", - "version": "v1.3.1", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", - "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -2231,132 +2592,177 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "name": "SpacePossum" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "sebastian/diff v2 backport support for PHP5.6", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } ], + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "abandoned": true, - "time": "2020-10-14T08:39:05+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.10.3", + "name": "php-cs-fixer/diff", + "version": "v1.3.1", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", + "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "php": "^5.6 || ^7.0 || ^8.0" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", + "symfony/process": "^3.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" - } - }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" }, { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "SpacePossum" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" + "diff" ], "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" }, - "time": "2020-03-05T15:02:03+00:00" + "abandoned": true, + "time": "2020-10-14T08:39:05+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.8", + "version": "7.0.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -2371,7 +2777,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -2383,33 +2789,41 @@ "xunit" ], "support": { - "irc": "irc://irc.freenode.net/phpunit", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" }, - "time": "2017-04-02T07:44:40+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:09:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -2424,7 +2838,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -2435,11 +2849,16 @@ "iterator" ], "support": { - "irc": "irc://irc.freenode.net/phpunit", "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" }, - "time": "2017-11-27T13:52:08+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:39:50+00:00" }, { "name": "phpunit/php-text-template", @@ -2488,28 +2907,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -2524,7 +2943,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -2535,35 +2954,41 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" }, - "time": "2017-02-26T11:10:40+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:42:41+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.12", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2588,58 +3013,61 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "abandoned": true, - "time": "2017-12-04T08:55:13+00:00" + "time": "2020-08-04T08:28:15+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.27", + "version": "8.5.52", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1015741814413c156abb0f53d7db7bbd03c6e858", + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.5.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "^1.4.3", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "^1.0.6|^2.0.1", - "symfony/yaml": "~2.1|~3.0|~4.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" - }, - "require-dev": { - "ext-pdo": "*" + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.2", + "phpunit/php-code-coverage": "^7.0.17", + "phpunit/php-file-iterator": "^2.0.6", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.4", + "sebastian/comparator": "^3.0.7", + "sebastian/diff": "^3.0.6", + "sebastian/environment": "^4.2.5", + "sebastian/exporter": "^3.1.8", + "sebastian/global-state": "^3.0.6", + "sebastian/object-enumerator": "^3.0.5", + "sebastian/resource-operations": "^2.0.3", + "sebastian/type": "^1.1.5", + "sebastian/version": "^2.0.1" }, "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" }, "bin": [ "phpunit" @@ -2647,7 +3075,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7.x-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -2675,74 +3103,32 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" - }, - "time": "2018-02-01T05:50:59+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.52" }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "support": { - "irc": "irc://irc.freenode.net/phpunit", - "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", - "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" - }, - "abandoned": true, - "time": "2017-06-30T09:13:00+00:00" + "time": "2026-01-27T05:20:18+00:00" }, { "name": "prestashop/autoindex", @@ -2841,24 +3227,21 @@ "time": "2020-12-09T16:40:38+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -2867,25 +3250,180 @@ } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -2893,34 +3431,34 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "3.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2933,6 +3471,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2944,14 +3486,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", @@ -2959,34 +3497,53 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.7" }, - "time": "2017-01-29T09:50:25+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:20:25+00:00" }, { "name": "sebastian/diff", - "version": "1.4.3", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2999,50 +3556,62 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" }, - "time": "2017-05-22T07:24:03+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:16:36+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "4.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "56932f6049a0482853056ffd617c91ffcc754205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3069,36 +3638,42 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/master" + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" }, - "time": "2016-11-26T07:53:53+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:49:59+00:00" }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "3.1.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64cfeaa341951ceb2019d7b98232399d57bb2296", + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": ">=7.2", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -3111,6 +3686,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -3119,17 +3698,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -3140,29 +3715,50 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/master" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.8" }, - "time": "2016-11-19T08:54:04+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T05:55:14+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/800689427e3e8cf57a8fe38fcd1d4344c9b2f046", + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -3170,7 +3766,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -3195,35 +3791,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.6" }, - "time": "2015-10-12T03:26:01+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T05:40:12+00:00" }, { "name": "sebastian/object-enumerator", - "version": "2.0.1", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -3245,34 +3860,95 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:54:02+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, - "time": "2017-02-18T15:18:39+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "2.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/8fe7e75986a9d24b4cceae847314035df7703a5a", + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -3285,14 +3961,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -3302,31 +3978,49 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.3" }, - "time": "2016-11-19T07:33:16+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T05:25:53+00:00" }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3347,10 +4041,71 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:59:09+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" }, - "time": "2015-07-28T20:34:47+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T14:04:07+00:00" }, { "name": "sebastian/version", @@ -3401,37 +4156,32 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -3439,63 +4189,167 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-10T16:43:36+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.4.47", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "31fde73757b6bad247c54597beef974919ec6860" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/31fde73757b6bad247c54597beef974919ec6860", - "reference": "31fde73757b6bad247c54597beef974919ec6860", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/debug": "~3.4|~4.4", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3503,18 +4357,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Generic abstractions related to dispatching event", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v3.4.47" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -3530,25 +4392,30 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/filesystem", - "version": "v3.4.47", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e58d7841cddfed6e846829040dca2cca0ebbbbb3", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -3573,10 +4440,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v3.4.47" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -3592,7 +4459,7 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-10-22T13:05:35+00:00" }, { "name": "symfony/finder", @@ -3657,20 +4524,23 @@ }, { "name": "symfony/options-resolver", - "version": "v3.4.47", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744" + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744", - "reference": "c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", + "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3695,7 +4565,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ "config", @@ -3703,7 +4573,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v3.4.47" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" }, "funding": [ { @@ -3719,33 +4589,163 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "metapackage", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + }, + "branch-alias": { + "dev-main": "1.20-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.19.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "beecef6b463b06954638f02378f52496cb84bacc" + "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/beecef6b463b06954638f02378f52496cb84bacc", - "reference": "beecef6b463b06954638f02378f52496cb84bacc", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", + "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2" }, - "type": "library", + "type": "metapackage", "extra": { - "branch-alias": { - "dev-main": "1.19-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3753,8 +4753,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3770,7 +4773,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -3779,7 +4782,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -3790,29 +4793,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v3.4.47", + "version": "v5.4.51", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca" + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b8648cf1d5af12a44a51d07ef9bf980921f15fca", - "reference": "b8648cf1d5af12a44a51d07ef9bf980921f15fca", + "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3837,10 +4845,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v3.4.47" + "source": "https://github.com/symfony/process/tree/v5.4.51" }, "funding": [ { @@ -3851,37 +4859,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2026-01-26T15:53:37+00:00" }, { - "name": "symfony/stopwatch", - "version": "v3.4.47", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/298b81faad4ce60e94466226b2abbb8c9bca7462", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" + "Symfony\\Contracts\\Service\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3890,18 +4916,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/stopwatch/tree/v3.4.47" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -3912,44 +4946,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { - "name": "symfony/yaml", - "version": "v3.4.47", + "name": "symfony/stopwatch", + "version": "v5.4.45", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "88289caa3c166321883f67fe5130188ebbb47094" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", - "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Yaml\\": "" + "Symfony\\Component\\Stopwatch\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3969,10 +4998,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v3.4.47" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" }, "funding": [ { @@ -3988,24 +5017,74 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": { + "facebook/webdriver": 20, "invertus/dpdbaltics-api": 20, - "invertus/psmoduletabs": 20, - "facebook/webdriver": 20 + "invertus/psmoduletabs": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.6" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "5.6" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.9.0" } diff --git a/config/command.yml b/config/command.yml index f8cd8e74..f6aa5600 100644 --- a/config/command.yml +++ b/config/command.yml @@ -3,9 +3,10 @@ services: public: true invertus.dpdbaltics.console_command.update_parcel_shops_command: - class : 'Invertus\dpdBaltics\ConsoleCommand\UpdateParcelShopsCommand' + class: 'Invertus\dpdBaltics\ConsoleCommand\UpdateParcelShopsCommand' arguments: - '@invertus.dpdbaltics.logger.logger' - '@invertus.dpdbaltics.service.import.api.parcel_shop_import' + - '@invertus.dpdbaltics.provider.zone_range_provider' tags: - - { name: 'console.command', command: 'dpdbaltics:update-parcel-shops'} + - { name: 'console.command', command: 'dpdbaltics:update-parcel-shops' } diff --git a/config/factory.yml b/config/factory.yml index 94132248..c47b2f30 100644 --- a/config/factory.yml +++ b/config/factory.yml @@ -20,6 +20,7 @@ services: - '@invertus.dpdbaltics.repository.order_repository' - '@invertus.dpdbaltics.repository.shipment_repository' - '@invertus.dpdbaltics.repository.pudo_repository' + - '@invertus.dpdbaltics.repository.order_delivery_time_repository' invertus.dpdbaltics.factory.shop_factory: class: 'Invertus\dpdBaltics\Factory\ShopFactory' diff --git a/config/requestFactory.yml b/config/requestFactory.yml index d9b50611..26e8d32b 100644 --- a/config/requestFactory.yml +++ b/config/requestFactory.yml @@ -30,7 +30,7 @@ services: invertus.dpdbaltics_api.factory.apirequest.parcel_shop_search_factory: - class: 'Invertus\dpdBalticsApi\Factory\APIRequest\ParcelShopSearchFactory' + class: 'Invertus\dpdBaltics\Factory\APIRequest\ExtendedParcelShopSearchFactory' arguments: - '@invertus.dpdbaltics.logger.logger' - '@invertus.dpdbaltics.factory.apiparams_factory' diff --git a/config/service.yml b/config/service.yml index 1271387d..7cfbbfea 100644 --- a/config/service.yml +++ b/config/service.yml @@ -223,7 +223,7 @@ services: class: 'Invertus\dpdBaltics\Service\Parcel\ParcelUpdateService' arguments: - '@invertus.dpdbaltics.repository.parcel_shop_repository' - + - '@invertus.dpdbaltics.logger.logger' invertus.dpdbaltics.service.parcel.parcel_shop_service: class: 'Invertus\dpdBaltics\Service\Parcel\ParcelShopService' arguments: @@ -249,8 +249,9 @@ services: - '@invertus.dpdbaltics.service.api.parcel_shop_search_api_service' - '@invertus.dpdbaltics.service.parcel.parcel_update_service' - '@dpdbaltics' + - '@invertus.dpdbaltics.logger.logger' - # AccessibilityChecker + # AccessibilityChecker invertus.dpdbaltics.grid.row.print_accessibility_checker: class: 'Invertus\dpdBaltics\Grid\Row\PrintAccessibilityChecker' arguments: @@ -283,6 +284,7 @@ services: arguments: - '@invertus.dpdbaltics.factory.parcel_tracking_url_factory' - '@dpdbaltics' + - '@invertus.dpdbaltics.logger.logger' invertus.dpdbaltics.service.label_printing_service: class: 'Invertus\dpdBaltics\Service\LabelPrintingService' diff --git a/config/services.yml b/config/services.yml new file mode 100644 index 00000000..ffec09f2 --- /dev/null +++ b/config/services.yml @@ -0,0 +1,10 @@ +services: + _defaults: + public: true + autowire: false + autoconfigure: false + + Invertus\dpdBaltics\ConsoleCommand\UpdateParcelShopsCommand: + class: 'Invertus\dpdBaltics\ConsoleCommand\UpdateParcelShopsCommand' + tags: + - { name: 'console.command' } diff --git a/controllers/admin/AdminDPDBalticsAjaxController.php b/controllers/admin/AdminDPDBalticsAjaxController.php index d7ffdaf1..af0eb23a 100644 --- a/controllers/admin/AdminDPDBalticsAjaxController.php +++ b/controllers/admin/AdminDPDBalticsAjaxController.php @@ -48,11 +48,72 @@ public function ajaxProcessImportZones() public function ajaxProcessImportParcels() { + $countryId = Tools::getValue('countryId'); + $countryIso = Country::getIsoById($countryId); + + // Validate country + if (empty($countryIso)) { + $this->ajaxDie(json_encode([ + 'success' => false, + 'error' => $this->module->l('Invalid country selected', 'AdminDPDBalticsAjaxController') + ])); + return; + } + + $countryIso = strtoupper($countryIso); + /** @var ParcelShopImport $parcelShopImport */ $parcelShopImport = $this->module->getModuleContainer('invertus.dpdbaltics.service.import.api.parcel_shop_import'); - $countryId = Tools::getValue('countryId'); - $countryIso = Country::getIsoById($countryId); - $this->ajaxDie(json_encode($parcelShopImport->importParcelShops($countryIso))); + try { + $result = $parcelShopImport->importParcelShops($countryIso); + $this->ajaxDie(json_encode($result)); + } catch (\Exception $e) { + $this->handleImportError($e, $countryIso); + } catch (\Error $e) { + $this->handleImportError($e, $countryIso); + } } + + /** + * Handle import errors with helpful messages for timeout scenarios. + * + * @param \Exception|\Error $e + * @param string $countryIso + */ + private function handleImportError($e, $countryIso) + { + $errorMsg = $e->getMessage(); + $isTimeout = stripos($errorMsg, 'timeout') !== false + || stripos($errorMsg, 'execution time') !== false + || stripos($errorMsg, 'Maximum execution') !== false; + + if ($isTimeout) { + $this->ajaxDie(json_encode($this->buildCronRequiredResponse($countryIso))); + } else { + $this->ajaxDie(json_encode([ + 'success' => false, + 'error' => $e instanceof \Error + ? 'PHP Error: ' . $errorMsg + : 'Error: ' . $errorMsg + ])); + } + } + + /** + * Build response for when cron is required (timeout or large country). + * + * @param string $countryIso + * @return array + */ + private function buildCronRequiredResponse($countryIso) + { + return [ + 'success' => false, + 'error' => $this->module->l('This country requires automatic updates.', 'AdminDPDBalticsAjaxController'), + 'requires_cron' => true, + 'cron_command' => 'php bin/console dpdbaltics:update-parcel-shops --country=' . $countryIso + ]; + } + } diff --git a/controllers/admin/AdminDPDBalticsAjaxShipmentsController.php b/controllers/admin/AdminDPDBalticsAjaxShipmentsController.php index fee24c34..a2377534 100644 --- a/controllers/admin/AdminDPDBalticsAjaxShipmentsController.php +++ b/controllers/admin/AdminDPDBalticsAjaxShipmentsController.php @@ -105,6 +105,14 @@ public function postProcess() $carrierId = (int) Tools::getValue('carrier_id'); $carrier = new Carrier($carrierId); } + + $countryCode = null; + if (Validate::isLoadedObject($order)) { + /** @var \Invertus\dpdBaltics\Provider\CurrentCountryProvider $currentCountryProvider */ + $currentCountryProvider = $this->module->getModuleContainer('invertus.dpdbaltics.provider.current_country_provider'); + $countryCode = $currentCountryProvider->getCountryIsoCodeByAddress($order->id_address_delivery); + } + /** @var PudoService $pudoService */ $pudoService = $this->module->getModuleContainer('invertus.dpdbaltics.service.pudo_service'); try { @@ -112,7 +120,8 @@ public function postProcess() $pudoService->searchPudoServices( $cityName, $carrier->id_reference, - $cartId + $cartId, + $countryCode ) ); } catch (Exception $e) { diff --git a/controllers/admin/AdminDPDBalticsImportExportController.php b/controllers/admin/AdminDPDBalticsImportExportController.php index ba4239c3..265220d8 100644 --- a/controllers/admin/AdminDPDBalticsImportExportController.php +++ b/controllers/admin/AdminDPDBalticsImportExportController.php @@ -136,16 +136,7 @@ protected function initOptions() $this->module->getLocalPath() . 'views/templates/admin/partials/break.tpl' ); - $href = $this->context->link->getModuleLink( - $this->module->name, - 'CronJob', - [ - 'action' => 'updateParcelShops', - 'token' => Configuration::get(Config::DPDBALTICS_HASH_TOKEN) - ] - ); - $cronJobText = - $this->module->l('You can setup cronjob with: ' . $href); + $cronJobText = $this->module->l('For large countries (e.g. Poland), we recommend automatic daily updates via cron:') . ' php bin/console dpdbaltics:update-parcel-shops --all'; if (Shop::CONTEXT_GROUP == $shopContext) { $info = $this->module->l('Data will be imported to all group shops'); diff --git a/controllers/admin/AdminDPDBalticsLogsController.php b/controllers/admin/AdminDPDBalticsLogsController.php index 1924b2aa..e289518f 100644 --- a/controllers/admin/AdminDPDBalticsLogsController.php +++ b/controllers/admin/AdminDPDBalticsLogsController.php @@ -18,8 +18,9 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use Invertus\dpdBaltics\Config\Config; use Invertus\dpdBaltics\Controller\AbstractAdminController; -use Invertus\dpdBaltics\Logger\Logger; +use Invertus\dpdBaltics\Infrastructure\Bootstrap\ModuleTabs; require_once dirname(__DIR__).'/../vendor/autoload.php'; @@ -29,6 +30,11 @@ class AdminDPDBalticsLogsController extends AbstractAdminController { + const FILE_NAME = 'AdminDPDBalticsLogsController'; + + const LOG_INFORMATION_TYPE_REQUEST = 'request'; + const LOG_INFORMATION_TYPE_RESPONSE = 'response'; + public function __construct() { $this->className = 'DPDProduct'; @@ -46,37 +52,280 @@ public function initToolbar() unset($this->toolbar_btn['new']); } + public function setMedia($isNewTheme = false) + { + parent::setMedia($isNewTheme); + + Media::addJsDef([ + 'dpdbaltics' => [ + 'logsUrl' => $this->context->link->getAdminLink(ModuleTabs::ADMIN_LOGS_CONTROLLER), + ], + ]); + + $this->addCSS($this->module->getPathUri() . 'views/css/admin/logs_tab.css'); + $this->addJS($this->module->getPathUri() . 'views/js/admin/log.js'); + } + + public function printRequestButton($request, $data) + { + return $this->getDisplayButton($data['id_dpd_log'], $request, self::LOG_INFORMATION_TYPE_REQUEST); + } + + public function printResponseButton($response, $data) + { + return $this->getDisplayButton($data['id_dpd_log'], $response, self::LOG_INFORMATION_TYPE_RESPONSE); + } + + public function printSeverity($severity, $data) + { + $level = strtolower((string) $severity); + $levelMap = [ + 'emergency' => 1, 'alert' => 1, 'critical' => 1, 'error' => 1, + 'warning' => 2, + 'notice' => 3, 'info' => 3, + 'debug' => 4, + ]; + $num = isset($levelMap[$level]) ? $levelMap[$level] : null; + $cssClass = $num ? 'dpd-log-severity dpd-log-severity-' . $num : 'dpd-log-severity'; + $label = $level !== '' ? ucfirst($level) : '--'; + + return sprintf( + '%s', + htmlspecialchars($cssClass, ENT_QUOTES, 'UTF-8'), + $num ? sprintf('%d · %s', $num, htmlspecialchars($label, ENT_QUOTES, 'UTF-8')) : htmlspecialchars($label, ENT_QUOTES, 'UTF-8') + ); + } + + public function printMessage($message, $data) + { + if ($message === null || $message === '') { + return '--'; + } + $value = (string) $message; + $decoded = json_decode($value, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded) && isset($decoded['message'])) { + $value = (string) $decoded['message']; + } + $clean = trim(preg_replace('/\s+/', ' ', strip_tags($value))); + if (function_exists('mb_strlen') && mb_strlen($clean) > 90) { + $clean = mb_substr($clean, 0, 87) . '...'; + } + return htmlspecialchars($clean, ENT_QUOTES, 'UTF-8'); + } + + public function printContext($context, $data) + { + if ($context === null || $context === '') { + return '--'; + } + $endpoint = ''; + $decoded = json_decode((string) $context, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded) && !empty($decoded['endpoint'])) { + $endpoint = (string) $decoded['endpoint']; + } else { + $endpoint = (string) $context; + } + $endpoint = strtok($endpoint, '?'); + $endpoint = $endpoint !== false ? basename($endpoint) : ''; + if ($endpoint === '') { + return '--'; + } + return htmlspecialchars($endpoint, ENT_QUOTES, 'UTF-8'); + } + + public function processExport($textDelimiter = '"') + { + if (ob_get_level() && ob_get_length() > 0) { + ob_clean(); + } + + $fileName = sprintf('dpdbaltics_logs_%s.csv', date('Y-m-d_His')); + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=' . $fileName); + header('Cache-Control: no-store, no-cache'); + + $fd = fopen('php://output', 'wb'); + + $storeInfo = [ + 'PrestaShop Version' => _PS_VERSION_, + 'PHP Version' => phpversion(), + 'Module Version' => $this->module->version, + 'MySQL Version' => Db::getInstance()->getVersion(), + 'Shop URL' => $this->context->shop ? $this->context->shop->getBaseURL(true) : '', + 'Shop Name' => Configuration::get('PS_SHOP_NAME'), + ]; + + $moduleConfigurations = [ + 'Test mode' => Configuration::get(Config::SHIPMENT_TEST_MODE) ? 'Yes' : 'No', + 'API country' => Configuration::get(Config::WEB_SERVICE_COUNTRY), + 'Track logs' => Configuration::get(Config::TRACK_LOGS) ? 'Yes' : 'No', + ]; + + $psSettings = [ + 'Default country' => Configuration::get('PS_COUNTRY_DEFAULT'), + 'Default currency' => Configuration::get('PS_CURRENCY_DEFAULT'), + 'Default language' => Configuration::get('PS_LANG_DEFAULT'), + 'Round mode' => Configuration::get('PS_PRICE_ROUND_MODE'), + 'Round type' => Configuration::get('PS_ROUND_TYPE'), + 'PHP memory limit' => ini_get('memory_limit'), + ]; + + fputcsv($fd, array_keys($storeInfo), ';', $textDelimiter); + fputcsv($fd, array_values($storeInfo), ';', $textDelimiter); + fputcsv($fd, [], ';', $textDelimiter); + + $moduleConfigInfo = "**Module configurations:**\n"; + foreach ($moduleConfigurations as $key => $value) { + $moduleConfigInfo .= '- ' . $key . ': ' . $value . "\n"; + } + + $psSettingsInfo = "**Prestashop settings:**\n"; + foreach ($psSettings as $key => $value) { + $psSettingsInfo .= '- ' . $key . ': ' . $value . "\n"; + } + + fputcsv($fd, [$moduleConfigInfo], ';', $textDelimiter); + fputcsv($fd, [$psSettingsInfo], ';', $textDelimiter); + fputcsv($fd, [], ';', $textDelimiter); + + fputcsv($fd, [ + $this->module->l('ID', self::FILE_NAME), + $this->module->l('Severity', self::FILE_NAME), + $this->module->l('Message', self::FILE_NAME), + $this->module->l('Request', self::FILE_NAME), + $this->module->l('Response', self::FILE_NAME), + $this->module->l('Context', self::FILE_NAME), + $this->module->l('Date', self::FILE_NAME), + ], ';', $textDelimiter); + + $rows = Db::getInstance()->executeS( + 'SELECT id_dpd_log, request, response, status, date_add FROM `' . _DB_PREFIX_ . pSQL($this->table) . '` ORDER BY id_dpd_log ASC' + ); + if ($rows === false) { + $rows = []; + } + + foreach ($rows as $row) { + $message = (string) ($row['response'] ?? ''); + $decodedRes = $message !== '' ? json_decode($message, true) : null; + if (json_last_error() === JSON_ERROR_NONE && is_array($decodedRes) && isset($decodedRes['message'])) { + $message = (string) $decodedRes['message']; + } + + $endpoint = ''; + $decodedReq = isset($row['request']) ? json_decode((string) $row['request'], true) : null; + if (json_last_error() === JSON_ERROR_NONE && is_array($decodedReq) && !empty($decodedReq['endpoint'])) { + $endpoint = basename(strtok((string) $decodedReq['endpoint'], '?')); + } + + fputcsv($fd, [ + $row['id_dpd_log'], + $row['status'], + $message, + $row['request'], + $row['response'], + $endpoint, + $row['date_add'], + ], ';', $textDelimiter); + } + + fclose($fd); + exit; + } + + public function displayAjaxGetLog() + { + $logId = (int) Tools::getValue('log_id'); + $log = new DPDLog($logId); + + if (!Validate::isLoadedObject($log)) { + $this->ajaxDie(json_encode([ + 'error' => true, + 'message' => $this->module->l('No log information found.', self::FILE_NAME), + ])); + } + + $this->ajaxDie(json_encode([ + 'error' => false, + 'log' => [ + self::LOG_INFORMATION_TYPE_REQUEST => $log->request, + self::LOG_INFORMATION_TYPE_RESPONSE => $log->response, + ], + ])); + } private function initList() { $this->list_no_link = true; + $this->_select = 'a.status AS severity, a.response AS message, a.request AS context'; $this->fields_list = [ 'id_dpd_log' => [ - 'title' => $this->module->l('ID'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('ID', self::FILE_NAME), + 'align' => 'text-center', + 'class' => 'fixed-width-xs', + 'havingFilter' => true, + ], + 'severity' => [ + 'title' => $this->module->l('Severity (1-4)', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printSeverity', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, + ], + 'message' => [ + 'title' => $this->module->l('Message', self::FILE_NAME), + 'callback' => 'printMessage', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, ], 'request' => [ - 'title' => $this->module->l('request'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('Request', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printRequestButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, ], 'response' => [ - 'title' => $this->module->l('response'), - 'type' => 'text', - 'havingFilter' => true + 'title' => $this->module->l('Response', self::FILE_NAME), + 'align' => 'text-center', + 'callback' => 'printResponseButton', + 'orderby' => false, + 'search' => false, + 'remove_onclick' => true, ], - 'status' => [ - 'title' => $this->module->l('status'), - 'type' => 'text', - 'havingFilter' => true + 'context' => [ + 'title' => $this->module->l('Context', self::FILE_NAME), + 'callback' => 'printContext', + 'search' => false, + 'orderby' => false, + 'remove_onclick' => true, ], 'date_add' => [ - 'title' => $this->module->l('Created date'), + 'title' => $this->module->l('Date', self::FILE_NAME), + 'align' => 'right', 'type' => 'datetime', - 'havingFilter' => true - ] + 'havingFilter' => true, + ], ]; } + + private function getDisplayButton($logId, $data, $logInformationType) + { + if (empty($data)) { + return '--'; + } + + $this->context->smarty->assign([ + 'log_id' => $logId, + 'log_information_type' => $logInformationType, + ]); + + return $this->context->smarty->fetch( + $this->module->getLocalPath() . 'views/templates/admin/logs/log_modal.tpl' + ); + } } diff --git a/controllers/admin/AdminDPDBalticsShipmentController.php b/controllers/admin/AdminDPDBalticsShipmentController.php index 1aafef8b..bd75499e 100644 --- a/controllers/admin/AdminDPDBalticsShipmentController.php +++ b/controllers/admin/AdminDPDBalticsShipmentController.php @@ -49,12 +49,15 @@ public function initToolbar() private function initList() { $this->list_no_link = true; + $this->_defaultOrderBy = 'id_dpd_shipment'; + $this->_defaultOrderWay = 'DESC'; $this->fields_list = [ 'tracking_number' => [ 'title' => $this->module->l('Tracking number'), 'type' => 'text', - 'havingFilter' => true + 'havingFilter' => true, + 'order_key' => 'oc!tracking_number', ], 'date_print' => [ 'title' => $this->module->l('Printing date'), @@ -69,17 +72,20 @@ private function initList() 'receiver_name' => [ 'title' => $this->module->l('Receiver'), 'type' => 'text', - 'havingFilter' => true + 'havingFilter' => true, + 'order_key' => 'c!lastname', ], 'address1' => [ 'title' => $this->module->l('Address'), 'type' => 'text', - 'havingFilter' => true + 'havingFilter' => true, + 'order_key' => 'addr!address1', ], 'city' => [ 'title' => $this->module->l('City'), 'type' => 'text', - 'havingFilter' => true + 'havingFilter' => true, + 'order_key' => 'addr!city', ], 'reference1' => [ 'title' => $this->module->l('Cust Ref 1'), diff --git a/controllers/front/Ajax.php b/controllers/front/Ajax.php index a17c1c77..0a911b87 100644 --- a/controllers/front/Ajax.php +++ b/controllers/front/Ajax.php @@ -84,7 +84,7 @@ public function postProcess() try { $response = $this->searchPudoServices($countryCode, $city, $carrierId, $cartId); } catch (Exception $e) { - $this->messages[] = $this->module->l('Parcel shop search failed!'); + $this->messages[] = $this->module->l('Parcel shop search failed!', self::FILENAME); $this->ajaxDie(json_encode( [ 'status' => false, @@ -157,7 +157,7 @@ public function postProcess() try { $response = $this->searchPudoServices($countryCode, $city, $carrierId, $cartId, $street); } catch (Exception $e) { - $this->messages[] = $this->module->l('Parcel shop search failed!'); + $this->messages[] = $this->module->l('Parcel shop search failed!', self::FILENAME); $this->ajaxDie(json_encode( [ 'status' => false, @@ -250,7 +250,7 @@ private function savePudoPickupPoint($pudoId, $countryCode) ); if (!$addPudoCartOrderStatus) { - $this->messages[] = $this->l('Failed to save pickup point.'); + $this->messages[] = $this->l('Failed to save pickup point.', self::FILENAME); $this->ajaxDie(json_encode([ 'template' => $this->getMessageTemplate('danger'), 'status' => false @@ -375,24 +375,24 @@ private function saveParcelShop($countryCode, $city, $street) */ private function setErrorMessage($exception) { - switch ($exception->getMessage()) { + switch ($exception->getCode()) { case Config::ERROR_COULD_NOT_SAVE_PHONE_NUMBER: - $this->messages[] = $this->module->l('Could not save phone number'); + $this->messages[] = $this->module->l('Could not save phone number', self::FILENAME); break; case Config::ERROR_BAD_PHONE_NUMBER_PREFIX: - $this->messages[] = $this->module->l('Phone number prefix is empty'); + $this->messages[] = $this->module->l('Phone number prefix is empty', self::FILENAME); break; case Config::ERROR_PHONE_EMPTY: - $this->messages[] = $this->module->l('Phone number is empty'); + $this->messages[] = $this->module->l('Phone number is empty', self::FILENAME); break; case Config::ERROR_PHONE_HAS_INVALID_CHARACTERS: - $this->messages[] = $this->module->l('Phone number contains invalid characters'); + $this->messages[] = $this->module->l('Phone number contains invalid characters', self::FILENAME); break; case Config::ERROR_PHONE_HAS_INVALID_LENGTH: - $this->messages[] = $this->module->l('Phone number length is invalid'); + $this->messages[] = $this->module->l('Phone number length is invalid', self::FILENAME); break; case Config::ERROR_INVALID_PUDO_TERMINAL: - $this->messages[] = $this->module->l('Pudo point is missing, please select valid terminal point'); + $this->messages[] = $this->module->l('Pudo point is missing, please select valid terminal point', self::FILENAME); break; default: $this->messages[] = $exception->getMessage(); diff --git a/controllers/front/CronJob.php b/controllers/front/CronJob.php index 7525f309..e4439ce8 100644 --- a/controllers/front/CronJob.php +++ b/controllers/front/CronJob.php @@ -27,18 +27,33 @@ exit; } +/** + * @deprecated Use CLI command instead for reliable imports without timeout issues: + * php bin/console dpdbaltics:update-parcel-shops --all + * + * This HTTP-based cron endpoint may timeout for large countries (PL). + * The CLI command has no timeout limitations. + */ class DpdbalticsCronJobModuleFrontController extends AbstractFrontController { public function postProcess() { - set_time_limit(0); + // Note: We intentionally do NOT use set_time_limit() here because: + // 1. It doesn't work on many servers (disabled in php.ini) + // 2. wget/curl have their own timeout limits anyway + // For reliable imports, use CLI: php bin/console dpdbaltics:update-parcel-shops --all - $token = Tools::getValue('token'); - if ($token !== Configuration::get(Config::DPDBALTICS_HASH_TOKEN)) { - $this->ajaxDie([ + $token = (string) Tools::getValue('token'); + $expectedToken = Configuration::get(Config::DPDBALTICS_HASH_TOKEN); + + // Use hash_equals to prevent timing attacks + // Ensure both values are strings for PHP 8 compatibility + if (empty($expectedToken) || empty($token) || !hash_equals((string) $expectedToken, $token)) { + $this->ajaxDie(json_encode([ 'success' => false, - 'message' => 'wrong token' - ]); + 'message' => 'Invalid token' + ])); + return; } $action = Tools::getValue('action'); @@ -50,6 +65,8 @@ public function postProcess() $zoneRangeProvider = $this->module->getModuleContainer('invertus.dpdbaltics.provider.zone_range_provider'); $countriesInZoneRange = $zoneRangeProvider->getAllZoneRangesCountryIsoCodes(); + $response = ['success' => true, 'message' => 'No countries to import']; + if ($countriesInZoneRange) { foreach ($countriesInZoneRange as $country) { $response = $parcelShopImport->importParcelShops($country); @@ -70,7 +87,10 @@ public function postProcess() break; default: - return; + $this->ajaxDie(json_encode([ + 'success' => false, + 'message' => 'Unknown action. For parcel shop import, use CLI: php bin/console dpdbaltics:update-parcel-shops --all' + ])); } } } diff --git a/dpdbaltics.php b/dpdbaltics.php index c2bf51c3..39c72f17 100644 --- a/dpdbaltics.php +++ b/dpdbaltics.php @@ -85,7 +85,7 @@ public function __construct() $this->author = 'Invertus'; $this->tab = 'shipping_logistics'; $this->description = 'DPD Baltics shipping integration'; - $this->version = '3.3.0'; + $this->version = '3.3.1'; $this->ps_versions_compliancy = ['min' => '1.7.1.0', 'max' => _PS_VERSION_]; $this->need_instance = 0; parent::__construct(); @@ -214,6 +214,12 @@ public function hookActionFrontControllerSetMedia() 'isOnePageCheckout' => $opcModuleCompatibilityValidator->isOpcModuleInUse() ] ]); + } else { + Media::addJsDef([ + 'dpdbaltics' => [ + 'isOnePageCheckout' => false + ] + ]); } /** @var \Invertus\dpdBaltics\Provider\CurrentCountryProvider $currentCountryProvider */ @@ -956,12 +962,13 @@ private function displayInAdminOrderPage(array $params) /** @var null|\Invertus\dpdBalticsApi\Api\DTO\Object\ParcelShop $selectedPudoService */ $selectedPudoService = null; $hasParcelShops = false; - if ($parcelShops) { - if ($selectedPudo->pudo_id) { - $selectedPudoService = $parcelShopService->getParcelShopByShopId($selectedPudo->pudo_id)[0]; - } else { - $selectedPudoService = $parcelShops[0]; - } + if ($selectedPudo->pudo_id) { + $pudoShops = $parcelShopService->getParcelShopByShopId($selectedPudo->pudo_id); + $selectedPudoService = !empty($pudoShops) ? $pudoShops[0] : null; + } elseif ($parcelShops) { + $selectedPudoService = isset($parcelShops[0]) ? $parcelShops[0] : null; + } + if ($selectedPudoService || $parcelShops) { $hasParcelShops = true; } diff --git a/src/Adapter/AddressAdapter.php b/src/Adapter/AddressAdapter.php index c951fa24..91246911 100644 --- a/src/Adapter/AddressAdapter.php +++ b/src/Adapter/AddressAdapter.php @@ -88,7 +88,7 @@ private function getFormattedZipCode(Country $country, $postCode) { $countryCodePosition = $this->getCountryCodePosition($country); - $postCode = preg_replace("/[^a-zA-Z0-9]+/", "", $postCode); + $postCode = preg_replace("/[^a-zA-Z0-9]+/", "", $postCode ?? ''); // If C doesn't exist in zip code format - don't modify the zip code if (false === $countryCodePosition) { return $postCode; @@ -120,7 +120,7 @@ private function getFormattedZipCode(Country $country, $postCode) /** Changes zip code format from pudo service to the one used in prestashop as based on country and returns it*/ public function getFormattedZipCodePudoToPrestashop($iso, $zipCode) { - $zipCode = preg_replace("/[^a-zA-Z0-9]+/", "", $zipCode); + $zipCode = preg_replace("/[^a-zA-Z0-9]+/", "", $zipCode ?? ''); $country = new Country(Country::getByIso($iso)); $formattedZipCode = $zipCode; $isoAdded = false; @@ -153,6 +153,6 @@ public function formatPostCodeByCountry($postCode, $countryIsoCode) return str_replace(' ', '', $postCode); } - return preg_replace('/[^0-9]/', '', $postCode); + return preg_replace('/[^0-9]/', '', $postCode ?? ''); } } diff --git a/src/Config/Config.php b/src/Config/Config.php index da755019..67023e47 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -160,6 +160,8 @@ class Config const FETCH_PUDO_POINT = 1; const RETRIEVE_OPENING_HOURS = 1; + const SKIP_OPENING_HOURS = 0; + const COUNTRIES_SKIP_OPENING_HOURS = ['PL']; const MAXIMUM_PUDO_POINTS_IN_MAP = 30; diff --git a/src/ConsoleCommand/UpdateParcelShopsCommand.php b/src/ConsoleCommand/UpdateParcelShopsCommand.php new file mode 100644 index 00000000..5d3093ed --- /dev/null +++ b/src/ConsoleCommand/UpdateParcelShopsCommand.php @@ -0,0 +1,237 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\ConsoleCommand; + +use Country; +use Invertus\dpdBaltics\Provider\ZoneRangeProvider; +use Invertus\dpdBaltics\Service\Import\API\ParcelShopImport; +use Module; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * CLI command for importing parcel shops. + * + * Usage: + * php bin/console dpdbaltics:update-parcel-shops --country=PL + * php bin/console dpdbaltics:update-parcel-shops --all + * + * Cron setup (daily at 2 AM): + * 0 2 * * * cd /var/www/html && php bin/console dpdbaltics:update-parcel-shops --all + */ +class UpdateParcelShopsCommand extends Command +{ + protected static $defaultName = 'dpdbaltics:update-parcel-shops'; + + /** + * @var LoggerInterface|null + */ + private $logger; + + /** + * @var ParcelShopImport|null + */ + private $parcelShopImport; + + /** + * @var ZoneRangeProvider|null + */ + private $zoneRangeProvider; + + /** + * @var \DPDBaltics|null + */ + private $module; + + protected function configure() + { + $this + ->setDescription('Import/update DPD parcel shops from API') + ->addOption( + 'country', + 'c', + InputOption::VALUE_OPTIONAL, + 'Country ISO code to import (e.g., PL, LT, LV, EE)' + ) + ->addOption( + 'all', + 'a', + InputOption::VALUE_NONE, + 'Import all default countries (LT, LV, EE, PL)' + ) + ->setHelp(<<<'EOF' +The %command.name% command imports parcel shops from DPD API. + +Import a single country: + php %command.full_name% --country=PL + +Import all countries from configured zone ranges: + php %command.full_name% --all + +Cron setup (daily at 2 AM): + 0 2 * * * cd /var/www/html && php bin/console dpdbaltics:update-parcel-shops --all + +This command is recommended for importing parcel shops as it has no timeout limitations +unlike the web interface which may timeout for countries with many parcel shops. +EOF + ); + } + + /** + * Initialize services from the module's container. + */ + private function initServices() + { + if ($this->module !== null) { + return; + } + + $this->module = Module::getInstanceByName('dpdbaltics'); + + if (!$this->module) { + throw new \RuntimeException('DPD Baltics module is not installed or not active.'); + } + + $this->parcelShopImport = $this->module->getService('invertus.dpdbaltics.service.import.api.parcel_shop_import'); + $this->zoneRangeProvider = $this->module->getService('invertus.dpdbaltics.provider.zone_range_provider'); + $this->logger = $this->module->getService('invertus.dpdbaltics.logger.logger'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + // Initialize services from module's container + try { + $this->initServices(); + } catch (\Exception $e) { + $output->writeln(sprintf('%s', $e->getMessage())); + return 1; + } + + $country = $input->getOption('country'); + $all = $input->getOption('all'); + + if (!$country && !$all) { + $output->writeln('Please specify --country=XX or --all'); + $output->writeln(''); + $output->writeln('Examples:'); + $output->writeln(' php bin/console dpdbaltics:update-parcel-shops --country=PL'); + $output->writeln(' php bin/console dpdbaltics:update-parcel-shops --all'); + return 1; + } + + // Get countries to import + if ($all) { + $countries = $this->getCountriesToImport(); + if (empty($countries)) { + $output->writeln('No countries configured in zone ranges.'); + $output->writeln('Please configure zone ranges in DPD module settings first.'); + return 1; + } + } else { + $countries = [strtoupper($country)]; + } + $totalStartTime = microtime(true); + $hasError = false; + + $output->writeln(''); + $output->writeln('DPD Parcel Shop Import'); + $output->writeln(str_repeat('=', 50)); + $output->writeln(''); + + foreach ($countries as $countryCode) { + $output->write(sprintf('Importing %s... ', $countryCode)); + + $startTime = microtime(true); + + try { + $result = $this->parcelShopImport->importParcelShops($countryCode); + + $elapsed = round(microtime(true) - $startTime, 1); + + if (isset($result['success']) && $result['success']) { + $output->writeln(sprintf('OK (%ss)', $elapsed)); + if (isset($result['success_message'])) { + $output->writeln(sprintf(' %s', $result['success_message'])); + } + } else { + $hasError = true; + $output->writeln(sprintf('FAILED (%ss)', $elapsed)); + if (isset($result['error'])) { + $output->writeln(sprintf(' %s', $result['error'])); + } + } + } catch (\Exception $e) { + $hasError = true; + $elapsed = round(microtime(true) - $startTime, 1); + $output->writeln(sprintf('ERROR (%ss)', $elapsed)); + $output->writeln(sprintf(' %s', $e->getMessage())); + + if ($this->logger) { + $this->logger->error(sprintf( + '[CLI] Import failed for %s: %s', + $countryCode, + $e->getMessage() + )); + } + } + + $output->writeln(''); + } + + $totalElapsed = round(microtime(true) - $totalStartTime, 1); + $output->writeln(str_repeat('=', 50)); + $output->writeln(sprintf('Total time: %ss', $totalElapsed)); + $output->writeln(''); + + return $hasError ? 1 : 0; + } + + /** + * Get countries to import from zone range configuration. + * Falls back to active countries if no zone ranges configured. + * + * @return array + */ + private function getCountriesToImport() + { + // First try to get countries from zone ranges (same as CronJob.php) + $countries = $this->zoneRangeProvider->getAllZoneRangesCountryIsoCodes(); + + if (!empty($countries)) { + return $countries; + } + + // Fallback: get all active countries + $activeCountries = Country::getCountries((int) \Configuration::get('PS_LANG_DEFAULT'), true); + $countryCodes = []; + + foreach ($activeCountries as $country) { + if (!empty($country['iso_code'])) { + $countryCodes[] = strtoupper($country['iso_code']); + } + } + + return $countryCodes; + } +} diff --git a/src/ConsoleCommand/index.php b/src/ConsoleCommand/index.php new file mode 100644 index 00000000..3c82fdf6 --- /dev/null +++ b/src/ConsoleCommand/index.php @@ -0,0 +1,29 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Entity/index.php b/src/Entity/index.php index 58b8b473..74851d77 100644 --- a/src/Entity/index.php +++ b/src/Entity/index.php @@ -19,12 +19,12 @@ */ -header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); -header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); -header('Cache-Control: no-store, no-cache, must-revalidate'); -header('Cache-Control: post-check=0, pre-check=0', false); -header('Pragma: no-cache'); +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); -header('Location: ../'); +header("Location: ../"); exit; diff --git a/src/Factory/APIRequest/ExtendedApiClient.php b/src/Factory/APIRequest/ExtendedApiClient.php new file mode 100644 index 00000000..9a834fb9 --- /dev/null +++ b/src/Factory/APIRequest/ExtendedApiClient.php @@ -0,0 +1,60 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\Factory\APIRequest; + +use Invertus\dpdBalticsApi\Factory\APIRequest\ApiClient; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Extended API client with configurable timeout. + * Used to override the hardcoded 20-second timeout in the vendor package + * for large API requests like Poland parcel shop imports. + */ +class ExtendedApiClient extends ApiClient +{ + /** + * @var int + */ + private $timeout; + + /** + * @param string $url + * @param string $username + * @param string $password + * @param int $timeout Timeout in seconds (default 120 for large imports) + */ + public function __construct($url, $username, $password, $timeout = 120) + { + parent::__construct($url, $username, $password); + $this->timeout = $timeout; + } + + /** + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } +} diff --git a/src/Factory/APIRequest/ExtendedParcelShopSearchFactory.php b/src/Factory/APIRequest/ExtendedParcelShopSearchFactory.php new file mode 100644 index 00000000..1d9d2507 --- /dev/null +++ b/src/Factory/APIRequest/ExtendedParcelShopSearchFactory.php @@ -0,0 +1,85 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\Factory\APIRequest; + +use Invertus\dpdBalticsApi\Api\ApiRequest; +use Invertus\dpdBalticsApi\Factory\APIParamsFactoryInterface; +use Invertus\dpdBalticsApi\Factory\APIRequest\ParcelShopSearchFactory; +use Psr\Log\LoggerInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Extended factory for ParcelShopSearch that uses: + * 1. Longer API timeout (120s instead of 20s) + * 2. Fast response parser (bypasses slow Symfony serializer) + * + * This allows importing large countries like Poland (3000+ parcel shops) + * even on servers with 30-second PHP timeout limits. + */ +class ExtendedParcelShopSearchFactory extends ParcelShopSearchFactory +{ + const PARCEL_SHOP_SEARCH_TIMEOUT = 120; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var APIParamsFactoryInterface + */ + private $APIParamsFactory; + + public function __construct(LoggerInterface $logger, APIParamsFactoryInterface $APIParamsFactory) + { + parent::__construct($logger, $APIParamsFactory); + $this->logger = $logger; + $this->APIParamsFactory = $APIParamsFactory; + } + + /** + * Create FastParcelShopSearch with extended timeout and fast parser. + * + * @return FastParcelShopSearch + */ + public function makeParcelShopSearch() + { + $httpClientFactory = new ExtendedApiClient( + $this->APIParamsFactory->getUrl(), + $this->APIParamsFactory->getUsername(), + $this->APIParamsFactory->getPassword(), + self::PARCEL_SHOP_SEARCH_TIMEOUT + ); + + $apiRequest = new ApiRequest( + $httpClientFactory, + $this->logger, + $this->APIParamsFactory->getModuleVersion(), + $this->APIParamsFactory->getPSVersion() + ); + + // Use FastParcelShopSearch with simple JSON parser instead of slow Symfony serializer + return new FastParcelShopSearch($apiRequest, $this->logger); + } +} diff --git a/src/Factory/APIRequest/FastParcelShopResponseParser.php b/src/Factory/APIRequest/FastParcelShopResponseParser.php new file mode 100644 index 00000000..f3257682 --- /dev/null +++ b/src/Factory/APIRequest/FastParcelShopResponseParser.php @@ -0,0 +1,133 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\Factory\APIRequest; + +use Invertus\dpdBalticsApi\Api\DTO\Object\ParcelShop; +use Invertus\dpdBalticsApi\Api\DTO\Response\ParcelShopSearchResponse; +use stdClass; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Fast parser for ParcelShopSearchResponse that bypasses slow Symfony serializer. + * Uses simple property assignment instead of ReflectionExtractor. + * + * Performance: ~100x faster than Symfony serializer for large datasets. + */ +class FastParcelShopResponseParser +{ + /** + * Parse API response array into ParcelShopSearchResponse object. + * This is much faster than Symfony's ObjectNormalizer with ReflectionExtractor. + * + * @param array $responseData Raw response data from API + * @return ParcelShopSearchResponse + */ + public function parse(array $responseData) + { + $response = new ParcelShopSearchResponse(); + + if (isset($responseData['status'])) { + $response->setStatus($responseData['status']); + } + + if (isset($responseData['errlog'])) { + $response->setErrLog($responseData['errlog']); + } + + $parcelShops = []; + if (isset($responseData['parcelshops']) && is_array($responseData['parcelshops'])) { + foreach ($responseData['parcelshops'] as $shopData) { + $parcelShops[] = $this->parseParcelShop($shopData); + } + } + + $response->setParcelShops($parcelShops); + + return $response; + } + + /** + * Parse single parcel shop data into ParcelShop object. + * + * @param array|object $data + * @return ParcelShop + */ + private function parseParcelShop($data) + { + // Handle both array and object (stdClass) formats + $data = (array) $data; + + $shop = new ParcelShop(); + + // PHP 5.6 compatible - use isset() instead of ?? operator + $shop->setParcelShopId(isset($data['parcelshop_id']) ? $data['parcelshop_id'] : null); + $shop->setCompany(isset($data['company']) ? $data['company'] : null); + $shop->setCountry(isset($data['country']) ? $data['country'] : null); + $shop->setCity(isset($data['city']) ? $data['city'] : null); + $shop->setPCode(isset($data['pcode']) ? $data['pcode'] : null); + $shop->setStreet(isset($data['street']) ? $data['street'] : null); + $shop->setEmail(isset($data['email']) ? $data['email'] : null); + $shop->setPhone(isset($data['phone']) ? $data['phone'] : null); + $shop->setDistance(isset($data['distance']) ? $data['distance'] : null); + $shop->setLongitude(isset($data['longitude']) ? $data['longitude'] : null); + $shop->setLatitude(isset($data['latitude']) ? $data['latitude'] : null); + $shop->setCoordinateX(isset($data['coordinateX']) ? $data['coordinateX'] : null); + $shop->setCoordinateY(isset($data['coordinateY']) ? $data['coordinateY'] : null); + $shop->setCoordinateZ(isset($data['coordinateZ']) ? $data['coordinateZ'] : null); + + $openingHours = array(); + if (isset($data['openingHours']) && is_array($data['openingHours'])) { + foreach ($data['openingHours'] as $hoursData) { + $openingHours[] = $this->parseOpeningHours($hoursData); + } + } + $shop->setOpeningHours($openingHours); + + return $shop; + } + + /** + * Parse opening hours data into stdClass object. + * Using stdClass because existing code accesses properties directly (e.g., $item->weekday) + * + * @param array|object $data + * @return stdClass + */ + private function parseOpeningHours($data) + { + // Handle both array and object (stdClass) formats + $data = (array) $data; + + // Create stdClass to match expected property access pattern + // PHP 5.6 compatible - use isset() instead of ?? operator + $hours = new stdClass(); + $hours->weekday = isset($data['weekday']) ? $data['weekday'] : null; + $hours->openMorning = isset($data['openMorning']) ? $data['openMorning'] : null; + $hours->closeMorning = isset($data['closeMorning']) ? $data['closeMorning'] : null; + $hours->openAfternoon = isset($data['openAfternoon']) ? $data['openAfternoon'] : null; + $hours->closeAfternoon = isset($data['closeAfternoon']) ? $data['closeAfternoon'] : null; + + return $hours; + } +} diff --git a/src/Factory/APIRequest/FastParcelShopSearch.php b/src/Factory/APIRequest/FastParcelShopSearch.php new file mode 100644 index 00000000..c05fc797 --- /dev/null +++ b/src/Factory/APIRequest/FastParcelShopSearch.php @@ -0,0 +1,123 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\Factory\APIRequest; + +use Exception; +use Invertus\dpdBalticsApi\Api\ApiRequest; +use Invertus\dpdBalticsApi\Api\DTO\Request\ParcelShopSearchRequest; +use Invertus\dpdBalticsApi\Api\DTO\Response\ParcelShopSearchResponse; +use Invertus\dpdBalticsApi\ApiConfig\ApiConfig; +use Invertus\dpdBalticsApi\Exception\DPDBalticsAPIException; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Fast ParcelShopSearch that uses simple JSON parsing instead of slow Symfony serializer. + * This can handle large responses (3000+ parcel shops) without timeout. + */ +class FastParcelShopSearch +{ + /** + * @var ApiRequest + */ + private $apiRequest; + + /** + * @var FastParcelShopResponseParser + */ + private $parser; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ApiRequest $apiRequest + * @param LoggerInterface|null $logger + */ + public function __construct(ApiRequest $apiRequest, $logger = null) + { + $this->apiRequest = $apiRequest; + $this->parser = new FastParcelShopResponseParser(); + $this->logger = $logger instanceof LoggerInterface ? $logger : new NullLogger(); + } + + /** + * Search for parcel shops using fast parsing. + * + * @param ParcelShopSearchRequest $request + * @return ParcelShopSearchResponse + * @throws DPDBalticsAPIException + */ + public function parcelShopSearch(ParcelShopSearchRequest $request) + { + try { + $response = $this->apiRequest->post( + ApiConfig::SQ_PARCEL_SHOP_SEARCH, + [ + 'query' => $request->jsonSerialize(), + 'verify' => false, + ] + ); + } catch (Exception $e) { + $this->logger->error(sprintf( + '[FastParcelShopSearch] API call FAILED | Error: %s', + $e->getMessage() + )); + + throw new DPDBalticsAPIException( + 'An error occurred trying to search for parcel shops: ' . $e->getMessage(), + DPDBalticsAPIException::PARCEL_SHOP_SEARCH, + $e + ); + } + + // Handle null or empty response + if (empty($response)) { + $this->logger->error('[FastParcelShopSearch] API returned empty response'); + $emptyResponse = new ParcelShopSearchResponse(); + $emptyResponse->setStatus('err'); + $emptyResponse->setErrLog('API returned empty response'); + $emptyResponse->setParcelShops(array()); + return $emptyResponse; + } + + // Convert stdClass to array (Unirest returns decoded JSON as object) + $responseData = json_decode(json_encode($response), true); + + // Handle JSON conversion failure + if (!is_array($responseData)) { + $this->logger->error('[FastParcelShopSearch] Failed to convert response to array'); + $errorResponse = new ParcelShopSearchResponse(); + $errorResponse->setStatus('err'); + $errorResponse->setErrLog('Failed to parse API response'); + $errorResponse->setParcelShops(array()); + return $errorResponse; + } + + return $this->parser->parse($responseData); + } +} diff --git a/src/Factory/APIRequest/index.php b/src/Factory/APIRequest/index.php new file mode 100644 index 00000000..58b8b473 --- /dev/null +++ b/src/Factory/APIRequest/index.php @@ -0,0 +1,30 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Factory/ShipmentDataFactory.php b/src/Factory/ShipmentDataFactory.php index 15dfe6ea..0e79413d 100644 --- a/src/Factory/ShipmentDataFactory.php +++ b/src/Factory/ShipmentDataFactory.php @@ -25,12 +25,13 @@ use Carrier; use Configuration; use Customer; -use DPDOrderPhone; +use DPDOrderDeliveryTime; use DPDProduct; use DPDPudo; use DPDShipment; use Invertus\dpdBaltics\Config\Config; use Invertus\dpdBaltics\DTO\ShipmentData; +use Invertus\dpdBaltics\Repository\OrderDeliveryTimeRepository; use Invertus\dpdBaltics\Repository\OrderRepository; use Invertus\dpdBaltics\Repository\PudoRepository; use Invertus\dpdBaltics\Repository\ShipmentRepository; @@ -55,14 +56,21 @@ class ShipmentDataFactory */ private $pudoRepository; + /** + * @var OrderDeliveryTimeRepository + */ + private $orderDeliveryTimeRepository; + public function __construct( OrderRepository $orderRepository, ShipmentRepository $shipmentRepository, - PudoRepository $pudoRepository + PudoRepository $pudoRepository, + OrderDeliveryTimeRepository $orderDeliveryTimeRepository ) { $this->orderRepository = $orderRepository; $this->shipmentRepository = $shipmentRepository; $this->pudoRepository = $pudoRepository; + $this->orderDeliveryTimeRepository = $orderDeliveryTimeRepository; } public function getShipmentDataByIdOrder($orderId) @@ -94,6 +102,15 @@ public function getShipmentDataByIdOrder($orderId) $shipmentData->setPhone($dpdOrderPhone['phone']); } + /** Delivery Time */ + $deliveryTimeId = $this->orderDeliveryTimeRepository->getOrderDeliveryIdByCartId($order->id_cart); + if ($deliveryTimeId) { + $orderDeliveryTime = new DPDOrderDeliveryTime($deliveryTimeId); + if ($orderDeliveryTime->delivery_time) { + $shipmentData->setDeliveryTime($orderDeliveryTime->delivery_time); + } + } + /** Shipment */ $shipmentId = $this->shipmentRepository->getIdByOrderId($orderId); $shipment = new DPDShipment($shipmentId); diff --git a/src/Logger/Logger.php b/src/Logger/Logger.php index ecaa083a..e4dc9eaa 100644 --- a/src/Logger/Logger.php +++ b/src/Logger/Logger.php @@ -178,10 +178,78 @@ public function log($level, $message, array $context = []): void } $log = new DPDLog(); - $log->response = $message; - $log->request = !empty($context['request']) ? $this->logsService->hideUsernameAndPasswordFromRequest($context['request']) : null; + $log->response = $this->encodeResponse($level, $message, $context); + $log->request = !empty($context['request']) + ? $this->encodeRequest($this->logsService->hideUsernameAndPasswordFromRequest($context['request'])) + : null; $log->status = $level; $log->add(); } + + private function encodeRequest($raw): ?string + { + if ($raw === null || $raw === '') { + return null; + } + + if (is_array($raw)) { + return $this->jsonEncode($raw); + } + + $rawString = (string) $raw; + $parsed = parse_url($rawString); + + if (!$parsed || empty($parsed['scheme']) || empty($parsed['host'])) { + return $this->jsonEncode(['raw' => $rawString]); + } + + $endpoint = $parsed['scheme'] . '://' . $parsed['host']; + if (isset($parsed['path'])) { + $endpoint .= $parsed['path']; + } + + $params = []; + if (!empty($parsed['query'])) { + parse_str($parsed['query'], $params); + if (array_key_exists('password', $params)) { + $params['password'] = '***'; + } + if (array_key_exists('username', $params) && $params['username'] !== '') { + $params['username'] = '***'; + } + } + + return $this->jsonEncode([ + 'endpoint' => $endpoint, + 'params' => $params, + ]); + } + + private function encodeResponse($level, $message, array $context): ?string + { + $payload = [ + 'level' => (string) $level, + ]; + + $decoded = is_string($message) ? json_decode($message, true) : null; + if (json_last_error() === JSON_ERROR_NONE && (is_array($decoded) || is_object($decoded))) { + $payload['body'] = $decoded; + } else { + $payload['message'] = is_scalar($message) || $message === null ? (string) $message : $message; + } + + $extra = $context; + unset($extra['request']); + if (!empty($extra)) { + $payload['context'] = $extra; + } + + return $this->jsonEncode($payload); + } + + private function jsonEncode($value): string + { + return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } } diff --git a/src/Provider/CurrentCountryProvider.php b/src/Provider/CurrentCountryProvider.php index ed43d750..463d5ffb 100644 --- a/src/Provider/CurrentCountryProvider.php +++ b/src/Provider/CurrentCountryProvider.php @@ -95,7 +95,7 @@ private function getWebServiceCountryCode() * * @return string|null */ - private function getCountryIsoCodeByAddress($idAddress) + public function getCountryIsoCodeByAddress($idAddress) { $address = new Address($idAddress); diff --git a/src/Repository/ParcelShopRepository.php b/src/Repository/ParcelShopRepository.php index e18df98f..b8477d52 100644 --- a/src/Repository/ParcelShopRepository.php +++ b/src/Repository/ParcelShopRepository.php @@ -33,9 +33,8 @@ class ParcelShopRepository extends AbstractEntityRepository { public function deleteShopsByCountryCode($countryCode) { - - $sql = 'DELETE w FROM `' . _DB_PREFIX_ . 'dpd_shop_work_hours` w - INNER JOIN `' . _DB_PREFIX_ . 'dpd_shop` s ON s.country = "' . pSQL($countryCode) . '" + $sql = 'DELETE w FROM `' . _DB_PREFIX_ . 'dpd_shop_work_hours` w + INNER JOIN `' . _DB_PREFIX_ . 'dpd_shop` s ON s.country = "' . pSQL($countryCode) . '" WHERE s.parcel_shop_id = w.parcel_shop_id'; if (!Db::getInstance()->execute($sql)) { @@ -51,7 +50,7 @@ public function deleteShopsByCountryCode($countryCode) public function getShopsByCity($country, $city) { $query = new DbQuery(); - $query->select('*'); + $query->select('s.*'); $query->from('dpd_shop', 's'); $query->where('s.`country` = "' . pSQL($country) . '" AND s.`city` = "' . pSQL($city) . '"'); @@ -61,11 +60,9 @@ public function getShopsByCity($country, $city) public function getShopsByShopId($shopId) { $query = new DbQuery(); - $query->select('*'); + $query->select('s.*'); $query->from('dpd_shop', 's'); - $query->innerJoin('dpd_shop_work_hours', 'wh', 's.parcel_shop_id = wh.parcel_shop_id'); $query->where('s.`parcel_shop_id` = "' . pSQL($shopId) . '"'); - $query->groupBy('s.parcel_shop_id'); return $this->db->executeS($query); } diff --git a/src/Service/API/ShipmentApiService.php b/src/Service/API/ShipmentApiService.php index c3f07e3b..47c59b38 100644 --- a/src/Service/API/ShipmentApiService.php +++ b/src/Service/API/ShipmentApiService.php @@ -23,7 +23,6 @@ use Address; use Country; -use DPDAddressTemplate; use DPDProduct; use Invertus\dpdBaltics\Adapter\AddressAdapter; use Invertus\dpdBaltics\Config\Config; @@ -108,15 +107,18 @@ public function createShipment($addressId, ShipmentData $shipmentData, $orderId) $hasAddressFields = (bool) !$postCode || !$firstName || !$address->city || !$country; // Post code might be wrong in order adress, so we set terminal post code instead + $selectedParcel = null; if ($shipmentData->isPudo()) { $parcel = $this->parcelShopService->getParcelShopByShopId($shipmentData->getSelectedPudoId()); - $selectedParcel = is_array($parcel) ? reset($parcel) : $parcel; - $postCode = $selectedParcel->getPCode(); - $address->address1 = $selectedParcel->getStreet(); + $selectedParcel = is_array($parcel) && !empty($parcel) ? reset($parcel) : $parcel; + if ($selectedParcel && is_object($selectedParcel)) { + $postCode = $selectedParcel->getPCode(); + $address->address1 = $selectedParcel->getStreet(); + } } // IF prestashop allows, we take selected parcel terminal address in case information is missing in checkout address in specific cases. - if (($hasAddressFields) && $shipmentData->isPudo()) { + if (($hasAddressFields) && $shipmentData->isPudo() && $selectedParcel && is_object($selectedParcel)) { $firstName = $selectedParcel->getCompany(); $address->address1 = $selectedParcel->getStreet(); $address->city = $selectedParcel->getCity(); @@ -142,7 +144,7 @@ public function createShipment($addressId, ShipmentData $shipmentData, $orderId) $shipmentCreationRequest->setName2($address->lastname); } - $shipmentCreationRequest = $this->setNotRequiredData($shipmentCreationRequest, $shipmentData); + $shipmentCreationRequest = $this->setNotRequiredData($shipmentCreationRequest, $shipmentData, $parcelType); if ($dpdProduct->is_cod) { $shipmentCreationRequest->setCodAmount($shipmentData->getGoodsPrice()); @@ -165,10 +167,20 @@ public function createShipment($addressId, ShipmentData $shipmentData, $orderId) $shipmentCreationRequest->setDnoteReference($shipmentData->getDpdDocumentReturnNumber()); } - if ($shipmentData->getDeliveryTime()) { + if (Config::productHasDeliveryTime($parcelType) && $shipmentData->getDeliveryTime()) { $timeFrames = explode('-', $shipmentData->getDeliveryTime()); - $shipmentCreationRequest->setTimeFrameFrom($timeFrames[0]); - $shipmentCreationRequest->setTimeFrameTo($timeFrames[1]); + if (count($timeFrames) === 2) { + // Format should be HH:mm (e.g., "18:00", "22:00") + $timeFrom = trim($timeFrames[0]); + $timeTo = trim($timeFrames[1]); + + // Validate HH:mm format + if (preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeFrom) && + preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeTo)) { + $shipmentCreationRequest->setTimeFrameFrom($timeFrom); + $shipmentCreationRequest->setTimeFrameTo($timeTo); + } + } } $shipmentCreator = $this->shipmentCreationFactory->makeShipmentCreation(); @@ -197,7 +209,7 @@ public function createReturnServiceShipment($addressTemplateId, $orderId, Shipme $address1 = $selectedPudo->street; $city = $selectedPudo->city; $countryIso = $selectedPudo->country_code; - $postCode = $selectedPudo->post_code; + $postCode = preg_replace('/[^0-9]/', '', $selectedPudo->post_code); } else { $address1 = $address->address1; $city = $address->city; @@ -219,22 +231,25 @@ public function createReturnServiceShipment($addressTemplateId, $orderId, Shipme $customer->email, 1 ); - $shipmentCreationRequest = $this->setNotRequiredData($shipmentCreationRequest, $shipmentData); + $shipmentCreationRequest = $this->setNotRequiredData($shipmentCreationRequest, $shipmentData, $parcelType); $shipmentCreator = $this->shipmentCreationFactory->makeShipmentCreation(); return $shipmentCreator->createShipment($shipmentCreationRequest); } - private function setNotRequiredData(ShipmentCreationRequest $shipmentCreationRequest, ShipmentData $shipmentData) + private function setNotRequiredData(ShipmentCreationRequest $shipmentCreationRequest, ShipmentData $shipmentData, $parcelType) { $shipmentCreationRequest->setOrderNumber($shipmentData->getReference1()); $shipmentCreationRequest->setOrderNumber1($shipmentData->getReference2()); $shipmentCreationRequest->setOrderNumber2($shipmentData->getReference3()); $shipmentCreationRequest->setOrderNumber3($shipmentData->getReference4()); $shipmentCreationRequest->setWeight($shipmentData->getWeight()); - $shipmentCreationRequest->setIdmSmsNumber($shipmentData->getPhone()); - $shipmentCreationRequest->setOrderNumber($shipmentData->getReference1()); + + if (Config::productHasDeliveryTime($parcelType)) { + $shipmentCreationRequest->setIdmSmsNumber($shipmentData->getPhone()); + $shipmentCreationRequest->setPredict('y'); + } return $shipmentCreationRequest; } diff --git a/src/Service/Import/API/ParcelShopImport.php b/src/Service/Import/API/ParcelShopImport.php index 49d29c94..26756353 100644 --- a/src/Service/Import/API/ParcelShopImport.php +++ b/src/Service/Import/API/ParcelShopImport.php @@ -21,15 +21,13 @@ namespace Invertus\dpdBaltics\Service\Import\API; -use Configuration; use DPDBaltics; use EntityAddException; -use Exception; use Invertus\dpdBaltics\Config\Config; use Invertus\dpdBaltics\Service\API\ParcelShopSearchApiService; use Invertus\dpdBaltics\Service\Parcel\ParcelUpdateService; use Invertus\dpdBalticsApi\Api\DTO\Response\ParcelShopSearchResponse; -use Tools; +use Psr\Log\LoggerInterface; if (!defined('_PS_VERSION_')) { exit; @@ -43,61 +41,170 @@ class ParcelShopImport * @var ParcelShopSearchApiService */ private $apiService; + /** * @var ParcelUpdateService */ private $parcelUpdateService; + /** * @var DPDBaltics */ private $module; + /** + * @var LoggerInterface + */ + private $logger; + public function __construct( ParcelShopSearchApiService $apiService, ParcelUpdateService $parcelUpdateService, - DPDBaltics $module + DPDBaltics $module, + LoggerInterface $logger ) { $this->apiService = $apiService; $this->parcelUpdateService = $parcelUpdateService; $this->module = $module; + $this->logger = $logger; } + /** + * Import parcel shops for a country. + * + * @param string $selectedCountry Country ISO code + * @return array + */ public function importParcelShops($selectedCountry) { + $startTime = microtime(true); + + $retrieveOpeningHours = $this->shouldRetrieveOpeningHours($selectedCountry); + /** @var ParcelShopSearchResponse $shops */ $shops = $this->apiService->getAllCountryParcels( $selectedCountry, Config::FETCH_PUDO_POINT, - Config::RETRIEVE_OPENING_HOURS + $retrieveOpeningHours ); + + $apiTime = round(microtime(true) - $startTime, 2); + if ($shops->getStatus() === Config::API_RESPONSE_ERROR_STATUS) { - return - [ - 'success' => false, - 'error' => sprintf($this->module->l('Failed to update parcel shops: %s', self::FILE_NAME), $shops->getErrLog()) - ]; + $this->logger->error(sprintf( + '[ParcelImport] API ERROR for %s | Error: %s | API took: %ss', + $selectedCountry, + $shops->getErrLog(), + $apiTime + )); + + return [ + 'success' => false, + 'error' => sprintf($this->module->l('Failed to update parcel shops: %s', self::FILE_NAME), $shops->getErrLog()) + ]; + } + + $parcelShops = $shops->getParcelShops(); + + if ($parcelShops === null || !is_array($parcelShops)) { + $this->logger->error(sprintf( + '[ParcelImport] API returned NO DATA for %s | API took: %ss', + $selectedCountry, + $apiTime + )); + + return [ + 'success' => false, + 'error' => sprintf($this->module->l('Failed to update parcel shops: API returned no data for country %s', self::FILE_NAME), $selectedCountry) + ]; } + + $parcelCount = count($parcelShops); + $dbStartTime = microtime(true); + try { - $this->parcelUpdateService->updateParcels($shops->getParcelShops(), $selectedCountry); + $this->parcelUpdateService->updateParcels($parcelShops, $selectedCountry); } catch (EntityAddException $e) { - return - [ - 'success' => false, - 'error' => $e->getMessage() - ]; + $totalTime = round(microtime(true) - $startTime, 2); + $dbTime = round(microtime(true) - $dbStartTime, 2); + + $this->logger->error(sprintf( + '[ParcelImport] DATABASE ERROR for %s | Error: %s | DB time: %ss | Total time: %ss', + $selectedCountry, + $e->getMessage(), + $dbTime, + $totalTime + )); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } catch (\Exception $e) { + $totalTime = round(microtime(true) - $startTime, 2); + $dbTime = round(microtime(true) - $dbStartTime, 2); + + $this->logger->error(sprintf( + '[ParcelImport] EXCEPTION for %s | Type: %s | Error: %s | DB time: %ss | Total time: %ss', + $selectedCountry, + get_class($e), + $e->getMessage(), + $dbTime, + $totalTime + )); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; } catch (\Error $e) { - return - [ - 'success' => false, - 'error' => $e->getMessage() - ]; - } + $totalTime = round(microtime(true) - $startTime, 2); + $dbTime = round(microtime(true) - $dbStartTime, 2); + + $this->logger->error(sprintf( + '[ParcelImport] PHP ERROR for %s | Type: %s | Error: %s | File: %s:%d | DB time: %ss | Total time: %ss', + $selectedCountry, + get_class($e), + $e->getMessage(), + $e->getFile(), + $e->getLine(), + $dbTime, + $totalTime + )); - return - [ - 'success' => true, - 'success_message' => $this->module->l('Successfully updated parcel shops', self::FILE_NAME) + return [ + 'success' => false, + 'error' => $e->getMessage() ]; + } + + $totalTime = round(microtime(true) - $startTime, 2); + + return [ + 'success' => true, + 'success_message' => sprintf( + $this->module->l('Successfully imported %d parcel shops in %ss', self::FILE_NAME), + $parcelCount, + $totalTime + ) + ]; } -} \ No newline at end of file + /** + * Check if opening hours should be retrieved for this country. + * Large countries may timeout when retrieving opening hours due to API limits. + * + * @param string $countryIso + * @return int + */ + private function shouldRetrieveOpeningHours($countryIso) + { + $countryIso = strtoupper($countryIso); + + if (in_array($countryIso, Config::COUNTRIES_SKIP_OPENING_HOURS, true)) { + return Config::SKIP_OPENING_HOURS; + } + + return Config::RETRIEVE_OPENING_HOURS; + } +} diff --git a/src/Service/Parcel/ParcelUpdateService.php b/src/Service/Parcel/ParcelUpdateService.php index 062dd7b4..514138ea 100644 --- a/src/Service/Parcel/ParcelUpdateService.php +++ b/src/Service/Parcel/ParcelUpdateService.php @@ -1,143 +1,236 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - - -namespace Invertus\dpdBaltics\Service\Parcel; - -use DPDShop; -use DPDShopWorkHours; -use EntityAddException; -use Exception; -use Invertus\dpdBaltics\Repository\ParcelShopRepository; -use Invertus\dpdBalticsApi\Api\DTO\Object\OpeningHours; -use Invertus\dpdBalticsApi\Api\DTO\Object\ParcelShop; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class ParcelUpdateService -{ - - /** - * @var ParcelShopRepository - */ - private $parcelShopRepository; - - public function __construct(ParcelShopRepository $parcelShopRepository) - { - $this->parcelShopRepository = $parcelShopRepository; - } - - public function updateParcels(array $parcels, $countryCode) - { - $isDeleteSuccess = $this->parcelShopRepository->deleteShopsByCountryCode($countryCode); - if (!$isDeleteSuccess) { - return false; - } - - foreach ($parcels as $parcel) { - if ($parcel instanceof ParcelShop) { - $this->addParcelShop($parcel); - } else { - $parcelShop = $this->resetParcelObject($parcel); - $this->addParcelShop($parcelShop); - } - } - - return true; - } - - public function addParcelShop(ParcelShop $parcel) - { - $parcelShop = new DPDShop(); - $parcelShop->parcel_shop_id = $parcel->getParcelShopId(); - $parcelShop->company = $parcel->getCompany(); - $parcelShop->country = $parcel->getCountry(); - $parcelShop->city = $parcel->getCity(); - $parcelShop->p_code = $parcel->getPCode(); - $parcelShop->street = $parcel->getStreet(); - $parcelShop->email = $parcel->getEmail(); - $parcelShop->phone = $parcel->getPhone(); - $parcelShop->longitude = $parcel->getLongitude(); - $parcelShop->latitude = $parcel->getLatitude(); - - try { - $parcelShop->add(); - } catch (Exception $e) { - throw new EntityAddException( - 'Failed to add parcel shop', - EntityAddException::DPD_PARCEL_SHOP_EXCEPTION, - $e - ); - } - - foreach ($parcel->getOpeningHours() as $openingHours) { - $parcelShopWorkHours = new DPDShopWorkHours(); - $parcelShopWorkHours->parcel_shop_id = $parcel->getParcelShopId(); - $parcelShopWorkHours->week_day = $openingHours->weekday; - $parcelShopWorkHours->open_morning = $openingHours->openMorning; - $parcelShopWorkHours->close_morning = $openingHours->closeMorning; - $parcelShopWorkHours->open_afternoon = $openingHours->openAfternoon; - $parcelShopWorkHours->close_afternoon = $openingHours->closeAfternoon; - - try { - $parcelShopWorkHours->add(); - } catch (Exception $e) { - throw new EntityAddException( - 'Failed to add parcel shop work hours', - EntityAddException::DPD_PARCEL_SHOP_WORK_HOURS_EXCEPTION, - $e - ); - } - } - - return true; - } - - /** - *This function is needed for prestashop versions below 1704 as API response loses object instance - * - * @param $parcel - * - * @return ParcelShop - */ - private function resetParcelObject($parcel) - { - $parcelShop = new ParcelShop(); - $parcelShop->setParcelShopId($parcel->parcelshop_id); - $parcelShop->setCompany($parcel->company); - $parcelShop->setCountry($parcel->country); - $parcelShop->setCity($parcel->city); - $parcelShop->setPCode($parcel->pcode); - $parcelShop->setStreet($parcel->street); - $parcelShop->setEmail($parcel->email); - $parcelShop->setPhone($parcel->phone); - $parcelShop->setDistance($parcel->distance); - $parcelShop->setLongitude($parcel->longitude); - $parcelShop->setLatitude($parcel->latitude); - $parcelShop->setCoordinateX($parcel->coordinateX); - $parcelShop->setCoordinateY($parcel->coordinateY); - $parcelShop->setCoordinateZ($parcel->coordinateZ); - $parcelShop->setOpeningHours($parcel->openingHours); - - return $parcelShop; - } -} + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Invertus\dpdBaltics\Service\Parcel; + +use Db; +use EntityAddException; +use Invertus\dpdBaltics\Repository\ParcelShopRepository; +use Invertus\dpdBalticsApi\Api\DTO\Object\ParcelShop; +use Psr\Log\LoggerInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class ParcelUpdateService +{ + const BATCH_SIZE = 100; + + /** + * @var ParcelShopRepository + */ + private $parcelShopRepository; + + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(ParcelShopRepository $parcelShopRepository, LoggerInterface $logger) + { + $this->parcelShopRepository = $parcelShopRepository; + $this->logger = $logger; + } + + /** + * Update parcels using batch insert for better performance + * + * @param array $parcels + * @param string $countryCode + * @return bool + * @throws EntityAddException + */ + public function updateParcels(array $parcels, $countryCode) + { + $isDeleteSuccess = $this->parcelShopRepository->deleteShopsByCountryCode($countryCode); + + if (!$isDeleteSuccess) { + $this->logger->error(sprintf( + '[ParcelUpdate] FAILED to delete existing shops for %s', + $countryCode + )); + return false; + } + + $shopsBatch = []; + $workHoursBatch = []; + + foreach ($parcels as $parcel) { + if (!($parcel instanceof ParcelShop)) { + $parcel = $this->resetParcelObject($parcel); + } + + $shopsBatch[] = $this->prepareShopData($parcel); + + $openingHours = $parcel->getOpeningHours(); + if (is_array($openingHours) && !empty($openingHours)) { + foreach ($openingHours as $openingHoursItem) { + $workHoursBatch[] = $this->prepareWorkHoursData($parcel->getParcelShopId(), $openingHoursItem); + } + } + + if (count($shopsBatch) >= self::BATCH_SIZE) { + $this->insertShopsBatch($shopsBatch); + $shopsBatch = []; + } + + if (count($workHoursBatch) >= self::BATCH_SIZE * 7) { + $this->insertWorkHoursBatch($workHoursBatch); + $workHoursBatch = []; + } + } + + if (!empty($shopsBatch)) { + $this->insertShopsBatch($shopsBatch); + } + + if (!empty($workHoursBatch)) { + $this->insertWorkHoursBatch($workHoursBatch); + } + + return true; + } + + /** + * Prepare shop data for batch insert + * + * @param ParcelShop $parcel + * @return array + */ + private function prepareShopData(ParcelShop $parcel) + { + return [ + 'parcel_shop_id' => pSQL($parcel->getParcelShopId()), + 'company' => pSQL($parcel->getCompany()), + 'country' => pSQL($parcel->getCountry()), + 'city' => pSQL($parcel->getCity()), + 'p_code' => pSQL($parcel->getPCode()), + 'street' => pSQL($parcel->getStreet()), + 'email' => pSQL($parcel->getEmail()), + 'phone' => pSQL($parcel->getPhone()), + 'longitude' => pSQL($parcel->getLongitude()), + 'latitude' => pSQL($parcel->getLatitude()), + ]; + } + + /** + * Prepare work hours data for batch insert + * + * @param string $parcelShopId + * @param object $openingHours + * @return array + */ + private function prepareWorkHoursData($parcelShopId, $openingHours) + { + return [ + 'parcel_shop_id' => pSQL($parcelShopId), + 'week_day' => pSQL($openingHours->weekday), + 'open_morning' => pSQL($openingHours->openMorning), + 'close_morning' => pSQL($openingHours->closeMorning), + 'open_afternoon' => pSQL($openingHours->openAfternoon), + 'close_afternoon' => pSQL($openingHours->closeAfternoon), + ]; + } + + /** + * Insert shops batch + * + * @param array $batch + * @throws EntityAddException + */ + private function insertShopsBatch(array $batch) + { + if (empty($batch)) { + return; + } + + $result = Db::getInstance()->insert('dpd_shop', $batch); + + if (!$result) { + $this->logger->error(sprintf( + '[ParcelUpdate] FAILED to insert shops batch | Batch size: %d | DB error: %s', + count($batch), + Db::getInstance()->getMsgError() + )); + + throw new EntityAddException( + 'Failed to add parcel shops batch: ' . Db::getInstance()->getMsgError(), + EntityAddException::DPD_PARCEL_SHOP_EXCEPTION + ); + } + } + + /** + * Insert work hours batch + * + * @param array $batch + * @throws EntityAddException + */ + private function insertWorkHoursBatch(array $batch) + { + if (empty($batch)) { + return; + } + + $result = Db::getInstance()->insert('dpd_shop_work_hours', $batch); + + if (!$result) { + $this->logger->error(sprintf( + '[ParcelUpdate] FAILED to insert work hours batch | Batch size: %d | DB error: %s', + count($batch), + Db::getInstance()->getMsgError() + )); + + throw new EntityAddException( + 'Failed to add parcel shop work hours batch: ' . Db::getInstance()->getMsgError(), + EntityAddException::DPD_PARCEL_SHOP_WORK_HOURS_EXCEPTION + ); + } + } + + /** + * This function is needed for prestashop versions below 1704 as API response loses object instance + * + * @param $parcel + * @return ParcelShop + */ + private function resetParcelObject($parcel) + { + $parcelShop = new ParcelShop(); + $parcelShop->setParcelShopId($parcel->parcelshop_id); + $parcelShop->setCompany($parcel->company); + $parcelShop->setCountry($parcel->country); + $parcelShop->setCity($parcel->city); + $parcelShop->setPCode($parcel->pcode); + $parcelShop->setStreet($parcel->street); + $parcelShop->setEmail($parcel->email); + $parcelShop->setPhone($parcel->phone); + $parcelShop->setDistance($parcel->distance); + $parcelShop->setLongitude($parcel->longitude); + $parcelShop->setLatitude($parcel->latitude); + $parcelShop->setCoordinateX($parcel->coordinateX); + $parcelShop->setCoordinateY($parcel->coordinateY); + $parcelShop->setCoordinateZ($parcel->coordinateZ); + $parcelShop->setOpeningHours($parcel->openingHours); + + return $parcelShop; + } +} diff --git a/src/Service/PudoService.php b/src/Service/PudoService.php index a150b347..3f0c5641 100644 --- a/src/Service/PudoService.php +++ b/src/Service/PudoService.php @@ -186,31 +186,29 @@ public function savePudoCartOrder($idCarrier, $idCart, $idPudo, $countryCode) return true; } - public function searchPudoServices($city, $carrierId, $cartId) + public function searchPudoServices($city, $carrierId, $cartId, $countryCode = null) { - $cart = null; - - if ($cartId) { - $cart = new Cart($cartId); + if (!$countryCode) { + $cart = null; + + if ($cartId) { + $cart = new Cart($cartId); + } + /** @var CurrentCountryProvider $currentCountryProvider */ + $currentCountryProvider = $this->module->getModuleContainer('invertus.dpdbaltics.provider.current_country_provider'); + $countryCode = $currentCountryProvider->getCurrentCountryIsoCode($cart); } - /** @var CurrentCountryProvider $currentCountryProvider */ - $currentCountryProvider = $this->module->getModuleContainer('invertus.dpdbaltics.provider.current_country_provider'); - $countryCode = $currentCountryProvider->getCurrentCountryIsoCode($cart); /** @var ParcelShopService $parcelShopService */ - /** @var PudoService $pudoService */ - $parcelShopService= $this->module->getModuleContainer('invertus.dpdbaltics.service.parcel.parcel_shop_service'); - $pudoService = $this->module->getModuleContainer('invertus.dpdbaltics.service.pudo_service'); + $parcelShopService = $this->module->getModuleContainer('invertus.dpdbaltics.service.parcel.parcel_shop_service'); /** @var ParcelShop[] $parcelShops */ $parcelShops = $parcelShopService->getParcelShopsByCountryAndCity($countryCode, $city); - $pudoServices = $pudoService->setPudoServiceTypes($parcelShops); - $pudoServices = $pudoService->formatPudoServicesWorkHours($pudoServices); + $pudoServices = $this->setPudoServiceTypes($parcelShops); + $pudoServices = $this->formatPudoServicesWorkHours($pudoServices); - /** @var PudoRepository $pudoRepo */ - $pudoRepo = $this->module->getModuleContainer('invertus.dpdbaltics.repository.pudo_repository'); - $pudoId = $pudoRepo->getIdByCart($cartId); + $pudoId = $this->pudoRepository->getIdByCart($cartId); $selectedPudo = new DPDPudo($pudoId); $this->smarty->assign( @@ -256,9 +254,18 @@ public function savePudoOrder($productId, $pudoId, $isoCode, $cartId, $city, $st $pudoOrder->id_carrier = $carrier->id; $pudoOrder->country_code = $countryCode; $pudoOrder->id_cart = $cartId; - $pudoOrder->city = $city; - $pudoOrder->street = $street; - $pudoOrder->post_code = $zipCode; + + $pudoShop = DPDShop::getShopByPudoId($pudoId); + if ($pudoShop && $pudoShop->id) { + $pudoOrder->city = $pudoShop->city; + $pudoOrder->street = $pudoShop->street; + $pudoOrder->post_code = $pudoShop->p_code; + } else { + $pudoOrder->city = $city; + $pudoOrder->street = $street; + $pudoOrder->post_code = $zipCode; + } + $pudoOrder->save(); } diff --git a/src/Service/ShipmentService.php b/src/Service/ShipmentService.php index 1a1f2e8e..5f1636bc 100644 --- a/src/Service/ShipmentService.php +++ b/src/Service/ShipmentService.php @@ -232,7 +232,6 @@ private function createNonDistributedShipment(Order $order, $idProduct, $isTestM $parcelWeight += $product['weight'] * $product['product_quantity']; } - $goodsPrice = $this->calculateParcelPriceWithOrderDiscount($order, $goodsPrice); $shipment = $this->createShipment($order, $idProduct, $isTestMode, 1, $parcelWeight, $goodsPrice); if (!$shipment->id) { diff --git a/translations/lt.php b/translations/lt.php index e98d6e51..728f025f 100755 --- a/translations/lt.php +++ b/translations/lt.php @@ -75,7 +75,7 @@ $_MODULE['<{dpdbaltics}prestashop>markers-list_f4ec5f57bd4d31b803312d873be40da9'] = 'Keisti'; $_MODULE['<{dpdbaltics}prestashop>markers-list_b689ff1685e45b92f4cbff6570d96647'] = 'Darbo valandos:'; $_MODULE['<{dpdbaltics}prestashop>markers-list_6b9d52ad995244cbf32d9cc75aadbded'] = 'Nerasta jokių atsiėmimo taškų'; -$_MODULE['<{dpdbaltics}prestashop>admin-order_48300820b47f97208960c55d476de0d1'] = 'dpdbaltics laivyba'; +$_MODULE['<{dpdbaltics}prestashop>admin-order_48300820b47f97208960c55d476de0d1'] = 'dpdbaltics siuntimas'; $_MODULE['<{dpdbaltics}prestashop>admin-order_5e34f5e134425db6f1d6a68370aab105'] = '[išplėsti]'; $_MODULE['<{dpdbaltics}prestashop>admin-order_ed5c01a46e47055b81a911c21915af84'] = 'BANDYMO UŽSAKYMAS'; $_MODULE['<{dpdbaltics}prestashop>admin-order_6bb311efd788bb4b3123896667e767a7'] = 'Siuntimas'; diff --git a/translations/lv.php b/translations/lv.php index 47437fb6..7418b182 100644 --- a/translations/lv.php +++ b/translations/lv.php @@ -72,7 +72,7 @@ $_MODULE['<{dpdbaltics}prestashop>markers-list_f4ec5f57bd4d31b803312d873be40da9'] = 'Mainīt'; $_MODULE['<{dpdbaltics}prestashop>markers-list_b689ff1685e45b92f4cbff6570d96647'] = 'Darba stundas:'; $_MODULE['<{dpdbaltics}prestashop>markers-list_6b9d52ad995244cbf32d9cc75aadbded'] = 'Netika atrasti saņemšanas punkti'; -$_MODULE['<{dpdbaltics}prestashop>admin-order_48300820b47f97208960c55d476de0d1'] = 'dpdbaltics kuģniecība'; +$_MODULE['<{dpdbaltics}prestashop>admin-order_48300820b47f97208960c55d476de0d1'] = 'dpdbaltics piegāde'; $_MODULE['<{dpdbaltics}prestashop>admin-order_5e34f5e134425db6f1d6a68370aab105'] = '[izvērst]'; $_MODULE['<{dpdbaltics}prestashop>admin-order_ed5c01a46e47055b81a911c21915af84'] = 'TESTA PASŪTĪJUMS'; $_MODULE['<{dpdbaltics}prestashop>admin-order_6bb311efd788bb4b3123896667e767a7'] = 'Sūtījums'; diff --git a/views/css/admin/global_module.css b/views/css/admin/global_module.css index 521ba1d7..03093bfe 100644 --- a/views/css/admin/global_module.css +++ b/views/css/admin/global_module.css @@ -16,6 +16,7 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -#subtab-AdminDPDBalticsModule{ +#subtab-AdminDPDBalticsModule > ul, +#subtab-AdminDPDBalticsModule .submenu { display: none; } diff --git a/views/css/admin/logs_tab.css b/views/css/admin/logs_tab.css new file mode 100644 index 00000000..0c444e63 --- /dev/null +++ b/views/css/admin/logs_tab.css @@ -0,0 +1,182 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License version 3.0 + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +table.dpd_log td { + word-break: break-word; + vertical-align: middle; +} + +.button { + cursor: pointer; +} + +.dpd-log-severity { + display: inline-block; + padding: 3px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + line-height: 1; + color: #ffffff; + background: #6b7280; + white-space: nowrap; +} + +.dpd-log-severity-1 { + background: #dc3545; +} + +.dpd-log-severity-2 { + background: #f0ad4e; +} + +.dpd-log-severity-3 { + background: #0d6efd; +} + +.dpd-log-severity-4 { + background: #6c757d; +} + +.log-modal-overlay { + transition: opacity 0.2s ease-out; + pointer-events: none; + background: rgba(15, 23, 42, 0.8); + position: fixed; + opacity: 0; + bottom: 0; + right: 0; + left: 0; + top: 0; + z-index: 1040; +} + +.modal.open .log-modal-overlay { + pointer-events: all; + opacity: 0.5; +} + +.log-modal-window { + position: relative; + width: 90%; + max-width: 880px; + margin: 5vh auto; + + background: #ffffff; + border-radius: 0.5em; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); + pointer-events: all; + text-align: left; + max-height: 90vh; + min-height: 50vh; + display: flex; + flex-direction: column; + + overflow: hidden; + z-index: 1050; +} + +.modal.open { + display: block; +} + +.log-modal-title { + position: relative; + color: #111827; + padding: 14px 48px; + border-bottom: solid 1px #e5e7eb; + pointer-events: all; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.log-modal-title-text { + margin: 0; + font-size: 15px; + font-weight: 600; + text-align: center; +} + +.log-modal-close { + position: absolute; + top: 50%; + right: 14px; + transform: translateY(-50%); + background: transparent; + border: 0; + font-size: 24px; + line-height: 1; + color: #6b7280; + padding: 0 6px; + cursor: pointer; +} + +.log-modal-close:hover { + color: #111827; +} + +.log-modal-content { + padding: 18px; + flex: 1 1 auto; + overflow: auto; +} + +.log-modal-content-data { + white-space: pre-wrap; + word-break: break-all; + margin: 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + color: #111827; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 6px; + padding: 14px 16px; +} + +.log-modal-content-spinner { + min-height: 200px; +} + +.log-modal-content-spinner:not(.hidden) { + display: flex; + justify-content: center; + align-items: center; +} + +.log-modal-content-spinner::after { + content: ""; + width: 40px; + height: 40px; + border: 2px solid #f3f3f3; + border-top: 3px solid #DC0032; + border-radius: 100%; + will-change: transform; + animation: dpd-log-spin 1s infinite linear; +} + +@keyframes dpd-log-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/views/css/front/pudo-shipment.css b/views/css/front/pudo-shipment.css index be2c6e7e..9f5ddb08 100644 --- a/views/css/front/pudo-shipment.css +++ b/views/css/front/pudo-shipment.css @@ -146,6 +146,7 @@ border-radius: 4px; width: 100%; background-color: white; + -webkit-appearance: none !important; } .search-block-container .form-control { diff --git a/views/js/admin/import/import_parcels.js b/views/js/admin/import/import_parcels.js index 226a0d31..c3f99b35 100644 --- a/views/js/admin/import/import_parcels.js +++ b/views/js/admin/import/import_parcels.js @@ -24,6 +24,7 @@ $(window).load(function () { $('.import-parcels-button').click(function () { $.ajax(dpdAjaxUrl, { method: 'POST', + timeout: 360000, // 6 minutes - allows for API call (120s) + parsing + DB operations data: { ajax: 1, countryId: countryId, @@ -36,11 +37,29 @@ $(window).load(function () { loadImport(); }, success: function(response) { - response = JSON.parse(response); - if (response.success) { - showSuccessMessage(response.success_message); + try { + if (typeof response === 'string') { + response = JSON.parse(response); + } + if (response.success) { + showSuccessMessage(response.success_message); + } else if (response.requires_cron) { + showCronRequiredModal(response.cron_command); + } else { + showErrorMessage(response.error || 'Unknown error occurred'); + } + } catch (e) { + showErrorMessage('Failed to parse server response: ' + e.message); + } + }, + error: function(xhr, status, error) { + if (status === 'timeout' || xhr.status === 500 || xhr.status === 504) { + // Server timeout - show cron modal + showCronRequiredModal('php bin/console dpdbaltics:update-parcel-shops --all'); + } else if (xhr.status === 0) { + showErrorMessage('Network error. Please check your connection.'); } else { - showErrorMessage(response.error); + showErrorMessage('Import failed: ' + (error || status || 'Unknown error')); } }, complete: function () { @@ -67,6 +86,12 @@ $(window).load(function () { clearInterval(toggleInterval); } + function showCronRequiredModal(cronCommand) { + var $modal = $('#import-cron-required-modal'); + $('#cron-command-display').text(cronCommand || 'php bin/console dpdbaltics:update-parcel-shops --all'); + $modal.modal('show'); + } + function nextOnBoardStep(nextStep) { $.ajax(onBoard.ajaxUrl, { method: 'POST', diff --git a/views/js/admin/log.js b/views/js/admin/log.js new file mode 100644 index 00000000..112a12aa --- /dev/null +++ b/views/js/admin/log.js @@ -0,0 +1,104 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License version 3.0 + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +$(document).ready(function () { + $(document).on('click', '.log-modal-overlay, .js-log-modal-close', function (event) { + $('.modal.open').removeClass('open'); + event.preventDefault(); + }); + + $(document).on('keydown', function (event) { + if (event.key === 'Escape') { + $('.modal.open').removeClass('open'); + } + }); + + $('.js-log-button').on('click', function (event) { + var logId = $(this).data('log-id'); + var informationType = $(this).data('information-type'); + + $('#' + $(this).data('target')).addClass('open'); + + var $contentData = $('#log-modal-' + logId + '-' + informationType + ' .log-modal-content-data'); + + if (!$contentData.hasClass('hidden')) { + return; + } + + var $spinner = $('#log-modal-' + logId + '-' + informationType + ' .log-modal-content-spinner'); + $spinner.removeClass('hidden'); + + $.ajax({ + type: 'POST', + url: dpdbaltics.logsUrl, + data: { + ajax: true, + action: 'getLog', + log_id: logId + } + }) + .then(function (response) { return jQuery.parseJSON(response); }) + .then(function (data) { + $spinner.addClass('hidden'); + + if (data.error) { + $contentData.removeClass('hidden').text(data.message || ''); + return; + } + + $('#log-modal-' + logId + '-request .log-modal-content-data').removeClass('hidden').text(formatLogPayload(data.log.request)); + $('#log-modal-' + logId + '-response .log-modal-content-data').removeClass('hidden').text(formatLogPayload(data.log.response)); + }); + }); +}); + +function formatLogPayload(payload) { + if (payload === null || typeof payload === 'undefined' || payload === '') { + return ''; + } + + try { + return JSON.stringify(JSON.parse(payload), null, 2); + } catch (e) { + // not JSON + } + + var queryIndex = payload.indexOf('?'); + if (queryIndex !== -1 && payload.indexOf('&') !== -1) { + var base = payload.substring(0, queryIndex); + var query = payload.substring(queryIndex + 1); + var parts = query.split('&').map(function (part) { + var eq = part.indexOf('='); + if (eq === -1) { + return decodeURIComponentSafe(part); + } + return decodeURIComponentSafe(part.substring(0, eq)) + ' = ' + decodeURIComponentSafe(part.substring(eq + 1)); + }); + return base + '\n\n' + parts.join('\n'); + } + + return payload; +} + +function decodeURIComponentSafe(value) { + try { + return decodeURIComponent(value.replace(/\+/g, ' ')); + } catch (e) { + return value; + } +} diff --git a/views/js/front/modules/supercheckout.js b/views/js/front/modules/supercheckout.js index 1f712f0a..aefc5c7d 100644 --- a/views/js/front/modules/supercheckout.js +++ b/views/js/front/modules/supercheckout.js @@ -16,6 +16,13 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ + +// Fix for PrestaShop 8.x and 9.x compatibility +// currentController global variable was removed in PS 1.7.7+ and doesn't exist in PS 8/9 +if (typeof window.currentController === 'undefined') { + window.currentController = ''; +} + function isDPdCarrierSelected() { if (document.querySelector('.supercheckout_shipping_option:checked')) { var selectedCarrierValue = parseInt(document.querySelector('.supercheckout_shipping_option:checked').value); diff --git a/views/js/front/pudo-search.js b/views/js/front/pudo-search.js index ef907e65..431fc6c9 100644 --- a/views/js/front/pudo-search.js +++ b/views/js/front/pudo-search.js @@ -30,7 +30,9 @@ $(document).ready(function () { $(document).on('change', 'select[name="dpd-street"]', function () { var city = $('select[name="dpd-city"]').val(); var street = $('select[name="dpd-street"]').val(); - saveSelectedStreet(city, street); + if (street) { + saveSelectedStreet(city, street); + } }); $(document).on('keyup', 'input[name="dpd-street"]', function () { @@ -100,8 +102,10 @@ function updateStreetSelect(city) { $streetSelectDiv.empty().append(response.template); $('select.chosen-select').chosen({inherit_select_classes: true}); var street = $('select[name="dpd-street"]').val(); - saveSelectedStreet(city, street); - isPudoPointSelected = true; + if (street) { + saveSelectedStreet(city, street); + isPudoPointSelected = true; + } } }, error: function (response) { diff --git a/views/js/front/pudo.js b/views/js/front/pudo.js index b7d0debf..7a332502 100644 --- a/views/js/front/pudo.js +++ b/views/js/front/pudo.js @@ -107,10 +107,16 @@ $(document).ready(function () { return; } + var $extraContent = $(params.deliveryOption).next('.carrier-extra-content'); + //Js to open extra content failed from theme, doing it manually. - if (params.deliveryOption.length > 0 && !$(params.deliveryOption).next('.carrier-extra-content').is(':visible') && !dpdbaltics.isOnePageCheckout) { + if (params.deliveryOption.length > 0 && !$extraContent.is(':visible') && !dpdbaltics.isOnePageCheckout) { $('.carrier-extra-content').hide(); - $(params.deliveryOption).next('.carrier-extra-content').slideDown(); + $extraContent.slideDown(400, function () { + DPDrefreshChosenSelects($extraContent); + }); + } else { + DPDrefreshChosenSelects($extraContent); } var deliveryOption = params.deliveryOption; @@ -157,9 +163,9 @@ $(document).ready(function () { $(document).on('click', '.dpd-pudo-select', selectPickupPointEvent); } - // $(document).on('change', 'select[name="dpd-city"]',function (){ - // searchPudoServicesEvent($(this)); - // }); + $(document).on('change', 'select[name="dpd-city"]', function () { + searchPudoServicesEvent($(this)); + }); function resizeMapEvent(e) { for (var idReference in dpdMap) { @@ -701,6 +707,38 @@ function dpdHidePopOversEvent() { }); } +/** + * Re-initialize Chosen widgets inside the given container so they pick up the + * correct width once the carrier extra content is visible. Without this, + * Chosen instances initialized while the parent was hidden render at 0px. + * Retries while the container is still hidden (theme animations, OPC modules). + */ +function DPDrefreshChosenSelects($container, attempts) { + if (!$container || !$container.length || typeof $.fn.chosen !== 'function') { + return; + } + + attempts = attempts || 0; + + if (!$container.is(':visible') || $container.outerWidth() === 0) { + if (attempts >= 10) { + return; + } + setTimeout(function () { + DPDrefreshChosenSelects($container, attempts + 1); + }, 100); + return; + } + + $container.find('select.chosen-select').each(function () { + var $select = $(this); + if ($select.data('chosen')) { + $select.chosen('destroy'); + } + $select.chosen({inherit_select_classes: true}); + }); +} + /** * expands working hour information */ diff --git a/views/templates/admin/import/importing-parcels-popup.tpl b/views/templates/admin/import/importing-parcels-popup.tpl index 94fc1af7..a710d100 100644 --- a/views/templates/admin/import/importing-parcels-popup.tpl +++ b/views/templates/admin/import/importing-parcels-popup.tpl @@ -54,3 +54,38 @@ + + diff --git a/views/templates/admin/logs/index.php b/views/templates/admin/logs/index.php new file mode 100644 index 00000000..58b8b473 --- /dev/null +++ b/views/templates/admin/logs/index.php @@ -0,0 +1,30 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/views/templates/admin/logs/log_modal.tpl b/views/templates/admin/logs/log_modal.tpl new file mode 100644 index 00000000..7334b4ff --- /dev/null +++ b/views/templates/admin/logs/log_modal.tpl @@ -0,0 +1,49 @@ +{** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License version 3.0 + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA and Contributors + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + *} +
+ {l s='View' mod='dpdbaltics'} +
+ + diff --git a/views/templates/hook/admin/partials/pudo-info.tpl b/views/templates/hook/admin/partials/pudo-info.tpl index 100d4934..a7a89dff 100644 --- a/views/templates/hook/admin/partials/pudo-info.tpl +++ b/views/templates/hook/admin/partials/pudo-info.tpl @@ -16,6 +16,7 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 *} +{if $selectedPudo}
@@ -68,4 +69,13 @@
- \ No newline at end of file + +{else} +
+
+
+ {l s='No pickup point selected' mod='dpdbaltics'} +
+
+
+{/if} diff --git a/views/templates/hook/front/partials/markers-list.tpl b/views/templates/hook/front/partials/markers-list.tpl index b89fbd63..d0a8b963 100644 --- a/views/templates/hook/front/partials/markers-list.tpl +++ b/views/templates/hook/front/partials/markers-list.tpl @@ -44,22 +44,24 @@
-
-

- {l s='more information' mod='dpdbaltics'} -

-
+ {if $service->getCountry() != 'PL'} +
+

+ {l s='more information' mod='dpdbaltics'} +

+
+ {/if}
{else} -
-
- {l s='No pickup points found' mod='dpdbaltics'} +
+
+ {l s='Select a city to view pickup points' mod='dpdbaltics'}
{/if} diff --git a/views/templates/hook/front/partials/pudo-search-street.tpl b/views/templates/hook/front/partials/pudo-search-street.tpl index cc3cf730..384722c3 100644 --- a/views/templates/hook/front/partials/pudo-search-street.tpl +++ b/views/templates/hook/front/partials/pudo-search-street.tpl @@ -27,6 +27,9 @@