diff --git a/.gitignore b/.gitignore index ddede569..d8539745 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .phpunit.result.cache tests/files/ *.min.* +var/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 5fd203df..ddf4ebac 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -5,16 +5,17 @@ $finder = Finder::create() ->in(__DIR__) - ->name('*.php'); + ->name('*.php') + ->ignoreVCSIgnored(true); $config = new Config(); $rules = [ - '@PER-CS2.0' => true, - 'trailing_comma_in_multiline' => ['elements' => ['arguments', 'array_destructuring', 'arrays']], // For PHP 7.4 compatibility + '@PER-CS' => true, // Latest PER rules. ]; return $config ->setRules($rules) ->setFinder($finder) - ->setUsingCache(false); + ->setCacheFile(__DIR__ . '/var/php-cs-fixer/.php-cs-fixer.cache') +; diff --git a/ajax/container.php b/ajax/container.php index fdad86a9..ed4b6433 100644 --- a/ajax/container.php +++ b/ajax/container.php @@ -52,6 +52,7 @@ if ($items_id > 0 && !$item->getFromDB($items_id)) { throw new NotFoundHttpException(); } + $item->input = $input; $display_condition = new PluginFieldsContainerDisplayCondition(); diff --git a/ajax/field_specific_fields.php b/ajax/field_specific_fields.php index 7888b672..9360b99f 100644 --- a/ajax/field_specific_fields.php +++ b/ajax/field_specific_fields.php @@ -59,9 +59,9 @@ 'multiple' => true, ]); } else { - $allowed_itemtypes = !empty($field->fields['allowed_values']) - ? json_decode($field->fields['allowed_values']) - : []; + $allowed_itemtypes = empty($field->fields['allowed_values']) + ? [] + : json_decode((string) $field->fields['allowed_values']); echo implode( ', ', array_map( @@ -72,10 +72,11 @@ ), ); } + echo ''; } else { $dropdown_matches = []; - $is_dropdown = $type == 'dropdown' || preg_match('/^dropdown-(?.+)$/', $type, $dropdown_matches) === 1; + $is_dropdown = $type == 'dropdown' || preg_match('/^dropdown-(?.+)$/', (string) $type, $dropdown_matches) === 1; $is_dropdown_multi = ($is_dropdown && ($type != 'dropdown-Document')); // Display "default value(s)" field @@ -84,10 +85,12 @@ echo __('Multiple dropdown', 'fields') . ' :'; echo '
'; } + echo __('Default value', 'fields') . ' :'; if (in_array($type, ['date', 'datetime'])) { echo ''; } + echo ''; echo ''; @@ -107,6 +110,7 @@ } else { echo Dropdown::getYesNo($multiple); } + echo '
'; } else { $multiple = false; @@ -119,6 +123,7 @@ if (!$multiple) { echo ''; } + echo ''; } else { $itemtype = $type == 'dropdown' @@ -127,6 +132,7 @@ if ($field->fields['default_value'] === null) { $field->fields['default_value'] = ''; } + $default_value = $multiple ? json_decode($field->fields['default_value']) : $field->fields['default_value']; Dropdown::show( $itemtype, @@ -139,10 +145,11 @@ ], ); } + echo ''; Ajax::updateItemOnSelectEvent( - "dropdown_multiple$rand", - "plugin_fields_specific_fields_$rand", + 'dropdown_multiple' . $rand, + 'plugin_fields_specific_fields_' . $rand, '../ajax/field_specific_fields.php', [ 'id' => $id, @@ -159,5 +166,6 @@ ], ); } + echo ''; } diff --git a/composer.json b/composer.json index 84684e17..be0ca272 100644 --- a/composer.json +++ b/composer.json @@ -4,23 +4,14 @@ "symfony/yaml": "^7.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.89", - "friendsoftwig/twigcs": "^6.1", - "glpi-project/tools": "^0.8.3", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-deprecation-rules": "^2.0" + "glpi-project/tools": "^0.8.3" }, "config": { "optimize-autoloader": true, "platform": { "php": "8.2.99" }, - "sort-packages": true, - "allow-plugins": { - "phpstan/extension-installer": true - } + "sort-packages": true }, "autoload-dev": { "psr-4": { diff --git a/composer.lock b/composer.lock index 541b05d6..a3d65312 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": "0020b7cfec0bb971d0b5ce8a142ad82c", + "content-hash": "af5be338ce2c5e21875a5e8d8fb63e22", "packages": [ { "name": "symfony/deprecation-contracts", @@ -235,2240 +235,166 @@ ], "packages-dev": [ { - "name": "clue/ndjson-react", - "version": "v1.3.0", + "name": "glpi-project/tools", + "version": "0.8.3", "source": { "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + "url": "https://github.com/glpi-project/tools.git", + "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "url": "https://api.github.com/repos/glpi-project/tools/zipball/8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", + "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", "shasum": "" }, "require": { - "php": ">=5.3", - "react/stream": "^1.2" + "symfony/console": "^5.4 || ^6.0", + "twig/twig": "^3.3" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + "nikic/php-parser": "^4.13", + "phpstan/phpstan-src": "^1.10" }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } + "bin": [ + "bin/extract-locales", + "bin/licence-headers-check", + "tools/plugin-release" ], - "time": "2022-12-23T10:58:28+00:00" - }, - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Composer\\Pcre\\": "src" + "GlpiProject\\Tools\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "GPL-3.0-or-later" ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Teclib'", + "email": "glpi@teclib.com", + "homepage": "http://teclib-group.com" } ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "description": "Various tools for GLPI and its plugins", "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" + "glpi", + "plugins", + "tools" ], "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "issues": "https://github.com/glpi-project/tools/issues", + "source": "https://github.com/glpi-project/tools" }, - "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-11-12T16:29:46+00:00" + "time": "2025-10-14T10:26:06+00:00" }, { - "name": "composer/semver", - "version": "3.4.4", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "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": "evenement/evenement", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Evenement\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "dev-master": "2.0.x-dev" } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" - }, - "time": "2023-08-08T05:53:35+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", - "shasum": "" }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", "autoload": { "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" + "Psr\\Container\\": "src/" } - ], - "time": "2025-08-14T07:29:31+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.89.1", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "f34967da2866ace090a2b447de1f357356474573" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/f34967da2866ace090a2b447de1f357356474573", - "reference": "f34967da2866ace090a2b447de1f357356474573", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.3", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.5", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.3", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.6", - "react/event-loop": "^1.5", - "react/socket": "^1.16", - "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", - "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0", - "symfony/polyfill-mbstring": "^1.33", - "symfony/polyfill-php80": "^1.33", - "symfony/polyfill-php81": "^1.33", - "symfony/polyfill-php84": "^1.33", - "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2", - "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.7", - "infection/infection": "^0.31.0", - "justinrainbow/json-schema": "^6.5", - "keradus/cli-executor": "^2.2", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.8", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", - "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2", - "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/Fixer/Internal/*" - ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.1" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2025-10-24T12:05:10+00:00" - }, - { - "name": "friendsoftwig/twigcs", - "version": "6.5.0", - "source": { - "type": "git", - "url": "https://github.com/friendsoftwig/twigcs.git", - "reference": "aaa3ba112bf4fcee7b51a00d9b45b13bc2cc23bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/friendsoftwig/twigcs/zipball/aaa3ba112bf4fcee7b51a00d9b45b13bc2cc23bc", - "reference": "aaa3ba112bf4fcee7b51a00d9b45b13bc2cc23bc", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", - "symfony/console": "^4.4 || ^5.3 || ^6.0 || ^7.0", - "symfony/filesystem": "^4.4 || ^5.3 || ^6.0 || ^7.0", - "symfony/finder": "^4.4 || ^5.3 || ^6.0 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6.19", - "symfony/phpunit-bridge": "^7.1.4" - }, - "bin": [ - "bin/twigcs" - ], - "type": "library", - "autoload": { - "psr-4": { - "FriendsOfTwig\\Twigcs\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tristan Maindron", - "email": "tmaindron@gmail.com" - } - ], - "description": "Checkstyle automation for Twig", - "support": { - "issues": "https://github.com/friendsoftwig/twigcs/issues", - "source": "https://github.com/friendsoftwig/twigcs/tree/6.5.0" - }, - "time": "2024-11-27T21:59:24+00:00" - }, - { - "name": "glpi-project/tools", - "version": "0.8.3", - "source": { - "type": "git", - "url": "https://github.com/glpi-project/tools.git", - "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/glpi-project/tools/zipball/8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", - "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026", - "shasum": "" - }, - "require": { - "symfony/console": "^5.4 || ^6.0", - "twig/twig": "^3.3" - }, - "require-dev": { - "nikic/php-parser": "^4.13", - "phpstan/phpstan-src": "^1.10" - }, - "bin": [ - "bin/extract-locales", - "bin/licence-headers-check", - "tools/plugin-release" - ], - "type": "library", - "autoload": { - "psr-4": { - "GlpiProject\\Tools\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Teclib'", - "email": "glpi@teclib.com", - "homepage": "http://teclib-group.com" - } - ], - "description": "Various tools for GLPI and its plugins", - "keywords": [ - "glpi", - "plugins", - "tools" - ], - "support": { - "issues": "https://github.com/glpi-project/tools/issues", - "source": "https://github.com/glpi-project/tools" - }, - "time": "2025-10-14T10:26:06+00:00" - }, - { - "name": "php-parallel-lint/php-parallel-lint", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", - "reference": "6db563514f27e19595a19f45a4bf757b6401194e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e", - "reference": "6db563514f27e19595a19f45a4bf757b6401194e", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=5.3.0" - }, - "replace": { - "grogy/php-parallel-lint": "*", - "jakub-onderka/php-parallel-lint": "*" - }, - "require-dev": { - "nette/tester": "^1.3 || ^2.0", - "php-parallel-lint/php-console-highlighter": "0.* || ^1.0", - "squizlabs/php_codesniffer": "^3.6" - }, - "suggest": { - "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" - }, - "bin": [ - "parallel-lint" - ], - "type": "library", - "autoload": { - "classmap": [ - "./src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "ahoj@jakubonderka.cz" - } - ], - "description": "This tool checks the syntax of PHP files about 20x faster than serial check.", - "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", - "keywords": [ - "lint", - "static analysis" - ], - "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", - "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0" - }, - "time": "2024-03-27T12:14:49+00:00" - }, - { - "name": "phpstan/extension-installer", - "version": "1.4.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "85e90b3942d06b2326fba0403ec24fe912372936" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", - "reference": "85e90b3942d06b2326fba0403ec24fe912372936", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^2.0", - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.0 || ^2.0" - }, - "require-dev": { - "composer/composer": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer plugin for automatic installation of PHPStan extensions", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" - }, - "time": "2024-09-04T20:21:43+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "2.1.31", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", - "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "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", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2025-10-10T14:14:11+00:00" - }, - { - "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", - "reference": "468e02c9176891cc901143da118f09dc9505fc2f", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.15" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", - "support": { - "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" - }, - "time": "2025-05-14T10:56:57+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "react/cache", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" - }, - { - "name": "react/child-process", - "version": "v0.6.6", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-01-01T16:37:48+00:00" - }, - { - "name": "react/dns", - "version": "v1.13.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-13T14:18:03+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-11-13T13:48:05+00:00" - }, - { - "name": "react/promise", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.12.28 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.3.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-08-19T18:57:03+00:00" - }, - { - "name": "react/socket", - "version": "v1.16.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-07-26T10:38:09+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, - { - "name": "sebastian/diff", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:53:05+00:00" - }, - { - "name": "symfony/console", - "version": "v6.4.27", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/13d3176cf8ad8ced24202844e9f95af11e2959fc", - "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "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": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.4.27" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-10-06T10:25:16+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v7.3.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", - "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-08-13T11:49:31+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "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": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v7.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-07T08:17:47+00:00" - }, - { - "name": "symfony/finder", - "version": "v7.3.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "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": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-10-15T18:45:57+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v7.3.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-08-05T10:16:07+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", - "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\\Grapheme\\": "" - } - }, - "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 grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.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": "2025-06-27T09:58:17+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "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.33.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" + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "time": "2024-09-09T11:45:10+00:00" + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "name": "symfony/console", + "version": "v6.4.27", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/symfony/console.git", + "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/console/zipball/13d3176cf8ad8ced24202844e9f95af11e2959fc", + "reference": "13d3176cf8ad8ced24202844e9f95af11e2959fc", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "ext-mbstring": "*" + "psr/log-implementation": "1.0|2.0|3.0" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2476,25 +402,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/console/tree/v6.4.27" }, "funding": [ { @@ -2514,25 +439,28 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2025-10-06T10:25:16+00:00" }, { - "name": "symfony/polyfill-php80", + "name": "symfony/polyfill-intl-grapheme", "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { "php": ">=7.2" }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -2545,21 +473,14 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2569,16 +490,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "grapheme", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -2598,25 +521,28 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "symfony/polyfill-php81", + "name": "symfony/polyfill-intl-normalizer", "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { "php": ">=7.2" }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -2629,7 +555,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "classmap": [ "Resources/stubs" @@ -2649,16 +575,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2681,22 +609,29 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php84", + "name": "symfony/polyfill-mbstring", "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "thanks": { @@ -2709,11 +644,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2729,81 +661,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ 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-php84/tree/v1.33.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": "2025-06-24T13:30:11+00:00" - }, - { - "name": "symfony/process", - "version": "v7.3.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "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": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v7.3.4" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2823,20 +691,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -2890,7 +758,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -2902,65 +770,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-25T09:37:31+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -2968,7 +778,7 @@ "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", diff --git a/front/container.form.php b/front/container.form.php index 68968f4d..adfb8a6b 100644 --- a/front/container.form.php +++ b/front/container.form.php @@ -41,7 +41,7 @@ if (isset($_POST['add'])) { $container->check(-1, CREATE, $_POST); $newID = $container->add($_POST); - Html::redirect(PLUGINFIELDS_WEB_DIR . "/front/container.form.php?id=$newID"); + Html::redirect(PLUGINFIELDS_WEB_DIR . ('/front/container.form.php?id=' . $newID)); } elseif (isset($_POST['delete'])) { $container->check($_POST['id'], DELETE); $ok = $container->delete($_POST); @@ -59,6 +59,7 @@ if ($right > READ) { $container->updateFieldsValues($_REQUEST, $_REQUEST['itemtype'], false); } + Html::back(); } else { diff --git a/front/containerdisplaycondition.form.php b/front/containerdisplaycondition.form.php index e43ddf46..43683b59 100644 --- a/front/containerdisplaycondition.form.php +++ b/front/containerdisplaycondition.form.php @@ -46,4 +46,5 @@ ]); Html::back(); } + Html::back(); diff --git a/front/labeltranslation.form.php b/front/labeltranslation.form.php index 2fe48d14..13de5eff 100644 --- a/front/labeltranslation.form.php +++ b/front/labeltranslation.form.php @@ -38,4 +38,5 @@ } elseif (isset($_POST['purge'])) { $translation->delete($_POST, true); } + Html::back(); diff --git a/front/profile.form.php b/front/profile.form.php index 1426e606..dcc7664f 100644 --- a/front/profile.form.php +++ b/front/profile.form.php @@ -33,4 +33,5 @@ if (isset($_POST['update'])) { PluginFieldsProfile::updateProfile($_POST); } + Html::back(); diff --git a/front/statusoverride.form.php b/front/statusoverride.form.php index 49d77a37..42aa9338 100644 --- a/front/statusoverride.form.php +++ b/front/statusoverride.form.php @@ -46,4 +46,5 @@ ]); Html::back(); } + Html::back(); diff --git a/hook.php b/hook.php index 86f11568..0eabc024 100644 --- a/hook.php +++ b/hook.php @@ -41,12 +41,14 @@ function plugin_fields_install() if ($memory_limit > 0 && $memory_limit < (512 * 1024 * 1024)) { ini_set('memory_limit', '512M'); } + if ($max_execution_time > 0 && $max_execution_time < 300) { ini_set('max_execution_time', '300'); } $plugin_fields = new Plugin(); $plugin_fields->getFromDBbyDir('fields'); + $version = $plugin_fields->fields['version']; @@ -79,6 +81,7 @@ function plugin_fields_install() $class::installBaseData($migration, $version); } } + $migration->executeMigration(); // Then process specific user classes/tables @@ -87,6 +90,7 @@ function plugin_fields_install() $class::installUserData($migration, $version); } } + $migration->executeMigration(); if (!isCommandLine()) { @@ -138,10 +142,10 @@ function plugin_fields_uninstall() foreach ($classesToUninstall as $class) { if ($plug = isPluginItemType($class)) { $dir = PLUGINFIELDS_DIR . '/inc/'; - $item = strtolower($plug['class']); + $item = strtolower((string) $plug['class']); - if (file_exists("$dir$item.class.php")) { - include_once("$dir$item.class.php"); + if (file_exists(sprintf('%s%s.class.php', $dir, $item))) { + include_once(sprintf('%s%s.class.php', $dir, $item)); if (!call_user_func([$class, 'uninstall'])) { return false; } @@ -169,7 +173,7 @@ function plugin_fields_getAddSearchOptions($itemtype) if ( isset($_SESSION['glpiactiveentities']) && is_array($_SESSION['glpiactiveentities']) - && count($_SESSION['glpiactiveentities']) > 0 + && $_SESSION['glpiactiveentities'] !== [] ) { $itemtypes = PluginFieldsContainer::getEntries('all'); if (in_array($itemtype, $itemtypes)) { @@ -190,7 +194,7 @@ function plugin_fields_getDropdown() foreach ($fields as $field) { $field['itemtype'] = PluginFieldsField::getType(); $label = PluginFieldsLabelTranslation::getLabelFor($field); - $dropdowns['PluginFields' . ucfirst($field['name']) . 'Dropdown'] = $label; + $dropdowns['PluginFields' . ucfirst((string) $field['name']) . 'Dropdown'] = $label; } asort($dropdowns); @@ -240,18 +244,15 @@ function plugin_fields_getRuleActions($params = []) { $actions = []; - switch ($params['rule_itemtype']) { - case 'PluginFusioninventoryTaskpostactionRule': - $options = PluginFieldsContainer::getAddSearchOptions('Computer'); - foreach ($options as $option) { - $actions[$option['linkfield']]['name'] = $option['name']; - $actions[$option['linkfield']]['type'] = $option['pfields_type']; - if ($option['pfields_type'] == 'dropdown') { - $actions[$option['linkfield']]['table'] = $option['table']; - } + if ($params['rule_itemtype'] === 'PluginFusioninventoryTaskpostactionRule') { + $options = PluginFieldsContainer::getAddSearchOptions('Computer'); + foreach ($options as $option) { + $actions[$option['linkfield']]['name'] = $option['name']; + $actions[$option['linkfield']]['type'] = $option['pfields_type']; + if ($option['pfields_type'] == 'dropdown') { + $actions[$option['linkfield']]['table'] = $option['table']; } - - break; + } } return $actions; @@ -315,10 +316,10 @@ function plugin_fields_giveItem($itemtype, $ID, $data, $num) //fix glpi default Search::giveItem who for empty date display "--" if ( - str_contains($table, 'glpi_plugin_fields') + str_contains((string) $table, 'glpi_plugin_fields') && isset($searchopt[$ID]['datatype']) && str_contains($searchopt[$ID]['datatype'], 'date') - && empty($data['raw']["ITEM_$num"]) + && empty($data['raw']['ITEM_' . $num]) ) { return ' '; } @@ -375,15 +376,16 @@ function plugin_fields_addWhere($link, $nott, $itemtype, $ID, $val, $searchtype) if ($nott) { $link .= ' NOT '; } - return $link . 'CAST(' . $DB->quoteName("$table" . '_' . "$field") . '.' . $DB->quoteName($field) . ' AS DECIMAL(10,7))' . $operator . ' ' . $DB->quoteValue($val) ; + + return $link . 'CAST(' . $DB->quoteName($table . '_' . $field) . '.' . $DB->quoteName($field) . ' AS DECIMAL(10,7))' . $operator . ' ' . $DB->quoteValue($val) ; } else { // if 'number' field with name is found with <= or >= or < or > search // update WHERE clause with the correct operator - $val = html_entity_decode($val); + $val = html_entity_decode((string) $val); if (preg_match('/(<=|>=|>|<)/', $val, $matches)) { $operator = $matches[1]; $val = trim(str_replace($operator, '', $val)); - return $link . $DB->quoteName("$table" . '_' . "$field") . '.' . $DB->quoteName($field) . $operator . ' ' . $DB->quoteValue($val); + return $link . $DB->quoteName($table . '_' . $field) . '.' . $DB->quoteName($field) . $operator . ' ' . $DB->quoteValue($val); } } } @@ -398,7 +400,7 @@ function plugin_fields_addWhere($link, $nott, $itemtype, $ID, $val, $searchtype) ], ) ) { - $tablefield = "$table" . '_' . "$field"; + $tablefield = $table . '_' . $field; switch ($searchtype) { case 'equals': return PluginFieldsDropdown::multipleDropdownAddWhere($link, $tablefield, $field, $val, $nott ? 'notequals' : 'equals', $field_field); @@ -410,7 +412,7 @@ function plugin_fields_addWhere($link, $nott, $itemtype, $ID, $val, $searchtype) // update WHERE clause with LIKE statement $cleanfield = str_replace('plugin_fields_', '', $field); $cleanfield = str_replace('dropdowns_id', '', $cleanfield); - $tablefield = "$table" . '_' . "$cleanfield"; + $tablefield = $table . '_' . $cleanfield; if ( $field_field->getFromDBByCrit( [ @@ -429,4 +431,6 @@ function plugin_fields_addWhere($link, $nott, $itemtype, $ID, $val, $searchtype) return false; } } + + return null; } diff --git a/inc/abstractcontainerinstance.class.php b/inc/abstractcontainerinstance.class.php index c538b373..7b918fd2 100644 --- a/inc/abstractcontainerinstance.class.php +++ b/inc/abstractcontainerinstance.class.php @@ -31,6 +31,7 @@ abstract class PluginFieldsAbstractContainerInstance extends CommonDBChild { public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; /** @@ -69,6 +70,7 @@ public function addNeededInfoToInput($input) } } } + return $input; } @@ -84,15 +86,16 @@ public static function getSpecificValueToSelect($field, $name = '', $values = '' if ($field_id !== null && $field_specs->getFromDB($field_id)) { $dropdown_matches = []; if ( - preg_match('/^dropdown-(?.+)$/i', $field_specs->fields['type'], $dropdown_matches) === 1 + preg_match('/^dropdown-(?.+)$/i', (string) $field_specs->fields['type'], $dropdown_matches) === 1 && $field_specs->fields['multiple'] ) { $itemtype = $dropdown_matches['class']; if (!is_a($itemtype, CommonDBTM::class, true)) { return ''; // Itemtype not exists (maybe a deactivated plugin) } + $display_with = []; - if ($itemtype == User::class) { + if ($itemtype === User::class) { $display_with = ['realname', 'firstname']; } @@ -122,7 +125,7 @@ public static function getSpecificValueToDisplay($field, $values, array $options if ($field_id !== null && $field_specs->getFromDB($field_id)) { $dropdown_matches = []; if ( - preg_match('/^dropdown-(?.+)$/i', $field_specs->fields['type'], $dropdown_matches) === 1 + preg_match('/^dropdown-(?.+)$/i', (string) $field_specs->fields['type'], $dropdown_matches) === 1 && $field_specs->fields['multiple'] ) { $itemtype = $dropdown_matches['class']; @@ -133,7 +136,8 @@ public static function getSpecificValueToDisplay($field, $values, array $options if (empty($values[$field])) { return ''; // Value not defined } - $values = json_decode($values[$field]); + + $values = json_decode((string) $values[$field]); if (!is_array($values)) { return ''; // Invalid value } @@ -159,7 +163,8 @@ public static function getSpecificValueToDisplay($field, $values, array $options return ''; // Value not defined } } - $values = json_decode($values[$field]); + + $values = json_decode((string) $values[$field]); if (!is_array($values)) { return ''; // Invalid value } diff --git a/inc/autoload.php b/inc/autoload.php index 99397411..440b2715 100644 --- a/inc/autoload.php +++ b/inc/autoload.php @@ -57,7 +57,7 @@ public function setOptions($options) public function processClassname($classname) { $matches = []; - preg_match("/Plugin([A-Z][a-z0-9]+)([A-Z]\w+)/", $classname, $matches); + preg_match("/Plugin([A-Z][a-z0-9]+)([A-Z]\w+)/", (string) $classname, $matches); if (count($matches) < 3) { return false; @@ -71,8 +71,8 @@ public function autoload($classname) $matches = $this->processClassname($classname); if ($matches !== false) { - $plugin_name = strtolower($matches[1]); - $class_name = strtolower($matches[2]); + $plugin_name = strtolower((string) $matches[1]); + $class_name = strtolower((string) $matches[2]); if ($plugin_name !== 'fields') { return false; @@ -97,6 +97,6 @@ public function autoload($classname) public function register() { - spl_autoload_register([$this, 'autoload']); + spl_autoload_register($this->autoload(...)); } } diff --git a/inc/container.class.php b/inc/container.class.php index 9d9c445f..f0febac9 100644 --- a/inc/container.class.php +++ b/inc/container.class.php @@ -28,8 +28,8 @@ * ------------------------------------------------------------------------- */ -use Glpi\Features\Clonable; use Glpi\DBAL\QueryExpression; +use Glpi\Features\Clonable; class PluginFieldsContainer extends CommonDBTM { @@ -49,9 +49,9 @@ public static function canPurge(): bool public static function titleList() { - echo "
'; + echo "
'; } public function getForbiddenStandardMassiveAction() @@ -84,7 +84,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `name` VARCHAR(255) DEFAULT NULL, `label` VARCHAR(255) DEFAULT NULL, @@ -184,9 +184,9 @@ public static function installBaseData(Migration $migration, $version) $result = $DB->request(['FROM' => 'glpi_plugin_genericobject_types']); foreach ($result as $type) { $customasset_classname = 'Glpi\\\\CustomAsset\\\\' . $type['name'] . 'Asset'; - if (str_ends_with($type['itemtype'], 'Model')) { + if (str_ends_with((string) $type['itemtype'], 'Model')) { $customasset_classname = 'Glpi\\\\CustomAsset\\\\' . $type['name'] . 'AssetModel'; - } elseif (str_ends_with($type['itemtype'], 'Type')) { + } elseif (str_ends_with((string) $type['itemtype'], 'Type')) { $customasset_classname = 'Glpi\\\\CustomAsset\\\\' . $type['name'] . 'AssetType'; } @@ -211,19 +211,20 @@ public static function installBaseData(Migration $migration, $version) $container_class = new self(); foreach ($result as $container) { self::generateTemplate($container); - foreach (json_decode($container['itemtypes']) as $itemtype) { + foreach (json_decode((string) $container['itemtypes']) as $itemtype) { $classname = self::getClassname($itemtype, $container["name"]); $old_table = $classname::getTable(); // Rename genericobject container table if ( - $DB->tableExists($old_table) && - isset($migration_genericobject_itemtype[$itemtype]) && - str_contains($old_table, 'glpi_plugin_fields_plugingenericobject' . $migration_genericobject_itemtype[$itemtype]['genericobject_name']) + $DB->tableExists($old_table) + && isset($migration_genericobject_itemtype[$itemtype]) + && str_contains($old_table, 'glpi_plugin_fields_plugingenericobject' . $migration_genericobject_itemtype[$itemtype]['genericobject_name']) ) { $new_table = str_replace('plugingenericobject' . $migration_genericobject_itemtype[$itemtype]['genericobject_name'], 'glpicustomasset' . strtolower($migration_genericobject_itemtype[$itemtype]['name']), $old_table); $migration->renameTable($old_table, $new_table); } } + // Update old genericobject itemtypes in container $map = array_column($migration_genericobject_itemtype, 'itemtype', 'genericobject_itemtype'); $itemtypes = strtr($container['itemtypes'], $map); @@ -240,6 +241,7 @@ public static function installBaseData(Migration $migration, $version) ); } } + return true; } @@ -272,13 +274,13 @@ public static function installUserData(Migration $migration, $version) foreach ($itemtypes as $itemtype) { $sysname = self::getSystemName($itemtype, $container['name']); $class_filename = $sysname . '.class.php'; - if (file_exists(PLUGINFIELDS_DIR . "/inc/$class_filename")) { - unlink(PLUGINFIELDS_DIR . "/inc/$class_filename"); + if (file_exists(PLUGINFIELDS_DIR . ('/inc/' . $class_filename))) { + unlink(PLUGINFIELDS_DIR . ('/inc/' . $class_filename)); } $injclass_filename = $sysname . 'injection.class.php'; - if (file_exists(PLUGINFIELDS_DIR . "/inc/$injclass_filename")) { - unlink(PLUGINFIELDS_DIR . "/inc/$injclass_filename"); + if (file_exists(PLUGINFIELDS_DIR . ('/inc/' . $injclass_filename))) { + unlink(PLUGINFIELDS_DIR . ('/inc/' . $injclass_filename)); } } } @@ -320,6 +322,7 @@ public static function installUserData(Migration $migration, $version) $new_name = substr($new_name, 0, -1); } } + $container['name'] = $new_name; $container_obj = new PluginFieldsContainer(); $container_obj->update( @@ -376,9 +379,11 @@ public static function installUserData(Migration $migration, $version) ], ); } + if ($compdata instanceof PluginFieldsAbstractContainerInstance) { $compdata->addField($newname, $field['type']); } + $fieldnames[$field['name']] = $newname; } @@ -405,6 +410,7 @@ public static function installUserData(Migration $migration, $version) foreach ($fieldnames as $oldname => $newname) { $data[$newname] = $existing[$oldname]; } + $compdata->add($data); } @@ -450,6 +456,7 @@ public static function installUserData(Migration $migration, $version) // Regenerate files and install missing tables $migration->displayMessage(__('Updating generated containers files', 'fields')); + $obj = new self(); $containers = $obj->find(); foreach ($containers as $container) { @@ -492,9 +499,7 @@ public function post_getEmpty() public function rawSearchOptions() { - $tab = []; - - $tab[] = [ + return [[ 'id' => 1, 'table' => self::getTable(), 'field' => 'name', @@ -502,9 +507,7 @@ public function rawSearchOptions() 'datatype' => 'itemlink', 'itemlink_type' => self::getType(), 'massiveaction' => false, - ]; - - $tab[] = [ + ], [ 'id' => 2, 'table' => self::getTable(), 'field' => 'label', @@ -513,9 +516,7 @@ public function rawSearchOptions() 'itemlink_type' => self::getType(), 'massiveaction' => false, 'autocomplete' => true, - ]; - - $tab[] = [ + ], [ 'id' => 3, 'table' => self::getTable(), 'field' => 'itemtypes', @@ -523,54 +524,42 @@ public function rawSearchOptions() 'datatype' => 'specific', 'massiveaction' => false, 'nosearch' => true, - ]; - - $tab[] = [ + ], [ 'id' => 4, 'table' => self::getTable(), 'field' => 'type', 'name' => __('Type'), 'searchtype' => ['equals', 'notequals'], 'massiveaction' => false, - ]; - - $tab[] = [ + ], [ 'id' => 5, 'table' => self::getTable(), 'field' => 'is_active', 'name' => __('Active'), 'datatype' => 'bool', 'searchtype' => ['equals', 'notequals'], - ]; - - $tab[] = [ + ], [ 'id' => 6, 'table' => 'glpi_entities', 'field' => 'completename', 'name' => __('Entity'), 'massiveaction' => false, 'datatype' => 'dropdown', - ]; - - $tab[] = [ + ], [ 'id' => 7, 'table' => self::getTable(), 'field' => 'is_recursive', 'name' => __('Child entities'), 'massiveaction' => false, 'datatype' => 'bool', - ]; - - $tab[] = [ + ], [ 'id' => 8, 'table' => self::getTable(), 'field' => 'id', 'name' => __('ID'), 'datatype' => 'number', 'massiveaction' => false, - ]; - - return $tab; + ]]; } public static function getSpecificValueToDisplay($field, $values, array $options = []) @@ -578,6 +567,7 @@ public static function getSpecificValueToDisplay($field, $values, array $options if (!is_array($values)) { $values = [$field => $values]; } + switch ($field) { case 'type': $types = self::getTypes(); @@ -593,6 +583,7 @@ public static function getSpecificValueToDisplay($field, $values, array $options if (!class_exists($type)) { continue; } + $name_type = getItemForItemtype($type); if ($name_type !== false) { @@ -600,6 +591,7 @@ public static function getSpecificValueToDisplay($field, $values, array $options if ($count > $i) { $obj .= ', '; } + $i++; } } @@ -752,11 +744,13 @@ public static function create($fields) $classname = self::getClassname($itemtype, $fields['name']); $classname::install(); } + + return null; } public static function generateTemplate($fields) { - $itemtypes = strlen($fields['itemtypes']) > 0 + $itemtypes = strlen((string) $fields['itemtypes']) > 0 ? PluginFieldsToolbox::decodeJSONItemtypes($fields['itemtypes'], true) : []; foreach ($itemtypes as $itemtype) { @@ -774,8 +768,8 @@ public static function generateTemplate($fields) $template_class = str_replace('%%CONTAINER%%', $fields['id'], $template_class); $template_class = str_replace('%%ITEMTYPE_RIGHT%%', $itemtype::$rightname, $template_class); $class_filename = $sysname . '.class.php'; - if (file_put_contents(PLUGINFIELDS_CLASS_PATH . "/$class_filename", $template_class) === false) { - Toolbox::logDebug("Error : class file creation - $class_filename"); + if (file_put_contents(PLUGINFIELDS_CLASS_PATH . ('/' . $class_filename), $template_class) === false) { + Toolbox::logDebug('Error : class file creation - ' . $class_filename); return false; } @@ -787,8 +781,8 @@ public static function generateTemplate($fields) $template_class = str_replace('%%CONTAINER_ID%%', $fields['id'], $template_class); $template_class = str_replace('%%CONTAINER_NAME%%', $fields['label'], $template_class); $class_filename = $sysname . 'injection.class.php'; - if (file_put_contents(PLUGINFIELDS_CLASS_PATH . "/$class_filename", $template_class) === false) { - Toolbox::logDebug("Error : datainjection class file creation - $class_filename"); + if (file_put_contents(PLUGINFIELDS_CLASS_PATH . ('/' . $class_filename), $template_class) === false) { + Toolbox::logDebug('Error : datainjection class file creation - ' . $class_filename); return false; } @@ -842,19 +836,19 @@ public function pre_deleteItem() } else { //class does not exists; try to remove any existing table $tablename = getTableForItemType($classname); - $DB->doQuery("DROP TABLE IF EXISTS `$tablename`"); + $DB->doQuery(sprintf('DROP TABLE IF EXISTS `%s`', $tablename)); } //clean session unset($_SESSION['delete_container']); //remove file - if (file_exists(PLUGINFIELDS_CLASS_PATH . "/$class_filename")) { - unlink(PLUGINFIELDS_CLASS_PATH . "/$class_filename"); + if (file_exists(PLUGINFIELDS_CLASS_PATH . ('/' . $class_filename))) { + unlink(PLUGINFIELDS_CLASS_PATH . ('/' . $class_filename)); } - if (file_exists(PLUGINFIELDS_CLASS_PATH . "/$injection_filename")) { - unlink(PLUGINFIELDS_CLASS_PATH . "/$injection_filename"); + if (file_exists(PLUGINFIELDS_CLASS_PATH . ('/' . $injection_filename))) { + unlink(PLUGINFIELDS_CLASS_PATH . ('/' . $injection_filename)); } } @@ -863,7 +857,7 @@ public function pre_deleteItem() public static function preItemPurge($item) { - $itemtype = get_class($item); + $itemtype = $item::class; $containers = new self(); $founded_containers = $containers->find(); foreach ($founded_containers as $container) { @@ -936,8 +930,8 @@ public function showForm($ID, $options = []) ], ); Ajax::updateItemOnSelectEvent( - "dropdown_type$rand", - "itemtypes_$rand", + 'dropdown_type' . $rand, + 'itemtypes_' . $rand, '../ajax/container_itemtypes_dropdown.php', [ 'type' => '__VALUE__', @@ -947,6 +941,7 @@ public function showForm($ID, $options = []) ], ); } + echo ''; echo '' . __('Associated item type') . ' : '; echo ''; @@ -967,18 +962,21 @@ public function showForm($ID, $options = []) if ($count > $i) { $obj .= ', '; } + $i++; } } + echo $obj; } else { - echo " "; + echo sprintf(" ", $rand); self::showFormItemtype([ 'rand' => $rand, 'subtype' => $this->fields['subtype'], ]); echo ''; } + echo ''; echo ''; @@ -986,11 +984,12 @@ public function showForm($ID, $options = []) if (!empty($this->fields['subtype'])) { $display = ''; } - echo ""; + + echo sprintf("", $display); echo ""; echo '' . __('Tab', 'fields') . ' : '; echo ''; - echo " "; + echo sprintf(" ", $rand); if ($ID > 0 && !empty($this->fields['subtype'])) { $itemtypes = PluginFieldsToolbox::decodeJSONItemtypes($this->fields['itemtypes'], true); $itemtype = array_shift($itemtypes); @@ -1000,6 +999,7 @@ public function showForm($ID, $options = []) $tabs = self::getSubtypes($item); echo $tabs[$this->fields['subtype']]; } + echo ''; echo ''; @@ -1039,8 +1039,8 @@ public static function showFormItemtype($params = []) if ($is_domtab) { Ajax::updateItemOnSelectEvent( - ["dropdown_type$rand", "dropdown_itemtypes$rand"], - "subtype_$rand", + ['dropdown_type' . $rand, 'dropdown_itemtypes' . $rand], + 'subtype_' . $rand, '../ajax/container_subtype_dropdown.php', [ 'type' => '__VALUE0__', @@ -1058,7 +1058,7 @@ public static function showFormItemtype($params = []) * @param array $params Parameters * @param boolean $display Whether to display or not; defaults to false * - * @return string|void + * @return string|null */ public static function showFormSubtype($params, $display = false) { @@ -1104,11 +1104,14 @@ public static function showFormSubtype($params, $display = false) } } } + if ($display === false) { return $out; } else { echo $out; } + + return null; } /** @@ -1258,7 +1261,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) // needs to check if entity of item is in hierachy of $tab_name foreach ($container->find(['is_active' => 1, 'name' => $tab_name]) as $data) { $dataitemtypes = PluginFieldsToolbox::decodeJSONItemtypes($data['itemtypes']); - if (in_array(get_class($item), $dataitemtypes) != false) { + if (in_array($item::class, $dataitemtypes) != false) { $entities = [$data['entities_id']]; if ($data['is_recursive']) { $entities = getSonsOf(getTableForItemType('Entity'), $data['entities_id']); @@ -1273,6 +1276,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) } } } + return $tabs_entries; } @@ -1290,7 +1294,7 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ $container = new self(); if ($container->getFromDB($tabnum)) { $dataitemtypes = PluginFieldsToolbox::decodeJSONItemtypes($container->fields['itemtypes']); - if (in_array(get_class($item), $dataitemtypes) != false) { + if (in_array($item::class, $dataitemtypes) != false) { return PluginFieldsField::showForTabContainer($container->fields['id'], $item); } } @@ -1341,6 +1345,7 @@ public function updateFieldsValues($data, $itemtype, $massiveaction = false) if ($field_data['type'] === 'dropdown') { $field_name = 'plugin_fields_' . $field_data['name'] . 'dropdowns_id'; } + if (array_key_exists($field_name, $data)) { if (isset($data['multiple_dropdown_action']) && $data['multiple_dropdown_action'] === 'append' && $exist) { // Add new values to existing ones @@ -1439,7 +1444,7 @@ public static function constructHistory( $items_id, $itemtype, $data, - $field_obj + $field_obj, ) { // Don't log few itemtypes $dbu = new DbUtils(); @@ -1470,7 +1475,7 @@ public static function constructHistory( foreach ($data as $key => $value) { //log only not empty values //do not log if value is empty or if dom name is part of file upload - if (!empty($value) && !str_contains($key, '_uploader_')) { + if (!empty($value) && !str_contains((string) $key, '_uploader_')) { //prepare log $changes = [0, 'N/A', $value]; @@ -1600,6 +1605,7 @@ public static function validateValues($data, $itemtype, $massiveaction) $relatedItem = $dbu->getItemForItemtype($itemtype); $status_value = $relatedItem->fields[$status_field_name] ?? null; } + // Apply status overrides $status_overrides = $status_value !== null ? PluginFieldsStatusOverride::getOverridesForItemtypeAndStatus($container->getID(), $itemtype, $status_value) @@ -1653,7 +1659,7 @@ public static function validateValues($data, $itemtype, $massiveaction) $field['mandatory'] == 1 && ( empty($value) - || (($field['type'] === 'dropdown' || preg_match('/^dropdown-.+/i', $field['type'])) && $value == 0) + || (($field['type'] === 'dropdown' || preg_match('/^dropdown-.+/i', (string) $field['type'])) && $value == 0) || (in_array($field['type'], ['date', 'datetime']) && $value == 'NULL') ) ) { @@ -1672,18 +1678,18 @@ public static function validateValues($data, $itemtype, $massiveaction) } if ($empty_errors !== []) { - Session::AddMessageAfterRedirect(__('Some mandatory fields are empty', 'fields') . - ' : ' . implode(', ', $empty_errors), false, ERROR); + Session::AddMessageAfterRedirect(__('Some mandatory fields are empty', 'fields') + . ' : ' . implode(', ', $empty_errors), false, ERROR); } if ($number_errors !== []) { - Session::AddMessageAfterRedirect(__('Some numeric fields contains non numeric values', 'fields') . - ' : ' . implode(', ', $number_errors), false, ERROR); + Session::AddMessageAfterRedirect(__('Some numeric fields contains non numeric values', 'fields') + . ' : ' . implode(', ', $number_errors), false, ERROR); } if ($url_errors !== []) { - Session::AddMessageAfterRedirect(__('Some URL fields contains invalid links', 'fields') . - ' : ' . implode(', ', $url_errors), false, ERROR); + Session::AddMessageAfterRedirect(__('Some URL fields contains invalid links', 'fields') + . ' : ' . implode(', ', $url_errors), false, ERROR); } return $valid; @@ -1812,12 +1818,14 @@ public static function preItem(CommonDBTM $item) if (isset($_REQUEST['_plugin_fields_type'])) { $type = $_REQUEST['_plugin_fields_type']; } + $subtype = ''; if ($type == 'domtab') { $subtype = $_REQUEST['_plugin_fields_subtype']; } + // tries for 'tab' - if (false === ($c_id = self::findContainer(get_Class($item), $type, $subtype)) && false === $c_id = self::findContainer(get_Class($item))) { + if (false === ($c_id = self::findContainer($item::class, $type, $subtype)) && false === $c_id = self::findContainer($item::class)) { return false; } } @@ -1926,12 +1934,14 @@ private static function populateData($c_id, CommonDBTM $item) //dropdown field $input = 'plugin_fields_' . $field['name'] . 'dropdowns_id'; } + if (isset($item->input[$input])) { $has_fields = true; // Before is_number check, help user to have a number correct, during a massive action of a number field if ($field['type'] == 'number') { $item->input[$input] = str_replace(',', '.', $item->input[$input]); } + $data[$input] = $item->input[$input]; if ($field['type'] === 'richtext') { $filename_input = sprintf('_%s', $input); @@ -1968,8 +1978,9 @@ private static function populateData($c_id, CommonDBTM $item) } } } + //managed multi GLPI item dropdown field - if (preg_match('/^dropdown-(?.+)$/', $field['type'], $match) === 1) { + if (preg_match('/^dropdown-(?.+)$/', (string) $field['type'], $match) === 1) { //values are defined by user if (isset($item->input[$field['name']])) { $data[$field['name']] = $item->input[$field['name']]; @@ -1981,7 +1992,7 @@ private static function populateData($c_id, CommonDBTM $item) } } - if ($has_fields === true) { + if ($has_fields) { return $data; } else { return false; @@ -2051,6 +2062,7 @@ public static function getAddSearchOptions($itemtype, $containers_id = false) if ($containers_id !== false) { $request['WHERE'][] = ['glpi_plugin_fields_containers.id' => $containers_id]; } + if (!Session::isCron()) { $request['WHERE'][] = ['glpi_plugin_fields_profiles.profiles_id' => (int) $_SESSION['glpiactiveprofile']['id']]; } @@ -2093,6 +2105,7 @@ public static function getAddSearchOptions($itemtype, $containers_id = false) if ($data['is_readonly']) { $opt[$i]['massiveaction'] = false; } + switch ($data['type']) { case 'yesno': $opt[$i]['datatype'] = 'bool'; @@ -2138,7 +2151,7 @@ public static function getAddSearchOptions($itemtype, $containers_id = false) $opt[$i]['joinparams']['beforejoin']['joinparams']['jointype'] = 'itemtype_item'; } } elseif ( - preg_match('/^dropdown-(?.+)$/i', $data['type'], $dropdown_matches) + preg_match('/^dropdown-(?.+)$/i', (string) $data['type'], $dropdown_matches) && class_exists($dropdown_matches['class']) ) { if ($data['multiple']) { @@ -2165,7 +2178,7 @@ public static function getAddSearchOptions($itemtype, $containers_id = false) $opt[$i]['linkfield'] = $itemtype_field; $opt[$i]['name'] = $data['container_label'] . ' - ' . $data['field_label'] . ' - ' . _n('Associated item type', 'Associated item types', Session::getPluralNumber()); $opt[$i]['datatype'] = 'itemtypename'; - $opt[$i]['types'] = !empty($data['allowed_values']) ? json_decode($data['allowed_values']) : []; + $opt[$i]['types'] = empty($data['allowed_values']) ? [] : json_decode((string) $data['allowed_values']); $opt[$i]['additionalfields'] = ['itemtype']; $opt[$i]['joinparams']['jointype'] = 'itemtype_item'; $opt[$i]['forcegroupby'] = true; @@ -2227,8 +2240,6 @@ private static function getSubtypes($item) * @param string $itemtype Name of associated itemtype * @param string $container_name Name of container * @param string $suffix Suffix to add - * - * @return string */ public static function getClassname(string $itemtype, string $container_name, string $suffix = ''): string { @@ -2244,8 +2255,6 @@ public static function getClassname(string $itemtype, string $container_name, st * * @param string $itemtype Name of associated itemtype * @param string $container_name Name of container - * - * @return string */ protected static function getSystemName(string $itemtype, string $container_name): string { diff --git a/inc/containerdisplaycondition.class.php b/inc/containerdisplaycondition.class.php index 71109c05..ae621035 100644 --- a/inc/containerdisplaycondition.class.php +++ b/inc/containerdisplaycondition.class.php @@ -27,22 +27,29 @@ * @link https://github.com/pluginsGLPI/fields * ------------------------------------------------------------------------- */ -use Glpi\Features\Clonable; use Glpi\Application\View\TemplateRenderer; +use Glpi\Features\Clonable; class PluginFieldsContainerDisplayCondition extends CommonDBChild { use Clonable; public static $itemtype = PluginFieldsContainer::class; + public static $items_id = 'plugin_fields_containers_id'; public const SHOW_CONDITION_EQ = 1; + public const SHOW_CONDITION_NE = 2; + public const SHOW_CONDITION_LT = 3; + public const SHOW_CONDITION_GT = 4; + public const SHOW_CONDITION_REGEX = 5; + public const SHOW_CONDITION_UNDER = 6; + public const SHOW_CONDITION_NOT_UNDER = 7; /** @@ -64,7 +71,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `plugin_fields_containers_id` INT {$default_key_sign} NOT NULL DEFAULT '0', `itemtype` VARCHAR(100) DEFAULT NULL, @@ -84,14 +91,11 @@ public static function installBaseData(Migration $migration, $version) /** * Get display condition comparison operators. * - * @param bool $only_simple_conditions - * @param bool $with_treedropdown_conditions * - * @return array */ private static function getComparisonOperators( bool $only_simple_conditions = false, - bool $with_treedropdown_conditions = false + bool $with_treedropdown_conditions = false, ): array { $conditions = [ self::SHOW_CONDITION_EQ => '=', @@ -158,6 +162,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) if (!($item instanceof CommonDBTM)) { return ''; } + return self::createTabEntry( self::getTypeName(Session::getPluralNumber()), countElementsInTable(self::getTable(), ['plugin_fields_containers_id' => $item->getID()]), @@ -199,7 +204,7 @@ public static function getDisplayConditionForContainer(int $container_id): array return $conditions; } - private static function getItemtypesForContainer(int $container_id): array + private function getItemtypesForContainer(int $container_id): array { /** @var DBmysql $DB */ global $DB; @@ -408,7 +413,7 @@ public function computeDisplayContainer($item, $container_id) { //load all condition for itemtype and container $displayCondition = new self(); - $found_dc = $displayCondition->find(['itemtype' => get_class($item), 'plugin_fields_containers_id' => $container_id]); + $found_dc = $displayCondition->find(['itemtype' => $item::class, 'plugin_fields_containers_id' => $container_id]); if (count($found_dc)) { $display = true; @@ -431,7 +436,7 @@ public function checkCondition($item) { $value = $this->fields['value']; $condition = $this->fields['condition']; - $searchOption = Search::getOptions(get_class($item))[$this->fields['search_option']]; + $searchOption = Search::getOptions($item::class)[$this->fields['search_option']]; $fields = array_merge($item->fields, $item->input); @@ -441,42 +446,43 @@ public function checkCondition($item) if ($value == $fields[$searchOption['linkfield']]) { return false; } + break; case self::SHOW_CONDITION_NE: // '≠' if ($value != $fields[$searchOption['linkfield']]) { return false; } + break; case self::SHOW_CONDITION_LT: - // '<'; - if ($fields[$searchOption['linkfield']] > $value) { - return false; - } - break; case self::SHOW_CONDITION_GT: - //'>'; + // '<'; if ($fields[$searchOption['linkfield']] > $value) { return false; } + break; case self::SHOW_CONDITION_REGEX: //'regex'; - if (self::checkRegex($value) && preg_match_all($value . 'i', $fields[$searchOption['linkfield']]) > 0) { + if (self::checkRegex($value) && preg_match_all($value . 'i', (string) $fields[$searchOption['linkfield']]) > 0) { return false; } + break; case self::SHOW_CONDITION_UNDER: $sons = getSonsOf($searchOption['table'], $value); if (in_array($fields[$searchOption['linkfield']], $sons)) { return false; } + break; case self::SHOW_CONDITION_NOT_UNDER: $sons = getSonsOf($searchOption['table'], $value); if (!in_array($fields[$searchOption['linkfield']], $sons)) { return false; } + break; } @@ -530,6 +536,7 @@ public static function showForTabContainer(CommonGLPI $item, $options = []) if (!$item instanceof CommonDBTM) { return; } + $displayCondition_id = $options['displaycondition_id'] ?? 0; $display_condition = null; @@ -558,7 +565,7 @@ public function showForm($ID, array $options = []) $twig_params = [ 'container_display_condition' => $this, 'container_id' => $container_id, - 'container_itemtypes' => self::getItemtypesForContainer($container_id), + 'container_itemtypes' => $this->getItemtypesForContainer($container_id), 'search_options' => $this->isNewItem() ? [] : self::removeBlackListedOption(Search::getOptions($this->fields['itemtype']), $this->fields['itemtype']), diff --git a/inc/destinationfield.class.php b/inc/destinationfield.class.php index 96348fbd..b54c67fc 100644 --- a/inc/destinationfield.class.php +++ b/inc/destinationfield.class.php @@ -42,7 +42,7 @@ class PluginFieldsDestinationField extends AbstractConfigField { - public function __construct(private AbstractCommonITILFormDestination $itil_destination) {} + public function __construct(private readonly AbstractCommonITILFormDestination $itil_destination) {} #[Override] public function getLabel(): string @@ -62,7 +62,7 @@ public function renderConfigForm( FormDestination $destination, JsonFieldInterface $config, string $input_name, - array $display_options + array $display_options, ): string { if (!$config instanceof SimpleValueConfig) { throw new InvalidArgumentException("Unexpected config class"); @@ -80,7 +80,7 @@ public function renderConfigForm( public function applyConfiguratedValueToInputUsingAnswers( JsonFieldInterface $config, array $input, - AnswersSet $answers_set + AnswersSet $answers_set, ): array { if (!$config instanceof SimpleValueConfig) { throw new InvalidArgumentException("Unexpected config class"); @@ -139,6 +139,7 @@ public function applyConfiguratedValueToInputUsingAnswers( } } } + return $input; } diff --git a/inc/dropdown.class.php b/inc/dropdown.class.php index c7048821..c62531cf 100644 --- a/inc/dropdown.class.php +++ b/inc/dropdown.class.php @@ -31,6 +31,7 @@ class PluginFieldsDropdown { public static $rightname = 'dropdown'; + public $can_be_translated = true; /** @@ -53,18 +54,18 @@ public static function installUserData(Migration $migration, $version) foreach ($fields as $field) { //First, drop old fields from plugin directories $class_filename = $field['name'] . 'dropdown.class.php'; - if (file_exists(PLUGINFIELDS_DIR . "/inc/$class_filename")) { - unlink(PLUGINFIELDS_DIR . "/inc/$class_filename"); + if (file_exists(PLUGINFIELDS_DIR . ('/inc/' . $class_filename))) { + unlink(PLUGINFIELDS_DIR . ('/inc/' . $class_filename)); } $front_filename = $field['name'] . 'dropdown.php'; - if (file_exists(PLUGINFIELDS_DIR . "/front/$front_filename")) { - unlink(PLUGINFIELDS_DIR . "/front/$front_filename"); + if (file_exists(PLUGINFIELDS_DIR . ('/front/' . $front_filename))) { + unlink(PLUGINFIELDS_DIR . ('/front/' . $front_filename)); } $form_filename = $field['name'] . 'dropdown.form.php'; - if (file_exists(PLUGINFIELDS_DIR . "/front/$form_filename")) { - unlink(PLUGINFIELDS_DIR . "/front/$form_filename"); + if (file_exists(PLUGINFIELDS_DIR . ('/front/' . $form_filename))) { + unlink(PLUGINFIELDS_DIR . ('/front/' . $form_filename)); } } @@ -76,6 +77,7 @@ public static function installUserData(Migration $migration, $version) // Regenerate files and install missing tables $migration->displayMessage(__('Updating generated dropdown files', 'fields')); + $obj = new PluginFieldsField(); $fields = $obj->find(['type' => 'dropdown']); foreach ($fields as $field) { @@ -137,11 +139,11 @@ public static function create($input) $class_filename = $input['name'] . 'dropdown.class.php'; if ( file_put_contents( - PLUGINFIELDS_CLASS_PATH . "/$class_filename", + PLUGINFIELDS_CLASS_PATH . ('/' . $class_filename), $template_class, ) === false ) { - Toolbox::logDebug("Error : dropdown class file creation - $class_filename"); + Toolbox::logDebug('Error : dropdown class file creation - ' . $class_filename); return false; } @@ -154,7 +156,7 @@ public static function create($input) //call install method (create table) if ($classname::install() === false) { - Toolbox::logDebug("Error : calling dropdown $classname installation"); + Toolbox::logDebug(sprintf('Error : calling dropdown %s installation', $classname)); return false; } @@ -172,7 +174,7 @@ public static function destroy($dropdown_name) //call uninstall method in dropdown class if ($classname::uninstall() === false) { - Toolbox::logDebug("Error : calling dropdown $classname uninstallation"); + Toolbox::logDebug(sprintf('Error : calling dropdown %s uninstallation', $classname)); return false; } @@ -202,7 +204,7 @@ public static function destroy($dropdown_name) public static function getClassname($system_name) { - return 'PluginFields' . ucfirst($system_name) . 'Dropdown'; + return 'PluginFields' . ucfirst((string) $system_name) . 'Dropdown'; } public static function multipleDropdownAddWhere($link, $tablefield, $field, $val, $searchtype, $field_field = []) @@ -222,7 +224,7 @@ public static function multipleDropdownAddWhere($link, $tablefield, $field, $val $operator = ($searchtype === 'equals') ? '' : 'NOT '; $condition = ($val == '0') ? $operator . 'IN("", "[]")' - : $operator . 'LIKE ' . $DB->quoteValue("%\"$val\"%"); + : $operator . 'LIKE ' . $DB->quoteValue(sprintf('%%"%s"%%', $val)); return $sqlBase . ' ' . $condition; } diff --git a/inc/field.class.php b/inc/field.class.php index 7c7a2cf2..85803800 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -27,9 +27,9 @@ * @link https://github.com/pluginsGLPI/fields * ------------------------------------------------------------------------- */ -use Glpi\Features\Clonable; -use Glpi\DBAL\QueryExpression; use Glpi\Application\View\TemplateRenderer; +use Glpi\DBAL\QueryExpression; +use Glpi\Features\Clonable; use Glpi\Form\Question; class PluginFieldsField extends CommonDBChild @@ -43,6 +43,7 @@ class PluginFieldsField extends CommonDBChild public const SEARCH_OPTION_STARTING_INDEX = 76665; public static $itemtype = PluginFieldsContainer::class; + public static $items_id = 'plugin_fields_containers_id'; public function getForbiddenStandardMassiveAction() @@ -75,7 +76,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `name` VARCHAR(255) DEFAULT NULL, `label` VARCHAR(255) DEFAULT NULL, @@ -98,19 +99,22 @@ public static function installBaseData(Migration $migration, $version) } } - $migration->displayMessage("Updating $table"); + $migration->displayMessage('Updating ' . $table); if (!$DB->fieldExists($table, 'is_active')) { $migration->addField($table, 'is_active', 'bool', ['value' => '1']); $migration->addKey($table, 'is_active', 'is_active'); } + if (!$DB->fieldExists($table, 'is_readonly')) { $migration->addField($table, 'is_readonly', 'bool', ['default' => 'false']); $migration->addKey($table, 'is_readonly', 'is_readonly'); } + if (!$DB->fieldExists($table, 'mandatory')) { $migration->addField($table, 'mandatory', 'bool', ['value' => '0']); } + if (!$DB->fieldExists($table, 'multiple')) { $migration->addField($table, 'multiple', 'bool', ['value' => '0']); } @@ -168,8 +172,6 @@ public static function installBaseData(Migration $migration, $version) * Producing an exact mapping between previous unstable SO ID and new stable SO ID is almost impossible in many cases, due to * previously described behaviours. Basically, we cannot know if the current SO ID in database is still correct * and what were the profile rights when it was generated. - * - * @param Migration $migration */ private static function migrateToStableSO(Migration $migration): void { @@ -513,9 +515,9 @@ public function prepareName($input, bool $prevent_duplicated = true) // MySQL/MariaDB official limit for a column name is 64 chars, // but there is a bug when trying to drop the column and the real max len is 53 chars // FIXME: see: https://bugs.mysql.com/bug.php?id=107165 - if (strlen($field_name) > 52) { + if (strlen((string) $field_name) > 52) { $rand = random_int(0, mt_getrandmax()); - $field_name = substr($field_name, 0, 52 - strlen((string) $rand)) . $rand; + $field_name = substr((string) $field_name, 0, 52 - strlen((string) $rand)) . $rand; } return $field_name; @@ -552,11 +554,8 @@ public function getNextRanking() public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { - if (!$withtemplate) { - switch ($item->getType()) { - case self::class: - return static::getTypeName(1); - } + if (!$withtemplate && $item->getType() === self::class) { + return static::getTypeName(1); } if (!($item instanceof CommonDBTM)) { @@ -610,7 +609,7 @@ public function showSummary($container) ]); $rand = mt_rand(); - echo "
"; + echo sprintf("
", $cID, $rand); $ajax_params = [ 'type' => self::class, @@ -627,8 +626,8 @@ public function showSummary($container) }; '); - echo "
" . - ""; + echo "
'; if (count($iterator) == 0) { @@ -659,14 +658,14 @@ public function showSummary($container) echo ""; echo ''; - $label = !empty($this->fields['label']) ? $this->fields['label'] : NOT_AVAILABLE; - echo "getID()}'>{$label}"; + $label = empty($this->fields['label']) ? NOT_AVAILABLE : $this->fields['label']; + echo "%s", $this->getID(), $label); echo ''; echo '' . $fields_type[$this->fields['type']] . ''; echo '' ; $dropdown_matches = []; if ( - preg_match('/^dropdown-(?.+)$/', $this->fields['type'], $dropdown_matches) === 1 + preg_match('/^dropdown-(?.+)$/', (string) $this->fields['type'], $dropdown_matches) === 1 && !empty($this->fields['default_value']) ) { $itemtype = $dropdown_matches['class']; @@ -674,13 +673,14 @@ public function showSummary($container) if (is_a($itemtype, CommonDBTM::class, true)) { $item = new $itemtype(); if ($this->fields['multiple']) { - $values = json_decode($this->fields['default_value']); + $values = json_decode((string) $this->fields['default_value']); $names = []; foreach ($values as $value) { if ($item->getFromDB($value)) { $names[] = $item->getName(); } } + echo implode(', ', $names); } elseif ($item->getFromDB($this->fields['default_value'])) { echo $item->getName(); @@ -691,7 +691,7 @@ public function showSummary($container) if ($this->fields['multiple']) { echo implode( ', ', - Dropdown::getDropdownArrayNames($table, json_decode($this->fields['default_value'])), + Dropdown::getDropdownArrayNames($table, json_decode((string) $this->fields['default_value'])), ); } else { echo Dropdown::getDropdownName($table, $this->fields['default_value']); @@ -699,6 +699,7 @@ public function showSummary($container) } else { echo $this->fields['default_value']; } + echo ''; echo "" . Dropdown::getYesNo($this->fields['mandatory']) . ''; echo ""; @@ -720,6 +721,7 @@ public function showSummary($container) } } } + echo ''; echo '
'; echo Html::scriptBlock('$(document).ready(function() { @@ -789,8 +791,8 @@ public function showForm($ID, $options = []) echo ''; echo ''; Ajax::updateItemOnSelectEvent( - "dropdown_type$rand", - "plugin_fields_specific_fields_$rand", + 'dropdown_type' . $rand, + 'plugin_fields_specific_fields_' . $rand, '../ajax/field_specific_fields.php', [ 'id' => $ID, @@ -799,7 +801,7 @@ public function showForm($ID, $options = []) ], ); Ajax::updateItem( - "plugin_fields_specific_fields_$rand", + 'plugin_fields_specific_fields_' . $rand, '../ajax/field_specific_fields.php', [ 'id' => $ID, @@ -838,8 +840,9 @@ public static function showForTabContainer($c_id, $item) //profile restriction $right = PluginFieldsProfile::getRightOnContainer($_SESSION['glpiactiveprofile']['id'], $c_id); if ($right < READ) { - return; + return null; } + $canedit = $right > READ; //get fields for this container @@ -905,8 +908,8 @@ public static function showForTab($params) $item = $params['item']; $functions = array_column(debug_backtrace(), 'function'); - $subtype = $_SESSION['glpi_tabs'][strtolower($item::getType())] ?? ''; - $type = str_ends_with($subtype, '$main') + $subtype = $_SESSION['glpi_tabs'][strtolower((string) $item::getType())] ?? ''; + $type = str_ends_with((string) $subtype, '$main') || in_array('showForm', $functions) || in_array('showPrimaryForm', $functions) || in_array('showFormHelpdesk', $functions) @@ -916,15 +919,16 @@ public static function showForTab($params) if ($subtype == -1) { $type = 'dom'; } + // if we are in 'dom' or 'tab' type, no need for subtype ('domtab' specific) - if ($type != 'domtab') { + if ($type !== 'domtab') { $subtype = ''; } //find container (if not exist, do nothing) if (isset($_REQUEST['c_id'])) { $c_id = $_REQUEST['c_id']; - } elseif (!$c_id = PluginFieldsContainer::findContainer(get_Class($item), $type, $subtype)) { + } elseif (!$c_id = PluginFieldsContainer::findContainer($item::class, $type, $subtype)) { return; } @@ -938,6 +942,7 @@ public static function showForTab($params) //need to check if container is usable on this object entity $loc_c = new PluginFieldsContainer(); $loc_c->getFromDB($c_id); + $entities = [$loc_c->fields['entities_id']]; if ($loc_c->fields['is_recursive']) { $entities = getSonsOf(getTableForItemType('Entity'), $loc_c->fields['entities_id']); @@ -954,13 +959,14 @@ public static function showForTab($params) if (!isset($_SERVER['REQUEST_URI'])) { return; } + $current_url = $_SERVER['REQUEST_URI']; if ( - !str_contains($current_url, '.form.php') - && !str_contains($current_url, '.injector.php') - && !str_contains($current_url, '.public.php') - && !str_contains($current_url, 'ajax/planning') - && !str_contains($current_url, 'ajax/timeline.php') // ITILSolution load from timeline + !str_contains((string) $current_url, '.form.php') + && !str_contains((string) $current_url, '.injector.php') + && !str_contains((string) $current_url, '.public.php') + && !str_contains((string) $current_url, 'ajax/planning') + && !str_contains((string) $current_url, 'ajax/timeline.php') // ITILSolution load from timeline ) { return; } @@ -969,7 +975,7 @@ public static function showForTab($params) $itemtypes = PluginFieldsContainer::getUsedItemtypes($type, true); //if no dom containers defined for this itemtype, do nothing (in_array case insensitive) - if (!in_array(strtolower($item::getType()), array_map('strtolower', $itemtypes))) { + if (!in_array(strtolower((string) $item::getType()), array_map('strtolower', $itemtypes))) { return; } @@ -981,7 +987,7 @@ public static function showForTab($params) }; $html_id = 'plugin_fields_container_' . mt_rand(); - echo "
"; + echo sprintf("
"; $display_condition = new PluginFieldsContainerDisplayCondition(); if ($display_condition->computeDisplayContainer($item, $c_id)) { self::showDomContainer( @@ -992,11 +998,12 @@ public static function showForTab($params) [], ); } + echo '
'; //JS to trigger any change and check if container need to be display or not $ajax_url = $CFG_GLPI['root_doc'] . '/plugins/fields/ajax/container.php'; - $items_id = !$item->isNewItem() ? $item->getID() : 0; + $items_id = $item->isNewItem() ? 0 : $item->getID(); echo Html::scriptBlock( <<getID()); if ($right < READ) { - return; + return null; } + $canedit = $right > READ; // Fill status overrides if needed @@ -1159,7 +1167,7 @@ public static function prepareHtmlFields( $field['itemtype'] = self::getType(); $field['label'] = PluginFieldsLabelTranslation::getLabelFor($field); - $field['allowed_values'] = !empty($field['allowed_values']) ? json_decode($field['allowed_values']) : []; + $field['allowed_values'] = empty($field['allowed_values']) ? [] : json_decode((string) $field['allowed_values']); if ($field['type'] === 'glpi_item') { // Convert allowed values to [$itemtype_class => $itemtype_name] format $allowed_itemtypes = []; @@ -1168,13 +1176,14 @@ public static function prepareHtmlFields( $allowed_itemtypes[$allowed_itemtype] = $allowed_itemtype::getTypeName(Session::getPluralNumber()); } } + $field['allowed_values'] = $allowed_itemtypes; } //compute classname for 'dropdown-XXXXXX' field $dropdown_matches = []; if ( - preg_match('/^dropdown-(?.+)$/i', $field['type'], $dropdown_matches) + preg_match('/^dropdown-(?.+)$/i', (string) $field['type'], $dropdown_matches) && class_exists($dropdown_matches['class']) ) { $dropdown_class = $dropdown_matches['class']; @@ -1188,6 +1197,7 @@ public static function prepareHtmlFields( if ($object->maybeDeleted()) { $field['dropdown_condition']['is_deleted'] = false; } + if ($object->maybeActive()) { $field['dropdown_condition']['is_active'] = true; } @@ -1226,6 +1236,7 @@ public static function prepareHtmlFields( } elseif (isset($item->input['itemtype_' . $field['name']])) { $value['itemtype'] = $item->input['itemtype_' . $field['name']] ?? ''; } + if (isset($_SESSION['plugin']['fields']['values_sent']['items_id_' . $field['name']])) { $value['items_id'] = $_SESSION['plugin']['fields']['values_sent']['items_id_' . $field['name']]; } elseif (isset($item->input['items_id_' . $field['name']])) { @@ -1262,7 +1273,7 @@ public static function prepareHtmlFields( // - either from a previous input (it will be an array). // // -> Decode it only if it is not already an array. - $value = json_decode($value); + $value = json_decode((string) $value); } $field['value'] = $value; @@ -1291,7 +1302,7 @@ public static function showSingle($itemtype, $searchOption, $massiveaction = fal $cleaned_linkfield = preg_replace( '/plugin_fields_(.*)dropdowns_id/', '$1', - $searchOption['linkfield'], + (string) $searchOption['linkfield'], ); //find field @@ -1314,7 +1325,7 @@ public static function showSingle($itemtype, $searchOption, $massiveaction = fal ], 'WHERE' => [ 'fields.name' => $cleaned_linkfield, - 'containers.itemtypes' => ['LIKE', "%$itemtype%"], + 'containers.itemtypes' => ['LIKE', sprintf('%%%s%%', $itemtype)], ], ]); @@ -1410,27 +1421,21 @@ public function post_addItem() public function rawSearchOptions() { - $tab = []; - - $tab[] = [ + return [[ 'id' => 2, 'table' => self::getTable(), 'field' => 'label', 'name' => __('Label'), 'massiveaction' => false, 'autocomplete' => true, - ]; - - $tab[] = [ + ], [ 'id' => 3, 'table' => self::getTable(), 'field' => 'default_value', 'name' => __('Default values'), 'massiveaction' => false, 'autocomplete' => true, - ]; - - return $tab; + ]]; } public function prepareInputForClone($input) @@ -1438,7 +1443,7 @@ public function prepareInputForClone($input) if (array_key_exists('allowed_values', $input) && !empty($input['allowed_values'])) { // $input has been transformed with `Toolbox::addslashes_deep()`, and `self::prepareInputForAdd()` // is expecting an array, so it have to be unslashed then json decoded. - $input['allowed_values'] = json_decode($input['allowed_values']); + $input['allowed_values'] = json_decode((string) $input['allowed_values']); } else { unset($input['allowed_values']); } diff --git a/inc/inventory.class.php b/inc/inventory.class.php index 93c2e50f..3c119ed8 100644 --- a/inc/inventory.class.php +++ b/inc/inventory.class.php @@ -91,6 +91,7 @@ public static function updateFields($containersData, $itemtype, $items_id) // $containersData contains only one element, encapsulate it into an array $containersData = [$containersData]; } + foreach ($containersData as $key => $containerData) { $container = new PluginFieldsContainer(); $container->getFromDB($containerData['ID']); @@ -99,30 +100,29 @@ public static function updateFields($containersData, $itemtype, $items_id) $data['itemtype'] = $itemtype; $data['plugin_fields_containers_id'] = $containerData['ID']; foreach ($containerData['FIELDS'] as $key => $value) { - $data[strtolower($key)] = $value; + $data[strtolower((string) $key)] = $value; } + $container->updateFieldsValues($data, $itemtype, false); } } public static function loadXMLFile($itemtype, $items_id) { - $pxml = false; - $folder = substr($items_id, 0, -1); - if (empty($folder)) { + $folder = substr((string) $items_id, 0, -1); + if ($folder === '' || $folder === '0') { $folder = '0'; } //Check if the file exists with the .xml extension (new format) /** @phpstan-ignore-next-line */ - $file = PLUGIN_FUSIONINVENTORY_XML_DIR . strtolower($itemtype) . '/' . $folder . '/' . $items_id; + $file = PLUGIN_FUSIONINVENTORY_XML_DIR . strtolower((string) $itemtype) . '/' . $folder . '/' . $items_id; if (file_exists($file . '.xml')) { $file .= '.xml'; } elseif (!file_exists($file)) { return false; } - $pxml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOCDATA); - return $pxml; + return simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOCDATA); } } diff --git a/inc/labeltranslation.class.php b/inc/labeltranslation.class.php index 4ea37984..cc77f060 100644 --- a/inc/labeltranslation.class.php +++ b/inc/labeltranslation.class.php @@ -35,6 +35,7 @@ class PluginFieldsLabelTranslation extends CommonDBChild use Clonable; public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; /** @@ -59,7 +60,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `itemtype` VARCHAR(30) NOT NULL, `items_id` INT {$default_key_sign} NOT NULL, @@ -86,7 +87,7 @@ public static function installBaseData(Migration $migration, $version) if ($DB->fieldExists($table, 'plugin_fields_items_id')) { $migration->dropKey($table, 'plugin_fields_items_id'); $migration->migrationOneTable($table); - $migration->changeField($table, 'plugin_fields_items_id', 'items_id', "INT {$default_key_sign} NOT NULL"); + $migration->changeField($table, 'plugin_fields_items_id', 'items_id', sprintf('INT %s NOT NULL', $default_key_sign)); $migration->addKey($table, 'items_id'); } @@ -130,6 +131,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) if (!($item instanceof CommonDBTM)) { return ''; } + $nb = countElementsInTable( self::getTable(), [ @@ -172,7 +174,7 @@ public static function showTranslations(CommonDBTM $item) $canedit = $item->can($item->getID(), UPDATE); $rand = mt_rand(); if ($canedit) { - echo "
"; + echo "
"); $ajax_params = [ 'type' => self::class, @@ -189,9 +191,9 @@ public static function showTranslations(CommonDBTM $item) }; '); - echo "
'; + echo "
'; } $obj = new self(); @@ -209,6 +211,7 @@ public static function showTranslations(CommonDBTM $item) $massiveactionparams = ['container' => 'mass' . self::class . $rand]; Html::showMassiveActions($massiveactionparams); } + echo "
"; echo ""; echo "'; @@ -217,16 +220,18 @@ public static function showTranslations(CommonDBTM $item) echo Html::getCheckAllAsCheckbox('mass' . self::class . $rand); echo ''; } + echo ''; echo ''; foreach ($found as $data) { echo "'; + onClick=\"viewEditTranslation" . $data['id'] . ($rand . '();"') : '') . '>'; if ($canedit) { echo "'; } + echo ''; } + echo '
" . __('List of translations') . '
' . __('Language', 'fields') . '' . __('Label', 'fields') . '
"; Html::showMassiveActionCheckBox(self::class, $data['id']); echo ''; if ($canedit) { $ajax_params = [ @@ -244,11 +249,13 @@ public static function showTranslations(CommonDBTM $item) }; '); } + echo Dropdown::getLanguageName($data['language']); echo ''; echo $data['label']; echo '
'; if ($canedit) { $massiveactionparams['ontop'] = false; @@ -259,8 +266,6 @@ public static function showTranslations(CommonDBTM $item) echo ""; echo "
" . __('No translation found') . '
'; } - - return; } /** @@ -280,12 +285,13 @@ public function showFormForItem($itemtype, $items_id, $id = -1) // Create item $this->check(-1, CREATE); } + $this->showFormHeader(); echo ""; echo '' . __('Language') . ' :'; echo ''; - echo ""; - echo ""; + echo sprintf("", $itemtype); + echo sprintf("", $items_id); if ($id > 0) { echo Dropdown::getLanguageName($this->fields['language']); } else { @@ -300,6 +306,7 @@ public function showFormForItem($itemtype, $items_id, $id = -1) ], ); } + echo " "; echo ""; @@ -312,8 +319,6 @@ public function showFormForItem($itemtype, $items_id, $id = -1) echo ''; $this->showFormButtons(); - - return; } /** diff --git a/inc/migration.class.php b/inc/migration.class.php index ebfdd404..5b7dffc8 100644 --- a/inc/migration.class.php +++ b/inc/migration.class.php @@ -38,11 +38,7 @@ public function displayMessage($msg) /** * Return SQL fields corresponding to given additionnal field. * - * @param string $field_name - * @param string $field_type - * @param array $options * - * @return array */ public static function getSQLFields(string $field_name, string $field_type, array $options = []): array { @@ -58,7 +54,8 @@ public static function getSQLFields(string $field_name, string $field_type, arra if ($field_type === 'dropdown') { $field_name = getForeignKeyFieldForItemType(PluginFieldsDropdown::getClassname($field_name)); } - $fields[$field_name] = $options['multiple'] ?? false ? 'LONGTEXT' : "INT {$default_key_sign} NOT NULL DEFAULT 0"; + + $fields[$field_name] = $options['multiple'] ?? false ? 'LONGTEXT' : sprintf('INT %s NOT NULL DEFAULT 0', $default_key_sign); break; case $field_type === 'textarea': case $field_type === 'url': @@ -72,7 +69,7 @@ public static function getSQLFields(string $field_name, string $field_type, arra break; case $field_type === 'glpi_item': $fields[sprintf('itemtype_%s', $field_name)] = 'varchar(100) DEFAULT NULL'; - $fields[sprintf('items_id_%s', $field_name)] = "int {$default_key_sign} NOT NULL DEFAULT 0"; + $fields[sprintf('items_id_%s', $field_name)] = sprintf('int %s NOT NULL DEFAULT 0', $default_key_sign); break; case $field_type === 'date': case $field_type === 'datetime': @@ -96,9 +93,7 @@ public static function getSQLFields(string $field_name, string $field_type, arra * should have been removed and list them. * If parameter $fix is true, fields are deleted from database. * - * @param bool $fix * - * @return array */ public static function checkDeadFields(bool $fix): array { @@ -120,7 +115,7 @@ public static function checkDeadFields(bool $fix): array // One table to handle per itemtype foreach ($itemtypes as $itemtype) { // Build table name - $table = getTableForItemType("PluginFields{$itemtype}{$name}"); + $table = getTableForItemType(sprintf('PluginFields%s%s', $itemtype, $name)); if (!$DB->tableExists($table)) { // Missing table; skip (abnormal) @@ -133,7 +128,7 @@ public static function checkDeadFields(bool $fix): array // Compute which fields should be removed $fields_to_drop = array_diff($found_fields, $valid_fields); - if (count($fields_to_drop) > 0) { + if ($fields_to_drop !== []) { $dead_fields[$table] = $fields_to_drop; } } @@ -158,8 +153,6 @@ public static function checkDeadFields(bool $fix): array * Get all fields defined for a container in glpi_plugin_fields_fields * * @param int $container_id Id of the container - * - * @return array */ private static function getValidFieldsForContainer(int $container_id): array { @@ -180,12 +173,10 @@ private static function getValidFieldsForContainer(int $container_id): array * This means all fields found in the table expect those defined in * $basic_fields * - * @param string $table * - * @return array */ private static function getCustomFieldsInContainerTable( - string $table + string $table, ): array { /** @var DBmysql $DB */ global $DB; diff --git a/inc/profile.class.php b/inc/profile.class.php index 300fabc9..8ef44fc7 100644 --- a/inc/profile.class.php +++ b/inc/profile.class.php @@ -35,8 +35,11 @@ class PluginFieldsProfile extends CommonDBRelation use Clonable; public static $itemtype_1 = PluginFieldsContainer::class; + public static $items_id_1 = 'plugin_fields_containers_id'; + public static $itemtype_2 = Profile::class; + public static $items_id_2 = 'profiles_id'; /** @@ -61,7 +64,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `profiles_id` INT {$default_key_sign} NOT NULL DEFAULT '0', `plugin_fields_containers_id` INT {$default_key_sign} NOT NULL DEFAULT '0', @@ -132,6 +135,7 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ echo ''; echo ''; } + echo '
    '; echo ""; echo ""; diff --git a/inc/questiontype.class.php b/inc/questiontype.class.php index 2d2df5bd..36e32d02 100644 --- a/inc/questiontype.class.php +++ b/inc/questiontype.class.php @@ -114,6 +114,7 @@ public function renderAdministrationTemplate(?Question $question): string if ($block_id === null) { $block_id = current(array_keys($this->getAvailableBlocks())); } + $available_fields = self::getFieldsFromBlock($block_id); // Retrieve current field @@ -126,7 +127,7 @@ public function renderAdministrationTemplate(?Question $question): string // Compute default value for the field $default_value = null; - if ($question !== null && !empty($question->fields['default_value'])) { + if ($question instanceof Question && !empty($question->fields['default_value'])) { $default_value = json_decode($question->fields['default_value'], true); } @@ -149,6 +150,7 @@ public function renderEndUserTemplate(Question $question): string if ($block_id === null) { $block_id = current(array_keys($this->getAvailableBlocks())); } + $available_fields = self::getFieldsFromBlock($block_id); // Retrieve current field @@ -182,7 +184,7 @@ public function formatRawAnswer(mixed $answer, Question $question): string throw new LogicException('No field configured for this question'); } - $current_field = PluginFieldsField::getById((int) $current_field_id); + $current_field = PluginFieldsField::getById($current_field_id); switch ($current_field->fields['type']) { case 'header': @@ -218,8 +220,8 @@ public function formatRawAnswer(mixed $answer, Question $question): string return $item->fields['name']; } - if (str_starts_with($current_field->fields['type'], 'dropdown-')) { - $itemtype = substr($current_field->fields['type'], strlen('dropdown-')); + if (str_starts_with((string) $current_field->fields['type'], 'dropdown-')) { + $itemtype = substr((string) $current_field->fields['type'], strlen('dropdown-')); if (!getItemForItemtype($itemtype)) { return ''; } @@ -227,6 +229,7 @@ public function formatRawAnswer(mixed $answer, Question $question): string if (!is_array($answer)) { $answer = [$answer]; } + $names = []; foreach ($answer as $items_id) { $item = $itemtype::getById($items_id); @@ -234,6 +237,7 @@ public function formatRawAnswer(mixed $answer, Question $question): string $names[] = $item->fields['name']; } } + return implode(', ', $names); } @@ -283,11 +287,10 @@ public function getTargetQuestionType(array $rawData): string * Retrieve the default value block from the question's extra data * * @param Question|null $question The question to retrieve the default value from - * @return ?int */ public function getDefaultValueBlockId(?Question $question): ?int { - if ($question === null) { + if (!$question instanceof Question) { return null; } @@ -304,11 +307,10 @@ public function getDefaultValueBlockId(?Question $question): ?int * Retrieve the default value field from the question's extra data * * @param Question|null $question The question to retrieve the default value from - * @return ?int */ public function getDefaultValueFieldId(?Question $question): ?int { - if ($question === null) { + if (!$question instanceof Question) { return null; } @@ -321,7 +323,7 @@ public function getDefaultValueFieldId(?Question $question): ?int return $config->getFieldId(); } - private function getAvailableBlocks(?Form $form = null): array + private function getAvailableBlocks(): array { $field_container = new PluginFieldsContainer(); $available_blocks = []; @@ -337,6 +339,7 @@ private function getAvailableBlocks(?Form $form = null): array foreach ($result as $id => $data) { $available_blocks[$id] = $data['label']; } + return $available_blocks; } @@ -363,8 +366,6 @@ public static function getFieldsFromBlock(?int $block_id): array /** * Check if there is at least one available field in the available blocks - * - * @return bool */ public static function hasAvailableFields(): bool { diff --git a/inc/questiontypeextradataconfig.class.php b/inc/questiontypeextradataconfig.class.php index 17550e82..ce07e6aa 100644 --- a/inc/questiontypeextradataconfig.class.php +++ b/inc/questiontypeextradataconfig.class.php @@ -34,11 +34,12 @@ class PluginFieldsQuestionTypeExtraDataConfig implements JsonFieldInterface { // Unique reference to hardcoded name used for serialization public const BLOCK_ID = "block_id"; + public const FIELD_ID = "field_id"; public function __construct( - private ?int $block_id = null, - private ?int $field_id = null, + private readonly ?int $block_id = null, + private readonly ?int $field_id = null, ) {} #[Override] diff --git a/inc/statusoverride.class.php b/inc/statusoverride.class.php index d8c95592..a1a0cb59 100644 --- a/inc/statusoverride.class.php +++ b/inc/statusoverride.class.php @@ -27,14 +27,15 @@ * @link https://github.com/pluginsGLPI/fields * ------------------------------------------------------------------------- */ -use Glpi\Features\Clonable; use Glpi\Application\View\TemplateRenderer; +use Glpi\Features\Clonable; class PluginFieldsStatusOverride extends CommonDBChild { use Clonable; public static $itemtype = PluginFieldsField::class; + public static $items_id = 'plugin_fields_fields_id'; /** @@ -59,7 +60,7 @@ public static function installBaseData(Migration $migration, $version) if (!$DB->tableExists($table)) { $migration->displayMessage(sprintf(__('Installing %s'), $table)); - $query = "CREATE TABLE IF NOT EXISTS `$table` ( + $query = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` INT {$default_key_sign} NOT NULL auto_increment, `plugin_fields_fields_id` INT {$default_key_sign} NOT NULL DEFAULT '0', `itemtype` VARCHAR(100) DEFAULT NULL, @@ -102,6 +103,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) 'ti ti-adjustments-alt', ); } + return ''; } @@ -138,8 +140,9 @@ public function prepareInputForUpdate($input) public function post_getFromDB() { if (isset($this->fields['states']) && !empty($this->fields['states'])) { - $this->fields['states'] = json_decode($this->fields['states']); + $this->fields['states'] = json_decode((string) $this->fields['states']); } + parent::post_getFromDB(); } @@ -217,9 +220,10 @@ public static function getOverridesForContainer(int $container_id): array $overrides = []; foreach ($iterator as $data) { - $data['states'] = !empty($data['states']) ? json_decode($data['states']) : []; + $data['states'] = empty($data['states']) ? [] : json_decode((string) $data['states']); $overrides[] = $data; } + self::addStatusNames($overrides); return $overrides; @@ -255,7 +259,7 @@ public static function getOverridesForItemtypeAndStatus(int $container_id, strin return array_filter($overrides, static fn($override) => $override['itemtype'] === $itemtype && in_array($status, $override['states'], false)); } - private static function getItemtypesForContainer(int $container_id): array + private function getItemtypesForContainer(int $container_id): array { /** @var DBmysql $DB */ global $DB; @@ -321,6 +325,7 @@ private static function addStatusNames(array &$overrides): void foreach ($iterator as $row) { $statuses['Project'][$row['id']] = $row['name']; } + $statuses['ProjectTask'] = $statuses['Project']; $iterator = $DB->request([ @@ -337,7 +342,7 @@ private static function addStatusNames(array &$overrides): void } } - private static function getFieldsChoiceForContainer(int $container_id): array + private function getFieldsChoiceForContainer(int $container_id): array { /** @var DBmysql $DB */ global $DB; @@ -393,6 +398,7 @@ public static function getStatusDropdownForItemtype(string $itemtype, array $val foreach ($iterator as $data) { $statuses[] = $data['name']; } + break; default: return State::dropdown([ @@ -415,6 +421,7 @@ public static function showForTabContainer(CommonGLPI $item, $options = []) if (!($item instanceof CommonDBTM)) { return; } + $container_id = $item->getID(); $has_fields = countElementsInTable(PluginFieldsField::getTable(), [ 'plugin_fields_containers_id' => $container_id, @@ -434,8 +441,8 @@ public function showForm($ID, array $options = []) $twig_params = [ 'override' => $this, 'container_id' => $container_id, - 'container_itemtypes' => self::getItemtypesForContainer($container_id), - 'container_fields' => self::getFieldsChoiceForContainer($container_id), + 'container_itemtypes' => $this->getItemtypesForContainer($container_id), + 'container_fields' => $this->getFieldsChoiceForContainer($container_id), ]; TemplateRenderer::getInstance()->display('@fields/forms/status_override.html.twig', $twig_params); diff --git a/inc/toolbox.class.php b/inc/toolbox.class.php index fc424eba..6b95ceb9 100644 --- a/inc/toolbox.class.php +++ b/inc/toolbox.class.php @@ -50,7 +50,7 @@ public function getSystemNameFromLabel($label) $name = preg_replace('/[^\da-z]/i', '', $name); // 3. if empty, uses a random number - if (strlen($name) == 0) { + if (strlen((string) $name) == 0) { $name = random_int(0, mt_getrandmax()); } @@ -92,9 +92,7 @@ private function replaceIntByLetters($str) /** * Fix dropdown names that were generated prior to Fields 1.9.2. * - * @param Migration $migration * @param mixed $condition - * * @return void */ public function fixFieldsNames(Migration $migration, $condition) @@ -117,7 +115,7 @@ public function fixFieldsNames(Migration $migration, $condition) } } - if (count($bad_named_fields) === 0) { + if ($bad_named_fields === []) { return; } @@ -134,6 +132,7 @@ public function fixFieldsNames(Migration $migration, $condition) // limit fields names to 64 chars (MySQL limit) $new_name = substr($new_name, 0, 64); } + while ( 'dropdown' === $field['type'] && strlen(getTableForItemType(PluginFieldsDropdown::getClassname($new_name))) > 64 @@ -141,6 +140,7 @@ public function fixFieldsNames(Migration $migration, $condition) // limit tables names to 64 chars (MySQL limit) $new_name = substr($new_name, 0, -1); } + $DB->update( PluginFieldsField::getTable(), ['name' => $new_name], @@ -188,6 +188,7 @@ public function fixFieldsNames(Migration $migration, $condition) // other cases can be ignored continue; } + $migration->changeField( $table_to_update['TABLE_NAME'], $old_field_name, @@ -204,8 +205,6 @@ public function fixFieldsNames(Migration $migration, $condition) * Return a list of GLPI itemtypes. * These itemtypes will be available to attach fields containers on them, * and will be usable in dropdown / glpi_item fields. - * - * @return array */ public static function getGlpiItemtypes(): array { @@ -280,12 +279,14 @@ public static function getGlpiItemtypes(): array foreach (CommonDevice::getDeviceTypes() as $device_itemtype) { $components_itemtypes[] = $device_itemtype; } + sort($components_itemtypes, SORT_NATURAL); $component_items_itemtypes = []; foreach (Item_Devices::getDeviceTypes() as $deviceitem_itemtype) { $component_items_itemtypes[] = $deviceitem_itemtype; } + sort($component_items_itemtypes, SORT_NATURAL); $plugins_itemtypes = []; @@ -333,9 +334,11 @@ public static function getGlpiItemtypes(): array if (!class_exists($go_itemtype)) { continue; } + $go_itemtypes[] = $go_itemtype; } - if (count($go_itemtypes) > 0) { + + if ($go_itemtypes !== []) { $all_itemtypes[$plugin->getInfo('genericobject', 'name')] = $go_itemtypes; } } @@ -350,11 +353,13 @@ public static function getGlpiItemtypes(): array if (!array_key_exists($plugin_key, $plugins_names)) { $plugins_names[$plugin_key] = Plugin::getInfo($plugin_key, 'name'); } + $prefix = $plugins_names[$plugin_key] . ' - '; } $named_itemtypes[$itemtype] = $prefix . $itemtype::getTypeName(Session::getPluralNumber()); } + $all_itemtypes[$section] = $named_itemtypes; } @@ -371,6 +376,7 @@ public static function decodeJSONItemtypes(string $itemtypes, ?bool $associative $fixed_json = str_replace('\\', '\\\\', $itemtypes); $jsonitemtype = json_decode($fixed_json, $associative); } + return $jsonitemtype; } } diff --git a/rector.php b/rector.php index 22ba2dd0..d7f5b473 100644 --- a/rector.php +++ b/rector.php @@ -31,68 +31,29 @@ require_once __DIR__ . '/../../src/Plugin.php'; use Rector\Caching\ValueObject\Storage\FileCacheStorage; -use Rector\CodeQuality\Rector as CodeQuality; use Rector\Config\RectorConfig; -use Rector\DeadCode\Rector as DeadCode; use Rector\ValueObject\PhpVersion; return RectorConfig::configure() ->withPaths([ __DIR__ . '/ajax', - __DIR__ . '/inc', __DIR__ . '/front', + __DIR__ . '/inc', + __DIR__ . '/src', + __DIR__ . '/tests', ]) ->withPhpVersion(PhpVersion::PHP_82) ->withCache( + cacheDirectory: __DIR__ . '/var/rector', cacheClass: FileCacheStorage::class, - cacheDirectory: sys_get_temp_dir() . '/fields-rector', ) ->withRootFiles() ->withParallel(timeoutSeconds: 300) ->withImportNames(removeUnusedImports: true) - ->withRules([ - CodeQuality\Assign\CombinedAssignRector::class, - CodeQuality\BooleanAnd\RemoveUselessIsObjectCheckRector::class, - CodeQuality\BooleanAnd\SimplifyEmptyArrayCheckRector::class, - CodeQuality\BooleanNot\ReplaceMultipleBooleanNotRector::class, - CodeQuality\Catch_\ThrowWithPreviousExceptionRector::class, - CodeQuality\Empty_\SimplifyEmptyCheckOnEmptyArrayRector::class, - CodeQuality\Expression\InlineIfToExplicitIfRector::class, - CodeQuality\Expression\TernaryFalseExpressionToIfRector::class, - CodeQuality\For_\ForRepeatedCountToOwnVariableRector::class, - CodeQuality\Foreach_\ForeachItemsAssignToEmptyArrayToAssignRector::class, - CodeQuality\Foreach_\ForeachToInArrayRector::class, - CodeQuality\Foreach_\SimplifyForeachToCoalescingRector::class, - CodeQuality\Foreach_\UnusedForeachValueToArrayKeysRector::class, - CodeQuality\FuncCall\ChangeArrayPushToArrayAssignRector::class, - CodeQuality\FuncCall\CompactToVariablesRector::class, - CodeQuality\FuncCall\InlineIsAInstanceOfRector::class, - CodeQuality\FuncCall\IsAWithStringWithThirdArgumentRector::class, - CodeQuality\FuncCall\RemoveSoleValueSprintfRector::class, - CodeQuality\FuncCall\SetTypeToCastRector::class, - CodeQuality\FuncCall\SimplifyFuncGetArgsCountRector::class, - CodeQuality\FuncCall\SimplifyInArrayValuesRector::class, - CodeQuality\FuncCall\SimplifyStrposLowerRector::class, - CodeQuality\FuncCall\UnwrapSprintfOneArgumentRector::class, - CodeQuality\Identical\BooleanNotIdenticalToNotIdenticalRector::class, - CodeQuality\Identical\SimplifyArraySearchRector::class, - CodeQuality\Identical\SimplifyConditionsRector::class, - CodeQuality\Identical\StrlenZeroToIdenticalEmptyStringRector::class, - CodeQuality\If_\CombineIfRector::class, - CodeQuality\If_\CompleteMissingIfElseBracketRector::class, - CodeQuality\If_\ConsecutiveNullCompareReturnsToNullCoalesceQueueRector::class, - CodeQuality\If_\ExplicitBoolCompareRector::class, - CodeQuality\If_\ShortenElseIfRector::class, - CodeQuality\If_\SimplifyIfElseToTernaryRector::class, - CodeQuality\If_\SimplifyIfNotNullReturnRector::class, - CodeQuality\If_\SimplifyIfNullableReturnRector::class, - CodeQuality\If_\SimplifyIfReturnBoolRector::class, - CodeQuality\Include_\AbsolutizeRequireAndIncludePathRector::class, - CodeQuality\LogicalAnd\AndAssignsToSeparateLinesRector::class, - CodeQuality\LogicalAnd\LogicalToBooleanRector::class, - CodeQuality\NotEqual\CommonNotEqualRector::class, - CodeQuality\Ternary\UnnecessaryTernaryExpressionRector::class, - DeadCode\Assign\RemoveUnusedVariableAssignRector::class, - ]) - ->withPhpSets(php74: true) // apply PHP sets up to PHP 7.4 + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + ) + ->withPhpSets(php82: true) // apply PHP sets up to PHP 8.2 ; diff --git a/setup.php b/setup.php index 6c64e5eb..89b4f368 100644 --- a/setup.php +++ b/setup.php @@ -41,6 +41,7 @@ if (!defined('PLUGINFIELDS_DIR')) { define('PLUGINFIELDS_DIR', Plugin::getPhpDir('fields')); } + if (!defined('PLUGINFIELDS_WEB_DIR')) { define('PLUGINFIELDS_WEB_DIR', $CFG_GLPI['root_doc'] . '/plugins/fields'); } @@ -48,6 +49,7 @@ if (!defined('PLUGINFIELDS_DOC_DIR')) { define('PLUGINFIELDS_DOC_DIR', GLPI_PLUGIN_DOC_DIR . '/fields'); } + if (!file_exists(PLUGINFIELDS_DOC_DIR)) { mkdir(PLUGINFIELDS_DOC_DIR); } @@ -55,6 +57,7 @@ if (!defined('PLUGINFIELDS_CLASS_PATH')) { define('PLUGINFIELDS_CLASS_PATH', PLUGINFIELDS_DOC_DIR . '/inc'); } + if (!file_exists(PLUGINFIELDS_CLASS_PATH)) { mkdir(PLUGINFIELDS_CLASS_PATH); } @@ -62,9 +65,11 @@ if (!defined('PLUGINFIELDS_FRONT_PATH')) { define('PLUGINFIELDS_FRONT_PATH', PLUGINFIELDS_DOC_DIR . '/front'); } + if (!file_exists(PLUGINFIELDS_FRONT_PATH)) { mkdir(PLUGINFIELDS_FRONT_PATH); } + use Glpi\Form\Destination\FormDestinationChange; use Glpi\Form\Destination\FormDestinationManager; use Glpi\Form\Destination\FormDestinationProblem; @@ -102,7 +107,7 @@ function plugin_init_fields() // When a Category is changed during ticket creation if ( - !empty($_POST) + $_POST !== [] && isset($_POST['_plugin_fields_type']) && ($_SERVER['REQUEST_URI'] == Ticket::getFormURL()) ) { @@ -133,7 +138,7 @@ function plugin_init_fields() // add tabs to itemtypes $itemtypes = array_unique(PluginFieldsContainer::getEntries()); - if (count($itemtypes) > 0) { + if ($itemtypes !== []) { Plugin::registerClass( 'PluginFieldsContainer', ['addtabon' => $itemtypes], @@ -215,7 +220,7 @@ function plugin_init_fields() */ function plugin_fields_script_endswith($scriptname) { - return str_contains($_SERVER['REQUEST_URI'], $scriptname); + return str_contains((string) $_SERVER['REQUEST_URI'], $scriptname); } @@ -232,7 +237,7 @@ function plugin_version_fields() return [ 'name' => __('Additional fields', 'fields'), 'version' => PLUGIN_FIELDS_VERSION, - 'author' => 'Teclib\', Olivier Moron', + 'author' => "Teclib', Olivier Moron", 'homepage' => 'https://github.com/pluginsGLPI/fields', 'license' => 'GPLv2+', 'requirements' => [ @@ -311,10 +316,11 @@ function plugin_fields_exportBlockAsYaml($container_id = null) if ($container_id != null) { $where['id'] = $container_id; } + $container_obj = new PluginFieldsContainer(); $containers = $container_obj->find($where); foreach ($containers as $container) { - $itemtypes = (strlen($container['itemtypes']) > 0) + $itemtypes = (strlen((string) $container['itemtypes']) > 0) ? PluginFieldsToolbox::decodeJSONItemtypes($container['itemtypes'], true) : []; @@ -334,7 +340,7 @@ function plugin_fields_exportBlockAsYaml($container_id = null) 'is_active' => true, 'is_readonly' => false, ]); - if (count($fields)) { + if (count($fields) > 0) { foreach ($fields as $field) { $tmp_field = []; $tmp_field['id'] = (int) $field['id']; @@ -342,7 +348,7 @@ function plugin_fields_exportBlockAsYaml($container_id = null) //to get translation $field['itemtype'] = PluginFieldsField::getType(); $tmp_field['label'] = PluginFieldsLabelTranslation::getLabelFor($field); - $tmp_field['xml_node'] = strtoupper($field['name']); + $tmp_field['xml_node'] = strtoupper((string) $field['name']); $tmp_field['type'] = $field['type']; $tmp_field['ranking'] = $field['ranking']; $tmp_field['default_value'] = $field['default_value']; @@ -367,6 +373,7 @@ function plugin_fields_exportBlockAsYaml($container_id = null) $items['value'] = $value['name']; $datas[] = $items; } + $tmp_field['possible_value'] = $datas; break; case 'yesno': @@ -382,6 +389,7 @@ function plugin_fields_exportBlockAsYaml($container_id = null) $tmp_field['possible_value'] = $datas['results']; break; } + $yaml_conf['container'][$container['id'] . '-' . $itemtype]['fields'][] = $tmp_field; } } @@ -389,7 +397,7 @@ function plugin_fields_exportBlockAsYaml($container_id = null) } } - if (!empty($yaml_conf['container'])) { + if ($yaml_conf['container'] !== []) { $dump = Yaml::dump($yaml_conf, 10); $filename = GLPI_TMP_DIR . '/fields_conf.yaml'; file_put_contents($filename, $dump); diff --git a/src/Controller/QuestionTypeAjaxController.php b/src/Controller/QuestionTypeAjaxController.php index 4fea927e..0fc4a9b3 100644 --- a/src/Controller/QuestionTypeAjaxController.php +++ b/src/Controller/QuestionTypeAjaxController.php @@ -65,7 +65,7 @@ public function __invoke(Request $request): Response if ($field_id && is_numeric($field_id) && isset($available_fields[$field_id])) { $current_field_id = (int) $field_id; } else { - $current_field_id = !empty($available_fields) ? (int) current(array_keys($available_fields)) : null; + $current_field_id = $available_fields === [] ? null : (int) current(array_keys($available_fields)); } if ($current_field_id === null) { @@ -83,10 +83,8 @@ public function __invoke(Request $request): Response // Process default value if provided if ($default_value !== null && !empty($default_value)) { // If the field is multiple, convert the default value to an array - if ($current_field->fields['multiple']) { - if (!is_array($default_value)) { - $default_value = explode(',', $default_value); - } + if ($current_field->fields['multiple'] && !is_array($default_value)) { + $default_value = explode(',', $default_value); } } else { $default_value = null; diff --git a/tests/FieldTestCase.php b/tests/FieldTestCase.php index d9c067f1..01e6ab07 100644 --- a/tests/FieldTestCase.php +++ b/tests/FieldTestCase.php @@ -38,6 +38,7 @@ trait FieldTestTrait { /** @var PluginFieldsContainer[] */ private static array $createdContainers = []; + /** @var PluginFieldsField[] */ private static array $createdFields = []; diff --git a/tests/QuestionTypeTestCase.php b/tests/QuestionTypeTestCase.php index 1e3a1609..b6f9b385 100644 --- a/tests/QuestionTypeTestCase.php +++ b/tests/QuestionTypeTestCase.php @@ -47,6 +47,7 @@ abstract class QuestionTypeTestCase extends DbTestCase use FieldTestTrait; protected ?PluginFieldsContainer $block = null; + protected ?PluginFieldsField $field = null; public function createFieldAndContainer(): void @@ -72,12 +73,10 @@ public function createFieldAndContainer(): void public function setUp(): void { $this->createFieldAndContainer(); - parent::setUp(); } public function tearDown(): void { - parent::tearDown(); $this->tearDownFieldTest(); } @@ -113,6 +112,7 @@ protected function deleteSingletonInstance(array $classes) $reflection_property = $reflection_class->getProperty('instance'); $reflection_property->setValue(null, null); } + if ($reflection_class->hasProperty('_instances')) { $reflection_property = $reflection_class->getProperty('_instances'); $reflection_property->setValue(null, []); diff --git a/tests/Units/FieldDestinationFieldTest.php b/tests/Units/FieldDestinationFieldTest.php index 603dae98..c65329e6 100644 --- a/tests/Units/FieldDestinationFieldTest.php +++ b/tests/Units/FieldDestinationFieldTest.php @@ -58,6 +58,7 @@ final class FieldDestinationFieldTest extends AbstractDestinationFieldTest use FieldTestTrait; private array $blocks = []; + private array $fields = []; private function initFieldTest(): void @@ -93,7 +94,7 @@ private function initFieldTest(): void 'is_active' => 1, 'is_readonly' => 0, 'allowed_values' => [User::class, Group::class], - ], ['allowed_values']); + ]); $this->fields[] = $this->createField([ 'label' => 'Short text', 'type' => 'text', @@ -110,7 +111,7 @@ private function initFieldTest(): void 'is_active' => 1, 'is_readonly' => 0, 'allowed_values' => [User::class, Group::class], - ], ['allowed_values']); + ]); $this->fields[] = $this->createField([ 'label' => 'Location Field', 'type' => 'dropdown-Location', @@ -294,7 +295,7 @@ private function sendFormAndAssertITILObjectAdditionalFields( fn(?CommonITILObject $carry, CommonITILObject $item) => $carry ?? ($item instanceof $itil_class ? $item : null), null, ); - $this->assertNotNull($itil_object, "No created item of type $itil_class found."); + $this->assertNotNull($itil_object, sprintf('No created item of type %s found.', $itil_class)); // Check field values $classname = PluginFieldsContainer::getClassname($itil_class, $this->blocks[$itil_class]->fields['name']); @@ -314,7 +315,7 @@ private function sendFormAndAssertITILObjectAdditionalFields( $this->assertEquals( $expected_value, $values[$field_name], - "Field '$field_name' does not have the expected value.", + sprintf("Field '%s' does not have the expected value.", $field_name), ); } } diff --git a/tests/Units/FieldQuestionTypeMigrationTest.php b/tests/Units/FieldQuestionTypeMigrationTest.php index 959ee4ba..8f0b21ab 100644 --- a/tests/Units/FieldQuestionTypeMigrationTest.php +++ b/tests/Units/FieldQuestionTypeMigrationTest.php @@ -43,8 +43,6 @@ public static function setUpBeforeClass(): void { global $DB; - parent::setUpBeforeClass(); - $tables = $DB->listTables('glpi\_plugin\_formcreator\_%'); foreach ($tables as $table) { $DB->dropTable($table['TABLE_NAME']); @@ -67,8 +65,6 @@ public static function tearDownAfterClass(): void foreach ($tables as $table) { $DB->dropTable($table['TABLE_NAME']); } - - parent::tearDownAfterClass(); } public function testFieldsQuestionIsMigrated(): void diff --git a/tests/Units/FieldQuestionTypeTest.php b/tests/Units/FieldQuestionTypeTest.php index 2217d3c4..8ccfb06a 100644 --- a/tests/Units/FieldQuestionTypeTest.php +++ b/tests/Units/FieldQuestionTypeTest.php @@ -33,6 +33,9 @@ use Glpi\Form\QuestionType\QuestionTypesManager; use Glpi\Tests\FormBuilder; use GlpiPlugin\Field\Tests\QuestionTypeTestCase; +use LogicException; +use PluginFieldsContainer; +use PluginFieldsField; use PluginFieldsQuestionType; use PluginFieldsQuestionTypeCategory; use PluginFieldsQuestionTypeExtraDataConfig; @@ -50,7 +53,7 @@ public function testFieldsQuestionCategoryIsAvailableWhenValidFieldExists(): voi // Assert: check that Field question type category is registered $this->assertContains( PluginFieldsQuestionTypeCategory::class, - array_map(fn($category) => get_class($category), $categories), + array_map(fn($category) => $category::class, $categories), ); } @@ -69,7 +72,7 @@ public function testFieldsQuestionCategoryIsNotAvailableWhenNoValidFieldExists() // Assert: check that Field question type category isn't registered $this->assertNotContains( PluginFieldsQuestionTypeCategory::class, - array_map(fn($category) => get_class($category), $categories), + array_map(fn($category) => $category::class, $categories), ); } @@ -82,7 +85,7 @@ public function testFieldsQuestionIsAvailableWhenValidFieldExists(): void // Assert: check that Field question type is registered $this->assertContains( PluginFieldsQuestionType::class, - array_map(fn($type) => get_class($type), $types), + array_map(fn($type) => $type::class, $types), ); } @@ -101,7 +104,7 @@ public function testFieldsQuestionIsNotAvailableWhenNoValidFieldExists(): void // Assert: check that Field question type isn't registered $this->assertNotContains( PluginFieldsQuestionType::class, - array_map(fn($type) => get_class($type), $types), + array_map(fn($type) => $type::class, $types), ); } @@ -147,8 +150,8 @@ public function testFieldsQuestionHelpdeskRendering(): void private function getFieldExtraDataConfig(): PluginFieldsQuestionTypeExtraDataConfig { - if ($this->block === null || $this->field === null) { - throw new \LogicException("Field and container must be created before getting extra data config"); + if (!$this->block instanceof PluginFieldsContainer || !$this->field instanceof PluginFieldsField) { + throw new LogicException("Field and container must be created before getting extra data config"); } return new PluginFieldsQuestionTypeExtraDataConfig($this->block->getID(), $this->field->getID());