From 683ab7f59fbe62eb48eaf43e1666bee5af3b4776 Mon Sep 17 00:00:00 2001 From: rivanuff Date: Thu, 27 Aug 2026 12:22:34 +0200 Subject: [PATCH 1/3] refactor: modernize with php 8.2 features, etc. --- .github/CODEOWNERS | 13 + .github/dependabot.yml | 25 + .github/workflows/dependabot-automerge.yml | 8 + .github/workflows/format-php.yml | 13 + .github/workflows/test-php.yml | 47 + .idea/openklacht.iml | 10 +- .idea/php.xml | 11 +- .php-cs-fixer.php | 78 +- CHANGELOG.md | 12 + autoloader.php | 35 +- composer.json | 108 +- composer.lock | 1837 +++++++---------- config/core.php | 18 +- config/metaboxes.php | 58 +- config/settings_pages.php | 62 +- languages/openklacht.pot | 77 +- openklacht.php | 23 +- phpstan.neon | 19 + publiccode.yaml | 12 +- src/OpenKlacht/Admin/AdminServiceProvider.php | 90 +- .../ElasticSearchServiceProvider.php | 18 +- src/OpenKlacht/Foundation/Config.php | 314 ++- src/OpenKlacht/Foundation/ServiceProvider.php | 32 +- .../GravityForms/AbstractAfterSubmit.php | 32 +- .../GravityFormsServiceProvider.php | 16 +- .../GravityForms/SubmissionHandler.php | 156 +- src/OpenKlacht/OpenKlachtServiceProvider.php | 44 +- src/OpenKlacht/PostType/MetaboxFields.php | 60 +- .../PostType/PostTypeServiceProvider.php | 53 +- src/OpenKlacht/RestAPI/Hooks.php | 30 +- .../RestAPI/RestAPIServiceProvider.php | 137 +- .../Settings/SettingsPageOptions.php | 86 +- .../Settings/SettingsServiceProvider.php | 103 +- tests/phpstan/bootstrap.php | 11 + 34 files changed, 1732 insertions(+), 1916 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-automerge.yml create mode 100644 .github/workflows/format-php.yml create mode 100644 .github/workflows/test-php.yml create mode 100644 phpstan.neon create mode 100644 tests/phpstan/bootstrap.php diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..416f56c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# Lines starting with '#' are comments. + +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in the repo. + +- @yardinternet/wordpress-backend + +# Don't automatically assign reviewers on PRs that only modify these files. + +# As they have no owner listed, these files are not owned by anyone. + +composer.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dce489c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + commit-message: + prefix: "chore" + + - package-ecosystem: "composer" + registries: "*" + directory: "/" + schedule: + interval: "weekly" + groups: + composer: + patterns: + - "*" + commit-message: + prefix: "chore" + versioning-strategy: lockfile-only diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..4ccf790 --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,8 @@ +name: Dependabot automerge + +on: pull_request + +jobs: + dependabot-automerge: + uses: yardinternet/workflows/.github/workflows/dependabot-automerge.yml@main + secrets: inherit diff --git a/.github/workflows/format-php.yml b/.github/workflows/format-php.yml new file mode 100644 index 0000000..d1f8e5a --- /dev/null +++ b/.github/workflows/format-php.yml @@ -0,0 +1,13 @@ +name: Code Style + +on: + pull_request: + paths: + - "**.php" + +jobs: + php-cs-fixer: + uses: yardinternet/workflows/.github/workflows/format-php.yml@main + secrets: inherit + with: + php-version: "8.2" diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml new file mode 100644 index 0000000..da46198 --- /dev/null +++ b/.github/workflows/test-php.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + pull_request: + paths: + - "**.php" + - "composer.json" + - "composer.lock" + - ".github/workflows/test-php.yml" + +jobs: + phpunit: + name: PHPUnit (PHP ${{ matrix.php-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-version: ["8.2"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + tools: composer:v2 + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php-version }}-composer- + + - name: Install dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run PHPUnit + run: composer test diff --git a/.idea/openklacht.iml b/.idea/openklacht.iml index e04649f..1a62c34 100644 --- a/.idea/openklacht.iml +++ b/.idea/openklacht.iml @@ -2,16 +2,13 @@ - + - - - @@ -27,7 +24,6 @@ - @@ -58,18 +54,14 @@ - - - - diff --git a/.idea/php.xml b/.idea/php.xml index b952116..8f0493e 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -19,21 +19,16 @@ - - - - - @@ -60,10 +55,8 @@ - - @@ -71,7 +64,6 @@ - @@ -84,9 +76,10 @@ + - + diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index d91e8b7..ffbb94f 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -2,66 +2,20 @@ declare(strict_types=1); -$finder = PhpCsFixer\Finder::create() - ->notPath('vendor') - ->notPath('node_modules') - ->notPath('wp-content') - ->in(__DIR__) - ->in('./resources') - ->in('./config') - ->name('*.php') - ->notName('*.blade.php') - ->ignoreDotFiles(true) - ->ignoreVCS(true); +use PhpCsFixer\Finder; +use Yard\PhpCsFixerRules\Config; -return (new PhpCsFixer\Config) - ->setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - ], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'phpdoc_scalar' => true, - 'phpdoc_var_without_name' => true, - 'phpdoc_single_line_var_spacing' => true, - 'unary_operator_spaces' => true, - 'phpdoc_trim' => true, - 'phpdoc_trim_consecutive_blank_line_separation' => true, - 'align_multiline_comment' => true, - 'array_indentation' => true, - 'no_superfluous_elseif' => true, - 'single_blank_line_before_namespace' => true, - 'blank_line_after_opening_tag' => false, - 'no_blank_lines_after_phpdoc' => true, - 'phpdoc_separation' => true, - 'binary_operator_spaces' => [ - 'default' => 'single_space', - 'operators' => [ - '=' => 'single_space', - '=>' => 'single_space', - '|' => 'no_space', - ], - ], - 'return_type_declaration' => [ - 'space_before' => 'none', - ], - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'full_opening_tag' => true, - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'yoda_style' => [ - 'always_move_variable' => true, - 'equal' => true, - 'identical' => true, - 'less_and_greater' => true, - ], - 'declare_strict_types' => true, - ]) - ->setRiskyAllowed(true) - ->setFinder($finder); +# PHP CS Fixer can be run by using the composer script `composer format` + +$finder = Finder::create() + ->in(__DIR__) + ->name('*.php') + ->notName('*.blade.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true) + ->exclude('vendor') + ->exclude('node_modules') + ->exclude('wp-content') + ->append(['.php-cs-fixer.php']); + +return Config::create($finder); diff --git a/CHANGELOG.md b/CHANGELOG.md index 4005dc1..3feab1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## Version 2.0.0 + +- Breaking: PHP 8.2 is now the minimum supported version. +- Breaking: WordPress 6.5 is now the minimum supported version, required for the `Requires Plugins` header. +- Chore: CMB2 is declared through the `Requires Plugins` plugin header instead of as a Composer runtime dependency, since it is installed as a separate WordPress plugin. It moves to `require-dev` for local development. +- Feature: `okl_date_received` and `okl_judgement_date` are superseded by `okl_date_received_date` and `okl_judgement_date_date`, which render a datepicker and store a fixed `d-m-Y` value instead of a locale-dependent display string. +- Feature: add `okl_judgement_date_sortable` to the Elasticsearch document, alongside the existing `okl_date_received_sortable`. +- Deprecated: `okl_date_received` and `okl_judgement_date` remain registered, writable and exposed via the REST API, and are still used as a fallback when the new fields are empty. No existing data is modified. +- Fix: the Elasticsearch sortable dates are derived from the new fixed-format fields, so they no longer depend on the site locale at submission time. +- Chore: metabox field definitions are passed to CMB2 in full, so field types can carry their own options. +- Chore: add a PHPStan configuration, repair the php-cs-fixer file finder, and modernise the codebase for PHP 8.2. + ## Version 1.0.4 - Chore: update deps diff --git a/autoloader.php b/autoloader.php index 3d86529..e17b90d 100644 --- a/autoloader.php +++ b/autoloader.php @@ -4,22 +4,21 @@ class Autoloader { - - /** - * Autoloader constructor. - * PSR autoloader - */ - public function __construct() - { - spl_autoload_register(function ($className) { - $baseDir = __DIR__.'/src/'; - $namespace = str_replace('\\', '/', __NAMESPACE__); - $className = str_replace('\\', '/', $className); - $class = $baseDir.(empty($namespace) ? '' : $namespace.'/').$className.'.php'; - $class = str_replace('/OWC/OpenKlacht/', '/OpenKlacht/', $class); - if (file_exists($class)) { - require_once($class); - } - }); - } + /** + * Autoloader constructor. + * PSR autoloader + */ + public function __construct() + { + spl_autoload_register(function ($className) { + $baseDir = __DIR__.'/src/'; + $namespace = str_replace('\\', '/', __NAMESPACE__); + $className = str_replace('\\', '/', $className); + $class = $baseDir.(empty($namespace) ? '' : $namespace.'/').$className.'.php'; + $class = str_replace('/OWC/OpenKlacht/', '/OpenKlacht/', $class); + if (file_exists($class)) { + require_once($class); + } + }); + } } diff --git a/composer.json b/composer.json index ff00d01..7dbb00d 100644 --- a/composer.json +++ b/composer.json @@ -1,56 +1,56 @@ { - "name": "plugin/openklacht", - "description": "OpenKlacht plugin", - "license": "MIT", - "authors": [ - { - "name": "Yard | Digital Agency", - "email": "info@yard.nl", - "homepage": "https://www.yard.nl" - } - ], - "config": { - "allow-plugins": { - "composer/installers": true - }, - "preferred-install": "dist", - "optimize-autoloader": true, - "sort-packages": true - }, - "type": "wordpress-plugin", - "repositories": [ - { - "type": "composer", - "url": "https://wpackagist.org" - } - ], - "require": { - "php": "^7.4|^8.0", - "wpackagist-plugin/cmb2": "^2.12" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "~8.0 | ~9.0", - "10up/wp_mock": "~0.5", - "friendsofphp/php-cs-fixer": "^3.0", - "szepeviktor/phpstan-wordpress": "0.7.*", - "symfony/var-dumper": "^5.0", - "wpackagist-plugin/elasticpress": "^3.0", - "zircote/swagger-php": "^3.0" - }, - "autoload": { - "psr-4": { - "OWC\\OpenKlacht\\": "./src/OpenKlacht" - } - }, - "scripts": { - "doc": "clear && php ./docs/openapi-create.php > ./docs/specs/openapi.yaml", - "format": "./vendor/bin/php-cs-fixer fix", - "phpstan": "./vendor/bin/phpstan analyse --no-progress", - "unit": "clear && ./vendor/bin/phpunit --colors=always", - "unit-coverage": "clear && XDEBUG_MODE=coverage ./vendor/bin/phpunit --stderr --colors=always --coverage-html ./tests/logs/coverage", - "test": [ - "@unit" - ] - } + "name": "plugin/openklacht", + "description": "OpenKlacht plugin", + "license": "MIT", + "authors": [ + { + "name": "Yard | Digital Agency", + "email": "info@yard.nl", + "homepage": "https://www.yard.nl" + } + ], + "config": { + "allow-plugins": { + "composer/installers": true + }, + "preferred-install": "dist", + "optimize-autoloader": true, + "sort-packages": true, + "platform": { + "php": "8.2" + } + }, + "type": "wordpress-plugin", + "repositories": [ + { + "name": "wp-packages", + "type": "composer", + "url": "https://repo.wp-packages.org" + } + ], + "require": { + "php": "^8.2", + "ext-intl": "*" + }, + "require-dev": { + "10up/wp_mock": "^1.1", + "php-stubs/gravity-forms-stubs": "^2.9", + "szepeviktor/phpstan-wordpress": "^2.0", + "wp-plugin/cmb2": "^2.12", + "yard/php-cs-fixer-rules": "^1.0" + }, + "autoload": { + "psr-4": { + "OWC\\OpenKlacht\\": "./src/OpenKlacht" + } + }, + "scripts": { + "format": "./vendor/bin/php-cs-fixer fix", + "phpstan": "./vendor/bin/phpstan analyse --no-progress --memory-limit 1G", + "unit": "clear && ./vendor/bin/phpunit --colors=always", + "unit-coverage": "clear && XDEBUG_MODE=coverage ./vendor/bin/phpunit --stderr --colors=always --coverage-html ./tests/logs/coverage", + "test": [ + "@unit" + ] + } } diff --git a/composer.lock b/composer.lock index 40b9244..199c587 100644 --- a/composer.lock +++ b/composer.lock @@ -4,197 +4,40 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a42ee0d1fed5f6bcbb7ed1a3fee03364", - "packages": [ - { - "name": "composer/installers", - "version": "v2.3.0", - "source": { - "type": "git", - "url": "https://github.com/composer/installers.git", - "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", - "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "composer/composer": "^1.10.27 || ^2.7", - "composer/semver": "^1.7.2 || ^3.4.0", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-phpunit": "^1", - "symfony/phpunit-bridge": "^7.1.1", - "symfony/process": "^5 || ^6 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "Composer\\Installers\\Plugin", - "branch-alias": { - "dev-main": "2.x-dev" - }, - "plugin-modifies-install-path": true - }, - "autoload": { - "psr-4": { - "Composer\\Installers\\": "src/Composer/Installers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle Robinson Young", - "email": "kyle@dontkry.com", - "homepage": "https://github.com/shama" - } - ], - "description": "A multi-framework Composer library installer", - "homepage": "https://composer.github.io/installers/", - "keywords": [ - "Dolibarr", - "Eliasis", - "Hurad", - "ImageCMS", - "Kanboard", - "Lan Management System", - "MODX Evo", - "MantisBT", - "Mautic", - "Maya", - "OXID", - "Plentymarkets", - "Porto", - "RadPHP", - "SMF", - "Starbug", - "Thelia", - "Whmcs", - "WolfCMS", - "agl", - "annotatecms", - "attogram", - "bitrix", - "cakephp", - "chef", - "cockpit", - "codeigniter", - "concrete5", - "concreteCMS", - "croogo", - "dokuwiki", - "drupal", - "eZ Platform", - "elgg", - "expressionengine", - "fuelphp", - "grav", - "installer", - "itop", - "known", - "kohana", - "laravel", - "lavalite", - "lithium", - "magento", - "majima", - "mako", - "matomo", - "mediawiki", - "miaoxing", - "modulework", - "modx", - "moodle", - "osclass", - "pantheon", - "phpbb", - "piwik", - "ppi", - "processwire", - "puppet", - "pxcms", - "reindex", - "roundcube", - "shopware", - "silverstripe", - "sydes", - "sylius", - "tastyigniter", - "wordpress", - "yawik", - "zend", - "zikula" - ], - "support": { - "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v2.3.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-06-24T20:46:46+00:00" - }, - { - "name": "wpackagist-plugin/cmb2", - "version": "2.12.0", - "source": { - "type": "svn", - "url": "https://plugins.svn.wordpress.org/cmb2/", - "reference": "tags/2.12.0" - }, - "dist": { - "type": "zip", - "url": "https://downloads.wordpress.org/plugin/cmb2.2.12.0.zip" - }, - "require": { - "composer/installers": "^1.0 || ^2.0" - }, - "type": "wordpress-plugin", - "homepage": "https://wordpress.org/plugins/cmb2/" - } - ], + "content-hash": "2d1d8c36b8bea3890878be200362d9a5", + "packages": [], "packages-dev": [ { "name": "10up/wp_mock", - "version": "0.5.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/10up/wp_mock.git", - "reference": "5cd57c63b1a946301ce0d7aaaa37cdc25805c163" + "reference": "446ea70837dda6174c5a1018a40c213ffe6b29f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/10up/wp_mock/zipball/5cd57c63b1a946301ce0d7aaaa37cdc25805c163", - "reference": "5cd57c63b1a946301ce0d7aaaa37cdc25805c163", + "url": "https://api.github.com/repos/10up/wp_mock/zipball/446ea70837dda6174c5a1018a40c213ffe6b29f7", + "reference": "446ea70837dda6174c5a1018a40c213ffe6b29f7", "shasum": "" }, "require": { "antecedent/patchwork": "^2.1", - "mockery/mockery": "^1.5", - "php": ">=7.3 < 9.0", - "phpunit/phpunit": "^9.5.24" + "mockery/mockery": "^1.6", + "php": ">=7.4 < 9", + "phpunit/phpunit": "^9.6" }, "require-dev": { "behat/behat": "^v3.11.0", - "php-coveralls/php-coveralls": "^v2.5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "friendsofphp/php-cs-fixer": "^3.4", + "php-coveralls/php-coveralls": "^v2.7", + "php-stubs/wordpress-globals": "^0.2", + "php-stubs/wordpress-stubs": "^6.3", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", "sebastian/comparator": "^4.0.8", "sempro/phpunit-pretty-print": "^1.4" }, @@ -209,14 +52,14 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "GPL-2.0-or-later" + "BSD-3-Clause" ], "description": "A mocking library to take the pain out of unit testing for WordPress", "support": { "issues": "https://github.com/10up/wp_mock/issues", - "source": "https://github.com/10up/wp_mock/tree/0.5.0" + "source": "https://github.com/10up/wp_mock/tree/1.1.1" }, - "time": "2022-11-01T03:01:40+00:00" + "time": "2025-12-02T21:19:19+00:00" }, { "name": "antecedent/patchwork", @@ -266,6 +109,49 @@ }, "time": "2025-09-17T09:00:56+00:00" }, + { + "name": "ckr/arraymerger", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/ckressibucher/array_merger.git", + "reference": "e9ac57360ca8c3002a674fdca9fd0b527dd5b651" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ckressibucher/array_merger/zipball/e9ac57360ca8c3002a674fdca9fd0b527dd5b651", + "reference": "e9ac57360ca8c3002a674fdca9fd0b527dd5b651", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpspec/phpspec": "~7.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ckr\\Util\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Claudio Kressibucher", + "email": "ckressibucher@graphicworks.ch" + } + ], + "description": "Provides functionality to merge arrays recursively", + "support": { + "issues": "https://github.com/ckressibucher/array_merger/issues", + "source": "https://github.com/ckressibucher/array_merger/tree/3.0.0" + }, + "time": "2022-04-23T20:26:40+00:00" + }, { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -330,30 +216,185 @@ ], "time": "2022-12-23T10:58:28+00:00" }, + { + "name": "composer/installers", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-24T20:46:46+00:00" + }, { "name": "composer/pcre", - "version": "3.1.4", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "04229f163664973f68f38f6f73d917799168ef24" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", - "reference": "04229f163664973f68f38f6f73d917799168ef24", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { "dev-main": "3.x-dev" } @@ -383,7 +424,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.4" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -393,13 +434,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-05-27T13:40:54+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/semver", @@ -530,244 +567,51 @@ }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "doctrine/annotations", - "version": "1.14.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/253dca476f70808a5aeed3a47cc2cc88c5cab915", - "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1 || ^2", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "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" - }, - "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" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.4" - }, - "abandoned": true, - "time": "2024-09-05T10:15:52+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": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "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", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "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.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/composer", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "doctrine/lexer", - "version": "2.1.1", + "name": "doctrine/instantiator", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "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" + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -776,30 +620,20 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" + "constructor", + "instantiate" ], "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -811,11 +645,11 @@ "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", "type": "tidelift" } ], - "time": "2024-02-05T11:35:39+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "ergebnis/agent-detector", @@ -996,16 +830,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.15", + "version": "v3.95.23", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "3e47e5d50046f87e3244acde2fe655d1a3b72555" + "reference": "b2932a5af3157a0c28324b1efe5e3b556dee09c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3e47e5d50046f87e3244acde2fe655d1a3b72555", - "reference": "3e47e5d50046f87e3244acde2fe655d1a3b72555", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b2932a5af3157a0c28324b1efe5e3b556dee09c4", + "reference": "b2932a5af3157a0c28324b1efe5e3b556dee09c4", "shasum": "" }, "require": { @@ -1041,14 +875,13 @@ "infection/infection": "^0.32.7", "justinrainbow/json-schema": "^6.10.0", "keradus/cli-executor": "^2.3", - "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", - "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31", + "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31 || ^13.0.6", "symfony/polyfill-php85": "^1.38", - "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.0", - "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.0" + "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.1", + "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1089,7 +922,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.15" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.23" }, "funding": [ { @@ -1097,23 +930,25 @@ "type": "github" } ], - "time": "2026-07-15T09:51:47+00:00" + "time": "2026-08-26T19:54:54+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-dom": "*", "php": "^7.4|^8.0" }, "replace": { @@ -1122,13 +957,15 @@ "kodova/hamcrest-php": "*" }, "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1146,35 +983,34 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" }, - "time": "2025-04-30T06:54:44+00:00" + "time": "2026-03-17T11:56:53+00:00" }, { "name": "mockery/mockery", - "version": "1.6.12", + "version": "1.6.15", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", + "hamcrest/hamcrest-php": "^2.0 || ^3.0", "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" }, "type": "library", "autoload": { @@ -1231,24 +1067,24 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-05-16T03:13:13+00:00" + "time": "2026-08-19T19:37:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -1283,15 +1119,15 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nikic/php-parser", @@ -1468,29 +1304,77 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-stubs/gravity-forms-stubs", + "version": "v2.9.3", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/gravity-forms-stubs.git", + "reference": "eabedbf99ff023329cffba624356389cf99cd176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/gravity-forms-stubs/zipball/eabedbf99ff023329cffba624356389cf99cd176", + "reference": "eabedbf99ff023329cffba624356389cf99cd176", + "shasum": "" + }, + "require": { + "php-stubs/wordpress-stubs": "^5.3 || ^6.0" + }, + "require-dev": { + "php": "~7.1 || ~8.0", + "php-stubs/generator": "^0.8.0" + }, + "suggest": { + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Gravity Forms function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/gravity-forms-stubs", + "keywords": [ + "PHPStan", + "gravity-forms", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/gravity-forms-stubs/issues", + "source": "https://github.com/php-stubs/gravity-forms-stubs/tree/v2.9.3" + }, + "time": "2025-02-21T16:58:14+00:00" + }, { "name": "php-stubs/wordpress-stubs", - "version": "v5.9.9", + "version": "v6.9.4", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "06c51c4863659ea9e9f4c2a23293728a677cb059" + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/06c51c4863659ea9e9f4c2a23293728a677cb059", - "reference": "06c51c4863659ea9e9f4c2a23293728a677cb059", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/90a9412826b9944f93b10bf41d795b5fe68abcd5", + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5", "shasum": "" }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "nikic/php-parser": "^4.13", - "php": "^7.4 || ~8.0.0", - "php-stubs/generator": "^0.8.3", - "phpdocumentor/reflection-docblock": "5.3", - "phpstan/phpstan": "^1.10.49", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.6", + "phpdocumentor/reflection-docblock": "^6.0", + "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^9.5", - "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.11" + "symfony/polyfill-php80": "*", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -1511,26 +1395,21 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.9.9" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.4" }, - "time": "2024-04-14T17:16:00+00:00" + "time": "2026-05-01T20:36:01+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.100", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "48236ddf823547081b2b153d1cd2994b784328c3" - }, + "version": "2.2.9", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/48236ddf823547081b2b153d1cd2994b784328c3", - "reference": "48236ddf823547081b2b153d1cd2994b784328c3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1540,11 +1419,6 @@ "phpstan.phar" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.12-dev" - } - }, "autoload": { "files": [ "bootstrap.php" @@ -1554,10 +1428,28 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.100" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -1567,13 +1459,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2022-11-01T09:52:08+00:00" + "time": "2026-08-22T07:38:16+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1896,16 +1784,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.35", + "version": "9.6.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b" + "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/abab27ed286d3e1246fbbfe6b56bfd732d945ec9", + "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9", "shasum": "" }, "require": { @@ -1930,7 +1818,7 @@ "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.8", + "sebastian/exporter": "^4.0.9", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -1979,7 +1867,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.36" }, "funding": [ { @@ -1987,75 +1875,31 @@ "type": "other" } ], - "time": "2026-07-06T14:48:07+00:00" + "time": "2026-08-11T06:25:15+00:00" }, { - "name": "psr/cache", - "version": "1.0.1", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "dev-master": "2.0.x-dev" } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/master" }, - "time": "2016-08-06T20:24:11+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -2082,9 +1926,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -2138,30 +1982,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2182,9 +2026,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "react/cache", @@ -3153,16 +2997,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.8", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", "shasum": "" }, "require": { @@ -3218,7 +3062,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.9" }, "funding": [ { @@ -3238,7 +3082,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:03:27+00:00" + "time": "2026-08-11T04:55:59+00:00" }, { "name": "sebastian/global-state", @@ -3487,16 +3331,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.6", + "version": "4.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + "reference": "c85be6922b7fd365942b986b9a50397d65407611" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c85be6922b7fd365942b986b9a50397d65407611", + "reference": "c85be6922b7fd365942b986b9a50397d65407611", "shasum": "" }, "require": { @@ -3538,7 +3382,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.7" }, "funding": [ { @@ -3558,7 +3402,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T06:57:39+00:00" + "time": "2026-08-11T05:25:24+00:00" }, { "name": "sebastian/resource-operations", @@ -3725,52 +3569,47 @@ }, { "name": "symfony/console", - "version": "v5.4.47", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "url": "https://api.github.com/repos/symfony/console/zipball/962e18f09ebe68a49039b4c82fc0ea4871824fca", + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -3804,7 +3643,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.47" + "source": "https://github.com/symfony/console/tree/v7.4.17" }, "funding": [ { @@ -3815,29 +3654,33 @@ "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": "2024-11-06T11:30:55+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.4", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { @@ -3846,7 +3689,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -3871,7 +3714,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -3882,53 +3725,53 @@ "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": "2024-09-25T14:11:13+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.45", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", - "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.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": "" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -3956,7 +3799,7 @@ "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" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" }, "funding": [ { @@ -3967,34 +3810,35 @@ "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": "2024-09-25T14:11:13+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.4", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -4002,7 +3846,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4035,7 +3879,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -4046,35 +3890,38 @@ "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": "2024-09-25T14:11:13+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.45", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" + "reference": "ee7bc7bca4c7079b88e57d5000aeeb20df570c8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", - "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ee7bc7bca4c7079b88e57d5000aeeb20df570c8d", + "reference": "ee7bc7bca4c7079b88e57d5000aeeb20df570c8d", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^5.4|^6.4" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4102,7 +3949,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.45" + "source": "https://github.com/symfony/filesystem/tree/v7.4.17" }, "funding": [ { @@ -4113,31 +3960,36 @@ "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": "2024-10-22T13:05:35+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/finder", - "version": "v5.4.45", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "63741784cd7b9967975eec610b256eed3ede022b" + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", - "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -4165,7 +4017,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.45" + "source": "https://github.com/symfony/finder/tree/v7.4.17" }, "funding": [ { @@ -4176,32 +4028,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": "2024-09-28T13:32:08+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.45", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -4234,7 +4088,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" }, "funding": [ { @@ -4245,12 +4099,16 @@ "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": "2024-09-25T14:11:13+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4337,16 +4195,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -4395,7 +4253,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -4415,116 +4273,27 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "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 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-intl-normalizer/tree/v1.38.0" - }, - "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-05-25T13:48:31+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" + "php": ">=7.2" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { @@ -4538,8 +4307,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4555,17 +4327,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "intl", + "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -4585,25 +4358,32 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:59:30+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.37.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -4616,11 +4396,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4636,16 +4413,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -4665,7 +4443,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php80", @@ -4913,21 +4691,20 @@ }, { "name": "symfony/process", - "version": "v5.4.51", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f" + "reference": "058d17fc284cce14efb2385783b55014a461b176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", - "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -4955,7 +4732,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.51" + "source": "https://github.com/symfony/process/tree/v7.4.17" }, "funding": [ { @@ -4975,33 +4752,30 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:53:37+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.4", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -5009,13 +4783,16 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5042,7 +4819,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -5053,30 +4830,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": "2024-09-25T14:11:13+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.45", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" + "reference": "70a852d72fec4d51efb1f48dcd968efcaf5ccb89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/70a852d72fec4d51efb1f48dcd968efcaf5ccb89", + "reference": "70a852d72fec4d51efb1f48dcd968efcaf5ccb89", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -5104,7 +4885,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" + "source": "https://github.com/symfony/stopwatch/tree/v7.4.8" }, "funding": [ { @@ -5115,43 +4896,48 @@ "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": "2024-09-25T14:11:13+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/string", - "version": "v5.4.47", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -5190,171 +4976,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.47" - }, - "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-10T20:33:58+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v5.4.48", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/42f18f170aa86d612c3559cfb3bd11a375df32c8", - "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/console": "<4.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "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 mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/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-08T15:21:10+00:00" - }, - { - "name": "symfony/yaml", - "version": "v5.4.53", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77", - "reference": "ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.3" - }, - "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "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": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.53" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -5374,34 +4996,39 @@ "type": "tidelift" } ], - "time": "2026-05-20T17:13:41+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "szepeviktor/phpstan-wordpress", - "version": "v0.7.7", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/szepeviktor/phpstan-wordpress.git", - "reference": "bdbea69b2ba4a69998c3b6fe2b7106d78a23bd72" + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/bdbea69b2ba4a69998c3b6fe2b7106d78a23bd72", - "reference": "bdbea69b2ba4a69998c3b6fe2b7106d78a23bd72", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/aa722f037b2d034828cd6c55ebe9e5c74961927e", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "php-stubs/wordpress-stubs": "^4.7 || ^5.0", - "phpstan/phpstan": "^0.12.26", - "symfony/polyfill-php73": "^1.12.0" + "php": "^7.4 || ^8.0", + "php-stubs/wordpress-stubs": "^6.6.2", + "phpstan/phpstan": "^2.0" }, "require-dev": { - "composer/composer": "^1.10.22", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "composer/composer": "^2.1.14", + "composer/semver": "^3.4", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.1", - "phpstan/phpstan-strict-rules": "^0.12", - "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.6" + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" }, "type": "phpstan-extension", "extra": { @@ -5430,15 +5057,9 @@ ], "support": { "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", - "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v0.7.7" + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.3" }, - "funding": [ - { - "url": "https://www.paypal.me/szepeviktor", - "type": "custom" - } - ], - "time": "2021-07-14T09:19:15+00:00" + "time": "2025-09-14T02:58:22+00:00" }, { "name": "theseer/tokenizer", @@ -5491,95 +5112,137 @@ "time": "2025-11-17T20:03:58+00:00" }, { - "name": "wpackagist-plugin/elasticpress", - "version": "3.6.6", + "name": "webmozart/assert", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.12.1" + }, + "time": "2025-10-29T15:56:20+00:00" + }, + { + "name": "wp-plugin/cmb2", + "version": "2.12.0", "source": { "type": "svn", - "url": "https://plugins.svn.wordpress.org/elasticpress/", - "reference": "tags/3.6.6" + "url": "https://plugins.svn.wordpress.org/cmb2/", + "reference": "tags/2.12.0" }, "dist": { "type": "zip", - "url": "https://downloads.wordpress.org/plugin/elasticpress.3.6.6.zip" + "url": "https://downloads.wordpress.org/plugin/cmb2.2.12.0.zip" }, "require": { - "composer/installers": "^1.0 || ^2.0" + "composer/installers": "~1.0|~2.0" }, "type": "wordpress-plugin", - "homepage": "https://wordpress.org/plugins/elasticpress/" + "notification-url": "https://wp-packages.org/downloads", + "authors": [ + { + "name": "Justin Sternberg" + } + ], + "description": "CMB2 is a metabox, custom fields, and forms library for WordPress that will blow your mind.", + "homepage": "https://github.com/CMB2/CMB2", + "support": { + "changelog": "https://wordpress.org/plugins/cmb2/#developers", + "issues": "https://wordpress.org/support/plugin/cmb2", + "source": "https://plugins.svn.wordpress.org/cmb2" + }, + "time": "2026-05-31T02:46:28+00:00" }, { - "name": "zircote/swagger-php", - "version": "3.3.7", + "name": "yard/php-cs-fixer-rules", + "version": "v1.0.5", "source": { "type": "git", - "url": "https://github.com/zircote/swagger-php.git", - "reference": "e8c3bb316e385e93a0c7e8b2aa0681079244c381" + "url": "https://github.com/yardinternet/php-cs-fixer-rules.git", + "reference": "32d91d38e08e7cc75df82467467670a446429330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e8c3bb316e385e93a0c7e8b2aa0681079244c381", - "reference": "e8c3bb316e385e93a0c7e8b2aa0681079244c381", + "url": "https://api.github.com/repos/yardinternet/php-cs-fixer-rules/zipball/32d91d38e08e7cc75df82467467670a446429330", + "reference": "32d91d38e08e7cc75df82467467670a446429330", "shasum": "" }, "require": { - "doctrine/annotations": "^1.7", - "ext-json": "*", - "php": ">=7.2", - "psr/log": "^1.1 || ^2.0 || ^3.0", - "symfony/finder": ">=2.2", - "symfony/yaml": ">=3.3" + "ckr/arraymerger": "^3.0", + "friendsofphp/php-cs-fixer": "^3.88", + "php": ">=7.4", + "webmozart/assert": "^1.11" }, "require-dev": { - "composer/package-versions-deprecated": "1.11.99.2", - "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", - "phpunit/phpunit": ">=8.5.14" + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.1" }, - "bin": [ - "bin/openapi" - ], - "type": "library", + "type": "package", "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "OpenApi\\": "src" + "Yard\\PhpCsFixerRules\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Robert Allen", - "email": "zircote@gmail.com" - }, - { - "name": "Bob Fanger", - "email": "bfanger@gmail.com", - "homepage": "https://bfanger.nl" - }, - { - "name": "Martin Rademacher", - "email": "mano@radebatz.net", - "homepage": "https://radebatz.net" - } + "MIT" ], - "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", - "homepage": "https://github.com/zircote/swagger-php/", + "description": "Yard PHP CS Fixer rules", "keywords": [ - "api", - "json", - "rest", - "service discovery" + "static analysis" ], "support": { - "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/3.3.7" + "issues": "https://github.com/yardinternet/php-cs-fixer-rules/issues", + "source": "https://github.com/yardinternet/php-cs-fixer-rules/tree/v1.0.5" }, - "time": "2023-01-03T21:17:10+00:00" + "time": "2025-10-17T15:41:39+00:00" } ], "aliases": [], @@ -5588,8 +5251,12 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4|^8.0" + "php": "^8.2", + "ext-intl": "*" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.2" + }, "plugin-api-version": "2.9.0" } diff --git a/config/core.php b/config/core.php index aa98fd8..7f62a51 100644 --- a/config/core.php +++ b/config/core.php @@ -1,12 +1,14 @@ [ - OWC\OpenKlacht\RestAPI\RestAPIServiceProvider::class, - OWC\OpenKlacht\Admin\AdminServiceProvider::class, - OWC\OpenKlacht\GravityForms\GravityFormsServiceProvider::class, - OWC\OpenKlacht\PostType\PostTypeServiceProvider::class, - OWC\OpenKlacht\ElasticSearch\ElasticSearchServiceProvider::class, - OWC\OpenKlacht\Settings\SettingsServiceProvider::class, - ], + 'providers' => [ + OWC\OpenKlacht\RestAPI\RestAPIServiceProvider::class, + OWC\OpenKlacht\Admin\AdminServiceProvider::class, + OWC\OpenKlacht\GravityForms\GravityFormsServiceProvider::class, + OWC\OpenKlacht\PostType\PostTypeServiceProvider::class, + OWC\OpenKlacht\ElasticSearch\ElasticSearchServiceProvider::class, + OWC\OpenKlacht\Settings\SettingsServiceProvider::class, + ], ]; diff --git a/config/metaboxes.php b/config/metaboxes.php index 8c35558..297b70d 100644 --- a/config/metaboxes.php +++ b/config/metaboxes.php @@ -1,21 +1,45 @@ __('Kenmerk', 'openklacht'), 'id' => 'reference', 'type' => 'text'], - ['name' => __('Onderwerp', 'openklacht'), 'id' => 'title', 'type' => 'text'], - ['name' => __('Ontvangstdatum', 'openklacht'), 'id' => 'date_received', 'type' => 'text'], - ['name' => __('Betrokken organisatie onderdeel', 'openklacht'), 'id' => 'organization', 'type' => 'text'], - ['name' => __('Functiebenaming ambtenaar over wie geklaagd is', 'openklacht'), 'id' => 'function', 'type' => 'text'], - ['name' => __('Omschrijving', 'openklacht'), 'id' => 'description', 'type' => 'wysiwyg'], - ['name' => __('Bevinding', 'openklacht'), 'id' => 'findings', 'type' => 'wysiwyg'], - ['name' => __('Oordeel', 'openklacht'), 'id' => 'judgement', 'type' => 'select', 'options' => - [ - 'Gegrond' => __('Gegrond', 'openklacht'), - 'Deels gegrond, deels ongegrond' => __('Deels gegrond, deels ongegrond', 'openklacht'), - 'Ongegrond' => __('Ongegrond', 'openklacht'), - 'Niet ontvankelijk' => __('Niet ontvankelijk', 'openklacht'), - ], - ], - ['name' => __('Conclusie', 'openklacht'), 'id' => 'conclusion', 'type' => 'wysiwyg'], - ['name' => __('Dagtekening van het oordeel', 'openklacht'), 'id' => 'judgement_date', 'type' => 'text'], + ['name' => __('Kenmerk', 'openklacht'), 'id' => 'reference', 'type' => 'text'], + ['name' => __('Onderwerp', 'openklacht'), 'id' => 'title', 'type' => 'text'], + [ + 'name' => __('Ontvangstdatum', 'openklacht'), + 'id' => 'date_received_date', + 'type' => 'text_date', + 'date_format' => 'd-m-Y', + ], + [ + 'name' => __('Ontvangstdatum (verouderd)', 'openklacht'), + 'id' => 'date_received', + 'type' => 'text', + 'desc' => __('Verouderd veld, alleen nog in gebruik voor bestaande klachten. Vul hierboven de Ontvangstdatum in.', 'openklacht'), + ], + ['name' => __('Betrokken organisatie onderdeel', 'openklacht'), 'id' => 'organization', 'type' => 'text'], + ['name' => __('Functiebenaming ambtenaar over wie geklaagd is', 'openklacht'), 'id' => 'function', 'type' => 'text'], + ['name' => __('Omschrijving', 'openklacht'), 'id' => 'description', 'type' => 'wysiwyg'], + ['name' => __('Bevinding', 'openklacht'), 'id' => 'findings', 'type' => 'wysiwyg'], + ['name' => __('Oordeel', 'openklacht'), 'id' => 'judgement', 'type' => 'select', 'options' => + [ + 'Gegrond' => __('Gegrond', 'openklacht'), + 'Deels gegrond, deels ongegrond' => __('Deels gegrond, deels ongegrond', 'openklacht'), + 'Ongegrond' => __('Ongegrond', 'openklacht'), + 'Niet ontvankelijk' => __('Niet ontvankelijk', 'openklacht'), + ], + ], + ['name' => __('Conclusie', 'openklacht'), 'id' => 'conclusion', 'type' => 'wysiwyg'], + [ + 'name' => __('Dagtekening van het oordeel', 'openklacht'), + 'id' => 'judgement_date_date', + 'type' => 'text_date', + 'date_format' => 'd-m-Y', + ], + [ + 'name' => __('Dagtekening van het oordeel (verouderd)', 'openklacht'), + 'id' => 'judgement_date', + 'type' => 'text', + 'desc' => __('Verouderd veld, alleen nog in gebruik voor bestaande klachten. Vul hierboven de Dagtekening van het oordeel in.', 'openklacht'), + ], ]; diff --git a/config/settings_pages.php b/config/settings_pages.php index 2a7fece..8cbb5f1 100644 --- a/config/settings_pages.php +++ b/config/settings_pages.php @@ -1,34 +1,36 @@ [ - 'id' => '_owc_openklacht_base_settings', - 'title' => __('OpenKlacht instellingen', 'openklacht'), - 'object_types' => ['options-page'], - 'option_key' => '_owc_openklacht_base_settings', - 'tab_group' => 'openklacht-base', - 'tab_title' => __('Algemeen', 'openklacht'), - 'position' => 30, - 'icon_url' => 'dashicons-admin-settings', - 'fields' => [ - 'openklacht_portal_url' => [ - 'name' => __('Portaal URL', 'openklacht'), - 'desc' => __('URL inclusief http(s)://', 'openklacht'), - 'id' => 'setting_openklacht_portal_url', - 'type' => 'text', - ], - 'openklacht_item_slug' => [ - 'name' => __('Portaal OpenKlacht item slug', 'openklacht'), - 'desc' => __("URL voor OpenKlacht items in het portaal, bijv. 'onderwerp'", 'openklacht'), - 'id' => 'setting_openklacht_item_slug', - 'type' => 'text', - ], - 'openklacht_use_portal_url' => [ - 'name' => __('Portaal URL (API)', 'openklacht'), - 'desc' => __('Gebruik het portaal URL in de REST API.', 'openklacht'), - 'id' => 'setting_openklacht_use_portal_url', - 'type' => 'checkbox', - ], - ], - ], + 'openklacht-base' => [ + 'id' => '_owc_openklacht_base_settings', + 'title' => __('OpenKlacht instellingen', 'openklacht'), + 'object_types' => ['options-page'], + 'option_key' => '_owc_openklacht_base_settings', + 'tab_group' => 'openklacht-base', + 'tab_title' => __('Algemeen', 'openklacht'), + 'position' => 30, + 'icon_url' => 'dashicons-admin-settings', + 'fields' => [ + 'openklacht_portal_url' => [ + 'name' => __('Portaal URL', 'openklacht'), + 'desc' => __('URL inclusief http(s)://', 'openklacht'), + 'id' => 'setting_openklacht_portal_url', + 'type' => 'text', + ], + 'openklacht_item_slug' => [ + 'name' => __('Portaal OpenKlacht item slug', 'openklacht'), + 'desc' => __("URL voor OpenKlacht items in het portaal, bijv. 'onderwerp'", 'openklacht'), + 'id' => 'setting_openklacht_item_slug', + 'type' => 'text', + ], + 'openklacht_use_portal_url' => [ + 'name' => __('Portaal URL (API)', 'openklacht'), + 'desc' => __('Gebruik het portaal URL in de REST API.', 'openklacht'), + 'id' => 'setting_openklacht_use_portal_url', + 'type' => 'checkbox', + ], + ], + ], ]; diff --git a/languages/openklacht.pot b/languages/openklacht.pot index a2f91d4..504b526 100644 --- a/languages/openklacht.pot +++ b/languages/openklacht.pot @@ -1,16 +1,17 @@ -# Copyright (C) 2024 Yard | Digital Agency +# Copyright (C) 2026 Yard | Digital Agency # This file is distributed under the EUPL-1.2. msgid "" msgstr "" -"Project-Id-Version: Yard | OpenKlacht 0.0.1\n" +"Project-Id-Version: Yard | OpenKlacht 2.0.0\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/openklacht\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-07-09T13:08:34+00:00\n" +"POT-Creation-Date: 2026-08-27T09:57:08+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"X-Generator: WP-CLI 2.10.0\n" +"X-Generator: WP-CLI 2.12.0\n" "X-Domain: openklacht\n" #. Plugin Name of the plugin @@ -34,94 +35,110 @@ msgstr "" msgid "Yard | Digital Agency" msgstr "" -#: src/OpenKlacht/PostType/MetaboxFields.php:20 -msgid "Gegevens" -msgstr "" - -#: config/metaboxes.php:4 +#: config/metaboxes.php:15 msgid "Kenmerk" msgstr "" -#: config/metaboxes.php:5 +#: config/metaboxes.php:16 msgid "Onderwerp" msgstr "" -#: config/metaboxes.php:6 +#: config/metaboxes.php:18 msgid "Ontvangstdatum" msgstr "" -#: config/metaboxes.php:7 +#: config/metaboxes.php:24 +msgid "Ontvangstdatum (verouderd)" +msgstr "" + +#: config/metaboxes.php:27 +msgid "Verouderd veld, alleen nog in gebruik voor bestaande klachten. Vul hierboven de Ontvangstdatum in." +msgstr "" + +#: config/metaboxes.php:29 msgid "Betrokken organisatie onderdeel" msgstr "" -#: config/metaboxes.php:8 +#: config/metaboxes.php:30 msgid "Functiebenaming ambtenaar over wie geklaagd is" msgstr "" -#: config/metaboxes.php:9 +#: config/metaboxes.php:31 msgid "Omschrijving" msgstr "" -#: config/metaboxes.php:§0 +#: config/metaboxes.php:32 msgid "Bevinding" msgstr "" -#: config/metaboxes.php:11 +#: config/metaboxes.php:33 msgid "Oordeel" msgstr "" -#: config/metaboxes.php:13 +#: config/metaboxes.php:35 msgid "Gegrond" msgstr "" -#: config/metaboxes.php:14 +#: config/metaboxes.php:36 msgid "Deels gegrond, deels ongegrond" msgstr "" -#: config/metaboxes.php:15 +#: config/metaboxes.php:37 msgid "Ongegrond" msgstr "" -#: config/metaboxes.php:16 +#: config/metaboxes.php:38 msgid "Niet ontvankelijk" msgstr "" -#: config/metaboxes.php:19 +#: config/metaboxes.php:41 msgid "Conclusie" msgstr "" -#: config/metaboxes.php:20 +#: config/metaboxes.php:43 msgid "Dagtekening van het oordeel" msgstr "" -#: config/settings_pages.php:6 +#: config/metaboxes.php:49 +msgid "Dagtekening van het oordeel (verouderd)" +msgstr "" + +#: config/metaboxes.php:52 +msgid "Verouderd veld, alleen nog in gebruik voor bestaande klachten. Vul hierboven de Dagtekening van het oordeel in." +msgstr "" + +#: config/settings_pages.php:8 msgid "OpenKlacht instellingen" msgstr "" -#: config/settings_pages.php:10 +#: config/settings_pages.php:12 msgid "Algemeen" msgstr "" -#: config/settings_pages.php:15 +#: config/settings_pages.php:17 msgid "Portaal URL" msgstr "" -#: config/settings_pages.php:16 +#: config/settings_pages.php:18 msgid "URL inclusief http(s)://" msgstr "" -#: config/settings_pages.php:21 +#: config/settings_pages.php:23 msgid "Portaal OpenKlacht item slug" msgstr "" -#: config/settings_pages.php:22 +#: config/settings_pages.php:24 msgid "URL voor OpenKlacht items in het portaal, bijv. 'onderwerp'" msgstr "" -#: config/settings_pages.php:27 +#: config/settings_pages.php:29 msgid "Portaal URL (API)" msgstr "" -#: config/settings_pages.php:28 +#: config/settings_pages.php:30 msgid "Gebruik het portaal URL in de REST API." msgstr "" + +#: src/OpenKlacht/PostType/MetaboxFields.php:17 +msgid "Gegevens" +msgstr "" diff --git a/openklacht.php b/openklacht.php index 250731b..23a7290 100644 --- a/openklacht.php +++ b/openklacht.php @@ -6,7 +6,10 @@ * Plugin Name: Yard | OpenKlacht * Plugin URI: https://www.yard.nl/ * Description: OpenKlacht implementation - * Version: 1.0.4 + * Version: 2.0.0 + * Requires at least: 6.5 + * Requires PHP: 8.2 + * Requires Plugins: cmb2 * Author: Yard | Digital Agency * Author URI: https://www.yard.nl/ * License: EUPL-1.2 @@ -19,16 +22,16 @@ * If this file is called directly, abort. */ if (! defined('WPINC')) { - die; + die; } -if (!class_exists(OWC\OpenKlacht\OpenKlachtServiceProvider::class)) { - if (file_exists(__DIR__ . '/vendor/autoload.php')) { - require_once __DIR__ . '/vendor/autoload.php'; - } else { - require_once __DIR__ . '/autoloader.php'; - $autoloader = new Autoloader(); - } +if (! class_exists(OWC\OpenKlacht\OpenKlachtServiceProvider::class)) { + if (file_exists(__DIR__ . '/vendor/autoload.php')) { + require_once __DIR__ . '/vendor/autoload.php'; + } else { + require_once __DIR__ . '/autoloader.php'; + $autoloader = new Autoloader(); + } } define('OWC_OPENKLACHT_ROOT_PATH', __DIR__); @@ -43,6 +46,6 @@ */ add_action('plugins_loaded', function () { add_action('after_setup_theme', function () { - (new OWC\OpenKlacht\OpenKlachtServiceProvider())->register(); + (new OWC\OpenKlacht\OpenKlachtServiceProvider())->register(); }); }, 10); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..52ee927 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,19 @@ +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + level: 5 + phpVersion: 80200 + paths: + - src + - config + - openklacht.php + bootstrapFiles: + - tests/phpstan/bootstrap.php + scanFiles: + - autoloader.php + - vendor/php-stubs/gravity-forms-stubs/gravity-forms-stubs.php + scanDirectories: + # CMB2 is a require-dev wordpress-plugin, so composer/installers puts its + # real source here rather than in vendor/. + - wp-content/plugins/cmb2/includes diff --git a/publiccode.yaml b/publiccode.yaml index 9672250..839d435 100644 --- a/publiccode.yaml +++ b/publiccode.yaml @@ -31,12 +31,14 @@ legal: dependsOn: open: - - name: 'extended-cpts' - - versionMin: 4.5.2 - - name: 'cmb2' - - versionMin: 2.10.1 + - name: 'extended-cpts' + versionMin: '4.5.2' + - name: 'cmb2' + versionMin: '2.10.1' - name: 'WordPress' - - versionMin: 6.0.0 + versionMin: '6.5.0' + - name: 'PHP' + versionMin: '8.2' intendedAudience: countries: diff --git a/src/OpenKlacht/Admin/AdminServiceProvider.php b/src/OpenKlacht/Admin/AdminServiceProvider.php index e3893a4..c826b6a 100644 --- a/src/OpenKlacht/Admin/AdminServiceProvider.php +++ b/src/OpenKlacht/Admin/AdminServiceProvider.php @@ -1,5 +1,7 @@ post_type || ! $this->settings->isPortalSlugValid()) { - return $link; - } - - return sprintf('%s/%s/%s/', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug()), ($leavename ? '%postname%' : $post->post_name)); - } - - /** - * Change the url for preview of draft posts in the portal. - */ - public function filterPreviewLink(string $link, WP_Post $post): string - { - if ('openklacht' !== $post->post_type || ! $this->settings->isPortalSlugValid()) { - return $link; - } - - return sprintf('%s/%s?draft-preview=true', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug())); - } - - /** - * Change the url of "preview in new tab" button for preview in the portal. - */ - public function filterPreviewInNewTabLink(WP_REST_Response $response, WP_Post $post): WP_REST_Response - { - if ('publish' === $post->post_status || ! $this->settings->isPortalSlugValid()) { - return $response; - } - - $response->data['link'] = sprintf('%s/%s?draft-preview=true', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug())); - - return $response; - } + public function register(): void + { + add_filter('post_type_link', $this->filterPostLink(...), 10, 4); + add_filter('preview_post_link', $this->filterPreviewLink(...), 10, 2); + add_filter('rest_prepare_openklacht', $this->filterPreviewInNewTabLink(...), 10, 2); + } + + /** + * Change the url for preview of published posts in the portal. + */ + public function filterPostLink(string $link, WP_Post $post, bool $leavename, bool $sample): string + { + if ('openklacht' !== $post->post_type || ! $this->settings->isPortalSlugValid()) { + return $link; + } + + return sprintf('%s/%s/%s/', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug()), ($leavename ? '%postname%' : $post->post_name)); + } + + /** + * Change the url for preview of draft posts in the portal. + */ + public function filterPreviewLink(string $link, WP_Post $post): string + { + if ('openklacht' !== $post->post_type || ! $this->settings->isPortalSlugValid()) { + return $link; + } + + return sprintf('%s/%s?draft-preview=true', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug())); + } + + /** + * Change the url of "preview in new tab" button for preview in the portal. + */ + public function filterPreviewInNewTabLink(WP_REST_Response $response, WP_Post $post): WP_REST_Response + { + if ('publish' === $post->post_status || ! $this->settings->isPortalSlugValid()) { + return $response; + } + + $response->data['link'] = sprintf('%s/%s?draft-preview=true', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug())); + + return $response; + } } diff --git a/src/OpenKlacht/ElasticSearch/ElasticSearchServiceProvider.php b/src/OpenKlacht/ElasticSearch/ElasticSearchServiceProvider.php index 84f8f19..56ab88a 100644 --- a/src/OpenKlacht/ElasticSearch/ElasticSearchServiceProvider.php +++ b/src/OpenKlacht/ElasticSearch/ElasticSearchServiceProvider.php @@ -1,18 +1,20 @@ hooks(); - } + public function register(): void + { + $this->hooks(); + } - public function hooks(): void - { - add_filter('ep_post_sync_args', [new Hooks, 'extendSyncArgs'], 20, 2); - } + public function hooks(): void + { + add_filter('ep_post_sync_args', (new Hooks())->extendSyncArgs(...), 20, 2); + } } diff --git a/src/OpenKlacht/Foundation/Config.php b/src/OpenKlacht/Foundation/Config.php index 908f139..d368717 100644 --- a/src/OpenKlacht/Foundation/Config.php +++ b/src/OpenKlacht/Foundation/Config.php @@ -1,5 +1,7 @@ items = $items; - $this->path = $path; - } - - /** - * Boot up the configuration repository. - */ - public function boot(): void - { - $this->scanDirectory($this->getPath()); - } - - /** - * Retrieve a specific config value from the configuration repository. - * - * @param $setting - * - * @return array|mixed - */ - public function get($setting) - { - if (! $setting) { - return $this->all(); - } - - $parts = explode('.', $setting); - - $current = $this->items; - - foreach ($parts as $part) { - $current = $current[$part]; - } - - return $current; - } - - /** - * Set a given configuration value. - * - * @param array|string $key - * @param mixed $value - */ - public function set($key, $value = null): void - { - $keys = is_array($key) ? $key : [ $key => $value ]; - - $tempItems = &$this->items; - - foreach ($keys as $key => $value) { - if (in_array($key, $this->protectedNodes)) { - continue; - } - - $parts = explode('.', $key); - while (1 < count($parts)) { - $part = array_shift($parts); - // If the key doesn't exist at this depth, we will just create an empty array - // to hold the next value, allowing us to create the arrays to hold final - // values at the correct depth. Then we'll keep digging into the array. - if (! isset($tempItems[$part]) || ! is_array($tempItems[$part])) { - $tempItems[$part] = []; - } - $tempItems = &$tempItems[$part]; - } - - $tempItems[array_shift($parts)] = $value; - } - } - - /** - * Return all config values. - */ - public function all(): array - { - return $this->items; - } - - /** - * Get the path where the files will be fetched from. - */ - public function getPath(): string - { - return $this->path; - } - - /** - * Sets the path where the config files are fetched from. - */ - public function setPath(string $path) - { - $this->path = $path; - } - - /** - * Some nodes must not be changed by outside interference. - */ - public function setProtectedNodes(array $nodes = []) - { - $this->protectedNodes = $nodes; - } - - /** - * Scan a given directory for certain files. - */ - private function scanDirectory(string $path) - { - $files = glob($path.'/*', GLOB_NOSORT); - - foreach ($files as $file) { - $fileType = filetype($file); - - if ('dir' == $fileType) { - $this->scanDirectory($file); - } else { - $name = str_replace('.php', '', basename($file)); - $value = include $file; - - // If its in the first directory just add the file. - if ($path == $this->path) { - $this->items[$name] = $value; - - continue; - } - - // Get the path from the starting path. - $path = str_replace($this->path.'/', '', $path); - - // Build an array from the path. - $items = []; - $items[$name] = $value; - foreach (array_reverse(explode('/', $path)) as $key) { - $items = [ $key => $items ]; - } - - // Merge it recursively into items - $this->items = array_merge_recursive($this->items, $items); - } - } - } + /** + * Array with names of protected nodes in the config-items. + */ + protected array $protectedNodes = []; + + /** + * Config repository constructor. + * + * Boot the configuration files and get all the files from the + * config directory and add them to the config array. + * + * @param string $path Directory where config files are located. + * @param array $items Array with all the config values. + */ + public function __construct( + protected string $path, + protected array $items = [] + ) { + } + + /** + * Boot up the configuration repository. + */ + public function boot(): void + { + $this->scanDirectory($this->getPath()); + } + + /** + * Retrieve a specific config value from the configuration repository. + */ + public function get(?string $setting): mixed + { + if (! $setting) { + return $this->all(); + } + + $parts = explode('.', $setting); + + $current = $this->items; + + foreach ($parts as $part) { + $current = $current[$part]; + } + + return $current; + } + + /** + * Set a given configuration value. + * + * @param array|string $key + */ + public function set(array|string $key, mixed $value = null): void + { + $keys = is_array($key) ? $key : [ $key => $value ]; + + $tempItems = &$this->items; + + foreach ($keys as $key => $value) { + if (in_array($key, $this->protectedNodes)) { + continue; + } + + $parts = explode('.', $key); + while (1 < count($parts)) { + $part = array_shift($parts); + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (! isset($tempItems[$part]) || ! is_array($tempItems[$part])) { + $tempItems[$part] = []; + } + $tempItems = &$tempItems[$part]; + } + + $tempItems[array_shift($parts)] = $value; + } + } + + /** + * Return all config values. + */ + public function all(): array + { + return $this->items; + } + + /** + * Get the path where the files will be fetched from. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Sets the path where the config files are fetched from. + */ + public function setPath(string $path): void + { + $this->path = $path; + } + + /** + * Some nodes must not be changed by outside interference. + */ + public function setProtectedNodes(array $nodes = []): void + { + $this->protectedNodes = $nodes; + } + + /** + * Scan a given directory for certain files. + */ + private function scanDirectory(string $path): void + { + $files = glob($path.'/*', GLOB_NOSORT); + + foreach ($files as $file) { + $fileType = filetype($file); + + if ('dir' == $fileType) { + $this->scanDirectory($file); + } else { + $name = str_replace('.php', '', basename($file)); + $value = include $file; + + // If its in the first directory just add the file. + if ($path == $this->path) { + $this->items[$name] = $value; + + continue; + } + + // Get the path from the starting path. + $path = str_replace($this->path.'/', '', $path); + + // Build an array from the path. + $items = []; + $items[$name] = $value; + foreach (array_reverse(explode('/', $path)) as $key) { + $items = [ $key => $items ]; + } + + // Merge it recursively into items + $this->items = array_merge_recursive($this->items, $items); + } + } + } } diff --git a/src/OpenKlacht/Foundation/ServiceProvider.php b/src/OpenKlacht/Foundation/ServiceProvider.php index 322ee77..9d569fe 100644 --- a/src/OpenKlacht/Foundation/ServiceProvider.php +++ b/src/OpenKlacht/Foundation/ServiceProvider.php @@ -1,30 +1,32 @@ init(); - } + public function __construct() + { + $this->init(); + } - private function init(): void - { - $this->config = new Config(OWC_OPENKLACHT_ROOT_PATH . '/config'); - $this->config->boot(); + private function init(): void + { + $this->config = new Config(OWC_OPENKLACHT_ROOT_PATH . '/config'); + $this->config->boot(); - $this->settings = SettingsPageOptions::make(); + $this->settings = SettingsPageOptions::make(); - \load_plugin_textdomain(self::NAME, false, self::NAME . '/languages/'); - } + \load_plugin_textdomain(self::NAME, false, self::NAME . '/languages/'); + } - abstract public function register(): void; + abstract public function register(): void; } diff --git a/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php b/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php index f4c733a..d0ef2ed 100644 --- a/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php +++ b/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php @@ -4,25 +4,25 @@ namespace OWC\OpenKlacht\GravityForms; +/** + * @phpstan-consistent-constructor + */ abstract class AbstractAfterSubmit { - protected array $form; - protected string $formTitle; - protected array $values; - protected array $entry; + protected string $formTitle; - public function __construct(array $form, array $values, array $entry) - { - $this->form = $form; - $this->formTitle = $form['title']; - $this->values = $values; - $this->entry = $entry; - } + public function __construct( + protected readonly array $form, + protected array $values, + protected readonly array $entry + ) { + $this->formTitle = $form['title']; + } - public static function make(array $form, array $values, array $entry): self - { - return new static($form, $values, $entry); - } + public static function make(array $form, array $values, array $entry): static + { + return new static($form, $values, $entry); + } - abstract public function handle(): void; + abstract public function handle(): void; } diff --git a/src/OpenKlacht/GravityForms/GravityFormsServiceProvider.php b/src/OpenKlacht/GravityForms/GravityFormsServiceProvider.php index bb3ac69..977daa0 100644 --- a/src/OpenKlacht/GravityForms/GravityFormsServiceProvider.php +++ b/src/OpenKlacht/GravityForms/GravityFormsServiceProvider.php @@ -8,13 +8,13 @@ class GravityFormsServiceProvider extends ServiceProvider { - public function register(): void - { - add_action('gform_after_submission', [$this, 'handleFormSubmission'], 10, 2); - } + public function register(): void + { + add_action('gform_after_submission', $this->handleFormSubmission(...), 10, 2); + } - public function handleFormSubmission($entry, $form): void - { - SubmissionHandler::make($entry, $form)->handle(); - } + public function handleFormSubmission(array $entry, array $form): void + { + SubmissionHandler::make($entry, $form)->handle(); + } } diff --git a/src/OpenKlacht/GravityForms/SubmissionHandler.php b/src/OpenKlacht/GravityForms/SubmissionHandler.php index 22d25e1..8caf1c3 100644 --- a/src/OpenKlacht/GravityForms/SubmissionHandler.php +++ b/src/OpenKlacht/GravityForms/SubmissionHandler.php @@ -6,78 +6,90 @@ use GF_Field; +/** + * @phpstan-consistent-constructor + */ class SubmissionHandler { - private array $entry; - private array $form; - private const FORMS = [ - 'Formulier OpenKlacht' => AfterSubmit::class, - ]; - - public function __construct($entry, $form) - { - $this->entry = $entry; - $this->form = $form; - } - - public static function make($entry, $form): self - { - return new static($entry, $form); - } - - public function handle(): array - { - if (empty($form = $this->determineForm())) { - return $this->entry; - } - - $enteredValues = $this->getEnteredValues(); - $form::make($this->form, $enteredValues, $this->entry)->handle(); - - return $this->entry; - } - - private function determineForm(): string - { - return self::FORMS[$this->form['title']] ?? ''; - } - - private function getEnteredValues(): array - { - $values = $this->mapValues(); - - return array_filter($values); - } - - private function mapValues(): array - { - $mapping = []; - - foreach ($this->form['fields'] as $field) { - if (empty($field['id']) || empty($field['label'])) { - continue; - } - - if (! empty($field->inputs)) { - $mapping = $this->mapValuesFromInputs($mapping, $field); - } else { - $mapping[$field['adminLabel'] ? sanitize_title($field['adminLabel']) : sanitize_title($field['label'])] = rgar($this->entry, $field['id']); - } - } - - return $mapping; - } - - private function mapValuesFromInputs(array $mapping, GF_Field $field): array - { - foreach ($field->inputs as $input) { - if (empty($input['id'])) { - continue; - } - - $mapping[! empty($field['adminLabel']) ? sanitize_title($field['adminLabel']) : sanitize_title($field['label'])][] = rgar($this->entry, $input['id']); - } - - return $mapping; - } + private const FORMS = [ + 'Formulier OpenKlacht' => AfterSubmit::class, + ]; + + public function __construct( + private readonly array $entry, + private readonly array $form + ) { + } + + public static function make(array $entry, array $form): static + { + return new static($entry, $form); + } + + public function handle(): array + { + if (empty($form = $this->determineForm())) { + return $this->entry; + } + + $enteredValues = $this->getEnteredValues(); + $form::make($this->form, $enteredValues, $this->entry)->handle(); + + return $this->entry; + } + + private function determineForm(): string + { + return self::FORMS[$this->form['title']] ?? ''; + } + + private function getEnteredValues(): array + { + $values = $this->mapValues(); + + return array_filter($values); + } + + private function mapValues(): array + { + $mapping = []; + + foreach ($this->form['fields'] as $field) { + if (empty($field['id']) || empty($field['label'])) { + continue; + } + + if (! empty($field->inputs)) { + $mapping = $this->mapValuesFromInputs($mapping, $field); + } else { + $mapping[$this->fieldKey($field)] = rgar($this->entry, $field['id']); + } + } + + return $mapping; + } + + private function mapValuesFromInputs(array $mapping, GF_Field $field): array + { + foreach ($field->inputs as $input) { + if (empty($input['id'])) { + continue; + } + + $mapping[$this->fieldKey($field)][] = rgar($this->entry, $input['id']); + } + + return $mapping; + } + + /** + * The admin label wins when set, so form labels can be reworded without breaking + * the mapping the AfterSubmit handlers read. + */ + private function fieldKey(GF_Field $field): string + { + return ! empty($field['adminLabel']) + ? sanitize_title($field['adminLabel']) + : sanitize_title($field['label']); + } } diff --git a/src/OpenKlacht/OpenKlachtServiceProvider.php b/src/OpenKlacht/OpenKlachtServiceProvider.php index e3de057..3db7364 100644 --- a/src/OpenKlacht/OpenKlachtServiceProvider.php +++ b/src/OpenKlacht/OpenKlachtServiceProvider.php @@ -1,30 +1,32 @@ bootProviders(); - } - - protected function bootProviders(): void - { - $providers = $this->config->get('core.providers'); - - if (! is_array($providers) || empty($providers)) { - return; - } - - foreach ($providers as $provider) { - if (! class_exists($provider)) { - continue; - } - - (new $provider)->register(); - } - } + public function register(): void + { + $this->bootProviders(); + } + + protected function bootProviders(): void + { + $providers = $this->config->get('core.providers'); + + if (! is_array($providers) || empty($providers)) { + return; + } + + foreach ($providers as $provider) { + if (! class_exists($provider)) { + continue; + } + + (new $provider())->register(); + } + } } diff --git a/src/OpenKlacht/PostType/MetaboxFields.php b/src/OpenKlacht/PostType/MetaboxFields.php index 9315564..50aa29c 100644 --- a/src/OpenKlacht/PostType/MetaboxFields.php +++ b/src/OpenKlacht/PostType/MetaboxFields.php @@ -6,38 +6,30 @@ class MetaboxFields { - protected array $fields; - - public function __construct(array $fields) - { - $this->fields = $fields; - - } - public function createMetaFields(): void - { - $metaboxes = new_cmb2_box([ - 'id' => 'klacht_metabox', - 'title' => __('Gegevens', 'openklacht'), - 'object_types' => [ - 'openklacht', - ], - 'context' => 'normal', - 'priority' => 'high', - 'show_names' => true, - ]); - - foreach ($this->fields as $field) { - $fieldData = [ - 'name' => $field['name'], - 'id' => sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']), - 'type' => $field['type'], - ]; - - if ('select' === $field['type'] && isset($field['options'])) { - $fieldData['options'] = $field['options']; - } - - $metaboxes->add_field($fieldData); - } - } + public function __construct(protected readonly array $fields) + { + } + + public function createMetaFields(): void + { + $metaboxes = new_cmb2_box([ + 'id' => 'klacht_metabox', + 'title' => __('Gegevens', 'openklacht'), + 'object_types' => [ + 'openklacht', + ], + 'context' => 'normal', + 'priority' => 'high', + 'show_names' => true, + ]); + + foreach ($this->fields as $field) { + // The whole definition is passed through so field types can carry their + // own options ('date_format', 'desc', 'attributes', ...). CMB2 ignores + // keys it does not recognise for a given type. + $field['id'] = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); + + $metaboxes->add_field($field); + } + } } diff --git a/src/OpenKlacht/PostType/PostTypeServiceProvider.php b/src/OpenKlacht/PostType/PostTypeServiceProvider.php index 1bdff94..5f4d3bc 100644 --- a/src/OpenKlacht/PostType/PostTypeServiceProvider.php +++ b/src/OpenKlacht/PostType/PostTypeServiceProvider.php @@ -1,37 +1,38 @@ registerPostTypes(...)); + add_action('cmb2_admin_init', $this->registerMetaFields(...)); + } - public function registerPostTypes(): void - { - register_post_type('openklacht', [ - 'label' => 'OpenKlacht', - 'public' => true, - 'publicly_queryable' => true, - 'show_ui' => true, - 'show_in_menu' => true, - 'show_in_rest' => true, - 'query_var' => false, - 'capability_type' => 'post', - 'has_archive' => false, - 'hierarchical' => false, - 'menu_position' => null, - 'supports' => ['title', 'author'], - ]); - } + public function registerPostTypes(): void + { + register_post_type('openklacht', [ + 'label' => 'OpenKlacht', + 'public' => true, + 'publicly_queryable' => true, + 'show_ui' => true, + 'show_in_menu' => true, + 'show_in_rest' => true, + 'query_var' => false, + 'capability_type' => 'post', + 'has_archive' => false, + 'hierarchical' => false, + 'supports' => ['title', 'author'], + ]); + } - public function registerMetaFields(): void - { - (new MetaboxFields($this->config->get('metaboxes')))->createMetaFields(); - } + public function registerMetaFields(): void + { + (new MetaboxFields($this->config->get('metaboxes')))->createMetaFields(); + } } diff --git a/src/OpenKlacht/RestAPI/Hooks.php b/src/OpenKlacht/RestAPI/Hooks.php index 0b6f7ff..acf39c2 100644 --- a/src/OpenKlacht/RestAPI/Hooks.php +++ b/src/OpenKlacht/RestAPI/Hooks.php @@ -1,24 +1,28 @@ get_param('slug'); + public function extendRestQuery(array $args, WP_REST_Request $request): array + { + $slug = $request->get_param('slug'); - if (! empty($slug)) { - $args['name'] = $slug; // 'name' is the WP query parameter for slug. - } + if (! empty($slug)) { + $args['name'] = $slug; // 'name' is the WP query parameter for slug. + } - return $args; - } + return $args; + } } diff --git a/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php b/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php index 9d62eb6..9fc9e7e 100644 --- a/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php +++ b/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php @@ -1,5 +1,7 @@ hooks(); - $this->registerPortalURLField(); - $this->registerExtraRestFields(); - } - - public function hooks(): void - { - $hooks = new Hooks; - - add_filter('query_vars', [$hooks, 'addSlugQueryVar'], 10, 1); - add_filter('rest_post_query', [$hooks, 'extendRestQuery'], 10, 2); - } - - public function registerPortalURLField() - { - if (! $this->settings->usePortalURL()) { - return; - } - - register_rest_field(['openklacht'], 'portal_url', [ - 'get_callback' => function ($object) { - $post = get_post($object['id']); - - if (! $post instanceof WP_Post) { - return null; - } - - return sprintf('%s/%s/%s', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug()), $post->post_name); - }, - ]); - } - - protected function registerExtraRestFields() - { - $fields = $this->config->get('metaboxes'); - - if (! is_array($fields) || empty($fields)) { - return; - } - - foreach ($fields as $field) { - if (empty($field['id'])) { - continue; - } - - $this->registerField($field); - } - } - - protected function registerField(array $field) - { - register_rest_field(['openklacht'], sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']), [ - 'get_callback' => function ($object) use ($field) { - return $this->getFieldValue($object, $field); - }, - ]); - } - - public function getFieldValue($object, $field) - { - $postID = $object['id']; - $metaKey = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); - - return get_post_meta($postID, $metaKey, true); - } + public function register(): void + { + $this->hooks(); + $this->registerPortalURLField(); + $this->registerExtraRestFields(); + } + + public function hooks(): void + { + $hooks = new Hooks(); + + add_filter('query_vars', $hooks->addSlugQueryVar(...), 10, 1); + add_filter('rest_post_query', $hooks->extendRestQuery(...), 10, 2); + } + + public function registerPortalURLField(): void + { + if (! $this->settings->usePortalURL()) { + return; + } + + register_rest_field(['openklacht'], 'portal_url', [ + 'get_callback' => function ($object) { + $post = get_post($object['id']); + + if (! $post instanceof WP_Post) { + return null; + } + + return sprintf('%s/%s/%s', untrailingslashit($this->settings->getPortalURL()), untrailingslashit($this->settings->getPortalItemSlug()), $post->post_name); + }, + ]); + } + + protected function registerExtraRestFields(): void + { + $fields = $this->config->get('metaboxes'); + + if (! is_array($fields) || empty($fields)) { + return; + } + + foreach ($fields as $field) { + if (empty($field['id'])) { + continue; + } + + $this->registerField($field); + } + } + + protected function registerField(array $field): void + { + register_rest_field(['openklacht'], sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']), [ + 'get_callback' => fn ($object) => $this->getFieldValue($object, $field), + ]); + } + + /** + * @param array $object REST response of the post being prepared. + * @param array $field Field definition from the metaboxes config. + */ + public function getFieldValue(array $object, array $field): mixed + { + $metaKey = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); + + return get_post_meta($object['id'], $metaKey, true); + } } diff --git a/src/OpenKlacht/Settings/SettingsPageOptions.php b/src/OpenKlacht/Settings/SettingsPageOptions.php index 6049b2f..14e3f89 100644 --- a/src/OpenKlacht/Settings/SettingsPageOptions.php +++ b/src/OpenKlacht/Settings/SettingsPageOptions.php @@ -1,49 +1,51 @@ settings = $settings; - } - - public function getPortalURL(): string - { - return $this->settings['okl_setting_openklacht_portal_url'] ?? ''; - } - - public function getPortalItemSlug(): string - { - return $this->settings['okl_setting_openklacht_item_slug'] ?? ''; - } - - public function isPortalSlugValid(): bool - { - return ! empty($this->getPortalURL()) && ! empty($this->getPortalItemSlug()); - } - - public function usePortalURL(): bool - { - $setting = $this->settings['okl_setting_openklacht_use_portal_url'] ?? false; - - return filter_var($setting, FILTER_VALIDATE_BOOLEAN); - } - - public static function make(): self - { - $defaultSettings = [ - 'okl_setting_openklacht_portal_url' => '', - 'okl_setting_openklacht_item_slug' => '', - 'okl_setting_openklacht_use_portal_url' => 'on', - ]; - - return new static(wp_parse_args(get_option('_owc_openklacht_base_settings'), $defaultSettings)); - } + /** + * @param array $settings Settings defined on the settings page. + */ + public function __construct(private readonly array $settings) + { + } + + public function getPortalURL(): string + { + return $this->settings['okl_setting_openklacht_portal_url'] ?? ''; + } + + public function getPortalItemSlug(): string + { + return $this->settings['okl_setting_openklacht_item_slug'] ?? ''; + } + + public function isPortalSlugValid(): bool + { + return ! empty($this->getPortalURL()) && ! empty($this->getPortalItemSlug()); + } + + public function usePortalURL(): bool + { + $setting = $this->settings['okl_setting_openklacht_use_portal_url'] ?? false; + + return filter_var($setting, FILTER_VALIDATE_BOOLEAN); + } + + public static function make(): static + { + $defaultSettings = [ + 'okl_setting_openklacht_portal_url' => '', + 'okl_setting_openklacht_item_slug' => '', + 'okl_setting_openklacht_use_portal_url' => 'on', + ]; + + return new static(wp_parse_args(get_option('_owc_openklacht_base_settings'), $defaultSettings)); + } } diff --git a/src/OpenKlacht/Settings/SettingsServiceProvider.php b/src/OpenKlacht/Settings/SettingsServiceProvider.php index 9f72a50..8ccee15 100644 --- a/src/OpenKlacht/Settings/SettingsServiceProvider.php +++ b/src/OpenKlacht/Settings/SettingsServiceProvider.php @@ -1,61 +1,62 @@ config->get('settings_pages'); - - if (! is_array($settingsPages)) { - return; - } - - foreach ($settingsPages as $page) { - if (! is_array($page)) { - continue; - } - - $this->registerSettingsPage($page); - } - } - - protected function registerSettingsPage(array $page): void - { - $fields = $page['fields'] ?? []; - unset($page['fields']); // Fields will be added later on. - - $optionsPage = \new_cmb2_box($page); - - if (empty($fields) || ! is_array($fields)) { - return; - } - - $this->registerSettingsPageFields($optionsPage, $fields); - } - - protected function registerSettingsPageFields(CMB2 $optionsPage, array $fields) - { - foreach ($fields as $field) { - if (! is_array($field)) { - continue; - } - - if (isset($field['id'])) { - $field['id'] = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); - } - - $optionsPage->add_field($field); - } - } + public function register(): void + { + add_action('cmb2_admin_init', $this->registerSettingsPages(...), 10, 0); + } + + public function registerSettingsPages(): void + { + $settingsPages = $this->config->get('settings_pages'); + + if (! is_array($settingsPages)) { + return; + } + + foreach ($settingsPages as $page) { + if (! is_array($page)) { + continue; + } + + $this->registerSettingsPage($page); + } + } + + protected function registerSettingsPage(array $page): void + { + $fields = $page['fields'] ?? []; + unset($page['fields']); // Fields will be added later on. + + $optionsPage = \new_cmb2_box($page); + + if (empty($fields) || ! is_array($fields)) { + return; + } + + $this->registerSettingsPageFields($optionsPage, $fields); + } + + protected function registerSettingsPageFields(CMB2 $optionsPage, array $fields): void + { + foreach ($fields as $field) { + if (! is_array($field)) { + continue; + } + + if (isset($field['id'])) { + $field['id'] = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); + } + + $optionsPage->add_field($field); + } + } } diff --git a/tests/phpstan/bootstrap.php b/tests/phpstan/bootstrap.php new file mode 100644 index 0000000..a92b73d --- /dev/null +++ b/tests/phpstan/bootstrap.php @@ -0,0 +1,11 @@ + Date: Thu, 27 Aug 2026 13:12:44 +0200 Subject: [PATCH 2/3] refactor: align meta field applications, elastic search hooks, tests, git attributes --- .gitattributes | 31 ++++ .github/CODEOWNERS | 14 +- .gitignore | 2 + CHANGELOG.md | 6 +- composer.json | 13 +- composer.lock | 5 +- config/metaboxes.php | 6 +- phpunit.xml.dist | 20 +++ src/OpenKlacht/ElasticSearch/Hooks.php | 78 +++++++-- src/OpenKlacht/Foundation/Meta.php | 67 ++++++++ .../GravityForms/AbstractAfterSubmit.php | 5 +- src/OpenKlacht/GravityForms/AfterSubmit.php | 77 +++++---- src/OpenKlacht/PostType/MetaboxFields.php | 11 +- .../RestAPI/RestAPIServiceProvider.php | 30 ++-- .../Settings/SettingsServiceProvider.php | 19 +-- tests/Unit/ElasticSearch/HooksTest.php | 161 ++++++++++++++++++ tests/Unit/Foundation/MetaTest.php | 41 +++++ tests/Unit/GravityForms/AfterSubmitTest.php | 144 ++++++++++++++++ tests/bootstrap.php | 14 ++ 19 files changed, 628 insertions(+), 116 deletions(-) create mode 100644 .gitattributes create mode 100644 phpunit.xml.dist create mode 100644 src/OpenKlacht/Foundation/Meta.php create mode 100644 tests/Unit/ElasticSearch/HooksTest.php create mode 100644 tests/Unit/Foundation/MetaTest.php create mode 100644 tests/Unit/GravityForms/AfterSubmitTest.php create mode 100644 tests/bootstrap.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..42d74ae --- /dev/null +++ b/.gitattributes @@ -0,0 +1,31 @@ +# Normalise line endings on commit and check out LF everywhere, so the tab/LF +# style declared in .editorconfig survives contributors on other platforms. +* text=auto eol=lf + +# WordPress ships plugin readmes with CRLF; matches the .editorconfig rule. +*.txt text eol=crlf + +# Use git's built-in hunk-header drivers, so diffs name the enclosing +# function/class instead of showing bare line numbers. +*.php diff=php +*.md diff=markdown + +# Machine-written files. Collapsed by default in reviews and excluded from +# language stats; composer.lock is also deliberately unowned in CODEOWNERS. +composer.lock linguist-generated=true +languages/*.pot linguist-generated=true + +# Development-only files, excluded from `git archive` and from the dist zip +# Composer downloads for a wordpress-plugin package. +/.github/ export-ignore +/.idea/ export-ignore +/tests/ export-ignore +/.editorconfig export-ignore +/.eslintignore export-ignore +/.eslintrc export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.php export-ignore +/phpstan.neon export-ignore +/phpunit.xml.dist export-ignore +/composer.lock export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 416f56c..7349f34 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,13 +1,5 @@ -# Lines starting with '#' are comments. - -# Each line is a file pattern followed by one or more owners. - -# These owners will be the default owners for everything in the repo. - -- @yardinternet/wordpress-backend - -# Don't automatically assign reviewers on PRs that only modify these files. - -# As they have no owner listed, these files are not owned by anyone. +# These owners are the default owners for everything in the repo. +* @yardinternet/wordpress-backend +# Listed with no owner so PRs that only touch them don't request a review. composer.lock diff --git a/.gitignore b/.gitignore index 21627de..18a77f3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ .php_cs.cache .php-cs-fixer.cache .phpunit.result.cache +phpunit.xml # Folders vendor wp-content +tests/logs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3feab1e..aac8b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ - Chore: CMB2 is declared through the `Requires Plugins` plugin header instead of as a Composer runtime dependency, since it is installed as a separate WordPress plugin. It moves to `require-dev` for local development. - Feature: `okl_date_received` and `okl_judgement_date` are superseded by `okl_date_received_date` and `okl_judgement_date_date`, which render a datepicker and store a fixed `d-m-Y` value instead of a locale-dependent display string. - Feature: add `okl_judgement_date_sortable` to the Elasticsearch document, alongside the existing `okl_date_received_sortable`. -- Deprecated: `okl_date_received` and `okl_judgement_date` remain registered, writable and exposed via the REST API, and are still used as a fallback when the new fields are empty. No existing data is modified. -- Fix: the Elasticsearch sortable dates are derived from the new fixed-format fields, so they no longer depend on the site locale at submission time. +- Deprecated: `okl_date_received` and `okl_judgement_date` remain registered, exposed via the REST API and indexed, so existing complaints keep their stored value. They are no longer written on form submission, and no longer read as a fallback for the sortable dates. No existing data is modified. +- Breaking: complaints whose date is only in a deprecated field no longer produce `okl_date_received_sortable` or `okl_judgement_date_sortable`, so they drop out of date-sorted results until the date is re-entered in the datepicker field. +- Fix: the Elasticsearch sortable dates are derived from the fixed-format fields alone, so they no longer depend on the site locale at any point. +- Chore: the `intl` extension is no longer required, as nothing parses locale-formatted dates any more. - Chore: metabox field definitions are passed to CMB2 in full, so field types can carry their own options. - Chore: add a PHPStan configuration, repair the php-cs-fixer file finder, and modernise the codebase for PHP 8.2. diff --git a/composer.json b/composer.json index 7dbb00d..de272bb 100644 --- a/composer.json +++ b/composer.json @@ -29,12 +29,12 @@ } ], "require": { - "php": "^8.2", - "ext-intl": "*" + "php": "^8.2" }, "require-dev": { "10up/wp_mock": "^1.1", "php-stubs/gravity-forms-stubs": "^2.9", + "phpunit/phpunit": "^9.6", "szepeviktor/phpstan-wordpress": "^2.0", "wp-plugin/cmb2": "^2.12", "yard/php-cs-fixer-rules": "^1.0" @@ -44,11 +44,16 @@ "OWC\\OpenKlacht\\": "./src/OpenKlacht" } }, + "autoload-dev": { + "psr-4": { + "OWC\\OpenKlacht\\Tests\\": "./tests" + } + }, "scripts": { "format": "./vendor/bin/php-cs-fixer fix", "phpstan": "./vendor/bin/phpstan analyse --no-progress --memory-limit 1G", - "unit": "clear && ./vendor/bin/phpunit --colors=always", - "unit-coverage": "clear && XDEBUG_MODE=coverage ./vendor/bin/phpunit --stderr --colors=always --coverage-html ./tests/logs/coverage", + "unit": "./vendor/bin/phpunit --colors=always", + "unit-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --stderr --colors=always --coverage-html ./tests/logs/coverage", "test": [ "@unit" ] diff --git a/composer.lock b/composer.lock index 199c587..279d57a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2d1d8c36b8bea3890878be200362d9a5", + "content-hash": "755fa739f1deca818567fc0dca3a57f2", "packages": [], "packages-dev": [ { @@ -5251,8 +5251,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.2", - "ext-intl": "*" + "php": "^8.2" }, "platform-dev": {}, "platform-overrides": { diff --git a/config/metaboxes.php b/config/metaboxes.php index 297b70d..d26f700 100644 --- a/config/metaboxes.php +++ b/config/metaboxes.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use OWC\OpenKlacht\Foundation\Meta; + return [ ['name' => __('Kenmerk', 'openklacht'), 'id' => 'reference', 'type' => 'text'], ['name' => __('Onderwerp', 'openklacht'), 'id' => 'title', 'type' => 'text'], @@ -9,7 +11,7 @@ 'name' => __('Ontvangstdatum', 'openklacht'), 'id' => 'date_received_date', 'type' => 'text_date', - 'date_format' => 'd-m-Y', + 'date_format' => Meta::DATE_FORMAT, ], [ 'name' => __('Ontvangstdatum (verouderd)', 'openklacht'), @@ -34,7 +36,7 @@ 'name' => __('Dagtekening van het oordeel', 'openklacht'), 'id' => 'judgement_date_date', 'type' => 'text_date', - 'date_format' => 'd-m-Y', + 'date_format' => Meta::DATE_FORMAT, ], [ 'name' => __('Dagtekening van het oordeel (verouderd)', 'openklacht'), diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..dcea594 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/src/OpenKlacht/ElasticSearch/Hooks.php b/src/OpenKlacht/ElasticSearch/Hooks.php index fd1fbf6..a1a942b 100644 --- a/src/OpenKlacht/ElasticSearch/Hooks.php +++ b/src/OpenKlacht/ElasticSearch/Hooks.php @@ -1,21 +1,71 @@ toSortableDate($stored); + + // Left unset when unparseable + if (null !== $sortable) { + $args[Meta::key($field . '_sortable')] = $sortable; + } + } + + return $args; + } + + /** + * Reformats the value stored by the datepicker as an ISO-8601 date, so + * Elasticsearch maps the field as a date and can sort on it. The leading '!' + * zeroes the time component; without it the unspecified fields default to the + * current time, which can push the formatted date across a day boundary. + */ + private function toSortableDate(string $value): ?string + { + $date = DateTimeImmutable::createFromFormat('!' . Meta::DATE_FORMAT, trim($value)); + + if (false === $date) { + return null; + } + + $errors = DateTimeImmutable::getLastErrors(); + + if (false !== $errors && 0 < $errors['warning_count']) { + return null; + } + + return $date->format('Y-m-d'); + } } diff --git a/src/OpenKlacht/Foundation/Meta.php b/src/OpenKlacht/Foundation/Meta.php new file mode 100644 index 0000000..d780a94 --- /dev/null +++ b/src/OpenKlacht/Foundation/Meta.php @@ -0,0 +1,67 @@ + the year field derived from its successor. + * + * The key doubles as the base name of the pair: '_date' is the datepicker + * field holding a DATE_FORMAT value, and '_sortable' is the ISO-8601 value + * indexed for Elasticsearch -- the key the portal sorts on. The deprecated field + * itself is only kept so existing complaints keep their stored value; nothing + * reads or writes it. + * + * @var array + */ + public const DATE_FIELDS = [ + 'date_received' => 'year_received', + 'judgement_date' => 'judgement_year', + ]; + + /** + * Prefixes a field id from the config to the meta key it is stored under. + */ + public static function key(string $field): string + { + return sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field); + } + + /** + * Adds config-declared field definitions to a CMB2 box, prefixing their ids. + * Definitions are passed through whole so field types can carry their own + * options ('date_format', 'desc', ...); CMB2 ignores keys it does not recognise. + * + * @param array $fields + */ + public static function addFields(CMB2 $box, array $fields): void + { + foreach ($fields as $field) { + if (! is_array($field)) { + continue; + } + + if (isset($field['id'])) { + $field['id'] = self::key($field['id']); + } + + $box->add_field($field); + } + } +} diff --git a/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php b/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php index d0ef2ed..0de1f91 100644 --- a/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php +++ b/src/OpenKlacht/GravityForms/AbstractAfterSubmit.php @@ -9,14 +9,11 @@ */ abstract class AbstractAfterSubmit { - protected string $formTitle; - public function __construct( protected readonly array $form, - protected array $values, + protected readonly array $values, protected readonly array $entry ) { - $this->formTitle = $form['title']; } public static function make(array $form, array $values, array $entry): static diff --git a/src/OpenKlacht/GravityForms/AfterSubmit.php b/src/OpenKlacht/GravityForms/AfterSubmit.php index fb8b169..8cd2231 100644 --- a/src/OpenKlacht/GravityForms/AfterSubmit.php +++ b/src/OpenKlacht/GravityForms/AfterSubmit.php @@ -4,18 +4,15 @@ namespace OWC\OpenKlacht\GravityForms; -use DateTime; +use DateTimeImmutable; use Exception; +use OWC\OpenKlacht\Foundation\Meta; class AfterSubmit extends AbstractAfterSubmit { public function handle(): void { - $postID = wp_insert_post($this->getArgs(), true); - - if (! $postID) { - return; - } + wp_insert_post($this->getArgs()); } protected function getArgs(): array @@ -36,63 +33,69 @@ protected function getMetaArgs(): array 'reference', 'title', 'date_received', - 'year_received', 'description', 'organization', 'function', - 'function_other', 'findings', 'judgement', 'conclusion', 'judgement_date', - 'judgement_year', ]; $metaArgs = []; foreach ($fields as $field) { - if (in_array($field, ['date_received', 'judgement_date']) && isset($this->values[$field])) { - $metaArgs['okl_' . $field] = $this->formatDateField($field); + if (isset(Meta::DATE_FIELDS[$field])) { + // Yields the date in both representations plus the derived year. + $metaArgs += $this->dateMetaArgs($field); } elseif ('function' === $field) { - $metaArgs['okl_' . $field] = $this->handleFunctionField(); + $metaArgs[Meta::key($field)] = $this->handleFunctionField(); } else { - $metaArgs['okl_' . $field] = $this->values[$field] ?? ''; + $metaArgs[Meta::key($field)] = $this->values[$field] ?? ''; } } $metaArgs = array_filter($metaArgs); - $metaArgs['okl_year_received'] = $this->values['year_received'] ?? ''; - $metaArgs['okl_judgement_year'] = $this->values['judgement_year'] ?? ''; - - unset($metaArgs['okl_function_other']); - - return $metaArgs; + // Unioned after array_filter so the year keys persist even when empty: the + // Elasticsearch year facet relies on them being present. A year derived from + // a parsed date is already in $metaArgs and takes precedence over a + // form-supplied one. + return $metaArgs + [ + Meta::key('year_received') => $this->values['year_received'] ?? '', + Meta::key('judgement_year') => $this->values['judgement_year'] ?? '', + ]; } /** - * Converts a field value to a DateTime, formats it, and sets the year field for 'date_received' or 'judgement_date'. + * Stores a submitted date as a DATE_FORMAT value, plus the year derived from it. + * + * @return array */ - private function formatDateField(string $field): string + private function dateMetaArgs(string $field): array { - if (empty($this->values[$field])) { - return ''; - } + $date = $this->parseDateField($field); - try { - $date = new DateTime($this->values[$field]); - } catch(Exception $e) { - return ''; + if (null === $date) { + return []; } - $year = $date->format('Y'); + return [ + Meta::key($field . '_date') => $date->format(Meta::DATE_FORMAT), + Meta::key(Meta::DATE_FIELDS[$field]) => $date->format('Y'), + ]; + } - if ('date_received' === $field) { - $this->values['year_received'] = $year; - } elseif ('judgement_date' === $field) { - $this->values['judgement_year'] = $year; + private function parseDateField(string $field): ?DateTimeImmutable + { + if (empty($this->values[$field])) { + return null; } - return date_i18n('j F Y', $date->getTimestamp()); + try { + return new DateTimeImmutable($this->values[$field]); + } catch (Exception) { + return null; + } } /** @@ -101,8 +104,10 @@ private function formatDateField(string $field): string */ private function handleFunctionField(): string { - if ('function_other' === $this->values['function'] && ! empty($this->values['function_other'])) { - return $this->values['function_other'] ?? ''; + // Guarded because getEnteredValues() array_filters the submission, so an empty + // field is absent rather than an empty string. + if ('function_other' === ($this->values['function'] ?? '') && ! empty($this->values['function_other'])) { + return $this->values['function_other']; } return $this->values['function'] ?? ''; diff --git a/src/OpenKlacht/PostType/MetaboxFields.php b/src/OpenKlacht/PostType/MetaboxFields.php index 50aa29c..9eb55df 100644 --- a/src/OpenKlacht/PostType/MetaboxFields.php +++ b/src/OpenKlacht/PostType/MetaboxFields.php @@ -4,6 +4,8 @@ namespace OWC\OpenKlacht\PostType; +use OWC\OpenKlacht\Foundation\Meta; + class MetaboxFields { public function __construct(protected readonly array $fields) @@ -23,13 +25,6 @@ public function createMetaFields(): void 'show_names' => true, ]); - foreach ($this->fields as $field) { - // The whole definition is passed through so field types can carry their - // own options ('date_format', 'desc', 'attributes', ...). CMB2 ignores - // keys it does not recognise for a given type. - $field['id'] = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); - - $metaboxes->add_field($field); - } + Meta::addFields($metaboxes, $this->fields); } } diff --git a/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php b/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php index 9fc9e7e..7e81dc0 100644 --- a/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php +++ b/src/OpenKlacht/RestAPI/RestAPIServiceProvider.php @@ -4,6 +4,7 @@ namespace OWC\OpenKlacht\RestAPI; +use OWC\OpenKlacht\Foundation\Meta; use OWC\OpenKlacht\Foundation\ServiceProvider; use WP_Post; @@ -12,8 +13,11 @@ class RestAPIServiceProvider extends ServiceProvider public function register(): void { $this->hooks(); - $this->registerPortalURLField(); - $this->registerExtraRestFields(); + + add_action('rest_api_init', function (): void { + $this->registerPortalURLField(); + $this->registerExtraRestFields(); + }); } public function hooks(): void @@ -51,30 +55,26 @@ protected function registerExtraRestFields(): void return; } + // Share one callback for all fields to avoid creating a new closure for each field. + $getValue = $this->getFieldValue(...); + foreach ($fields as $field) { if (empty($field['id'])) { continue; } - $this->registerField($field); + register_rest_field(['openklacht'], Meta::key($field['id']), [ + 'get_callback' => $getValue, + ]); } } - protected function registerField(array $field): void - { - register_rest_field(['openklacht'], sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']), [ - 'get_callback' => fn ($object) => $this->getFieldValue($object, $field), - ]); - } - /** - * @param array $object REST response of the post being prepared. - * @param array $field Field definition from the metaboxes config. + * @param array $object REST response of the post being prepared. + * @param string $metaKey Name of the REST field being resolved. */ - public function getFieldValue(array $object, array $field): mixed + public function getFieldValue(array $object, string $metaKey): mixed { - $metaKey = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); - return get_post_meta($object['id'], $metaKey, true); } } diff --git a/src/OpenKlacht/Settings/SettingsServiceProvider.php b/src/OpenKlacht/Settings/SettingsServiceProvider.php index 8ccee15..39339b0 100644 --- a/src/OpenKlacht/Settings/SettingsServiceProvider.php +++ b/src/OpenKlacht/Settings/SettingsServiceProvider.php @@ -4,7 +4,7 @@ namespace OWC\OpenKlacht\Settings; -use CMB2; +use OWC\OpenKlacht\Foundation\Meta; use OWC\OpenKlacht\Foundation\ServiceProvider; class SettingsServiceProvider extends ServiceProvider @@ -42,21 +42,6 @@ protected function registerSettingsPage(array $page): void return; } - $this->registerSettingsPageFields($optionsPage, $fields); - } - - protected function registerSettingsPageFields(CMB2 $optionsPage, array $fields): void - { - foreach ($fields as $field) { - if (! is_array($field)) { - continue; - } - - if (isset($field['id'])) { - $field['id'] = sprintf('%s_%s', OWC_OPENKLACHT_PREFIX, $field['id']); - } - - $optionsPage->add_field($field); - } + Meta::addFields($optionsPage, $fields); } } diff --git a/tests/Unit/ElasticSearch/HooksTest.php b/tests/Unit/ElasticSearch/HooksTest.php new file mode 100644 index 0000000..0e953e5 --- /dev/null +++ b/tests/Unit/ElasticSearch/HooksTest.php @@ -0,0 +1,161 @@ + $meta + * + * @return array + */ + private function sync(array $meta, string $doctype = 'Klacht'): array + { + WP_Mock::userFunction('get_post_meta')->andReturnUsing( + fn (int $postID, string $key, bool $single = false): string => $meta[$key] ?? '' + ); + + return (new Hooks())->extendSyncArgs(['doctype' => $doctype], 1); + } + + public function testLeavesDocumentsOfOtherDoctypesUntouched(): void + { + $args = $this->sync(['okl_date_received_date' => '10-01-2026'], 'Anders'); + + $this->assertSame(['doctype' => 'Anders'], $args); + } + + public function testDerivesSortableDatesFromTheDatepickerFields(): void + { + $args = $this->sync([ + 'okl_date_received_date' => '10-01-2026', + 'okl_judgement_date_date' => '05-03-2026', + ]); + + $this->assertSame('2026-01-10', $args['okl_date_received_sortable']); + $this->assertSame('2026-03-05', $args['okl_judgement_date_sortable']); + } + + /** + * The deprecated free-text fields are indexed for existing complaints but are no + * longer parsed, so a complaint holding only a legacy date has nothing to sort on. + */ + public function testDoesNotDeriveSortableDatesFromTheDeprecatedFields(): void + { + $args = $this->sync([ + 'okl_date_received' => '10 januari 2026', + 'okl_judgement_date' => '5 maart 2026', + ]); + + $this->assertSame('10 januari 2026', $args['okl_date_received']); + $this->assertArrayNotHasKey('okl_date_received_sortable', $args); + $this->assertArrayNotHasKey('okl_judgement_date_sortable', $args); + } + + /** + * Elasticsearch derives an unmapped field's type from the first document it sees, + * so an empty string would pin these to `text` and break sorting index-wide. + */ + public function testOmitsTheSortableKeyRatherThanIndexingAnEmptyValue(): void + { + $args = $this->sync([]); + + $this->assertArrayNotHasKey('okl_date_received_sortable', $args); + $this->assertSame('', $args['okl_date_received_date']); + } + + /** + * @dataProvider unparseableDates + */ + public function testOmitsTheSortableKeyForUnparseableDates(string $stored): void + { + $args = $this->sync(['okl_date_received_date' => $stored]); + + $this->assertArrayNotHasKey('okl_date_received_sortable', $args); + } + + /** + * @return array + */ + public static function unparseableDates(): array + { + return [ + 'free text' => ['geen datum'], + 'wrong order' => ['2026-01-10'], + 'day overflow' => ['32-01-2026'], + 'month overflow' => ['10-13-2026'], + 'non-existent day' => ['29-02-2025'], + ]; + } + + public function testAcceptsALeapDay(): void + { + $args = $this->sync(['okl_date_received_date' => '29-02-2024']); + + $this->assertSame('2024-02-29', $args['okl_date_received_sortable']); + } + + public function testIgnoresSurroundingWhitespace(): void + { + $args = $this->sync(['okl_date_received_date' => ' 10-01-2026 ']); + + $this->assertSame('2026-01-10', $args['okl_date_received_sortable']); + } + + public function testIndexesTheDeprecatedAndCanonicalFieldsAlongsideThePlainOnes(): void + { + $args = $this->sync([ + 'okl_date_received' => 'legacy', + 'okl_date_received_date' => '10-01-2026', + 'okl_judgement_date' => 'legacy', + 'okl_judgement_date_date' => '05-03-2026', + 'okl_year_received' => '2026', + 'okl_judgement_year' => '2026', + 'okl_judgement' => 'Gegrond', + ]); + + $this->assertSame([ + 'doctype', + 'okl_date_received', + 'okl_date_received_date', + 'okl_date_received_sortable', + 'okl_judgement', + 'okl_judgement_date', + 'okl_judgement_date_date', + 'okl_judgement_date_sortable', + 'okl_judgement_year', + 'okl_year_received', + ], $this->sortedKeys($args)); + } + + /** + * @param array $args + * + * @return array + */ + private function sortedKeys(array $args): array + { + $keys = array_keys($args); + sort($keys); + + return $keys; + } +} diff --git a/tests/Unit/Foundation/MetaTest.php b/tests/Unit/Foundation/MetaTest.php new file mode 100644 index 0000000..d154fde --- /dev/null +++ b/tests/Unit/Foundation/MetaTest.php @@ -0,0 +1,41 @@ +assertSame('okl_reference', Meta::key('reference')); + } + + public function testKeyUsesThePluginPrefixConstant(): void + { + $this->assertStringStartsWith(OWC_OPENKLACHT_PREFIX . '_', Meta::key('anything')); + } + + /** + * The datepicker writes this format and the Elasticsearch sync parses it back, + * so the two must agree on one declaration. + */ + public function testDateFormatIsTheFixedStorageFormat(): void + { + $this->assertSame('d-m-Y', Meta::DATE_FORMAT); + } + + public function testEachDateFieldMapsToAYearField(): void + { + $this->assertSame( + [ + 'date_received' => 'year_received', + 'judgement_date' => 'judgement_year', + ], + Meta::DATE_FIELDS + ); + } +} diff --git a/tests/Unit/GravityForms/AfterSubmitTest.php b/tests/Unit/GravityForms/AfterSubmitTest.php new file mode 100644 index 0000000..a5e22f4 --- /dev/null +++ b/tests/Unit/GravityForms/AfterSubmitTest.php @@ -0,0 +1,144 @@ + $values + * + * @return array + */ + private function metaArgs(array $values): array + { + $handler = AfterSubmit::make(['title' => 'Formulier OpenKlacht'], $values, []); + + $method = new ReflectionMethod($handler, 'getMetaArgs'); + $method->setAccessible(true); + + return $method->invoke($handler); + } + + public function testStoresASubmittedDateInTheFixedStorageFormat(): void + { + $args = $this->metaArgs(['date_received' => '2026-01-10']); + + $this->assertSame('10-01-2026', $args['okl_date_received_date']); + } + + /** + * The deprecated free-text fields are kept for existing complaints only; new + * submissions must not write a locale-dependent display string to them. + */ + public function testDoesNotWriteTheDeprecatedDateFields(): void + { + $args = $this->metaArgs([ + 'date_received' => '2026-01-10', + 'judgement_date' => '2026-03-05', + ]); + + $this->assertArrayNotHasKey('okl_date_received', $args); + $this->assertArrayNotHasKey('okl_judgement_date', $args); + } + + public function testDerivesTheYearFromASubmittedDate(): void + { + $args = $this->metaArgs([ + 'date_received' => '2026-01-10', + 'judgement_date' => '2025-03-05', + ]); + + $this->assertSame('2026', $args['okl_year_received']); + $this->assertSame('2025', $args['okl_judgement_year']); + } + + /** + * The Elasticsearch year facet relies on these keys existing. + */ + public function testAlwaysEmitsTheYearKeysEvenWithoutADate(): void + { + $args = $this->metaArgs([]); + + $this->assertSame('', $args['okl_year_received']); + $this->assertSame('', $args['okl_judgement_year']); + } + + public function testADerivedYearTakesPrecedenceOverASubmittedOne(): void + { + $args = $this->metaArgs([ + 'date_received' => '2026-01-10', + 'year_received' => '1999', + ]); + + $this->assertSame('2026', $args['okl_year_received']); + } + + public function testFallsBackToASubmittedYearWhenTheDateIsUnparseable(): void + { + $args = $this->metaArgs([ + 'date_received' => 'geen datum', + 'year_received' => '1999', + ]); + + $this->assertSame('1999', $args['okl_year_received']); + $this->assertArrayNotHasKey('okl_date_received_date', $args); + } + + public function testDropsEmptyValues(): void + { + $args = $this->metaArgs(['reference' => 'R1', 'conclusion' => '']); + + $this->assertSame('R1', $args['okl_reference']); + $this->assertArrayNotHasKey('okl_conclusion', $args); + } + + public function testPrefersTheOtherFunctionValueWhenFunctionIsFunctionOther(): void + { + $args = $this->metaArgs([ + 'function' => 'function_other', + 'function_other' => 'Anders', + ]); + + $this->assertSame('Anders', $args['okl_function']); + } + + /** + * Documents current behaviour rather than endorsing it: with no 'other' value to + * fall back on, the sentinel 'function_other' is stored as the function itself. + */ + public function testStoresTheSentinelWhenNoOtherValueIsGiven(): void + { + $args = $this->metaArgs([ + 'function' => 'function_other', + 'function_other' => '', + ]); + + $this->assertSame('function_other', $args['okl_function']); + } + + public function testOmitsTheFunctionWhenNoneIsSubmitted(): void + { + $args = $this->metaArgs(['reference' => 'R1']); + + $this->assertArrayNotHasKey('okl_function', $args); + } + + /** + * 'function_other' is a helper input, not a stored field. + */ + public function testNeverStoresTheOtherFunctionHelperField(): void + { + $args = $this->metaArgs([ + 'function' => 'function_other', + 'function_other' => 'Anders', + ]); + + $this->assertArrayNotHasKey('okl_function_other', $args); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..9d7aca4 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ + Date: Fri, 28 Aug 2026 08:59:19 +0200 Subject: [PATCH 3/3] fix: don't mutate original `$path` variable --- src/OpenKlacht/Foundation/Config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenKlacht/Foundation/Config.php b/src/OpenKlacht/Foundation/Config.php index d368717..5ec9105 100644 --- a/src/OpenKlacht/Foundation/Config.php +++ b/src/OpenKlacht/Foundation/Config.php @@ -145,12 +145,12 @@ private function scanDirectory(string $path): void } // Get the path from the starting path. - $path = str_replace($this->path.'/', '', $path); + $relativePath = str_replace($this->path.'/', '', $path); // Build an array from the path. $items = []; $items[$name] = $value; - foreach (array_reverse(explode('/', $path)) as $key) { + foreach (array_reverse(explode('/', $relativePath)) as $key) { $items = [ $key => $items ]; }