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..32d96350 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,6 +19,8 @@ Thank you for improving the security of glpi-agent. | 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..0e252231 100644 --- a/ajax/dropdownZone.php +++ b/ajax/dropdownZone.php @@ -29,7 +29,8 @@ * * ------------------------------------------------------------------------- */ - +use Glpi\Exception\Http\AccessDeniedHttpException; +use Glpi\Exception\Http\NotFoundHttpException; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Zone; @@ -37,13 +38,9 @@ // 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/ajax/toggleZoneDownload.php b/ajax/toggleZoneDownload.php deleted file mode 100644 index a3f8fe0a..00000000 --- a/ajax/toggleZoneDownload.php +++ /dev/null @@ -1,69 +0,0 @@ -. - * - * ------------------------------------------------------------------------- - */ - -use Config as GlpiConfig; -use GlpiPlugin\Carbon\Source; -use GlpiPlugin\Carbon\Source_Zone; - -include(__DIR__ . '/../../../inc/includes.php'); - -// Check if plugin is activated... -if (!Plugin::isPluginActive('carbon')) { - echo __('Not found.', 'carbon'); - http_response_code(404); - die(); -} - -if (!Source::canView() || ! GlpiConfig::canUpdate()) { - // Will die - echo __('Access denied.', 'carbon'); - http_response_code(403); - die(); -} - -if (!isset($_GET['id'])) { - echo __('Bad request.', 'carbon'); - http_response_code(400); - die(); -} - -$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(); -} diff --git a/composer.json b/composer.json index e9a9a0b1..8cba91a4 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,6 @@ "php-http/message-factory": "^1.1" }, "require-dev": { - "glpi-project/tools": "^0.8.1", "marcocesarato/php-conventional-changelog": "^1.17" }, "config": { @@ -35,5 +34,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..547f38e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a821ef4c2ae32d2c6dce28a1a3b4ceb9", + "content-hash": "108f5d181fd03cee818d694c22fa7d8b", "packages": [ { "name": "clue/stream-filter", @@ -637,62 +637,6 @@ } ], "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", @@ -826,47 +770,47 @@ }, { "name": "symfony/console", - "version": "v6.4.44", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524" + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/60e3944c4859c487aa6ea2f0f7754917f70f7524", - "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524", + "url": "https://api.github.com/repos/symfony/console/zipball/23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "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" + "symfony/string": "^7.2|^8.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|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" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -900,7 +844,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.44" + "source": "https://github.com/symfony/console/tree/v7.4.18" }, "funding": [ { @@ -920,7 +864,7 @@ "type": "tidelift" } ], - "time": "2026-08-21T07:42:12+00:00" + "time": "2026-08-25T14:18:37+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1330,16 +1274,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.7.1", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", - "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -1393,7 +1337,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -1413,7 +1357,7 @@ "type": "tidelift" } ], - "time": "2026-06-16T09:55:08+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/string", @@ -1505,86 +1449,6 @@ } ], "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" } ], "aliases": [], 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..753a6b3c 100644 --- a/setup.php +++ b/setup.php @@ -32,6 +32,7 @@ use Config as GlpiConfig; use CronTask as GlpiCronTask; +use Glpi\Config\ProxyExclusion; use Glpi\Plugin\Hooks; use GlpiPlugin\Carbon\Config; use GlpiPlugin\Carbon\CronTask; @@ -46,10 +47,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 +67,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 +106,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() @@ -123,7 +132,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 +232,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 +249,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..7851a359 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) 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..2ef95b35 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; 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..b5b1f04b 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 . ')'; @@ -148,6 +155,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/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..62629b5a 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 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..0564e95a 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'), @@ -412,11 +415,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..2cee7b1a 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; /** @@ -387,11 +390,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..9bcf9db4 100644 --- a/src/DataSource/Lca/Boaviztapi/Client.php +++ b/src/DataSource/Lca/Boaviztapi/Client.php @@ -45,6 +45,8 @@ use Override; use RuntimeException; +use function Safe\json_encode; + class Client extends AbstractClient { private RestApiClientInterface $client; @@ -275,7 +277,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; 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..bf889620 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 */ 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/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/templates/config.html.twig b/templates/config.html.twig index 3f688a71..d199f31d 100644 --- a/templates/config.html.twig +++ b/templates/config.html.twig @@ -35,7 +35,6 @@
- {{ 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..436e6525 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') }}
@@ -96,10 +96,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/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/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/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/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..9d4eeda6 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, [ 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