diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dc3f4f9a..6530865b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -22,7 +22,7 @@ jobs: name: "Generate CI matrix" uses: "glpi-project/plugin-ci-workflows/.github/workflows/generate-ci-matrix.yml@v1" with: - glpi-version: "11.0.x" + glpi-version: "12.0.x" ci: name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" needs: "generate-ci-matrix" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..d4cfadbf --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +include ../../PluginsMakefile.mk + diff --git a/SECURITY.md b/SECURITY.md index 0484177c..32657117 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,12 +13,14 @@ Once the report will be handled, and if the issue is not yet fixed (or in progre we'll add it to the GitHub security tab, and add you as observer. Meanwhile, you will reserve a CVE for the issue. -Thank you for improving the security of glpi-agent. +Thank you for improving the security of the plugin Carbon. ## Supported Versions | Version | Supported | | ------- | ------------------ | -| 1.0.x | :heavy_check_mark: | +| 1.0.x | :x: | | 1.2.x | :x: | | 1.3.x | :heavy_check_mark: | +| 1.3.x | :heavy_check_mark: | +| 1.4.x | :heavy_check_mark: | diff --git a/ajax/dropdownZone.php b/ajax/dropdownZone.php index 1ed765d2..4488fa20 100644 --- a/ajax/dropdownZone.php +++ b/ajax/dropdownZone.php @@ -29,21 +29,16 @@ * * ------------------------------------------------------------------------- */ - +use Glpi\Exception\Http\AccessDeniedHttpException; +use Glpi\Exception\Http\NotFoundHttpException; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Zone; -include(__DIR__ . '/../../../inc/includes.php'); - // Check if plugin is activated... if (!Plugin::isPluginActive('carbon')) { - http_response_code(404); - die(); -} - -if (!Zone::canView()) { - http_response_code(403); - die(); + throw new NotFoundHttpException(); +} elseif (!Zone::canView()) { + throw new AccessDeniedHttpException(); } $source_zone_table = Source_Zone::getTable(); diff --git a/composer.json b/composer.json index e9a9a0b1..659812bf 100644 --- a/composer.json +++ b/composer.json @@ -3,13 +3,7 @@ "php": ">=8.2.0", "geocoder-php/google-maps-provider": "^4.7", "geocoder-php/nominatim-provider": "^5.7", - "league/iso3166": "^4.3", - "php-http/message": "^1.16", - "php-http/message-factory": "^1.1" - }, - "require-dev": { - "glpi-project/tools": "^0.8.1", - "marcocesarato/php-conventional-changelog": "^1.17" + "league/iso3166": "^4.3" }, "config": { "optimize-autoloader": true, @@ -35,5 +29,5 @@ "license-headers": "vendor/bin/licence-headers-check --ansi --no-interaction", "build-schema": "tools/build-db-schema.php carbon | plantuml -p -tpng > docs/db-schema.png" }, - "version": "1.3.0-dev" + "version": "1.4.0-dev" } diff --git a/composer.lock b/composer.lock index 3a534dbe..b5d2cf46 100644 --- a/composer.lock +++ b/composer.lock @@ -4,74 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a821ef4c2ae32d2c6dce28a1a3b4ceb9", + "content-hash": "20397c57834cf59058491e3259bd0375", "packages": [ - { - "name": "clue/stream-filter", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/clue/stream-filter.git", - "reference": "049509fef80032cb3f051595029ab75b49a3c2f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7", - "reference": "049509fef80032cb3f051595029ab75b49a3c2f7", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "Clue\\StreamFilter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "A simple and modern approach to stream filtering in PHP", - "homepage": "https://github.com/clue/stream-filter", - "keywords": [ - "bucket brigade", - "callback", - "filter", - "php_user_filter", - "stream", - "stream_filter_append", - "stream_filter_register" - ], - "support": { - "issues": "https://github.com/clue/stream-filter/issues", - "source": "https://github.com/clue/stream-filter/tree/v1.7.0" - }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2023-12-20T15:40:13+00:00" - }, { "name": "geocoder-php/common-http", "version": "4.7.0", @@ -396,183 +330,6 @@ }, "time": "2024-10-02T11:20:13+00:00" }, - { - "name": "php-http/message", - "version": "1.16.2", - "source": { - "type": "git", - "url": "https://github.com/php-http/message.git", - "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message/zipball/06dd5e8562f84e641bf929bfe699ee0f5ce8080a", - "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a", - "shasum": "" - }, - "require": { - "clue/stream-filter": "^1.5", - "php": "^7.2 || ^8.0", - "psr/http-message": "^1.1 || ^2.0" - }, - "provide": { - "php-http/message-factory-implementation": "1.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.6", - "ext-zlib": "*", - "guzzlehttp/psr7": "^1.0 || ^2.0", - "laminas/laminas-diactoros": "^2.0 || ^3.0", - "php-http/message-factory": "^1.0.2", - "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", - "slim/slim": "^3.0" - }, - "suggest": { - "ext-zlib": "Used with compressor/decompressor streams", - "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", - "laminas/laminas-diactoros": "Used with Diactoros Factories", - "slim/slim": "Used with Slim Framework PSR-7 implementation" - }, - "type": "library", - "autoload": { - "files": [ - "src/filters.php" - ], - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "HTTP Message related tools", - "homepage": "http://php-http.org", - "keywords": [ - "http", - "message", - "psr-7" - ], - "support": { - "issues": "https://github.com/php-http/message/issues", - "source": "https://github.com/php-http/message/tree/1.16.2" - }, - "time": "2024-10-02T11:34:13+00:00" - }, - { - "name": "php-http/message-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/message-factory.git", - "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", - "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Factory interfaces for PSR-7 HTTP Message", - "homepage": "http://php-http.org", - "keywords": [ - "factory", - "http", - "message", - "stream", - "uri" - ], - "support": { - "issues": "https://github.com/php-http/message-factory/issues", - "source": "https://github.com/php-http/message-factory/tree/1.1.0" - }, - "abandoned": "psr/http-factory", - "time": "2023-04-14T14:16:17+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "time": "2023-04-04T09:54:51+00:00" - }, { "name": "willdurand/geocoder", "version": "5.0.0", @@ -636,957 +393,7 @@ "time": "2025-01-01T15:52:42+00:00" } ], - "packages-dev": [ - { - "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": "marcocesarato/php-conventional-changelog", - "version": "1.17.3", - "source": { - "type": "git", - "url": "https://github.com/marcocesarato/php-conventional-changelog.git", - "reference": "c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/marcocesarato/php-conventional-changelog/zipball/c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2", - "reference": "c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": ">=7.1.3", - "symfony/console": "^4 || ^5 || ^6 || ^7 || ^8" - }, - "require-dev": { - "brainmaestro/composer-git-hooks": "^2.8", - "friendsofphp/php-cs-fixer": "^3.8", - "php-mock/php-mock": "^2.3", - "php-mock/php-mock-phpunit": "^2.6", - "phpunit/phpunit": "^9.6" - }, - "bin": [ - "conventional-changelog" - ], - "type": "library", - "extra": { - "hooks": { - "pre-push": "composer check-cs", - "post-merge": "composer install", - "pre-commit": "composer fix-cs" - } - }, - "autoload": { - "psr-4": { - "ConventionalChangelog\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Marco Cesarato", - "email": "cesarato.developer@gmail.com" - } - ], - "description": "Generate changelogs and release notes from a project's commit messages and metadata and automate versioning with semver.org and conventionalcommits.org", - "keywords": [ - "changelog", - "commit", - "commits", - "convention", - "conventional", - "conventional-changelog", - "conventional-changelog-preset", - "conventional-commit", - "conventional-commits", - "conventionalcommits", - "generation", - "git", - "history", - "php", - "readme", - "tag" - ], - "support": { - "issues": "https://github.com/marcocesarato/php-conventional-changelog/issues", - "source": "https://github.com/marcocesarato/php-conventional-changelog/tree/v1.17.3" - }, - "time": "2026-01-21T11:40:14+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": "symfony/console", - "version": "v6.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/60e3944c4859c487aa6ea2f0f7754917f70f7524", - "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524", - "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.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-08-21T07:42:12+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "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": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-05T06:23:12+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.41.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", - "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.41.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-28T08:25:59+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.42.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", - "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", - "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.42.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-08-07T06:33:24+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "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 the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.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": "2026-05-27T06:59:30+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "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 writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-06-16T09:55:08+00:00" - }, - { - "name": "symfony/string", - "version": "v7.4.15", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "e394af32256bf9e7bf80849d95e589167c10097b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", - "reference": "e394af32256bf9e7bf80849d95e589167c10097b", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v7.4.15" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-07-28T07:33:02+00:00" - }, - { - "name": "twig/twig", - "version": "v3.28.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", - "shasum": "" - }, - "require": { - "php": ">=8.1.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.0@stable", - "phpstan/phpstan": "^2.0@stable", - "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Resources/core.php", - "src/Resources/debug.php", - "src/Resources/escaper.php", - "src/Resources/string_loader.php" - ], - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.28.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2026-07-03T20:44:34+00:00" - } - ], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, diff --git a/front/embodiedimpact.form.php b/front/embodiedimpact.form.php index 9f402902..688a3d1a 100644 --- a/front/embodiedimpact.form.php +++ b/front/embodiedimpact.form.php @@ -75,6 +75,10 @@ $embodied_impact->check($_POST['id'], PURGE); $itemtype = $embodied_impact->fields['itemtype']; + if (!is_a($itemtype, CommonDBTM::class, true)) { + Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR); + Html::back(); + } $item = new $itemtype(); $item->getFromDB($embodied_impact->fields['items_id']); if (!$item->canUpdateItem()) { @@ -92,7 +96,7 @@ } $itemtype = $_POST['itemtype']; - if (!Toolbox::isCommonDBTM($itemtype)) { + if (!is_a($itemtype, CommonDBTM::class, true)) { Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR); Html::back(); } diff --git a/front/report.php b/front/report.php index b309e0e8..ad519ab3 100644 --- a/front/report.php +++ b/front/report.php @@ -30,6 +30,7 @@ * ------------------------------------------------------------------------- */ +use Config as GlpiConfig; use Glpi\Exception\Http\AccessDeniedHttpException; use Glpi\Exception\Http\NotFoundHttpException; use GlpiPlugin\Carbon\Config; @@ -48,7 +49,7 @@ } if (isset($_GET['disable_demo'])) { - Session::checkRight('config', UPDATE); + Session::checkRight(GlpiConfig::$rightname, UPDATE); Config::exitDemoMode(); Html::back(); } diff --git a/front/usageimpact.form.php b/front/usageimpact.form.php index 94d3650b..97105cef 100644 --- a/front/usageimpact.form.php +++ b/front/usageimpact.form.php @@ -92,6 +92,10 @@ /** @var AbstractAsset $history */ $gwp_impact = new $gwp_impact_class(); $itemtype = $gwp_impact->getItemtype(); + if (!is_a($itemtype, CommonDBTM::class, true)) { + Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR); + Html::back(); + } $item = new $itemtype(); $item->getFromDB($_POST['items_id']); if (!$item->canUpdateItem()) { @@ -110,7 +114,7 @@ } $itemtype = $_POST['itemtype']; - if (!Toolbox::isCommonDBTM($itemtype)) { + if (!is_a($itemtype, CommonDBTM::class, true)) { Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR); Html::back(); } diff --git a/hook.php b/hook.php index 54d99277..c0578f0c 100644 --- a/hook.php +++ b/hook.php @@ -60,6 +60,9 @@ use Profile as GlpiProfile; use Toolbox as GlpiToolbox; +use function Safe\ob_end_clean; +use function Safe\ob_start; + /** * Plugin install process * supported arguments for upgrade process @@ -289,6 +292,9 @@ function plugin_carbon_hook_pre_purge_assettype(CommonDBTM $item) } $carbon_type_itemtype = 'GlpiPlugin\\Carbon\\' . $itemtype; + if (!is_a($carbon_type_itemtype, CommonDBTM::class, true)) { + return; + } $carbon_type = new $carbon_type_itemtype(); $carbon_type->deleteByCriteria([ $item->getForeignKeyField() => $item->getID(), diff --git a/install/Install.php b/install/Install.php index bee40254..9e55c689 100644 --- a/install/Install.php +++ b/install/Install.php @@ -39,6 +39,11 @@ use Plugin; use RuntimeException; +use function Safe\ini_set; +use function Safe\preg_match; +use function Safe\preg_replace; +use function Safe\scandir; + class Install { /** diff --git a/install/install/create_automatic_actions.php b/install/install/create_automatic_actions.php index edef8bb2..9d48cd74 100644 --- a/install/install/create_automatic_actions.php +++ b/install/install/create_automatic_actions.php @@ -34,7 +34,7 @@ use GlpiPlugin\Carbon\CronTask; use GlpiPlugin\Carbon\DataSource\CronTaskProvider; -$cron_task_classes = CronTaskProvider::getCronTaskTypes(); +$cron_task_classes = CronTaskProvider::getCronTaskTypes(CronTaskProvider::getCronTaskDirectories()); $automatic_actions = []; foreach ($cron_task_classes as $cron_task_class) { $automatic_actions = array_merge($automatic_actions, $cron_task_class::enumerateTasks()); diff --git a/install/install/create_dashboard.php b/install/install/create_dashboard.php index ff2b2ec6..28e4287d 100644 --- a/install/install/create_dashboard.php +++ b/install/install/create_dashboard.php @@ -29,13 +29,15 @@ * * ------------------------------------------------------------------------- */ - use Glpi\Dashboard\Dashboard; use Glpi\Dashboard\Item as DashboardItem; use Glpi\Dashboard\Right as DashboardRight; use Glpi\DBAL\QueryExpression; use GlpiPlugin\Carbon\Report; +use function Safe\file_get_contents; +use function Safe\json_decode; + /** @var DBmysql $DB */ global $DB; diff --git a/install/install/init_datasources.php b/install/install/init_datasources.php index a195c0fa..fd033c36 100644 --- a/install/install/init_datasources.php +++ b/install/install/init_datasources.php @@ -29,7 +29,6 @@ * * ------------------------------------------------------------------------- */ - use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Config; use GlpiPlugin\Carbon\Install; @@ -39,6 +38,8 @@ use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\StreamOutput; +use function Safe\fopen; + /** @var DBmysql $DB */ global $DB; @@ -125,7 +126,9 @@ $progress_bar->setProgress($rows_count); } $file = null; // close the file - + if (isset($output)) { + $output->writeln(""); + } Config::setPluginConfigurationValues(['ember_dataset_date' => EMBER_DATASET_DATE]); } diff --git a/install/migration/update_1.0.0_to_1.0.1.php b/install/migration/update_1.0.0_to_1.0.1.php index ba4ba692..c11f8b44 100644 --- a/install/migration/update_1.0.0_to_1.0.1.php +++ b/install/migration/update_1.0.0_to_1.0.1.php @@ -30,6 +30,9 @@ * ------------------------------------------------------------------------- */ +use function Safe\preg_match; +use function Safe\scandir; + function update100to101(Migration $migration) { /** @var DBmysql $DB */ diff --git a/install/migration/update_1.0.1_to_1.1.0.php b/install/migration/update_1.0.1_to_1.1.0.php index 19714136..2b16446d 100644 --- a/install/migration/update_1.0.1_to_1.1.0.php +++ b/install/migration/update_1.0.1_to_1.1.0.php @@ -30,6 +30,9 @@ * ------------------------------------------------------------------------- */ +use function Safe\preg_match; +use function Safe\scandir; + function update101to110(Migration $migration) { /** @var DBmysql $DB */ diff --git a/install/migration/update_1.1.1_to_1.2.0.php b/install/migration/update_1.1.1_to_1.2.0.php index fd3d0e80..9050252f 100644 --- a/install/migration/update_1.1.1_to_1.2.0.php +++ b/install/migration/update_1.1.1_to_1.2.0.php @@ -29,6 +29,8 @@ * * ------------------------------------------------------------------------- */ +use function Safe\preg_match; +use function Safe\scandir; function update111to120(Migration $migration) { diff --git a/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php b/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php index 6b3d5a93..079261e8 100644 --- a/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php +++ b/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php @@ -35,6 +35,9 @@ use Glpi\Dashboard\Item as DashboardItem; +use function Safe\json_decode; +use function Safe\json_encode; + $new_criteria = [ 'gwppb' => '(unit g CO2 eq) Biogenic climate change potential', 'gwppf' => '(unit g CO2 eq) Fossil climate change potential', diff --git a/install/migration/update_x.x.x_to_y.y.y.php b/install/migration/update_x.x.x_to_y.y.y.php index 3c5899ff..8deb05c6 100644 --- a/install/migration/update_x.x.x_to_y.y.y.php +++ b/install/migration/update_x.x.x_to_y.y.y.php @@ -30,6 +30,9 @@ * ------------------------------------------------------------------------- */ +use function Safe\preg_match; +use function Safe\scandir; + function update001to100(Migration $migration) { /** @var DBmysql $DB */ diff --git a/package-lock.json b/package-lock.json index ce930f9d..a5955b40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon", - "version": "1.3.0-dev", + "version": "1.4.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "carbon", - "version": "1.3.0-dev", + "version": "1.4.0-dev", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { @@ -1930,10 +1930,21 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "peer": true, "dependencies": { "argparse": "^2.0.1" diff --git a/package.json b/package.json index 831cb333..dcf9a0ed 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "carbon", "description": "Measurement of carbon emissions of GLPI devices", "license": "GPL-3.0-or-later", - "version": "1.3.0-dev", + "version": "1.4.0-dev", "engines": { "node": ">= 18.12.0" }, diff --git a/phpstan.neon b/phpstan.neon index c02769b3..8580ab79 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,8 @@ +includes: + - ../../vendor/glpi-project/phpstan-glpi/extension.neon + - ../../vendor/phpstan/phpstan-deprecation-rules/rules.neon + - ../../vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon + parameters: parallel: maximumNumberOfProcesses: 2 @@ -5,6 +10,7 @@ parameters: bootstrapFiles: - ../../stubs/glpi_constants.php - ../../vendor/autoload.php + - setup.php paths: - src - front @@ -17,5 +23,3 @@ parameters: - ../../src stubFiles: - ../../stubs/glpi_constants.php -rules: - - GlpiProject\Tools\PHPStan\Rules\GlobalVarTypeRule diff --git a/setup.php b/setup.php index 5017da4b..6e35edff 100644 --- a/setup.php +++ b/setup.php @@ -32,7 +32,9 @@ use Config as GlpiConfig; use CronTask as GlpiCronTask; +use Glpi\Config\ProxyExclusion; use Glpi\Plugin\Hooks; +use GlpiPlugin\Carbon\CloudInventoryConnector; use GlpiPlugin\Carbon\Config; use GlpiPlugin\Carbon\CronTask; use GlpiPlugin\Carbon\Dashboard\Grid; @@ -46,10 +48,13 @@ use Location as GlpiLocation; use Profile as GlpiProfile; +use function Safe\define; +use function Safe\preg_match; + // Version of the plugin (major.minor.bugfix) -define('PLUGIN_CARBON_VERSION', '1.3.0-dev'); +define('PLUGIN_CARBON_VERSION', '1.4.0-dev'); // Schema version of this version (major.minor.bugfix) -define('PLUGIN_CARBON_SCHEMA_VERSION', '1.3.0'); +define('PLUGIN_CARBON_SCHEMA_VERSION', '1.4.0'); // Version compatibility check -- from GLPI developer documentation // > A bug in GLPI prior to 11.0.7 caused plugin routes with method constraints other than GET to never match. @@ -63,9 +68,9 @@ // Watch it when adding new controllers. // Minimal GLPI version, inclusive -define('PLUGIN_CARBON_MIN_GLPI_VERSION', '11.0.0'); +define('PLUGIN_CARBON_MIN_GLPI_VERSION', '12.0.0'); // Maximum GLPI version, exclusive -define('PLUGIN_CARBON_MAX_GLPI_VERSION', '12.0.0'); +define('PLUGIN_CARBON_MAX_GLPI_VERSION', '13.0.0'); define('PLUGIN_CARBON_DECIMALS', 3); define('EMBER_DATASET_DATE', '2025-07-30'); @@ -102,6 +107,11 @@ function plugin_init_carbon() plugin_carbon_registerClasses(); $CFG_GLPI['javascript']['tools'][strtolower(Report::class)] = ['dashboard']; + $proxy_exclusions = $CFG_GLPI['possible_proxy_exclusions']; + $proxy_exclusions->addExclusion(new ProxyExclusion( + Config::class, + plugin_carbon_getFriendlyName(), + )); } function plugin_carbon_setupHooks() @@ -115,6 +125,8 @@ function plugin_carbon_setupHooks() LcaClientFactory::getSecuredConfigs() ); + $PLUGIN_HOOKS[Hooks::POST_INIT]['carbon'] = [CloudInventoryConnector::class, 'checkPluginAvailability']; + // add new cards to the dashboard $PLUGIN_HOOKS[Hooks::DASHBOARD_CARDS]['carbon'] = [Grid::class, 'getDashboardCards']; $PLUGIN_HOOKS[Hooks::DASHBOARD_TYPES]['carbon'] = [Widget::class, 'WidgetTypes']; @@ -123,7 +135,7 @@ function plugin_carbon_setupHooks() $PLUGIN_HOOKS[Hooks::DASHBOARD_DEFAULTS]['carbon'] = [Grid::class, 'getDefaults']; } - if (Session::haveRight('config', UPDATE)) { + if (Session::haveRight(GlpiConfig::$rightname, UPDATE)) { $PLUGIN_HOOKS['config_page']['carbon'] = 'front/config.form.php'; } @@ -223,8 +235,9 @@ function plugin_carbon_check_prerequisites() $prerequisitesSuccess = true; + // In case GLPI is so old that the modern version checker is not implemented /** @phpstan-ignore if.alwaysFalse */ - if (version_compare(GLPI_VERSION, PLUGIN_CARBON_MIN_GLPI_VERSION, 'lt')) { + if (version_compare(GLPI_VERSION, "10.0.0", 'lt')) { echo "This plugin requires GLPI >= " . PLUGIN_CARBON_MIN_GLPI_VERSION . " and GLPI < " . PLUGIN_CARBON_MAX_GLPI_VERSION . "
"; $prerequisitesSuccess = false; } @@ -239,22 +252,23 @@ function plugin_carbon_check_prerequisites() $prerequisitesSuccess = false; } - if (getenv('CI') === false) { - // only when not under test - $version_string = $DB->getVersion(); - - $server = preg_match('/-MariaDB/', $version_string) ? 'MariaDB' : 'MySQL'; - $version = preg_replace('/^((\d+\.?)+).*$/', '$1', $version_string); - if ($server === 'MySQL' && version_compare($version, '8.0.0', '<')) { - echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
'; - $prerequisitesSuccess = false; - } - - if ($server === 'MariaDB' && version_compare($version, '10.2.0', '<')) { - echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
'; - $prerequisitesSuccess = false; - } - } + // GLPI 12 requires MySQL >= 8.0 or MariaDB >= 10.2, then the check below can be disabled + // if (getenv('CI') === false) { + // // only when not under test + // $version_string = $DB->getVersion(); + + // $server = preg_match('/-MariaDB/', $version_string) ? 'MariaDB' : 'MySQL'; + // $version = preg_replace('/^((\d+\.?)+).*$/', '$1', $version_string); + // if ($server === 'MySQL' && version_compare($version, '8.0.0', '<')) { + // echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
'; + // $prerequisitesSuccess = false; + // } + + // if ($server === 'MariaDB' && version_compare($version, '10.2.0', '<')) { + // echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
'; + // $prerequisitesSuccess = false; + // } + // } return $prerequisitesSuccess; } diff --git a/src/AbstractChildDropdown.php b/src/AbstractChildDropdown.php index 64675fb5..54994398 100644 --- a/src/AbstractChildDropdown.php +++ b/src/AbstractChildDropdown.php @@ -41,7 +41,7 @@ abstract class AbstractChildDropdown extends CommonDBChild { - public static $rightname = 'dropdown'; + public static string $rightname = 'dropdown'; #[Override] public static function getIcon(): string diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index 103ddc09..95a9fa71 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -44,10 +44,10 @@ abstract class AbstractImpact extends CommonDBChild { - public static $itemtype = 'itemtype'; - public static $items_id = 'items_id'; + public static string $itemtype = 'itemtype'; + public static string $items_id = 'items_id'; - public static $rightname = 'carbon:report'; + public static string $rightname = 'carbon:report'; #[Override] public function canEdit($ID): bool @@ -255,6 +255,6 @@ public function truncate(): bool return false; } - return $DB->delete(static::getTable(), [1]); + return $DB->delete(static::getTable(), ['NOT' => ['id' => null]]); } } diff --git a/src/Application/View/Extension/DataHelpersExtension.php b/src/Application/View/Extension/DataHelpersExtension.php deleted file mode 100644 index 2ccec211..00000000 --- a/src/Application/View/Extension/DataHelpersExtension.php +++ /dev/null @@ -1,303 +0,0 @@ -. - * - * ------------------------------------------------------------------------- - */ - -namespace GlpiPlugin\Carbon\Application\View\Extension; - -use Glpi\RichText\RichText; -use Glpi\Toolbox\Sanitizer; -use Html; -use Override; -use Toolbox; -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; - -/** - * @since 10.0.0 - */ -class DataHelpersExtension extends AbstractExtension -{ - #[Override] - public function getFilters(): array - { - return [ - new TwigFilter('formatted_datetime', [$this, 'getFormattedDatetime']), - new TwigFilter('formatted_date', [$this, 'getFormattedDate']), - new TwigFilter('formatted_duration', [$this, 'getFormattedDuration']), - new TwigFilter('formatted_integer', [$this, 'getFormattedInteger']), - new TwigFilter('formatted_number', [$this, 'getFormattedNumber']), - new TwigFilter('formatted_size', [$this, 'getFormattedSize']), - new TwigFilter('html_to_text', [$this, 'getTextFromHtml']), - new TwigFilter('long2ip', 'long2ip'), - new TwigFilter('picture_url', [$this, 'getPictureUrl']), - new TwigFilter('relative_datetime', [$this, 'getRelativeDatetime']), - new TwigFilter('safe_html', [$this, 'getSafeHtml'], ['is_safe' => ['html']]), - new TwigFilter('verbatim_value', [$this, 'getVerbatimValue']), - new TwigFilter('shortcut', [$this, 'underlineShortcutLetter'], ['is_safe' => ['html']]), - new TwigFilter('enhanced_html', [$this, 'getEnhancedHtml'], ['is_safe' => ['html']]), - new TwigFilter('truncate_left', [$this, 'truncateLeft']), - ]; - } - - /** - * Return date formatted to user preferred format. - * - * @param mixed $datetime - * @param bool $with_seconds - * - * @return string|null - */ - public function getFormattedDatetime($datetime, bool $with_seconds = false): ?string - { - if (!is_string($datetime)) { - return null; - } - return Html::convDateTime($datetime, null, $with_seconds); - } - - /** - * Return date formatted to user preferred format. - * - * @param mixed $date - * - * @return string|null - */ - public function getFormattedDate($date): ?string - { - if (!is_string($date)) { - return null; - } - return Html::convDate($date); - } - - /** - * Return relative representation of given date. - * - * @param mixed $datetime - * - * @return string|null - */ - public function getRelativeDatetime($datetime): ?string - { - if (!is_string($datetime)) { - return null; - } - return Html::timestampToRelativeStr($datetime); - } - - /** - * Return human readable duration. - * - * @param mixed $duration - * @param bool $display_seconds (default: true) - * - * @return string|null - */ - public function getFormattedDuration( - $duration, - bool $display_seconds = true - ): ?string { - if (!is_numeric($duration)) { - return null; - } - return Html::timestampToString($duration, $display_seconds); - } - - /** - * Return integer formatted to user preferred format. - * - * @param mixed $number Number to display - * - * @return string - */ - public function getFormattedInteger($number): string - { - return Html::formatNumber($number, false, 0); - } - - /** - * Return number formatted to user preferred format. - * - * @param mixed $number Number to display - * - * @return string - */ - public function getFormattedNumber($number): string - { - return Html::formatNumber($number); - } - - /** - * Return size formatted in a compact way (mo, ko, etc). - * - * @param mixed $number - * - * @return string - */ - public function getFormattedSize($number): string - { - if (!is_numeric($number)) { - return ''; - } - return Toolbox::getSize($number); - } - - /** - * Return URL for given picture. - * - * @param mixed $path - * - * @return null|string - */ - public function getPictureUrl($path): ?string - { - if (!is_string($path)) { - return null; - } - - return Toolbox::getPictureUrl($path, true); - } - - /** - * Return string having its shortcut letter underlined. - * - * @param string $string - * @param string $shortcut_letter - * - * @return string - */ - public function underlineShortcutLetter(string $string, string $shortcut_letter): string - { - if (empty($shortcut_letter)) { - return $string; - } - return Toolbox::shortcut($string, $shortcut_letter); - } - - /** - * Return plain text from HTML (rich text). - * - * @param mixed $string HTML string to be made safe - * @param bool $keep_presentation Indicates whether the presentation elements have to be replaced by plaintext equivalents - * @param bool $compact Indicates whether the output should be compact (limited line length, no links URL, ...) - * - * @return mixed - */ - public function getTextFromHtml($string, bool $keep_presentation = true, bool $compact = false) - { - if (!is_string($string)) { - return $string; - } - - return RichText::getTextFromHtml($string, $keep_presentation, $compact); - } - - /** - * Return safe HTML (rich text). - * Value will be made safe, whenever it has been sanitize (value fetched from DB), - * or not (value computed during runtime). - * Result will not be escaped, to prevent having to use `|raw` filter. - * - * @param mixed $string - * - * @return mixed - */ - public function getSafeHtml($string) - { - if (!is_string($string)) { - return $string; - } - - return RichText::getSafeHtml($string); - } - - /** - * Return enhanced HTML (rich text). - * Value will be made safe, whenever it has been sanitize (value fetched from DB), - * or not (value computed during runtime). - * Result will not be escaped, to prevent having to use `|raw` filter. - * - * @param mixed $string - * - * @return mixed - */ - public function getEnhancedHtml($string, array $params = []) - { - if (!is_string($string)) { - return $string; - } - - return RichText::getEnhancedHtml($string, $params); - } - - /** - * Return verbatim value for an itemtype field. - * Returned value will be unsanitized if it has been transformed by GLPI sanitizing process (value fetched from DB). - * Twig autoescaping system will then ensure that value is correctly escaped in rendered HTML. - * - * @param mixed $string - * - * @return mixed - * - * @deprecated 11.0 - */ - public function getVerbatimValue($string) - { - Toolbox::deprecated(); - - if (!is_string($string)) { - return $string; - } - - return Sanitizer::unsanitize($string); - } - - - /** - * return the provided string truncated on the left and prepend a prefix separator if length is reached - * - * @param string $string the string to left truncate - * @param int $length number of char to preserve - * @param string $separator prefix to prepend to the string - * - * @return string truncated string - */ - public function truncateLeft(string $string = "", int $length = 30, string $separator = "...") - { - if (mb_strlen($string) <= $length) { - return $string; - } - - return $separator . mb_substr($string, -$length); - } -} diff --git a/src/CarbonEmission.php b/src/CarbonEmission.php index 95619e15..5c717a49 100644 --- a/src/CarbonEmission.php +++ b/src/CarbonEmission.php @@ -44,8 +44,8 @@ class CarbonEmission extends CommonDBChild { - public static $itemtype = 'itemtype'; - public static $items_id = 'items_id'; + public static string $itemtype = 'itemtype'; + public static string $items_id = 'items_id'; #[Override] public static function getTypeName($nb = 0) @@ -229,6 +229,6 @@ public function truncate(): bool return false; } - return $DB->delete(static::getTable(), [1]); + return $DB->delete(static::getTable(), ['NOT' => ['id' => null]]); } } diff --git a/src/CarbonIntensity.php b/src/CarbonIntensity.php index 4c9fc7ec..e33992f2 100644 --- a/src/CarbonIntensity.php +++ b/src/CarbonIntensity.php @@ -34,14 +34,14 @@ use CommonDropdown; use DateInterval; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DBmysql; use Exception; use Glpi\DBAL\QueryParam; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface; use Override; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Symfony\Component\Console\Helper\ProgressBar; /** @@ -59,7 +59,7 @@ class CarbonIntensity extends CommonDropdown { private const MIN_HISTORY_LENGTH = '13 months ago'; - public static $rightname = 'carbon:report'; + public static string $rightname = 'carbon:report'; #[Override] public static function getTypeName($nb = 0) @@ -251,7 +251,7 @@ public function downloadOneZone(ClientInterface $data_source, Source_Zone $sourc $gap_end = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $gap['end']); $count = $data_source->fullDownload($source_zone, $gap_start, $gap_end, $this, $limit, $progress_bar); $total_count += $count; - if ($total_count >= $limit) { + if ($limit > 0 && $total_count >= $limit) { return $total_count; } } diff --git a/ajax/toggleZoneDownload.php b/src/CloudInventoryConnector.php similarity index 58% rename from ajax/toggleZoneDownload.php rename to src/CloudInventoryConnector.php index a3f8fe0a..491a8a73 100644 --- a/ajax/toggleZoneDownload.php +++ b/src/CloudInventoryConnector.php @@ -30,40 +30,28 @@ * ------------------------------------------------------------------------- */ -use Config as GlpiConfig; -use GlpiPlugin\Carbon\Source; -use GlpiPlugin\Carbon\Source_Zone; +namespace GlpiPlugin\Carbon; -include(__DIR__ . '/../../../inc/includes.php'); +use Plugin; -// Check if plugin is activated... -if (!Plugin::isPluginActive('carbon')) { - echo __('Not found.', 'carbon'); - http_response_code(404); - die(); -} +/** + * Establish the functional bridge between Carbon and the plugin CloudInventory + */ +class CloudInventoryConnector +{ + public static function checkPluginAvailability(): void + { + /** @var array $CFG_GLPI */ + global $CFG_GLPI; -if (!Source::canView() || ! GlpiConfig::canUpdate()) { - // Will die - echo __('Access denied.', 'carbon'); - http_response_code(403); - die(); -} + $CFG_GLPI['plugin:carbon']['use_cloudinventory'] = Plugin::isPluginActive('cloudinventory'); + } -if (!isset($_GET['id'])) { - echo __('Bad request.', 'carbon'); - http_response_code(400); - die(); -} + public function pluginAvailable(): bool + { + /** @var array $CFG_GLPI */ + global $CFG_GLPI; -$source_zone = new Source_Zone(); -if (!$source_zone->getFromDB($_GET['id'])) { - echo __('Item not found.', 'carbon'); - http_response_code(403); - die(); -} -if (!$source_zone->toggleZone()) { - echo __('Update failed.', 'carbon'); - http_response_code(500); - die(); + return $CFG_GLPI['plugin:carbon']['use_cloudinventory'] ?? false; + } } diff --git a/src/Command/CollectCarbonIntensityCommand.php b/src/Command/CollectCarbonIntensityCommand.php index 09941081..0b92bd2e 100644 --- a/src/Command/CollectCarbonIntensityCommand.php +++ b/src/Command/CollectCarbonIntensityCommand.php @@ -52,6 +52,8 @@ use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\Question; +use function Safe\define; + // 6 months define('DATE_MIN', 'P6M'); @@ -117,7 +119,7 @@ protected function interact(InputInterface $input, OutputInterface $output) } #[Override] - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { if (count($this->zones) > 1 && $input->getArgument('zone') === null) { // Null is not a valid key if there are several zones available for the source diff --git a/src/Command/CreateFakeCarbonIntensityCommand.php b/src/Command/CreateFakeCarbonIntensityCommand.php index 516b9d1e..339de6d7 100644 --- a/src/Command/CreateFakeCarbonIntensityCommand.php +++ b/src/Command/CreateFakeCarbonIntensityCommand.php @@ -33,12 +33,12 @@ namespace GlpiPlugin\Carbon\Command; use DateInterval; -use DateTime; use DateTimeZone; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Zone; use Override; +use Safe\DateTime; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; @@ -63,7 +63,7 @@ protected function configure() } #[Override] - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->output = $output; diff --git a/src/Command/CreateTestInventoryCommand.php b/src/Command/CreateTestInventoryCommand.php index 1ea9c7cf..bccd34c0 100644 --- a/src/Command/CreateTestInventoryCommand.php +++ b/src/Command/CreateTestInventoryCommand.php @@ -40,6 +40,7 @@ use GlpiPlugin\Carbon\ComputerType; use GlpiPlugin\Carbon\ComputerUsageProfile; use GlpiPlugin\Carbon\UsageInfo; +use InvalidArgumentException; use Location; use Override; use Symfony\Component\Console\Command\Command; @@ -141,7 +142,7 @@ protected function configure() } #[Override] - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->output = $output; @@ -161,6 +162,9 @@ protected function execute(InputInterface $input, OutputInterface $output) private function createItemIfNotExist(string $item_type, array $crit, ?array $input = null): CommonDBTM { + if (!is_a($item_type, CommonDBTM::class, true)) { + throw new InvalidArgumentException("Item type $item_type is not a subclass of CommonDBTM"); + } $item = new $item_type(); $ret = $item->getFromDBByCrit($crit); diff --git a/src/Command/ExportDashboardCommand.php b/src/Command/ExportDashboardCommand.php index 9c36d94b..061e14a2 100644 --- a/src/Command/ExportDashboardCommand.php +++ b/src/Command/ExportDashboardCommand.php @@ -38,10 +38,15 @@ use Glpi\Dashboard\Item; use Override; use Plugin; +use Safe\Exceptions\FilesystemException; +use Safe\Exceptions\JsonException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use function Safe\file_put_contents; +use function Safe\json_encode; + class ExportDashboardCommand extends Command { private OutputInterface $output; @@ -60,7 +65,7 @@ protected function configure() } #[Override] - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @var DBmysql $DB */ global $DB; @@ -93,10 +98,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->dashboard_description[] = $row; } - file_put_contents( - $this->output_path, - json_encode($this->dashboard_description, JSON_PRETTY_PRINT) - ); + try { + file_put_contents( + $this->output_path, + json_encode($this->dashboard_description, JSON_PRETTY_PRINT) + ); + } catch (FilesystemException|JsonException $e) { + $message = __('Error while saving dashboard description', 'carbon'); + $this->output->writeln("$message"); + return Command::FAILURE; + } $message = sprintf(__('Dashboard description saved to %s', 'carbon'), $this->output_path); $this->output->writeln("$message"); diff --git a/src/Command/ImportDashboardCommand.php b/src/Command/ImportDashboardCommand.php index fc605bf2..1d2e986b 100644 --- a/src/Command/ImportDashboardCommand.php +++ b/src/Command/ImportDashboardCommand.php @@ -52,7 +52,7 @@ protected function configure() } #[Override] - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->output = $output; diff --git a/src/CommonAsset.php b/src/CommonAsset.php index bc50614f..fa1df555 100644 --- a/src/CommonAsset.php +++ b/src/CommonAsset.php @@ -33,6 +33,7 @@ namespace GlpiPlugin\Carbon; use CommonDBTM; +use GlpiPlugin\Carbon\Impact\History\AbstractAsset; use Html; use MassiveAction; use Override; @@ -108,6 +109,9 @@ public static function deleteEmbodiedImpact(CommonDBTM $item): bool public static function deleteUsageImpact(CommonDBTM $item): bool { $gwp_impact_class = '\\GlpiPlugin\\Carbon\\Impact\\History\\' . get_class($item); + if (!is_a($gwp_impact_class, AbstractAsset::class, true)) { + return false; + } $gwp_impact = new $gwp_impact_class(); $success = $gwp_impact->resetForItem($item->getID()); diff --git a/src/ComputerModel.php b/src/ComputerModel.php index f08fb727..85ce4795 100644 --- a/src/ComputerModel.php +++ b/src/ComputerModel.php @@ -36,6 +36,6 @@ class ComputerModel extends AbstractModel { - public static $itemtype = GlpiComputerModel::class; - public static $items_id = 'computermodels_id'; + public static string $itemtype = GlpiComputerModel::class; + public static string $items_id = 'computermodels_id'; } diff --git a/src/ComputerType.php b/src/ComputerType.php index c20955d7..df0eb829 100644 --- a/src/ComputerType.php +++ b/src/ComputerType.php @@ -41,8 +41,8 @@ class ComputerType extends AbstractChildDropdown { - public static $itemtype = GlpiComputerType::class; - public static $items_id = 'computertypes_id'; + public static string $itemtype = GlpiComputerType::class; + public static string $items_id = 'computertypes_id'; public const CATEGORY_UNDEFINED = 0; public const CATEGORY_DESKTOP = 1; @@ -50,6 +50,7 @@ class ComputerType extends AbstractChildDropdown public const CATEGORY_LAPTOP = 3; public const CATEGORY_TABLET = 4; public const CATEGORY_SMARTPHONE = 5; + public const CATEGORY_CLOUD = 6; public static function getCategories(): array { @@ -60,6 +61,7 @@ public static function getCategories(): array self::CATEGORY_LAPTOP => __('Laptop', 'carbon'), self::CATEGORY_TABLET => __('Tablet', 'carbon'), self::CATEGORY_SMARTPHONE => __('Smartphone', 'carbon'), + self::CATEGORY_CLOUD => __('Cloud server', 'carbon'), ]; } diff --git a/src/ComputerUsageProfile.php b/src/ComputerUsageProfile.php index 9b84a490..a16444ea 100644 --- a/src/ComputerUsageProfile.php +++ b/src/ComputerUsageProfile.php @@ -40,8 +40,11 @@ use Html; use MassiveAction; use Override; +use Safe\Exceptions\PcreException; use Session; +use function Safe\preg_match; + /** * Usage profile of a computer */ @@ -141,7 +144,11 @@ protected function inputIntegrityCheck(array $input): array protected function isValidTime(string $time): bool { $time_pattern = '/^(([01]\d|2[0-3]):[0-5]\d)|(24:00)$/'; - $found = preg_match($time_pattern, $time, $matches); + try { + $found = preg_match($time_pattern, $time, $matches); + } catch (PcreException $e) { + return false; + } return ($found === 1); } diff --git a/src/Config.php b/src/Config.php index e9b38b9e..84609f14 100644 --- a/src/Config.php +++ b/src/Config.php @@ -52,6 +52,8 @@ use Session; use Twig\Extension\StringLoaderExtension; +use function Safe\json_encode; + class Config extends GlpiConfig { /** @@ -67,13 +69,19 @@ public static function getTypeName($nb = 0) return plugin_carbon_getFriendlyName(); } + #[Override] + public static function getIcon(): string + { + return 'fa-solid fa-solar-panel'; + } + #[Override] public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { $tabName = ''; if (!$withtemplate) { if ($item->getType() == GlpiConfig::class) { - $tabName = self::getTypeName(); + $tabName = GlpiConfig::createTabEntry(self::getTypeName(), 0, $item::class, self::getIcon()); } } return $tabName; @@ -123,7 +131,6 @@ public function showForm($ID, $options = []) $current_config = array_diff_key($current_config, array_flip($secured_config)); $reset_args = json_encode([ - '_glpi_csrf_token' => Session::getNewCSRFToken(), 'reset_all' => '', ]); $usage_impact_action_url = 'submitGetLink("' . $CFG_GLPI['root_doc'] . '/plugins/carbon/front/usageimpact.form.php", ' . $reset_args . ')'; @@ -137,6 +144,7 @@ public function showForm($ID, $options = []) $confirm_message = __('This action cannot be undone. Are you sure?', 'carbon'); $renderer->display('@carbon/config.html.twig', [ 'can_edit' => $canedit, + 'context' => self::CONFIG_CONTEXT, 'current_config' => $current_config, 'impact_engines' => Engine::getAvailableBackends(), 'include_configs' => $include_configs, @@ -148,6 +156,32 @@ public function showForm($ID, $options = []) return true; } + /** + * Set config values : create or update entry + * + * @param string $context context to get values (default for glpi is core) + * @param array $values config names to set + * + * @return void + */ + public static function setConfigurationValues($context, array $values = []) + { + GlpiConfig::setConfigurationValues($context, $values); + } + + /** + * Get config value + * + * @param $context string context to get values (default for glpi is core) + * @param $name string config name + * + * @return mixed + */ + public static function getConfigurationValue(string $context, string $name) + { + return GlpiConfig::getConfigurationValue($context, $name); + } + /** * Prepare input for configuration update * diff --git a/src/CronTask.php b/src/CronTask.php index 674a11a1..12b9d86f 100644 --- a/src/CronTask.php +++ b/src/CronTask.php @@ -39,6 +39,7 @@ use Geocoder\Exception\QuotaExceeded; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientFactory; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface; +use GlpiPlugin\Carbon\DataSource\CronTaskInterface; use GlpiPlugin\Carbon\DataSource\CronTaskProvider; use GlpiPlugin\Carbon\Impact\Embodied\Engine as EmbodiedEngine; use GlpiPlugin\Carbon\Impact\History\AssetInterface; @@ -60,7 +61,10 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) if (!$item instanceof GlpiCronTask) { return ''; } - if (!in_array($item->fields['itemtype'], CronTaskProvider::getCronTaskTypes())) { + if (!in_array($item->fields['itemtype'], CronTaskProvider::getCronTaskTypes(CronTaskProvider::getCronTaskDirectories()))) { + return ''; + } + if (!is_a($item->fields['itemtype'], CronTaskInterface::class, true)) { return ''; } $client_cron_task = new $item->fields['itemtype'](); @@ -81,7 +85,10 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $ public function showForCronTask(CommonDBTM $item) { $itemtype = $item->fields['itemtype']; - if (!in_array($itemtype, CronTaskProvider::getCronTaskTypes())) { + if (!in_array($itemtype, CronTaskProvider::getCronTaskTypes(CronTaskProvider::getCronTaskDirectories()))) { + return; + } + if (!is_a($itemtype, CronTaskInterface::class, true)) { return; } $crontask = new $itemtype(); @@ -151,7 +158,9 @@ public static function cronUsageImpact(GlpiCronTask $task): int // Calculate GWP $count = 0; foreach ($usage_impacts as $usage_impact_type) { - /** @var AssetInterface $usage_impact */ + if (!is_a($usage_impact_type, AssetInterface::class, true)) { + continue; + } $usage_impact = new $usage_impact_type(); $usage_impact->setLimit($limit_per_type); $count = $usage_impact->evaluateItems($usage_impact->getItemsToEvaluate()); diff --git a/src/Dashboard/Dashboard.php b/src/Dashboard/Dashboard.php index 14d18ccf..4efc4eb7 100644 --- a/src/Dashboard/Dashboard.php +++ b/src/Dashboard/Dashboard.php @@ -67,22 +67,4 @@ public static function getTotalPowerPerModel(): array { return Provider::getSumPowerPerModel([ComputerModel::getTableField('power_consumption') => ['>', '0']]); } - - public static function cardCarbonintensityProvider(array $params = []) - { - $default_params = [ - 'label' => __('Carbon dioxyde intensity', 'carbon'), - 'icon' => "fas fa-computer", - 'color' => '#ea9999', - ]; - $params = array_merge($default_params, $params); - - $data = Provider::getCarbonIntensity($params); - - return [ - 'data' => $data, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } } diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index 183c1e46..0103fe00 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -34,12 +34,12 @@ use Computer; use DateInterval; -use DateTime; -use DateTimeImmutable; use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Toolbox; use Monitor; use NetworkEquipment; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Session; class DemoProvider diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index e1fd1c19..a4569e78 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -37,9 +37,14 @@ use Glpi\Dashboard\Filter; use GlpiPlugin\Carbon\Config; use GlpiPlugin\Carbon\Impact\Type; +use JsonException; use Plugin; +use Safe\Exceptions\FilesystemException; use Session; +use function Safe\file_get_contents; +use function Safe\json_decode; + /** * @phpstan-import-type DashboardConfigDescription from Dashboard */ @@ -353,12 +358,14 @@ protected static function getReportCards(): array public static function getDefaults(?array $defaults = null): ?array { $cards_path = Plugin::getPhpDir('carbon') . '/install/data/report_dashboard.json'; - $cards = file_get_contents($cards_path); - if ($cards === false) { + try { + $cards = file_get_contents($cards_path); + } catch (FilesystemException $e) { return $defaults; } - $cards = json_decode($cards, true); - if ($cards === null) { + try { + $cards = json_decode($cards, true); + } catch (JsonException $e) { return $defaults; } diff --git a/src/Dashboard/Provider.php b/src/Dashboard/Provider.php index 6d928dc7..733e4c1f 100644 --- a/src/Dashboard/Provider.php +++ b/src/Dashboard/Provider.php @@ -37,8 +37,6 @@ use ComputerModel as GlpiComputerModel; use ComputerType as GlpiComputerType; use DateInterval; -use DateTime; -use DateTimeImmutable; use DBmysql; use DbUtils; use Glpi\Dashboard\Filter; @@ -55,6 +53,8 @@ use GlpiPlugin\Carbon\UsageImpact; use GlpiPlugin\Carbon\Zone; use RuntimeException; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Search; use Session; use Toolbox as GlpiToolbox; @@ -761,7 +761,7 @@ public static function getUsageCarbonEmissionYearToDate(array $params = []): arr $end_date->format('Y-m-d\TH:i:s.v\Z'), ], ]; - return self::getUsageCarbonEmission($params); + return self::getImpactOfUsageCriteria('gwp', $params, ['itemtype' => PLUGIN_CARBON_TYPES]); } public static function getCarbonIntensity(array $params): array diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index e0674257..863d49ba 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -34,8 +34,6 @@ use Computer; use DateInterval; -use DateTime; -use DateTimeImmutable; use Glpi\Application\View\TemplateRenderer; use Glpi\Dashboard\Widget as GlpiDashboardWidget; use GlpiPlugin\Carbon\Impact\Type; @@ -43,6 +41,8 @@ use Html; use Monitor; use NetworkEquipment; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Toolbox as GlpiToolbox; class Widget extends GlpiDashboardWidget @@ -102,13 +102,6 @@ public static function WidgetTypes(?array $types = null): array 'width' => 16, 'height' => 12, ], - 'usage_abiotic_depletion' => [ - 'label' => __('Usage abiotic depletion potential', 'carbon'), - 'function' => self::class . '::displayUsageAbioticDepletion', - 'image' => '', - 'width' => 6, - 'height' => 3, - ], 'impact_criteria_number' => [ 'label' => __('Impact criteria', 'carbon'), diff --git a/src/DataSource/CarbonIntensity/AbstractClient.php b/src/DataSource/CarbonIntensity/AbstractClient.php index 658b3794..d2952eab 100644 --- a/src/DataSource/CarbonIntensity/AbstractClient.php +++ b/src/DataSource/CarbonIntensity/AbstractClient.php @@ -34,8 +34,6 @@ use Config as GlpiConfig; use DateInterval; -use DateTime; -use DateTimeImmutable; use DateTimeZone; use DBmysql; use Generator; @@ -45,6 +43,8 @@ use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\Zone; use Override; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Symfony\Component\Console\Helper\ProgressBar; abstract class AbstractClient implements ClientInterface diff --git a/src/DataSource/CarbonIntensity/AbstractCronTask.php b/src/DataSource/CarbonIntensity/AbstractCronTask.php index 5df751ad..bd672f95 100644 --- a/src/DataSource/CarbonIntensity/AbstractCronTask.php +++ b/src/DataSource/CarbonIntensity/AbstractCronTask.php @@ -39,6 +39,7 @@ use GlpiPlugin\Carbon\CronTask; use GlpiPlugin\Carbon\DataSource\AbstractCronTask as DatasourceAbstractCronTask; use GlpiPlugin\Carbon\DataSource\CronTaskInterface; +use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Toolbox; use GlpiPlugin\Carbon\Zone; @@ -86,12 +87,18 @@ public function showGapsReport() {% import "components/form/fields_macros.html.twig" as fields %} {{ fields.largeTitle(__('Gaps in carbon intensity time series', 'carbon')) }}
{{ __('Only zones with download enabled are displayed.', 'carbon') }}
+
{{ __('You may view and enable them here.', 'carbon') }}
 
TWIG; - echo $renderer->renderFromStringTemplate($template); - $oldest_asset_date = (new Toolbox())->getOldestAssetDate(); $client = ClientFactory::create(static::$client_name); $source_name = $client->getSourceName(); + $source = new Source(); + $source->getFromDBByCrit(['name' => $source_name]); + $source_url = $source->getLinkURL(); + echo $renderer->renderFromStringTemplate($template, [ + 'url_to_zones' => $source_url, + ]); + $oldest_asset_date = (new Toolbox())->getOldestAssetDate(); foreach ($client->getSupportedZones() as $zone_name) { $source_zone = new Source_Zone(); if (!$source_zone->getFromDbBySourceAndZone($source_name, $zone_name)) { diff --git a/src/DataSource/CarbonIntensity/ClientFactory.php b/src/DataSource/CarbonIntensity/ClientFactory.php index 38c30aab..4b8d5310 100644 --- a/src/DataSource/CarbonIntensity/ClientFactory.php +++ b/src/DataSource/CarbonIntensity/ClientFactory.php @@ -36,6 +36,8 @@ use GlpiPlugin\Carbon\DataSource\RestApiClient; use InvalidArgumentException; +use function Safe\glob; + class ClientFactory { /** @@ -143,6 +145,9 @@ public static function createByName(string $name): AbstractClient $class_name = array_search($name, $names); $rest_api_client = new RestApiClient([]); + if (!is_a($class_name, AbstractClient::class, true)) { + throw new InvalidArgumentException("Class $class_name is not a subclass of AbstractClient"); + } return new $class_name($rest_api_client); } } diff --git a/src/DataSource/CarbonIntensity/ClientInterface.php b/src/DataSource/CarbonIntensity/ClientInterface.php index 16bdae9d..8d05517c 100644 --- a/src/DataSource/CarbonIntensity/ClientInterface.php +++ b/src/DataSource/CarbonIntensity/ClientInterface.php @@ -32,9 +32,9 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity; -use DateTimeImmutable; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Source_Zone; +use Safe\DateTimeImmutable; use Symfony\Component\Console\Helper\ProgressBar; /** diff --git a/src/DataSource/CarbonIntensity/ElectricityMaps/Client.php b/src/DataSource/CarbonIntensity/ElectricityMaps/Client.php index 1c9a6c08..9f93af96 100644 --- a/src/DataSource/CarbonIntensity/ElectricityMaps/Client.php +++ b/src/DataSource/CarbonIntensity/ElectricityMaps/Client.php @@ -33,11 +33,8 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity\ElectricityMaps; use DateInterval; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; -use DBmysql; use GLPIKey; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\AbortException; @@ -50,9 +47,18 @@ use GlpiPlugin\Carbon\Zone; use Override; use RuntimeException; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Safe\Exceptions\FilesystemException; use Symfony\Component\Console\Helper\ProgressBar; +use function Safe\file_get_contents; +use function Safe\file_put_contents; +use function Safe\json_decode; +use function Safe\json_encode; +use function Safe\mkdir; +use function Safe\preg_match; + /** * Query carbon intensity data from Electricity map * @@ -239,7 +245,7 @@ public function fetchDay(DateTimeImmutable $day, Source_Zone $source_zone): arra if ($response['message'] === 'Invalid auth-token') { throw new AbortException('Invalid auth-token'); } - if (preg_match("#^Zone '[^']*' does not exist.$#", $response['message']) !== false) { + if (preg_match("#^Zone '[^']*' does not exist.$#", $response['message']) === 1) { throw new AbortException($response['message']); } return []; @@ -249,9 +255,6 @@ public function fetchDay(DateTimeImmutable $day, Source_Zone $source_zone): arra $timezone = new DateTimeZone('UTC'); foreach ($response['history'] as $record) { $datetime = DateTime::createFromFormat('Y-m-d\TH:i:s+', $record['datetime'], $timezone); - if (!$datetime instanceof DateTimeInterface) { - continue; - } $data_quality = $this->getDataQuality($record); $intensities[] = [ 'datetime' => $datetime->format('Y-m-d\TH:i:s'), @@ -331,7 +334,7 @@ public function fetchRange(DateTimeImmutable $start, DateTimeImmutable $stop, So while ($current_date < $request_stop) { $stop = clone $current_date; $stop->add($step); - // For some reason, passing the parameters as a query stringthrough Guzzle + // For some reason, passing the parameters as a query string through Guzzle // Makes the request malformed from the point of view of Electricitymaps // Workarounded by building here the query string // $params = [ @@ -377,7 +380,9 @@ public function fetchRange(DateTimeImmutable $start, DateTimeImmutable $stop, So } $downloaded_year_month = $start->format('Y-m'); - if (count($full_response) > 0 && $downloaded_year_month < date('Y-m')) { + if (count($full_response) > 0 && $downloaded_year_month < date('Y-m') && date('j') > 1) { + // Cache only if the month being processed is older than the month of now + // and we are at least the 2nd day of the current month (to handle +14 hours overlap) $json = json_encode($full_response); file_put_contents($cache_file, $json); } @@ -412,11 +417,8 @@ protected function formatOutput(array $response, int $step): array */ protected function shiftToLocalTimezone(array $response): array { - /** @var DBmysql $DB */ - global $DB; - $shifted_response = []; - $local_timezone = new DateTimeZone($DB->guessTimezone()); + $local_timezone = new DateTimeZone(date_default_timezone_get()); array_walk($response, function ($item, $key) use (&$shifted_response, $local_timezone) { $shifted_date_object = DateTime::createFromFormat('Y-m-d\TH:i:s.vp', $item['datetime']) ->setTimezone($local_timezone); diff --git a/src/DataSource/CarbonIntensity/Rte/Client.php b/src/DataSource/CarbonIntensity/Rte/Client.php index ec7b66c7..44960896 100644 --- a/src/DataSource/CarbonIntensity/Rte/Client.php +++ b/src/DataSource/CarbonIntensity/Rte/Client.php @@ -33,11 +33,8 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity\Rte; use DateInterval; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; -use DBmysql; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\AbstractClient; use GlpiPlugin\Carbon\DataSource\RestApiClientInterface; use GlpiPlugin\Carbon\DataTracking\AbstractTracked; @@ -47,8 +44,14 @@ use GlpiPlugin\Carbon\Zone; use Override; use RuntimeException; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Safe\Exceptions\FilesystemException; +use function Safe\file_get_contents; +use function Safe\file_put_contents; +use function Safe\json_decode; +use function Safe\json_encode; use function Safe\mkdir; /** @@ -336,8 +339,9 @@ public function fetchRange(DateTimeImmutable $start, DateTimeImmutable $stop, So } } else { $downloaded_year_month = $start->format('Y-m'); - if ($downloaded_year_month < date('Y-m')) { + if (count($response) > 0 && $downloaded_year_month < date('Y-m') && date('j') > 1) { // Cache only if the month being processed is older than the month of now + // and we are at least the 2nd day of the current month (to handle +14 hours overlap) $json = json_encode($response); file_put_contents($cache_file, $json); } @@ -387,11 +391,8 @@ protected function formatOutput(array $response, int $step): array */ protected function shiftToLocalTimezone(array $response): array { - /** @var DBmysql $DB */ - global $DB; - $shifted_response = []; - $local_timezone = new DateTimeZone($DB->guessTimezone()); + $local_timezone = new DateTimeZone(date_default_timezone_get()); array_walk($response, function ($item, $key) use (&$shifted_response, $local_timezone) { $shifted_date_object = DateTime::createFromFormat('Y-m-d\TH:i:sP', $item['date_heure']) ->setTimezone($local_timezone); diff --git a/src/DataSource/CronTaskInterface.php b/src/DataSource/CronTaskInterface.php index 7fec58c5..d4b7150d 100644 --- a/src/DataSource/CronTaskInterface.php +++ b/src/DataSource/CronTaskInterface.php @@ -32,7 +32,14 @@ namespace GlpiPlugin\Carbon\DataSource; +use CommonDBTM; +use CommonGLPI; + interface CronTaskInterface { public static function enumerateTasks(): array; + + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0); + + public function showForCronTask(CommonDBTM $item); } diff --git a/src/DataSource/CronTaskProvider.php b/src/DataSource/CronTaskProvider.php index cf6166b7..0dc65b6b 100644 --- a/src/DataSource/CronTaskProvider.php +++ b/src/DataSource/CronTaskProvider.php @@ -32,17 +32,30 @@ namespace GlpiPlugin\Carbon\DataSource; +use DirectoryIterator; + class CronTaskProvider { - public static function getCronTaskTypes(): array + /** + * Get all the cron task types available in the plugin. + * + * @param array $subdirs The subdirectories to search for cron tasks. + * @return array> An associative array where the keys are the cron task names and the values are the fully qualified class names of the cron tasks. + */ + public static function getCronTaskTypes(array $subdirs): array { - $subdirs = ['CarbonIntensity', 'Lca']; - $types = []; + static $types = []; + if (!empty($types)) { + return $types; + } foreach ($subdirs as $subdir) { - foreach (glob(__DIR__ . '/' . $subdir . '/*', GLOB_ONLYDIR) as $connector_dir) { - $dir = basename($connector_dir); - $class_name = 'GlpiPlugin\\Carbon\\DataSource\\' . $subdir . '\\' - . $dir . '\\CronTask'; + foreach ($subdir as $connector_dir) { + if ($connector_dir->isDot() || !$connector_dir->isDir()) { + continue; + } + $type_dir = basename(dirname($connector_dir->getPathname())); + $dir = $connector_dir->getBasename(); + $class_name = 'GlpiPlugin\\Carbon\\DataSource\\' . $type_dir . '\\' . $dir . '\\CronTask'; if (!class_exists($class_name)) { continue; } @@ -55,4 +68,17 @@ public static function getCronTaskTypes(): array return $types; } + + /** + * Get the directories containing cron tasks. + * + * @return array The directories containing cron tasks. + */ + public static function getCronTaskDirectories(): array + { + return [ + new DirectoryIterator(__DIR__ . '/CarbonIntensity'), + new DirectoryIterator(__DIR__ . '/Lca'), + ]; + } } diff --git a/src/DataSource/Lca/Boaviztapi/Client.php b/src/DataSource/Lca/Boaviztapi/Client.php index 583e66d5..7685e38c 100644 --- a/src/DataSource/Lca/Boaviztapi/Client.php +++ b/src/DataSource/Lca/Boaviztapi/Client.php @@ -44,6 +44,9 @@ use GlpiPlugin\Carbon\Zone; use Override; use RuntimeException; +use Session; + +use function Safe\json_encode; class Client extends AbstractClient { @@ -259,6 +262,16 @@ public static function getZones() public function parseResponse(array $response, string $scope): array { $impacts = []; + if (!isset($response['impacts'])) { + if (Session::getLoginUserID(true)) { + $message = __('An error occured while processing the response from Boaviztapi', 'carbon'); + $message .= '
' . ($response['detail'] ?? 'unknown error'); + Session::addMessageAfterRedirect($message, true, ERROR); + } + trigger_error('Error parsing the response ' . var_export($response, true), E_USER_WARNING); + return $impacts; + } + $types = Type::getImpactTypes(); foreach ($response['impacts'] as $type => $impact) { if (!in_array($type, $types)) { @@ -275,7 +288,7 @@ public function parseResponse(array $response, string $scope): array return $impacts; } - protected function parseCriteria(string $name, $impact): ?TrackedFloat + protected function parseCriteria(string $name, string|array $impact): ?TrackedFloat { if ($impact === 'not implemented') { return null; @@ -302,4 +315,19 @@ public static function dropdownBoaviztaZone(string $name, array $options = []) { return Dropdown::showFromArray($name, self::getZones(), $options); } + + /** + * Get instances known types of a cloud provider + * + * @param string $provider + * @return array isntances types + */ + public function getCloudInstances(string $provider): array + { + $response = $this->get('cloud/instance/all_instances', [ + 'query' => ['provider' => $provider], + ]); + + return $response; + } } diff --git a/src/DataSource/Lca/Boaviztapi/Config.php b/src/DataSource/Lca/Boaviztapi/Config.php index 1b38f9f4..6fae7656 100644 --- a/src/DataSource/Lca/Boaviztapi/Config.php +++ b/src/DataSource/Lca/Boaviztapi/Config.php @@ -37,8 +37,11 @@ use GlpiPlugin\Carbon\DataSource\ConfigInterface; use GlpiPlugin\Carbon\DataSource\RestApiClient; use Override; +use Safe\Exceptions\UrlException; use Session; +use function Safe\parse_url; + class Config implements ConfigInterface { public const ENV_BOAVIZTAPI_BASE_URL = 'GLPI_PLUGIN_CARBON_BOAVIZTAPI_BASE_URL'; @@ -139,7 +142,11 @@ protected function validateBaseUrl(string $url): bool } // Check if the URL has a valid scheme (http or https) - $parsed_url = parse_url($url); + try { + $parsed_url = parse_url($url); + } catch (UrlException $e) { + return false; + } if (!isset($parsed_url['scheme']) || !in_array($parsed_url['scheme'], ['http', 'https'])) { return false; } diff --git a/src/DataSource/Lca/ClientFactory.php b/src/DataSource/Lca/ClientFactory.php index 6426e137..1c861557 100644 --- a/src/DataSource/Lca/ClientFactory.php +++ b/src/DataSource/Lca/ClientFactory.php @@ -36,6 +36,8 @@ use GlpiPlugin\Carbon\DataSource\RestApiClient; use InvalidArgumentException; +use function Safe\glob; + class ClientFactory { /** @@ -144,6 +146,9 @@ public static function createByName(string $name): AbstractClient $class_name = array_search($name, $names); $rest_api_client = new RestApiClient([]); + if (!is_a($class_name, AbstractClient::class, true)) { + throw new InvalidArgumentException("Class $class_name is not a subclass of AbstractClient"); + } return new $class_name($rest_api_client); } } diff --git a/src/DataSource/RestApiClient.php b/src/DataSource/RestApiClient.php index 9edf7ab7..9b4570f8 100644 --- a/src/DataSource/RestApiClient.php +++ b/src/DataSource/RestApiClient.php @@ -32,13 +32,17 @@ namespace GlpiPlugin\Carbon\DataSource; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Message; -use GuzzleHttp\Psr7\Request; +use Glpi\Toolbox\HttpClient; +use GlpiPlugin\Carbon\Config; use Override; +use RuntimeException; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Toolbox; +use function Safe\json_decode; + class RestApiClient implements RestApiClientInterface { public const DEFAULT_TIMEOUT = 5; @@ -47,54 +51,52 @@ class RestApiClient implements RestApiClientInterface ]; public const DEFAULT_HTTP_VERSION = '2.0'; - protected $api_client = null; - protected $last_error = ''; + protected ?HttpClient $api_client = null; + protected array $last_error = []; public function __construct(array $params = []) { $local_params = [ - 'timeout' => self::DEFAULT_TIMEOUT, - 'connect_timeout' => self::DEFAULT_TIMEOUT, - 'headers' => self::DEFAULT_HEADERS, - 'version' => self::DEFAULT_HTTP_VERSION, - 'http_errors' => false, - 'debug' => false, // ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE), - // This is insecure and not recommanded, but... - // 'verify' => false, + 'timeout' => self::DEFAULT_TIMEOUT, + 'max_connect_duration' => self::DEFAULT_TIMEOUT, + 'headers' => self::DEFAULT_HEADERS, + 'http_version' => self::DEFAULT_HTTP_VERSION, ]; // array_merge_recursive() is used because it merges headers - $this->api_client = new Client(array_merge_recursive($local_params, $params)); + $this->api_client = new HttpClient(Config::class, array_merge_recursive($local_params, $params)); } #[Override] public function request(string $method = 'GET', string $uri = '', array $options = []) { + $request = $this->api_client; try { - $request = $this->api_client; $response = $request->request($method, $uri, $options); - } catch (RequestException $e) { - $cleaned_request = new Request( - $e->getRequest()->getMethod(), - $e->getRequest()->getUri(), - [], - $request->getBody(), - $request->getProtocolVersion() - ); + } catch (RedirectionExceptionInterface|ClientExceptionInterface|ServerExceptionInterface $e) { + // Exception related to HTTP + $this->last_error = [ + 'title' => "Plugins API error", + 'exception' => $e->getMessage(), + 'request' => $method . ' ' . $uri, + ]; + $this->last_error['response'] = $e->getResponse()->getContent(false); + + Toolbox::logDebug($this->last_error); + + return false; + } catch (RuntimeException $e) { + // Other exceptions $this->last_error = [ 'title' => "Plugins API error", 'exception' => $e->getMessage(), - 'request' => Message::toString($cleaned_request), ]; - if ($e->hasResponse()) { - $this->last_error['response'] = Message::toString($e->getResponse()); - } Toolbox::logDebug($this->last_error); return false; } - return json_decode($response->getBody(), true); + return json_decode($response->getContent(), true); } } diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 757f8c5b..b46c3c01 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -47,20 +47,27 @@ public static function getTypeName($nb = 0) return _n("Embodied impact", "Embodied impacts", $nb, 'carbon'); } - public function calculateImpact(string $lca_type, int $limit = 0): int - { - $crit = []; - if ($limit > 0) { - $crit['LIMIT'] = $limit; - } - $iterator = self::getItemsToEvaluate($lca_type::getItemtype(), $crit); - $count = 0; - foreach ($iterator as $item) { - $lca = new $lca_type($item['id']); - $lca::calculate($item); - $count++; - } + // /** + // * Calculate the embodied impact of assets + // * + // * @param class-string $lca_type + // * @param int $limit + // * @return int + // */ + // public function calculateImpact(string $lca_type, int $limit = 0): int + // { + // $crit = []; + // if ($limit > 0) { + // $crit['LIMIT'] = $limit; + // } + // $iterator = self::getItemsToEvaluate($lca_type::getItemtype(), $crit); + // $count = 0; + // foreach ($iterator as $item) { + // $lca = new $lca_type($item['id']); + // $lca::calculate($item); + // $count++; + // } - return $iterator->count(); - } + // return $iterator->count(); + // } } diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index 8cf253c0..81c15b7f 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -38,6 +38,8 @@ use Override; use RuntimeException; +use function Safe\json_encode; + abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInterface { /** @var string $engine Name of the calculation engine */ @@ -55,6 +57,10 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte /** @var Client instance of the HTTP client */ protected ?Client $client = null; + protected const USAGE_NULL = [ + 'avg_power' => 0, + ]; + // abstract public static function getEngine(CommonDBTM $item): EngineInterface; /** diff --git a/src/Impact/Embodied/Boavizta/Computer.php b/src/Impact/Embodied/Boavizta/Computer.php index 9d16f684..ca1188ca 100644 --- a/src/Impact/Embodied/Boavizta/Computer.php +++ b/src/Impact/Embodied/Boavizta/Computer.php @@ -35,10 +35,19 @@ use CommonDBTM; use Computer as GlpiComputer; +use ComputerModel as GlpiComputerModel; use ComputerType as GlpiComputerType; +use GlpiPlugin\Carbon\CloudInventoryConnector; use GlpiPlugin\Carbon\ComputerType; use GlpiPlugin\Carbon\DataSource\Lca\Boaviztapi\ComputerModelizationAdapterTrait; +use GlpiPlugin\Cloudinventory\Amazon; +use GlpiPlugin\Cloudinventory\Azure; +use GlpiPlugin\Cloudinventory\CloudInstance; +use GlpiPlugin\Cloudinventory\Google; +use GlpiPlugin\Cloudinventory\Ovh; +use GlpiPlugin\Cloudinventory\Scaleway; use Override; +use UnhandledMatchError; class Computer extends AbstractAsset { @@ -48,34 +57,109 @@ class Computer extends AbstractAsset protected string $endpoint = 'server'; + /** + * If the plugin CloudInventory is available, this is an oblect from that + * plugin representing the cloud related data of the computer + */ + protected ?CloudInstance $cloud_instance = null; + + /** + * @var array Description of the asset for querying Boaviztapi + */ + protected array $description = []; + #[Override] protected function doEvaluation(): ?array { - // adapt $this->endpoint depending on the type of computer (server, laptop, ...) $type = $this->getType($this->item); - $this->endpoint = $this->getEndpoint($type); + + $response = null; + $this->chooseEvaluationMode($type); + + // select all impact types $this->endpoint .= '?' . $this->getCriteriasQueryString(); - // Ask for embodied impact only - $handle_hardware = in_array($type, [ - ComputerType::CATEGORY_SERVER, - ComputerType::CATEGORY_DESKTOP, - ComputerType::CATEGORY_UNDEFINED, - ]); - $configuration = $this->analyzeHardware(); - if ($handle_hardware && count($configuration) === 0) { - return null; + // Query Boaviztapi + $response = $this->query($this->description); + + $impacts = $this->client->parseResponse($response, 'embedded'); + return $impacts; + } + + private function chooseEvaluationMode(int $type): string + { + if ($type === ComputerType::CATEGORY_CLOUD) { + $cloud_provider = ''; + switch ($this->cloud_instance->fields['itemtype']) { + case Amazon::class: + $cloud_provider = 'aws'; + break; + case Azure::class: + $cloud_provider = 'azure'; + break; + case Google::class: + $cloud_provider = 'gcp'; + break; + case Ovh::class: + $cloud_provider = 'ovhcloud'; + break; + case Scaleway::class: + $cloud_provider = 'scaleway'; + break; + } + $glpi_computer_model = GlpiComputerModel::getById($this->cloud_instance->fields['computermodels_id']); + if ($glpi_computer_model !== false) { + $instance_types = $this->client->getCloudInstances($cloud_provider); + $model = $this->normalizeModel($cloud_provider, $glpi_computer_model->fields['name']); + if (in_array($model, $instance_types)) { + $this->prepareCloudDescription($cloud_provider, $model); + return 'cloud'; + } + } } - $description = [ - 'configuration' => $configuration, - 'usage' => [ - 'avg_power' => 0, - ], + + $this->prepareHardwareDescription($type); + return 'hardware'; + } + + /** + * Prepare description of the asset for the Boaviztapi query + */ + private function prepareHardwareDescription(int $type): void + { + try { + $this->endpoint = match ($type) { + ComputerType::CATEGORY_SERVER => 'server', + ComputerType::CATEGORY_LAPTOP => 'terminal/laptop', + ComputerType::CATEGORY_TABLET => 'terminal/tablet', + ComputerType::CATEGORY_SMARTPHONE => 'terminal/smartphone', + }; + } catch (UnhandledMatchError $e) { + $this->endpoint = 'terminal/desktop'; + } + + $this->description = [ + 'configuration' => $this->analyzeHardware(), + 'usage' => self::USAGE_NULL, ]; - $response = $this->query($description); - $impacts = $this->client->parseResponse($response, 'embedded'); + } - return $impacts; + /** + * Prepare description of the asset for the Boaviztapi query + * + * @param string $provider + * @param string $model + * @return void + */ + private function prepareCloudDescription(string $provider, string $model) + { + $this->endpoint = 'cloud/instance'; + + $this->description = [ + 'usage' => self::USAGE_NULL, + ]; + $this->description['provider'] = $provider; + $this->description['instance_type'] = $model; } /** @@ -85,6 +169,18 @@ protected function doEvaluation(): ?array */ protected function getType(CommonDBTM $item): int { + $cloudInventory_connector = new CloudInventoryConnector(); + if ($cloudInventory_connector->pluginAvailable()) { + $cloud_instance = new CloudInstance(); + $cloud_instance->getFromDBByCrit([ + 'computers_id' => $item->getID(), + ]); + if (!$cloud_instance->isNewItem()) { + $this->cloud_instance = $cloud_instance; + return ComputerType::CATEGORY_CLOUD; + } + } + $computer_table = GlpiComputer::getTable(); $computer_type_table = ComputerType::getTable(); $glpi_computer_type_table = GlpiComputerType::getTable(); @@ -115,24 +211,14 @@ protected function getType(CommonDBTM $item): int return $computer_type->fields['category']; } - /** - * Get the endpoint to use for the given type - */ - protected function getEndpoint(int $type) + protected function normalizeModel(string $provider, string $model): string { - switch ($type) { - case ComputerType::CATEGORY_SERVER: - return 'server'; - case ComputerType::CATEGORY_LAPTOP: - return 'terminal/laptop'; - case ComputerType::CATEGORY_TABLET: - return 'terminal/tablet'; - case ComputerType::CATEGORY_SMARTPHONE: - return 'terminal/smartphone'; + switch ($provider) { + case 'scaleway': + // CloudInventory sets scaleway models with the prefix "SCW-" + return strtolower(substr($model, 4)); } - // ComputerType::CATEGORY_UNDEFINED - // ComputerType::CATEGORY_DESKTOP - return 'terminal/desktop'; + return $provider; } } diff --git a/src/Impact/Embodied/Boavizta/Monitor.php b/src/Impact/Embodied/Boavizta/Monitor.php index 1e8acdb0..91298400 100644 --- a/src/Impact/Embodied/Boavizta/Monitor.php +++ b/src/Impact/Embodied/Boavizta/Monitor.php @@ -52,9 +52,7 @@ protected function doEvaluation(): ?array $description = [ 'configuration' => $configuration, - 'usage' => [ - 'avg_power' => 0, - ], + 'usage' => self::USAGE_NULL, ]; $response = $this->query($description); $impacts = $this->client->parseResponse($response, 'embedded'); diff --git a/src/Impact/History/AbstractAsset.php b/src/Impact/History/AbstractAsset.php index 4eab5059..3aec2e15 100644 --- a/src/Impact/History/AbstractAsset.php +++ b/src/Impact/History/AbstractAsset.php @@ -35,7 +35,6 @@ use CommonDBTM; use DateInterval; -use DateTime; use DateTimeImmutable; use DateTimeInterface; use DBmysql; @@ -50,6 +49,7 @@ use GlpiPlugin\Carbon\UsageImpact; use LogicException; use Override; +use Safe\DateTime; use Session; abstract class AbstractAsset extends CommonDBTM implements AssetInterface diff --git a/src/Impact/History/Computer.php b/src/Impact/History/Computer.php index bb89449b..65e1e27a 100644 --- a/src/Impact/History/Computer.php +++ b/src/Impact/History/Computer.php @@ -165,6 +165,7 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true 'AND' => [ self::$itemtype::getTableField('is_deleted') => 0, self::$itemtype::getTableField('is_template') => 0, + ['NOT' => [ComputerType::getTableField('category') => ComputerType::CATEGORY_CLOUD]], Location::getTableField('plugin_carbon_sources_zones_id') => ['>', 0], 'OR' => [ [ComputerType::getTableField('is_ignore') => 0], @@ -259,6 +260,7 @@ public static function getHistorizableDiagnosis(CommonDBTM $item): ?array $status['has_type_power_consumption'] = (($data['type_power_consumption'] ?? 0) !== 0); $status['has_usage_profile'] = !ComputerUsageProfile::isNewID($data['plugin_carbon_computerusageprofiles_id']); $status['has_category'] = (($data['category'] ?? 0) !== ComputerType::CATEGORY_UNDEFINED); + $status['is__category_not_cloud'] = (($data['category'] ?? 0) !== ComputerType::CATEGORY_CLOUD); $status['ci_download_enabled'] = $is_carbon_intensity_download_enabled; $status['ci_fallback_available'] = $is_carbon_intensity_fallback_available; $status['not_is_ignore'] = (($data['is_ignore'] ?? 0) === 0); diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index c5b8c6b3..9fd1f851 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -45,6 +45,8 @@ use Override; use RuntimeException; +use function Safe\json_encode; + abstract class AbstractAsset extends AbstractUsageImpact implements AssetInterface { protected static string $itemtype = ''; diff --git a/src/Location.php b/src/Location.php index 8d3af569..bf3e86aa 100644 --- a/src/Location.php +++ b/src/Location.php @@ -60,8 +60,8 @@ class Location extends CommonDBChild { // From CommonDBRelation - public static $itemtype = GlpiLocation::class; - public static $items_id = 'locations_id'; + public static string $itemtype = GlpiLocation::class; + public static string $items_id = 'locations_id'; #[Override] public static function getIcon() diff --git a/src/MonitorModel.php b/src/MonitorModel.php index d929e3d2..f144bb5f 100644 --- a/src/MonitorModel.php +++ b/src/MonitorModel.php @@ -36,6 +36,6 @@ class MonitorModel extends AbstractModel { - public static $itemtype = GlpiMonitorModel::class; - public static $items_id = 'monitormodels_id'; + public static string $itemtype = GlpiMonitorModel::class; + public static string $items_id = 'monitormodels_id'; } diff --git a/src/MonitorType.php b/src/MonitorType.php index 9d104059..e4e994bb 100644 --- a/src/MonitorType.php +++ b/src/MonitorType.php @@ -40,8 +40,8 @@ class MonitorType extends AbstractChildDropdown { - public static $itemtype = GlpiMonitorType::class; - public static $items_id = 'monitortypes_id'; + public static string $itemtype = GlpiMonitorType::class; + public static string $items_id = 'monitortypes_id'; #[Override] public static function showMassiveActionsSubForm(MassiveAction $ma) diff --git a/src/NetworkEquipmentModel.php b/src/NetworkEquipmentModel.php index 70bde975..1ed55dfe 100644 --- a/src/NetworkEquipmentModel.php +++ b/src/NetworkEquipmentModel.php @@ -36,6 +36,6 @@ class NetworkEquipmentModel extends AbstractModel { - public static $itemtype = GlpiNetworkEquipmentModel::class; - public static $items_id = 'networkequipmentmodels_id'; + public static string $itemtype = GlpiNetworkEquipmentModel::class; + public static string $items_id = 'networkequipmentmodels_id'; } diff --git a/src/NetworkEquipmentType.php b/src/NetworkEquipmentType.php index 64e0cb3e..c49e9d33 100644 --- a/src/NetworkEquipmentType.php +++ b/src/NetworkEquipmentType.php @@ -40,8 +40,8 @@ class NetworkEquipmentType extends AbstractChildDropdown { - public static $itemtype = GlpiNetworkEquipmentType::class; - public static $items_id = 'networkequipmenttypes_id'; + public static string $itemtype = GlpiNetworkEquipmentType::class; + public static string $items_id = 'networkequipmenttypes_id'; #[Override] public static function showMassiveActionsSubForm(MassiveAction $ma) diff --git a/src/Report.php b/src/Report.php index c0511dfa..572f0f29 100644 --- a/src/Report.php +++ b/src/Report.php @@ -33,17 +33,20 @@ namespace GlpiPlugin\Carbon; use CommonDBTM; -use DateTime; -use DateTimeImmutable; use Glpi\Application\View\TemplateRenderer; use Glpi\Dashboard\Grid as DashboardGrid; use GlpiPlugin\Carbon\Dashboard\Provider; use Override; +use Safe\DateTime; +use Safe\DateTimeImmutable; + +use function Safe\ob_get_clean; +use function Safe\ob_start; class Report extends CommonDBTM { - public static $rightname = 'carbon:report'; - protected static $notable = true; + public static string $rightname = 'carbon:report'; + protected static bool $notable = true; #[Override] public static function getTypeName($nb = 0) diff --git a/src/SearchOptions.php b/src/SearchOptions.php index 738952e7..774204f4 100644 --- a/src/SearchOptions.php +++ b/src/SearchOptions.php @@ -220,18 +220,22 @@ public static function getCoreSearchOptions(string $itemtype): array $fallback_carbon_intensity_subquery = Location::getCarbonIntensityDataSourceRequest([ Location::getTableField('id') => new QueryExpression('glpi_plugin_carbon_locations_3d6da7fccf9233a3f1a4e41183391a41.id'), ]); - $fallback_carbon_intensity_subquery = (new QuerySubQuery($fallback_carbon_intensity_subquery))->getQuery(); - $computation = "IF(`glpi_computers_id_963cd5e903dddc7ab00a3b70933369df`.`is_deleted` = 0 - AND `glpi_computers_id_963cd5e903dddc7ab00a3b70933369df`.`is_template` = 0 - AND `glpi_plugin_carbon_locations_3d6da7fccf9233a3f1a4e41183391a41`.`plugin_carbon_sources_zones_id` > 0 - AND `glpi_plugin_carbon_computerusageprofiles_09f8403aa14af64cd70f350288a0331b`.`id` > 0" - // Do not check if an asset is ignored - // . "AND COALESCE(`glpi_plugin_carbon_computertypes_a643ab3ffd70abf99533ed214da87d60`.`is_ignore`, 0) = 0" - . " AND ( - `glpi_plugin_carbon_computertypes_a643ab3ffd70abf99533ed214da87d60`.`power_consumption` > 0 - OR `glpi_computermodels`.`power_consumption` > 0 - ) - AND ($fallback_carbon_intensity_subquery) > 0, 1, 0)"; + $fallback_carbon_intensity_subquery = (new QuerySubQuery($fallback_carbon_intensity_subquery)); + $computation = new QueryExpression( + "IF(`glpi_computers_id_963cd5e903dddc7ab00a3b70933369df`.`is_deleted` = 0 + AND `glpi_computers_id_963cd5e903dddc7ab00a3b70933369df`.`is_template` = 0 + AND `glpi_plugin_carbon_locations_3d6da7fccf9233a3f1a4e41183391a41`.`plugin_carbon_sources_zones_id` > 0 + AND `glpi_plugin_carbon_computerusageprofiles_09f8403aa14af64cd70f350288a0331b`.`id` > 0" + // Do not check if an asset is ignored + // . "AND COALESCE(`glpi_plugin_carbon_computertypes_a643ab3ffd70abf99533ed214da87d60`.`is_ignore`, 0) = 0" + . " AND ( + `glpi_plugin_carbon_computertypes_a643ab3ffd70abf99533ed214da87d60`.`power_consumption` > 0 + OR `glpi_computermodels`.`power_consumption` > 0 + ) + AND ($fallback_carbon_intensity_subquery) > 0, 1, 0)", + null, + $fallback_carbon_intensity_subquery->getParams() + ); $sopt[] = [ 'id' => SearchOptions::IS_HISTORIZABLE, 'table' => getTableForItemType($itemtype), diff --git a/src/Source_Zone.php b/src/Source_Zone.php index c485486f..c1c89366 100644 --- a/src/Source_Zone.php +++ b/src/Source_Zone.php @@ -46,13 +46,13 @@ class Source_Zone extends CommonDBRelation { - public static $itemtype_1 = Source::class; // Type ref or field name (must start with itemtype) - public static $items_id_1 = 'plugin_carbon_sources_id'; // Field name - public static $checkItem_1_Rights = self::HAVE_SAME_RIGHT_ON_ITEM; + public static ?string $itemtype_1 = Source::class; // Type ref or field name (must start with itemtype) + public static ?string $items_id_1 = 'plugin_carbon_sources_id'; // Field name + public static int $checkItem_1_Rights = self::HAVE_SAME_RIGHT_ON_ITEM; - public static $itemtype_2 = Zone::class; // Type ref or field name (must start with itemtype) - public static $items_id_2 = 'plugin_carbon_zones_id'; // Field name - public static $checkItem_2_Rights = self::HAVE_SAME_RIGHT_ON_ITEM; + public static ?string $itemtype_2 = Zone::class; // Type ref or field name (must start with itemtype) + public static ?string $items_id_2 = 'plugin_carbon_zones_id'; // Field name + public static int $checkItem_2_Rights = self::HAVE_SAME_RIGHT_ON_ITEM; #[Override] public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) @@ -63,6 +63,15 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) return self::createTabEntry(Source::getTypeName(), 0); } + public function prepareInputForUpdate($input) + { + if (isset($input['_toggle_is_download_enabled'])) { + $input['is_download_enabled'] = ($this->fields['is_download_enabled'] == 1) ? 0 : 1; + unset($input['_toggle_is_download_enabled']); + } + return parent::prepareInputForUpdate($input); + } + #[Override] public function rawSearchOptions() { @@ -196,16 +205,14 @@ public static function showForSource(CommonDBTM $item) // At least 1 entry then add JS to toggle the state of zones echo Html::scriptBlock(' var plugin_carbon_toggleZone = function (id) { - fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php?id=" + id).then(response => { - if (response.status === 200) { - reloadTab(); - } else { - response.text().then(function (text) { - glpi_toast_error(text) - }); - } + debugger; + var url = CFG_GLPI["root_doc"] + "/plugins/carbon/front/source_zone.form.php?id=" + id; + submitGetLink(url, { + "update": "", + "id": id, + "_toggle_is_download_enabled": 1, }); - }; + } '); } } @@ -295,16 +302,14 @@ public static function showForZone(CommonDBTM $item) // At least 1 entry then add JS to toggle the state of zones echo Html::scriptBlock(' var plugin_carbon_toggleZone = function (id) { - fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php?id=" + id).then(response => { - if (response.status === 200) { - reloadTab(); - } else { - response.text().then(function (text) { - glpi_toast_error(text) - }); - } + debugger; + var url = CFG_GLPI["root_doc"] + "/plugins/carbon/front/source_zone.form.php?id=" + id; + submitGetLink(url, { + "update": "", + "id": id, + "_toggle_is_download_enabled": 1, }); - }; + } '); } } diff --git a/src/Toolbox.php b/src/Toolbox.php index f8c85078..1ab35ca0 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -33,8 +33,6 @@ namespace GlpiPlugin\Carbon; use DateInterval; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DBmysql; use Glpi\Dashboard\Dashboard as GlpiDashboard; @@ -45,6 +43,9 @@ use InvalidArgumentException; use Location; use Mexitek\PHPColors\Color; +use Safe\DateTime; +use Safe\DateTimeImmutable; +use Safe\Exceptions\DatetimeException; use Toolbox as GlpiToolbox; class Toolbox @@ -107,10 +108,15 @@ public function getOldestAssetDate(array $crit = []): ?DateTimeImmutable if ($oldest_date === null) { return null; } - if (($output = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $oldest_date)) === false) { - // Infocom dates are date (without time) - $output = DateTimeImmutable::createFromFormat('Y-m-d', $oldest_date); - $output = $output->setTime(0, 0, 0, 0); + try { + $output = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $oldest_date); + } catch (DatetimeException $e) { + try { + $output = DateTimeImmutable::createFromFormat('Y-m-d', $oldest_date); + $output = $output->setTime(0, 0, 0, 0); + } catch (DatetimeException $e) { + throw $e; + } } return $output; @@ -165,10 +171,15 @@ public function getLatestAssetDate(array $crit = []): ?DateTimeImmutable if ($latest_date === null) { return null; } - if (($output = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $latest_date)) === false) { - // Infocom dates are date (without time) - $output = DateTimeImmutable::createFromFormat('Y-m-d', $latest_date); - $output = $output->setTime(23, 59, 59, 0); + try { + $output = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $latest_date); + } catch (DatetimeException $e) { + try { + $output = DateTimeImmutable::createFromFormat('Y-m-d', $latest_date); + $output = $output->setTime(23, 59, 59, 0); + } catch (DatetimeException $e) { + throw $e; + } } return $output; @@ -563,6 +574,10 @@ public static function findTemporalGapsInTable(string $table, DateTimeInterface // Assume stop date is yesterday at midnight $stop = new DateTime('yesterday midnight'); } + if ($start > $stop) { + // Fix start so that it does not exeeds stop + $start = clone $stop; + } $sql_interval = self::dateIntervalToMySQLInterval($interval); $start_string = $start->format('Y-m-d H:i:s'); diff --git a/src/UsageInfo.php b/src/UsageInfo.php index 06274f96..4fb4f955 100644 --- a/src/UsageInfo.php +++ b/src/UsageInfo.php @@ -36,7 +36,6 @@ use CommonDBTM; use CommonGLPI; use Computer as GlpiComputer; -use DateTime; use Glpi\Application\View\TemplateRenderer; use GlpiPlugin\Carbon\Dashboard\Provider; use GlpiPlugin\Carbon\Dashboard\Widget; @@ -46,17 +45,17 @@ use Monitor as GlpiMonitor; use NetworkEquipment as GlpiNetworkEquipment; use Override; -use Toolbox as GlpiToolbox; +use Safe\DateTime; /** * Relation between a computer and a usage profile */ class UsageInfo extends CommonDBChild { - public static $itemtype = 'itemtype'; - public static $items_id = 'items_id'; + public static string $itemtype = 'itemtype'; + public static string $items_id = 'items_id'; - public static $rightname = 'carbon:report'; + public static string $rightname = 'carbon:report'; #[Override] public static function getTypeName($nb = 0) @@ -161,7 +160,7 @@ public function showForItem($ID, $withtemplate = '') ]; $this->initForm($this->getID(), $options); $asset_itemtype = $this->fields['itemtype']; - if (!GlpiToolbox::isCommonDBTM($asset_itemtype)) { + if (!is_a($asset_itemtype, CommonDBTM::class, true)) { return; } $asset = new $asset_itemtype(); diff --git a/stubs/OptionalClass.php.stub b/stubs/OptionalClass.php.stub new file mode 100644 index 00000000..6a363cf0 --- /dev/null +++ b/stubs/OptionalClass.php.stub @@ -0,0 +1,25 @@ + */ + public $fields = []; + + /** + * @param array $criteria + * @return bool + * + * @phpstan-impure + */ + public function getFromDBByCrit(array $criteria): bool {} + + public function isNewItem(): bool {} +} + +class Amazon {} +class Azure {} +class Google {} +class Ovh {} +class Scaleway {} diff --git a/templates/config.html.twig b/templates/config.html.twig index 3f688a71..182e0aa7 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -34,8 +34,7 @@ {% if can_edit %}
- - + {{ fields.largeTitle( __('Impact engine', 'carbon'), @@ -79,7 +78,7 @@ {{ __('Reset usage impacts. This will delete all calculated usage impacts for all assets.', 'carbon') }} - {% set reset_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', reset_all: ''}" %} + {% set reset_args = "{reset_all: ''}" %} {{ _x('button', 'Reset usage impacts') }} @@ -89,7 +88,7 @@ {{ __('Reset embodied impacts. This will delete all calculated embodied impacts for all assets.', 'carbon') }} - {% set reset_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', reset_all: ''}" %} + {% set reset_args = "{reset_all: ''}" %} {{ _x('button', 'Reset embodied impacts') }} diff --git a/templates/environmentalimpact-item.html.twig b/templates/environmentalimpact-item.html.twig index a499c1ec..f336a014 100644 --- a/templates/environmentalimpact-item.html.twig +++ b/templates/environmentalimpact-item.html.twig @@ -61,10 +61,10 @@
{% if not usage_impact.isNewItem() or usage_carbon_emission_count > 0 %} - {% set reset_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', reset: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} + {% set reset_args = "{reset: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} {{ __('Reset data', 'carbon') }} {% endif %} - {% set calculate_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', calculate: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} + {% set calculate_args = "{calculate: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} {{ __('Calculate data', 'carbon') }}
@@ -73,6 +73,12 @@ 'fas fa-chart-pie' ) }} +{% if ((embodied_impact.fields['engine'] is defined) and (embodied_impact.fields['engine_version'] is defined)) %} + {{ fields.smallTitle( + embodied_impact.fields['engine'] ~ ' ' ~ embodied_impact.fields['engine_version'], + ) }} +{% endif %} +
@@ -96,10 +102,10 @@
{% if not embodied_impact.isNewItem() %} - {% set reset_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', reset: '', id: '" ~ embodied_impact.getID() ~ "'}" %} + {% set reset_args = "{reset: '', id: '" ~ embodied_impact.getID() ~ "'}" %} {{ __('Reset data', 'carbon') }} {% endif %} - {% set calculate_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', calculate: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} + {% set calculate_args = "{calculate: '', itemtype: '" ~ asset.getType() ~ "', items_id: '" ~ asset.getID() ~ "'}" %} {{ __('Calculate data', 'carbon') }}
diff --git a/templates/history/status-item.html.twig b/templates/history/status-item.html.twig index 8031a6a2..aef066f1 100644 --- a/templates/history/status-item.html.twig +++ b/templates/history/status-item.html.twig @@ -134,6 +134,8 @@ {% set field = nok_field %} {% if status.has_model %} {% set field = ok_field %} + {% elseif status.has_type is defined and status.has_type %} + {% set field = warning_field %} {% endif %} {{ fields.field('', field, __('The asset has a model', 'carbon')) }} {% endif %} @@ -153,10 +155,8 @@ {% if status.has_type is defined %} {% set field = nok_field %} - {% if status.has_model is defined %} - {% if status.has_model %} - {% set field = warning_field %} - {% endif %} + {% if status.has_model is defined and status.has_model%} + {% set field = warning_field %} {% endif %} {% if status.has_type %} {% set field = ok_field %} @@ -193,6 +193,14 @@ {{ fields.field('', field, __('The asset has a category', 'carbon')) }} {% endif %} + {% if status.is_category_not_cloud is defined %} + {% set field = nok_field %} + {% if status.is_category_not_cloud %} + {% set field = ok_field %} + {% endif %} + {{ fields.field('', field, __('The asset is not in a cloud', 'carbon')) }} + {% endif %} + {% if status.has_usage_profile is defined %} {% set field = nok_field %} {% if status.has_usage_profile %} diff --git a/tests/fixtures/FakeDataSources.php b/tests/fixtures/FakeDataSources.php index d931c1f1..35274023 100644 --- a/tests/fixtures/FakeDataSources.php +++ b/tests/fixtures/FakeDataSources.php @@ -32,6 +32,8 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity\Foo; +use CommonDBTM; +use CommonGLPI; use GlpiPlugin\Carbon\DataSource\CronTaskInterface; class CronTask implements CronTaskInterface @@ -40,10 +42,22 @@ public static function enumerateTasks(): array { return []; } + + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + return 'Foo'; + } + + public function showForCronTask(CommonDBTM $item) + { + return true; + } } namespace GlpiPlugin\Carbon\DataSource\Lca\Bar; +use CommonDBTM; +use CommonGLPI; use GlpiPlugin\Carbon\DataSource\CronTaskInterface; class CronTask implements CronTaskInterface @@ -52,10 +66,22 @@ public static function enumerateTasks(): array { return []; } + + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + return 'Bar'; + } + + public function showForCronTask(CommonDBTM $item) + { + return true; + } } namespace GlpiPlugin\Carbon\DataSource\Lca\Baz; +use CommonDBTM; +use CommonGLPI; use GlpiPlugin\Carbon\DataSource\CronTaskInterface; class CronTask implements CronTaskInterface @@ -64,4 +90,14 @@ public static function enumerateTasks(): array { return []; } + + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + return 'Baz'; + } + + public function showForCronTask(CommonDBTM $item) + { + return true; + } } diff --git a/tests/install/PluginInstallTest.php b/tests/install/PluginInstallTest.php index c6a40cea..3d4a0401 100644 --- a/tests/install/PluginInstallTest.php +++ b/tests/install/PluginInstallTest.php @@ -67,7 +67,6 @@ use NetworkEquipmentModel; use NetworkEquipmentType; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\Depends; use Plugin; use Profile; @@ -170,7 +169,6 @@ public function testInstallPlugin() $this->checkRegisteredClasses(); } - #[CoversNothing()] public function testConfigurationExists() { $config = Config::getConfigurationValues(TEST_PLUGIN_NAME); @@ -181,7 +179,6 @@ public function testConfigurationExists() return $config; } - #[CoversNothing()] private function checkSchema( string $version, bool $strict = true, @@ -610,6 +607,10 @@ public function test_dashboard_is_configured() } } + /** + * Summary of zones + * @var array + */ private $zones = [ 'Afghanistan', 'Albania', @@ -939,7 +940,6 @@ public function checkRegisteredClasses() $this->assertEquals($expected, $result); } - #[CoversNothing()] #[Depends('testInstallPlugin')] public function test_version_is_consistent_across_files() { @@ -982,7 +982,6 @@ public function test_version_is_consistent_across_files() $this->assertStringContainsString($setup_version, $supported_versions_table, "Current version '$setup_version' not found in Supported Versions table in SECURITY.md"); } - #[CoversNothing()] #[Depends('testInstallPlugin')] public function test_tagged_version_is_declared_in_plugin_xml() { diff --git a/tests/migration/migration_to_1_2_0_Test.php b/tests/migration/migration_to_1_2_0_Test.php index b2af6b9f..ab29691d 100644 --- a/tests/migration/migration_to_1_2_0_Test.php +++ b/tests/migration/migration_to_1_2_0_Test.php @@ -38,7 +38,6 @@ use GlpiPlugin\Carbon\Uninstall; use Location as GlpiLocation; use Migration; -use PHPUnit\Framework\Attributes\CoversNothing; use Plugin; class migration_to_1_2_0_Test extends CommonTestCase @@ -60,7 +59,6 @@ public function setUp(): void $success = $DB->runFile(realpath($sql_file)); } - #[CoversNothing] public function testUpdateCountryLocationZoneRelation() { /** @var DBmysql $DB */ @@ -110,7 +108,6 @@ public function testUpdateCountryLocationZoneRelation() $this->assertEquals($expected, $result->current()); } - #[CoversNothing] public function testUpdateStateLocationZoneRelation() { /** @var DBmysql $DB */ @@ -159,7 +156,6 @@ public function testUpdateStateLocationZoneRelation() $this->assertEquals($expected, $result->current()); } - #[CoversNothing] public function testTimeformatInUsageProfile() { global $DB; diff --git a/tests/src/CommonTestCase.php b/tests/src/CommonTestCase.php index 35abfb7a..97d2e182 100644 --- a/tests/src/CommonTestCase.php +++ b/tests/src/CommonTestCase.php @@ -64,7 +64,7 @@ class CommonTestCase extends TestCase /** @var int $debugMode save state of GLPI debug mode */ private $debugMode = null; - protected $str = null; + protected ?string $str = null; protected function disableDebug() { @@ -115,7 +115,7 @@ protected function setupGLPIFramework(): void return; } - protected function login($name, $password, $noauto = false) + protected function login(string $name, string $password, $noauto = false) { Session::start(); $auth = new Auth(); @@ -539,7 +539,7 @@ protected function isolateInEntity(): int * * @return mixed */ - protected function callPrivateMethod($instance, string $methodName, ...$args) + protected function callPrivateMethod($instance, string $methodName, mixed ...$args) { $method = new ReflectionMethod($instance, $methodName); if (version_compare(PHP_VERSION, '8.1.0') < 0) { diff --git a/tests/units/CarbonEmissionTest.php b/tests/units/CarbonEmissionTest.php index 84ad9d3c..50369df5 100644 --- a/tests/units/CarbonEmissionTest.php +++ b/tests/units/CarbonEmissionTest.php @@ -209,7 +209,7 @@ public function test_truncate_fails_when_not_logged_in() $this->assertEquals(1, countElementsInTable($table)); } - public function test_truncate_fails_when_user_has_all_required_rights() + public function test_truncate_succeeds_when_user_has_all_required_rights() { /** @var DBmysql $DB */ global $DB; diff --git a/tests/units/CarbonIntensityTest.php b/tests/units/CarbonIntensityTest.php index 932014d5..16081814 100644 --- a/tests/units/CarbonIntensityTest.php +++ b/tests/units/CarbonIntensityTest.php @@ -33,8 +33,6 @@ namespace GlpiPlugin\Carbon\Tests; use Computer; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use DBmysql; use GlpiPlugin\Carbon\CarbonIntensity; @@ -44,6 +42,8 @@ use GlpiPlugin\Carbon\Zone; use Infocom; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTime; +use Safe\DateTimeImmutable; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\Output; @@ -299,9 +299,7 @@ public function testGetDownloadStartDate() { $instance = new CarbonIntensity(); - $data_source = $this->getMockBuilder(AbstractClient::class) - ->getMock(); - $result = $instance->getDownloadStartDate('foo', $data_source); + $result = $instance->getDownloadStartDate(); $expected = (new DateTime('13 months ago'))->setTime(0, 0, 0); // CarbonIntensity::MIN_HISTORY_LENGTH $this->assertEquals($expected, $result); @@ -312,7 +310,7 @@ public function testGetDownloadStartDate() 'buy_date' => '2022-02-01', ]); - $result = $instance->getDownloadStartDate('foo', $data_source); + $result = $instance->getDownloadStartDate(); $expected = (new DateTime('2022-02-01'))->setTime(0, 0, 0); // CarbonIntensity::MIN_HISTORY_LENGTH $this->assertEquals($expected, $result); } @@ -366,8 +364,7 @@ public function testDownloadOneZone() $zone::getForeignKeyField() => $zone->getID(), ]); - $data_source = $this->getMockBuilder(AbstractClient::class) - ->getMock(); + $data_source = $this->createStub(AbstractClient::class); $hours = null; $data_source->method('fullDownload')->willReturnCallback( function ($zone_name, $gap_start, $gap_end, $carbon_intensity, $limit, $progress_bar) use (&$hours) { @@ -379,8 +376,7 @@ function ($zone_name, $gap_start, $gap_end, $carbon_intensity, $limit, $progress $data_source->method('getHardStartDate')->willReturn( DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, '2021-01-01T00:00:00+00:00'), ); - $output = $this->getMockBuilder(Output::class) - ->getMock(); + $output = $this->createStub(Output::class); $progress_bar = new ProgressBar($output); $instance = new CarbonIntensity(); diff --git a/tests/units/Command/ImportDashboardCommandTest.php b/tests/units/Command/ImportDashboardCommandTest.php index 76174746..104ce551 100644 --- a/tests/units/Command/ImportDashboardCommandTest.php +++ b/tests/units/Command/ImportDashboardCommandTest.php @@ -133,7 +133,7 @@ public function test_import_dashboard_command() $this->fail("Failed to get dashboard item with id {$row['id']}"); } $item_fields = array_filter($item->fields, function ($key) { - return !in_array($key, ['id', 'dashboards_dashboards_id']); + return !in_array($key, ['id', 'dashboards_dashboards_id', 'date_creation', 'date_mod']); }, ARRAY_FILTER_USE_KEY); $item_fields['card_options'] = json_decode($item_fields['card_options'], true); $found = false; diff --git a/tests/units/ComputerTypeTest.php b/tests/units/ComputerTypeTest.php index 89db56c3..fd619a90 100644 --- a/tests/units/ComputerTypeTest.php +++ b/tests/units/ComputerTypeTest.php @@ -44,16 +44,21 @@ class ComputerTypeTest extends AbstractTypeTest protected static string $glpi_type_itemtype = GlpiComputerType::class; protected static string $type_itemtype = ComputerType::class; - public function testGetTabNameForItem() + public function test_GetTabNameForItem_returns_empty_string_for_computer() + { + $glpi_computer_type = $this->createItem(GlpiComputerType::class); + $instance = new ComputerType(); + $result = $instance->getTabNameForItem($glpi_computer_type, 1); + $this->assertEquals('', $result); + } + + public function test_GetTabNameForItem_returns_tab_name_for_computer_type() { $glpi_computer_type = $this->createItem(GlpiComputerType::class); $instance = new ComputerType(); $result = $instance->getTabNameForItem($glpi_computer_type); $crawler = new Crawler($result); $this->assertEquals('Carbon', $crawler->text()); - - $result = $instance->getTabNameForItem($glpi_computer_type, 1); - $this->assertEquals('', $result); } public function testShowForItemType() @@ -124,9 +129,7 @@ public function testUpdateCategory() public function testShowMassiveActionsSubForm() { // Test power consumption update form - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $massive_action->method('getItems')->willReturn([ ComputerType::class => $this->createItem(GlpiComputerType::class), @@ -153,9 +156,7 @@ public function testShowMassiveActionsSubForm() $this->assertTrue($result); // Test category update form - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdateCategory'); $massive_action->method('getItems')->willReturn([ ComputerType::class => $this->createItem(GlpiComputerType::class), @@ -181,9 +182,7 @@ public function testShowMassiveActionsSubForm() $this->assertTrue($result); // Test invalid action - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn(''); $massive_action->method('getItems')->willReturn([ ComputerType::class => $this->createItem(GlpiComputerType::class), @@ -200,9 +199,7 @@ public function testShowMassiveActionsSubForm() public function testProcessMassiveActionForOneItemtype() { // Test create power consumption - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $glpi_computer_type = $this->createItem(GlpiComputerType::class); $massive_action->POST = [ @@ -222,9 +219,7 @@ public function testProcessMassiveActionForOneItemtype() $this->assertEquals(25, $computer_type->fields['power_consumption']); // Test update category - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdateCategory'); $glpi_computer_type = $this->createItem(GlpiComputerType::class); $massive_action->POST = [ diff --git a/tests/units/ComputerUsageProfileTest.php b/tests/units/ComputerUsageProfileTest.php index b63e38a4..cd4a7503 100644 --- a/tests/units/ComputerUsageProfileTest.php +++ b/tests/units/ComputerUsageProfileTest.php @@ -180,9 +180,7 @@ public function testAssignToItem() public function testShowMassiveActionsSubForm() { // Test power consumption update form - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassAssociateItems'); $massive_action->method('getItems')->willReturn([ GlpiComputer::class => $this->createItem(GlpiComputer::class), diff --git a/tests/units/ConfigTest.php b/tests/units/ConfigTest.php index 5f0c5d25..7917a72b 100644 --- a/tests/units/ConfigTest.php +++ b/tests/units/ConfigTest.php @@ -56,14 +56,20 @@ public function testGetTypeName() $this->assertEquals('Environmental Impact', $result); } - public function testGetTabNameForItem() + public function test_GetTabNameForItem_returns_empty_string_for_computer() { $instance = new Config(); $result = $instance->getTabNameForItem(new GlpiComputer()); $this->assertEquals('', $result); + } + + public function test_GetTabNameForItem_returns_tab_name_for_config() + { + $instance = new Config(); $result = $instance->getTabNameForItem(new GlpiConfig()); - $this->assertEquals('Environmental Impact', $result); + $crawler = new Crawler($result); + $this->assertEquals('Environmental Impact', $crawler->text()); } public function testDisplayTabContentForItem() @@ -99,10 +105,8 @@ public function testShowForm() $crawler = new Crawler($output); $config_class = $crawler->filter('input[type="hidden"][name="config_class"]'); $config_context = $crawler->filter('input[type="hidden"][name="config_context"]'); - $csrf = $crawler->filter('input[type="hidden"][name="_glpi_csrf_token"]'); $this->assertEquals(1, $config_class->count()); $this->assertEquals(1, $config_context->count()); - $this->assertEquals(1, $csrf->count()); $electricitymaps_api = $crawler->filter('input[name="electricitymap_api_key"]'); $impact_engine = $crawler->filter('select[name="impact_engine"]'); $this->assertEquals(1, $electricitymaps_api->count()); diff --git a/tests/units/DataSource/CarbonIntensity/AbstractClientTest.php b/tests/units/DataSource/CarbonIntensity/AbstractClientTest.php index 2ffe9ca7..d0598d9a 100644 --- a/tests/units/DataSource/CarbonIntensity/AbstractClientTest.php +++ b/tests/units/DataSource/CarbonIntensity/AbstractClientTest.php @@ -32,10 +32,10 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity\Tests; -use DateTimeImmutable; use GlpiPlugin\Carbon\DataSource\CarbonIntensity\AbstractClient; use GlpiPlugin\Carbon\Tests\DbTestCase; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTimeImmutable; #[CoversClass(AbstractClient::class)] class AbstractClientTest extends DbTestCase @@ -126,7 +126,7 @@ public function testSliceDateRangeByMonth() { foreach ($this->sliceDateRangeByMonthProvider() as $data) { [$start, $stop, $expected] = $data; - $stub = $this->getMockBuilder(AbstractClient::class)->getMock(); + $stub = $this->createStub(AbstractClient::class); $output = $this->callPrivateMethod($stub, 'sliceDateRangeByMonth', $start, $stop); if (count($expected) === 0) { @@ -175,7 +175,7 @@ public function testSliceDateRangeByDay() { foreach ($this->sliceDateRangeByDayProvider() as $data) { [$start, $stop, $expected] = $data; - $stub = $this->getMockBuilder(AbstractClient::class)->getMock(); + $stub = $this->createStub(AbstractClient::class); $output = $this->callPrivateMethod($stub, 'sliceDateRangeByDay', $start, $stop); if (count($expected) === 0) { diff --git a/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ClientTest.php b/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ClientTest.php index 2c79a293..59496c87 100644 --- a/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ClientTest.php +++ b/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ClientTest.php @@ -34,7 +34,6 @@ use DateInterval; use DateTime; -use DateTimeImmutable; use DateTimeInterface; use GlpiPlugin\Carbon\DataSource\RestApiClientInterface; use GlpiPlugin\Carbon\Source; @@ -42,6 +41,7 @@ use GlpiPlugin\Carbon\Tests\DbTestCase; use GlpiPlugin\Carbon\Zone; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTimeImmutable; #[CoversClass(Client::class)] class ClientTest extends DbTestCase diff --git a/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ConfigTest.php b/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ConfigTest.php index da22ef07..034a2347 100644 --- a/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ConfigTest.php +++ b/tests/units/DataSource/CarbonIntensity/ElectricityMaps/ConfigTest.php @@ -99,10 +99,6 @@ public function testConfigUpdate(array $input, array $expected) /** @var array $CFG_GLPI */ global $CFG_GLPI; - $CFG_GLPI['plugi:carbon']['lca_datasources'] = [ - Client::class, - ]; - $instance = new Config(); $result = $instance->configUpdate($input); $this->assertEquals($expected, $result); diff --git a/tests/units/DataSource/CarbonIntensity/Rte/ClientTest.php b/tests/units/DataSource/CarbonIntensity/Rte/ClientTest.php index 477e9c64..4283aebe 100644 --- a/tests/units/DataSource/CarbonIntensity/Rte/ClientTest.php +++ b/tests/units/DataSource/CarbonIntensity/Rte/ClientTest.php @@ -33,7 +33,6 @@ namespace GlpiPlugin\Carbon\DataSource\CarbonIntensity\Rte; use DateTime; -use DateTimeImmutable; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\DataSource\RestApiClientInterface; use GlpiPlugin\Carbon\Source; @@ -41,6 +40,7 @@ use GlpiPlugin\Carbon\Tests\DbTestCase; use GlpiPlugin\Carbon\Zone; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTimeImmutable; #[CoversClass(Client::class)] class ClientTest extends DbTestCase diff --git a/tests/units/DataSource/CronTaskProviderTest.php b/tests/units/DataSource/CronTaskProviderTest.php index 35ae0f38..0d186048 100644 --- a/tests/units/DataSource/CronTaskProviderTest.php +++ b/tests/units/DataSource/CronTaskProviderTest.php @@ -37,22 +37,55 @@ require_once dirname(__DIR__, 2) . '/fixtures/FakeDataSources.php'; -// Redefine glob() in the namespace context to mock -function glob($pattern, $flags = 0) -{ - return [ - '/var/www/glpi/plugins/carbon/src/DataSource/Carbonintensity/Foo', - '/var/www/glpi/plugins/carbon/src/DataSource/Lca/Bar', - '/var/www/glpi/plugins/carbon/src/DataSource/Lca/Baz', - ]; -} - #[CoversClass(CronTaskProvider::class)] class CronTaskProviderTest extends CommonTestCase { public function test_getCronTaskTypes_returns_() { - $result = CronTaskProvider::getCronTaskTypes(); + $stub_carbon_intensity_dir = $this->createStub(\DirectoryIterator::class); + $stub_carbon_intensity_dir->method('getBasename')->willReturn('CarbonIntensity'); + $foo_item = $this->createStub(\DirectoryIterator::class); + $foo_item->method('isDot')->willReturn(false); + $foo_item->method('isDir')->willReturn(true); + $foo_item->method('getBasename')->willReturn('Foo'); + $foo_item->method('getPathName')->willReturn('/path/to/CarbonIntensity/Foo'); + $internal_iterator = new \ArrayIterator([ + $foo_item, + ]); + $stub_carbon_intensity_dir->method('rewind')->willReturnCallback(fn() => $internal_iterator->rewind()); + $stub_carbon_intensity_dir->method('valid')->willReturnCallback(fn() => $internal_iterator->valid()); + $stub_carbon_intensity_dir->method('current')->willReturnCallback(fn() => $internal_iterator->current()); + $stub_carbon_intensity_dir->method('key')->willReturnCallback(fn() => $internal_iterator->key()); + $stub_carbon_intensity_dir->method('next')->willReturnCallback(fn() => $internal_iterator->next()); + + $stub_lca_dir = $this->createStub(\DirectoryIterator::class); + $stub_lca_dir->method('getBasename')->willReturn('Lca'); + $bar_item = $this->createStub(\DirectoryIterator::class); + $bar_item->method('isDot')->willReturn(false); + $bar_item->method('isDir')->willReturn(true); + $bar_item->method('getBasename')->willReturn('Bar'); + $bar_item->method('getPathName')->willReturn('/path/to/Lca/Bar'); + $baz_item = $this->createStub(\DirectoryIterator::class); + $baz_item->method('isDot')->willReturn(false); + $baz_item->method('isDir')->willReturn(true); + $baz_item->method('getBasename')->willReturn('Baz'); + $baz_item->method('getPathName')->willReturn('/path/to/Lca/Baz'); + $internal_iterator_lca = new \ArrayIterator([ + $bar_item, + $baz_item, + ]); + $stub_lca_dir->method('rewind')->willReturnCallback(fn() => $internal_iterator_lca->rewind()); + $stub_lca_dir->method('valid')->willReturnCallback(fn() => $internal_iterator_lca->valid()); + $stub_lca_dir->method('current')->willReturnCallback(fn() => $internal_iterator_lca->current()); + $stub_lca_dir->method('key')->willReturnCallback(fn() => $internal_iterator_lca->key()); + $stub_lca_dir->method('next')->willReturnCallback(fn() => $internal_iterator_lca->next()); + + $dirs = [ + $stub_carbon_intensity_dir, + $stub_lca_dir, + ]; + + $result = CronTaskProvider::getCronTaskTypes($dirs); $expected = [ 'Foo' => 'GlpiPlugin\\Carbon\\DataSource\\CarbonIntensity\\Foo\\CronTask', 'Bar' => 'GlpiPlugin\\Carbon\\DataSource\\Lca\\Bar\\CronTask', diff --git a/tests/units/DataSource/Lca/Boaviztapi/ConfigTest.php b/tests/units/DataSource/Lca/Boaviztapi/ConfigTest.php index 7f5249e1..ef3a40b7 100644 --- a/tests/units/DataSource/Lca/Boaviztapi/ConfigTest.php +++ b/tests/units/DataSource/Lca/Boaviztapi/ConfigTest.php @@ -124,10 +124,6 @@ public function testConfigUpdate(array $input, array $expected) /** @var array $CFG_GLPI */ global $CFG_GLPI; - $CFG_GLPI['plugi:carbon']['lca_datasources'] = [ - Client::class, - ]; - $result = (new Config())->configUpdate($input); $this->assertEquals($expected, $result); } diff --git a/tests/units/HookTest.php b/tests/units/HookTest.php index cb5b2ffd..ae6e5877 100644 --- a/tests/units/HookTest.php +++ b/tests/units/HookTest.php @@ -103,7 +103,7 @@ public function testCarbonAssetTypeIsPurgedOnAssetTypePurge() 'computertypes_id' => $computer_type->getID(), ]); - $computer_type->delete($computer_type->fields, 1); + $computer_type->delete($computer_type->fields, true); $count = (new DbUtils())->countElementsInTable($carbon_computer_type::getTable(), [ 'computertypes_id' => $computer_type->getID(), ]); diff --git a/tests/units/Impact/History/ComputerTest.php b/tests/units/Impact/History/ComputerTest.php index 260850e2..1a4cebe0 100644 --- a/tests/units/Impact/History/ComputerTest.php +++ b/tests/units/Impact/History/ComputerTest.php @@ -36,7 +36,7 @@ use Computer as GlpiComputer; use ComputerModel as GlpiComputerModel; use ComputerType as GlpiComputerType; -use DateTime; +use DBmysql; use GlpiPlugin\Carbon\CarbonEmission; use GlpiPlugin\Carbon\ComputerModel; use GlpiPlugin\Carbon\ComputerType; @@ -51,6 +51,7 @@ use Infocom; use Location as GlpiLocation; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTime; #[CoversClass(Computer::class)] class ComputerTest extends CommonAsset @@ -153,7 +154,7 @@ public function testEvaluateItem() } $this->login('glpi', 'glpi'); - $entities_id = $this->isolateInEntity('glpi', 'glpi'); + $entities_id = $this->isolateInEntity(); $model_power = 55; $glpi_location = $this->createItem(GlpiLocation::class, [ @@ -290,6 +291,7 @@ public function test_getHistorizableDiagnosis_when_computer_is_historizable() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -328,6 +330,7 @@ public function test_getHistorizableDiagnosis_when_computer_is_deleted() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -366,6 +369,7 @@ public function test_getHistorizableDiagnosis_when_computer_is_template() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -404,6 +408,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_usage_profile 'has_usage_profile' => false, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -442,6 +447,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_location() 'has_usage_profile' => true, 'has_location' => false, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => false, // No location cascades this requirement to be not met 'has_model' => true, 'has_model_power_consumption' => true, @@ -480,6 +486,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_category() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => false, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -518,6 +525,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_carbon_intens 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => false, 'has_model' => true, 'has_model_power_consumption' => true, @@ -556,6 +564,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_model() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => false, 'has_model_power_consumption' => false, @@ -594,6 +603,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_model_power_c 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => false, @@ -632,6 +642,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_type() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => false, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -670,6 +681,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_type_power_co 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -708,6 +720,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_inventory_ent 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -746,6 +759,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_carbon_intens 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -786,6 +800,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_carbon_intens 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -824,6 +839,7 @@ public function test_getHistorizableDiagnosis_when_computer_is_ignored() 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -863,6 +879,7 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_decommission_ 'has_usage_profile' => true, 'has_location' => true, 'has_category' => true, + 'is__category_not_cloud' => true, 'has_carbon_intensity_zone' => true, 'has_model' => true, 'has_model_power_consumption' => true, @@ -878,6 +895,46 @@ public function test_getHistorizableDiagnosis_when_computer_has_no_decommission_ $this->assertEquals($expected, $result); } + public function test_getHistorizableDiagnosis_when_computer_has_cloud_category() + { + $history = new Computer(); + + [ + $glpi_computer, + $glpi_location, + $location, + $source_zone, + $glpi_computer_model, + $glpi_computer_type, + $computer_type, + $infocom, + $usage_profile, + $zone, + ] = $this->getHistorizableComputer(); + $this->updateItem($computer_type, ['category' => ComputerType::CATEGORY_CLOUD]); + + $expected = [ + 'is_deleted' => true, + 'is_template' => true, + 'has_usage_profile' => true, + 'has_location' => true, + 'has_category' => true, + 'is__category_not_cloud' => false, + 'has_carbon_intensity_zone' => true, + 'has_model' => true, + 'has_model_power_consumption' => true, + 'has_type' => true, + 'has_type_power_consumption' => true, + 'has_inventory_entry_date' => true, + 'ci_download_enabled' => true, + 'ci_fallback_available' => true, + 'not_is_ignore' => true, + 'has_decommission_date' => true, + ]; + $result = $history->getHistorizableDiagnosis($glpi_computer); + $this->assertEquals($expected, $result); + } + public function testComputerWithEverythingIsHistorizable() { $history = new Computer(); @@ -932,6 +989,7 @@ public function testComputerWithEverythingIsHistorizable() 'has_type_power_consumption' => true, 'has_usage_profile' => true, 'has_category' => false, + 'is__category_not_cloud' => true, 'has_inventory_entry_date' => true, 'ci_download_enabled' => true, 'ci_fallback_available' => true, diff --git a/tests/units/Impact/History/MonitorTest.php b/tests/units/Impact/History/MonitorTest.php index 1fdeb0c5..bbefa3fd 100644 --- a/tests/units/Impact/History/MonitorTest.php +++ b/tests/units/Impact/History/MonitorTest.php @@ -34,10 +34,8 @@ use CommonDBTM; use Computer as GlpiComputer; -use Computer_Item; use ComputerModel; use ComputerType as GlpiComputerType; -use DateTime; use DBmysql; use Glpi\Asset\Asset_PeripheralAsset; use GlpiPlugin\Carbon\CarbonEmission; @@ -57,6 +55,7 @@ use MonitorModel as GlpiMonitorModel; use MonitorType as GlpiMonitorType; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTime; #[CoversClass(Monitor::class)] class MonitorTest extends CommonAsset diff --git a/tests/units/Impact/History/NetworkEquipmentTest.php b/tests/units/Impact/History/NetworkEquipmentTest.php index 4186f2ad..76a3192e 100644 --- a/tests/units/Impact/History/NetworkEquipmentTest.php +++ b/tests/units/Impact/History/NetworkEquipmentTest.php @@ -33,7 +33,6 @@ namespace GlpiPlugin\Carbon\Impact\History\Tests; use CommonDBTM; -use DateTime; use GlpiPlugin\Carbon\CarbonEmission; use GlpiPlugin\Carbon\Impact\History\NetworkEquipment; use GlpiPlugin\Carbon\Location; @@ -50,6 +49,7 @@ use NetworkEquipmentModel as GlpiNetworkEquipmentModel; use NetworkEquipmentType as GlpiNetworkEquipmentType; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTime; #[CoversClass(NetworkEquipment::class)] class NetworkEquipmentTest extends CommonAsset diff --git a/tests/units/Impact/Usage/EngineTest.php b/tests/units/Impact/Usage/EngineTest.php index 69ba8e39..adbc725b 100644 --- a/tests/units/Impact/Usage/EngineTest.php +++ b/tests/units/Impact/Usage/EngineTest.php @@ -54,8 +54,7 @@ public function testGetEngineFromItemtypeForBoavizta() $version_response = [ '1.3.11', ]; - $client_stub = $this->getMockBuilder(RestApiClient::class) - ->getMock(); + $client_stub = $this->createStub(RestApiClient::class); $client_stub->method('request')->willReturn($version_response); $item = $this->createItem(GlpiComputer::class); diff --git a/tests/units/LocationTest.php b/tests/units/LocationTest.php index 45a87514..99bc93b4 100644 --- a/tests/units/LocationTest.php +++ b/tests/units/LocationTest.php @@ -116,8 +116,7 @@ public function testOnGlpiLocationAdd() $this->assertEquals('FRA', $location->fields['boavizta_zone']); // Test the geocoding feature - $geocoder = $this->getMockBuilder(Geocoder::class) - ->getMock(); + $geocoder = $this->createStub(Geocoder::class); $geocoder->method('geocodeQuery')->willReturn( new AddressCollection([ new NominatimAddress( @@ -169,8 +168,7 @@ public function testOnGlpiLocationPreUpdate() $this->assertEquals('FRA', $location->fields['boavizta_zone']); // Test the geocoding feature - $geocoder = $this->getMockBuilder(Geocoder::class) - ->getMock(); + $geocoder = $this->createStub(Geocoder::class); $geocoder->method('geocodeQuery')->willReturn( new AddressCollection([ new NominatimAddress( diff --git a/tests/units/MonitorTypeTest.php b/tests/units/MonitorTypeTest.php index 49010bfc..ccd4fa83 100644 --- a/tests/units/MonitorTypeTest.php +++ b/tests/units/MonitorTypeTest.php @@ -78,9 +78,7 @@ public function testUpdatePowerConsumption() public function testShowMassiveActionsSubForm() { - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $massive_action->method('getItems')->willReturn([ MonitorType::class => $this->createItem(GlpiMonitorType::class), @@ -106,9 +104,7 @@ public function testShowMassiveActionsSubForm() }); $this->assertTrue($result); - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn(''); $massive_action->method('getItems')->willReturn([ MonitorType::class => $this->createItem(GlpiMonitorType::class), @@ -125,9 +121,7 @@ public function testShowMassiveActionsSubForm() public function testProcessMassiveActionForOneItemtype() { // Test update power consumption - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $glpi_monitor_type = $this->createItem(GlpiMonitorType::class); $massive_action->POST = [ diff --git a/tests/units/NetworkEquipmentTypeTest.php b/tests/units/NetworkEquipmentTypeTest.php index c502edee..782dbf6d 100644 --- a/tests/units/NetworkEquipmentTypeTest.php +++ b/tests/units/NetworkEquipmentTypeTest.php @@ -78,9 +78,7 @@ public function testUpdatePowerConsumption() public function testShowMassiveActionsSubForm() { - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $massive_action->method('getItems')->willReturn([ NetworkEquipmentType::class => $this->createItem(GlpiNetworkEquipmentType::class), @@ -106,9 +104,7 @@ public function testShowMassiveActionsSubForm() }); $this->assertTrue($result); - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn(''); $massive_action->method('getItems')->willReturn([ NetworkEquipmentType::class => $this->createItem(GlpiNetworkEquipmentType::class), @@ -125,9 +121,7 @@ public function testShowMassiveActionsSubForm() public function testProcessMassiveActionForOneItemtype() { // Test update power consumption - $massive_action = $this->getMockBuilder(MassiveAction::class) - ->disableOriginalConstructor() - ->getMock(); + $massive_action = $this->createStub(MassiveAction::class); $massive_action->method('getAction')->willReturn('MassUpdatePower'); $glpi_networkequipment_type = $this->createItem(GlpiNetworkEquipmentType::class); $networkequipment_type = $this->createItem(NetworkEquipmentType::class, [ diff --git a/tests/units/Source_ZoneTest.php b/tests/units/Source_ZoneTest.php index 9b911743..e1e85909 100644 --- a/tests/units/Source_ZoneTest.php +++ b/tests/units/Source_ZoneTest.php @@ -71,6 +71,31 @@ public function testShowForSource() $this->assertNotEmpty($output); } + public function test_prepareInputForUpdate_toggles_the_download_flag() + { + $source = $this->createItem(Source::class, [ + 'name' => 'foo', + ]); + $zone = $this->createItem(Zone::class, [ + 'name' => 'bar', + ]); + $instance = $this->createItem(Source_Zone::class, [ + $source::getForeignKeyField() => $source->getID(), + $zone::getForeignKeyField() => $zone->getID(), + 'is_download_enabled' => 0, + ]); + + $input = ['_toggle_is_download_enabled' => 1]; + $output = $instance->prepareInputForUpdate($input); + $this->assertEquals(1, $output['is_download_enabled']); + + // Test toggling back to 0 + $instance->fields['is_download_enabled'] = 1; + $input = ['_toggle_is_download_enabled' => 1]; + $output = $instance->prepareInputForUpdate($input); + $this->assertEquals(0, $output['is_download_enabled']); + } + public function test_showForZone_shows_nothing_when_user_cannot_view_the_related_zone() { $source = $this->createItem(Source::class, [ diff --git a/tests/units/ToolboxTest.php b/tests/units/ToolboxTest.php index 62cee2c8..94ea0bb4 100644 --- a/tests/units/ToolboxTest.php +++ b/tests/units/ToolboxTest.php @@ -34,8 +34,6 @@ use Computer as GlpiComputer; use DateInterval; -use DateTime; -use DateTimeImmutable; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; @@ -44,6 +42,8 @@ use Infocom; use Location; use PHPUnit\Framework\Attributes\CoversClass; +use Safe\DateTime; +use Safe\DateTimeImmutable; #[CoversClass(Toolbox::class)] class ToolboxTest extends DbTestCase