From ab9e2cde1abf4e1e97eaeba6a21a0f201fd890c6 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 11 Feb 2026 08:31:41 +0100 Subject: [PATCH 01/25] docs: source if yearly carbon intensity per country --- install/data/carbon_intensity/carbon-intensity-electricity.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 install/data/carbon_intensity/carbon-intensity-electricity.txt diff --git a/install/data/carbon_intensity/carbon-intensity-electricity.txt b/install/data/carbon_intensity/carbon-intensity-electricity.txt new file mode 100644 index 00000000..a776620c --- /dev/null +++ b/install/data/carbon_intensity/carbon-intensity-electricity.txt @@ -0,0 +1,2 @@ +Data source: +https://ourworldindata.org/grapher/carbon-intensity-electricity \ No newline at end of file From 0926027e60c19f65ade8bf74c3cd2dee34e4ddc9 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 11 Feb 2026 10:52:59 +0100 Subject: [PATCH 02/25] feat(Embodied\Impact): support for all criterias of Boaviztapi --- install/mysql/plugin_carbon_empty.sql | 60 +++++++-- .../Embodied/Boavizta/AbstractAsset.php | 116 ++++++++++++++++-- src/Impact/Embodied/Boavizta/Computer.php | 1 + src/Impact/Embodied/Engine.php | 6 + src/Impact/Type.php | 48 +++++++- 5 files changed, 200 insertions(+), 31 deletions(-) diff --git a/install/mysql/plugin_carbon_empty.sql b/install/mysql/plugin_carbon_empty.sql index da2f1360..03eacbe1 100644 --- a/install/mysql/plugin_carbon_empty.sql +++ b/install/mysql/plugin_carbon_empty.sql @@ -132,17 +132,51 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_computerusageprofiles` ( CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_embodiedimpacts` ( `id` int unsigned NOT NULL AUTO_INCREMENT, - `itemtype` varchar(255) DEFAULT NULL, + `itemtype` varchar(255) DEFAULT NULL, `items_id` int unsigned NOT NULL DEFAULT '0', - `engine` varchar(255) DEFAULT NULL, - `engine_version` varchar(255) DEFAULT NULL, - `date_mod` timestamp NULL DEFAULT NULL, - `gwp` float unsigned DEFAULT '0' COMMENT '(unit gCO2eq) Global warming potential', + `engine` varchar(255) DEFAULT NULL, + `engine_version` varchar(255) DEFAULT NULL, + `date_mod` timestamp NULL DEFAULT NULL, + `gwp` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Global warming potential', `gwp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adp` float unsigned DEFAULT '0' COMMENT '(unit gSbeq) Abiotic depletion potential', + `adp` float unsigned DEFAULT '0' COMMENT '(unit g Sb eq) Abiotic depletion potential', `adp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pe` float unsigned DEFAULT '0' COMMENT '(unit J) Primary energy', + `pe` float unsigned DEFAULT '0' COMMENT '(unit J) Primary energy', `pe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwppb` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of biogenic emissions', + `gwppb_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwppf` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of fossil fuel emissions', + `gwppf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwpplu` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of emissions from land use change', + `gwpplu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ir` float unsigned DEFAULT '0' COMMENT '(unit g U235 eq) Emissions of radionizing substances', + `ir_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `lu` float unsigned DEFAULT '0' COMMENT '(unit none) Land use', + `lu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `odp` float unsigned DEFAULT '0' COMMENT '(unit g CFC-11 eq) Depletion of the ozone layer', + `odp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `pm` float unsigned DEFAULT '0' COMMENT '(unit Disease occurrence) Fine particle emissions', + `pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `pocp` float unsigned DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', + `pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `wu` float unsigned DEFAULT '0' COMMENT '(unit L) Use of water resources', + `wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `mips` float unsigned DEFAULT '0' COMMENT '(unit g) Material input per unit of service', + `mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `adpe` float unsigned DEFAULT '0' COMMENT '(unit g SB eq) Use of mineral and metal resources', + `adpe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `adpf` float unsigned DEFAULT '0' COMMENT '(unit J) Use of fossil resources (including nuclear)', + `adpf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ap` float unsigned DEFAULT '0' COMMENT '(unit mol H+ eq) Acidification', + `ap_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ctue` float unsigned DEFAULT '0' COMMENT '(unit CTUe) Freshwater ecotoxicity', + `ctue_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `epf` float unsigned DEFAULT '0' COMMENT '(unit g P eq) Eutrophication of freshwater', + `epf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `epm` float unsigned DEFAULT '0' COMMENT '(unit g N eq) Eutrophication of marine waters', + `epm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ept` float unsigned DEFAULT '0' COMMENT '(unit mol N eq) Terrestrial eutrophication', + `ept_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', PRIMARY KEY (`id`), UNIQUE KEY `unicity` (`itemtype`, `items_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -161,12 +195,12 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_monitormodels` ( `gwp` int DEFAULT '0' COMMENT '(unit gCO2eq) Global warming potential', `gwp_source` mediumtext DEFAULT NULL COMMENT 'any information to describe the source, URL preferred', `gwp_quality` int DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adp` int DEFAULT '0' COMMENT '(unit gSbEq) Abiotic depletion potential', - `adp_source` mediumtext DEFAULT NULL COMMENT 'any information to describe the source, URL preferred', - `adp_quality` int DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pe` int DEFAULT '0' COMMENT '(unit J) Primary energy', - `pe_source` mediumtext DEFAULT NULL COMMENT 'any information to describe the source, URL preferred', - `pe_quality` int DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `adp` int DEFAULT '0' COMMENT '(unit gSbEq) Abiotic depletion potential', + `adp_source` mediumtext DEFAULT NULL COMMENT 'any information to describe the source, URL preferred', + `adp_quality` int DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `pe` int DEFAULT '0' COMMENT '(unit J) Primary energy', + `pe_source` mediumtext DEFAULT NULL COMMENT 'any information to describe the source, URL preferred', + `pe_quality` int DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', PRIMARY KEY (`id`), UNIQUE KEY `unicity` (`monitormodels_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index f6db53a6..f215f3dc 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -55,6 +55,30 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte /** @var Client instance of the HTTP client */ protected ?Client $client = null; + protected array $criterias = [ + 'gwp' => 1000, // Kg + 'adp' => 1000, // Kg + 'pe' => 1000000, // MJ + 'gwppb' => 1000, // Kg + 'gwppf' => 1000, // Kg + 'gwpplu' => 1000, // Kg + 'ir' => 1000, // Kg + 'lu' => 1, // (no unit) + 'odp' => 1000, // Kg + 'pm' => 1, // (no unit) + 'pocp' => 1000, // Kg + 'wu' => 1000, // M^3 + 'mips' => 1000, // Kg + 'adpe' => 1000, // Kg + 'adpf' => 1000000, // MJ + 'ap' => 1, // mol + 'ctue' => 1, // CTUe + // 'ctuh_c' => '', // CTUh request fails when this criteria is added, not a URL encoding issue + 'epf' => 1000, // Kg + 'epm' => 1000, // Kg + 'ept' => 1, // mol + ]; + // abstract public static function getEngine(CommonDBTM $item): EngineInterface; /** @@ -98,7 +122,23 @@ protected function getVersion(): string return self::$engine_version; } - protected function query($description): array + /** + * Get the quety string specifying the impact criterias for the HTTP request + * + * @return string + */ + protected function getCriteriasQueryString(): string + { + return 'criteria=' . implode('&criteria=', array_keys($this->criterias)); + } + + /** + * Send a HTTP query + * + * @param array $description + * @return array + */ + protected function query(array $description): array { try { $response = $this->client->post($this->endpoint, [ @@ -115,6 +155,7 @@ protected function query($description): array /** * Read the response to find the impacts provided by Boaviztapi * + * @param array $response * @return array */ protected function parseResponse(array $response): array @@ -130,22 +171,73 @@ protected function parseResponse(array $response): array if ($impact_id === false) { continue; } - switch ($type) { - case 'gwp': - $impacts[$impact_id] = $this->parseGwp($response['impacts'][$type]); - break; - case 'adp': - $impacts[$impact_id] = $this->parseAdp($response['impacts'][$type]); - break; - case 'pe': - $impacts[$impact_id] = $this->parsePe($response['impacts'][$type]); - break; - } + $impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type]); + // switch ($type) { + // case 'gwp': + // $impacts[$impact_id] = $this->parseGwp($response['impacts'][$type]); + // break; + // case 'adp': + // $impacts[$impact_id] = $this->parseAdp($response['impacts'][$type]); + // break; + // case 'pe': + // $impacts[$impact_id] = $this->parsePe($response['impacts'][$type]); + // break; + // case 'gwppb': + // break; + // case 'gwppf': + // break; + // case 'gwpplu': + // break; + // case 'ir': + // break; + // case 'lu': + // break; + // case 'odp': + // break; + // case 'pm': + // break; + // case 'pocp': + // break; + // case 'wu': + // break; + // case 'mips': + // break; + // case 'adpe': + // break; + // case 'adpf': + // break; + // case 'ap': + // break; + // case 'ctue': + // break; + // case 'epf': + // break; + // case 'epm': + // break; + // case 'ept': + // break; + // } } return $impacts; } + protected function parseCriteria(string $name, array $impact): ?TrackedFloat + { + if ($impact['embedded'] === 'not implemented') { + return null; + } + + $unit_multiplier = $this->criterias[$name]; + $value = new TrackedFloat( + $impact['embedded']['value'] * $unit_multiplier, + null, + TrackedFloat::DATA_QUALITY_ESTIMATED + ); + + return $value; + } + protected function parseGwp(array $impact): ?TrackedFloat { if ($impact['embedded'] === 'not implemented') { diff --git a/src/Impact/Embodied/Boavizta/Computer.php b/src/Impact/Embodied/Boavizta/Computer.php index f395664e..12e6e4b8 100644 --- a/src/Impact/Embodied/Boavizta/Computer.php +++ b/src/Impact/Embodied/Boavizta/Computer.php @@ -59,6 +59,7 @@ protected function doEvaluation(): ?array // adapt $this->endpoint depending on the type of computer (server, laptop, ...) $type = $this->getType($this->item); $this->endpoint = $this->getEndpoint($type); + $this->endpoint .= '?' . $this->getCriteriasQueryString(); // Ask for embodied impact only $handle_hardware = in_array($type, [ diff --git a/src/Impact/Embodied/Engine.php b/src/Impact/Embodied/Engine.php index 7a3a2dbb..c033983f 100644 --- a/src/Impact/Embodied/Engine.php +++ b/src/Impact/Embodied/Engine.php @@ -131,6 +131,8 @@ protected static function configureEngine(EmbodiedImpactInterface $engine): Embo */ private static function hasModelData(CommonDBTM $item): bool { + global $DB; + $itemtype = get_class($item); $glpi_model_class = $itemtype . 'Model'; $glpi_model_class_fk = getForeignKeyFieldForItemType($glpi_model_class); @@ -138,12 +140,16 @@ private static function hasModelData(CommonDBTM $item): bool * @var class-string $model_class */ $model_class = 'GlpiPlugin\\Carbon\\' . $glpi_model_class; + $model_table = getTableForItemType($model_class); $glpi_model_id = $item->fields[$glpi_model_class_fk]; $crit = [ $glpi_model_class_fk => $glpi_model_id ]; $types = Type::getImpactTypes(); foreach ($types as $key => $type) { + if (!$DB->fieldExists($model_table, $type)) { + continue; + } $crit['OR'][] = [ $type . '_quality' => ['<>', AbstractTracked::DATA_QUALITY_UNSET_VALUE] ]; diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 7c3f5551..7a9de684 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -35,14 +35,50 @@ class Type { - const IMPACT_GWP = 0; // Global warming potential - const IMPACT_ADP = 1; // Abiotic Depletion Potential - const IMPACT_PE = 2; // Primary Energy + const IMPACT_GWP = 0; // Global warming potential + const IMPACT_ADP = 1; // Abiotic Depletion Potential + const IMPACT_PE = 3; // Primary Energy + const IMPACT_GWPPB = 4; + const IMPACT_GWPPF = 5; + const IMPACT_GWPPLU = 6; + const IMPACT_IR = 7; + const IMPACT_LU = 8; + const IMPACT_ODP = 9; + const IMPACT_PM = 10; + const IMPACT_POCP = 11; + const IMPACT_WU = 12; + const IMPACT_MIPS = 13; + const IMPACT_ADPE = 14; + const IMPACT_ADPF = 15; + const IMPACT_AP = 16; + const IMPACT_CTUE = 17; + // const IMPACT_CTUHC = 18; + const IMPACT_EPF = 19; + const IMPACT_EPM = 20; + const IMPACT_EPT = 21; private static array $impact_types = [ - self::IMPACT_GWP => 'gwp', - self::IMPACT_ADP => 'adp', - self::IMPACT_PE => 'pe', + self::IMPACT_GWP => 'gwp', + self::IMPACT_ADP => 'adp', + self::IMPACT_PE => 'pe', + self::IMPACT_GWPPB => 'gwppb', + self::IMPACT_GWPPF => 'gwppf', + self::IMPACT_GWPPLU => 'gwpplu', + self::IMPACT_IR => 'ir', + self::IMPACT_LU => 'lu', + self::IMPACT_ODP => 'odp', + self::IMPACT_PM => 'pm', + self::IMPACT_POCP => 'pocp', + self::IMPACT_WU => 'wu', + self::IMPACT_MIPS => 'mips', + self::IMPACT_ADPE => 'adpe', + self::IMPACT_ADPF => 'adpf', + self::IMPACT_AP => 'ap', + self::IMPACT_CTUE => 'ctue', + // self::IMPACT_CTUHC => 'ctuh_c', + self::IMPACT_EPF => 'epf', + self::IMPACT_EPM => 'epm', + self::IMPACT_EPT => 'ept', ]; public static function getImpactTypes(): array From ad2a1e9307be58e299482201b65f75c0fa5bc752 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 08:54:30 +0100 Subject: [PATCH 03/25] feat(Embodied\Impact): cards for all embodied impacts --- .../09_add_impact_criterias.php | 118 +++++++++++++++++ src/Dashboard/Grid.php | 123 ++++++++++++------ src/Dashboard/Provider.php | 39 ++++++ src/EmbodiedImpact.php | 4 +- .../Embodied/Boavizta/AbstractAsset.php | 55 +------- src/Impact/Type.php | 121 ++++++++++++++++- src/Toolbox.php | 77 +++++++---- 7 files changed, 423 insertions(+), 114 deletions(-) create mode 100644 install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php 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 new file mode 100644 index 00000000..4d718d4b --- /dev/null +++ b/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php @@ -0,0 +1,118 @@ +. + * + * ------------------------------------------------------------------------- + */ + +/** @var DBmysql $DB */ +/** @var Migration $migration */ + +use Glpi\Dashboard\Item as DashboardItem; + +$new_criterias = [ + 'gwppb' => '(unit g CO2 eq) Climate change - Contribution of biogenic emissions', + 'gwppf' => '(unit g CO2 eq) Climate change - Contribution of fossil fuel emissions', + 'gwpplu' => '(unit g CO2 eq) Climate change - Contribution of emissions from land use change', + 'ir' => '(unit g U235 eq) Emissions of radionizing substances', + 'lu' => '(unit none) Land use', + 'odp' => '(unit g CFC-11 eq) Depletion of the ozone layer', + 'pm' => '(unit Disease occurrence) Fine particle emissions', + 'pocp' => '(unit g NMVOC eq) Photochemical ozone formation', + 'wu' => '(unit L) Use of water resources', + 'mips' => '(unit g) Material input per unit of service', + 'adpe' => '(unit g SB eq) Use of mineral and metal resources', + 'adpf' => '(unit J) Use of fossil resources (including nuclear)', + 'ap' => '(unit mol H+ eq) Acidification', + 'ctue' => '(unit CTUe) Freshwater ecotoxicity', + // ctuh_c => '(unit CTUh) Human toxicity - non-carcinogenic effects', + 'epf' => '(unit g P eq) Eutrophication of freshwater', + 'epm' => '(unit g N eq) Eutrophication of marine waters', + 'ept' => '(unit mol N eq) Terrestrial eutrophication', +]; + +$table = 'glpi_plugin_carbon_embodiedimpacts'; +$previous_criteria = 'pe'; +foreach ($new_criterias as $criteria => $comment) { + $migration->addField( + $table, + $criteria, + 'float DEFAULT \'0\'', + [ + 'comment' => $comment, + 'after' => $previous_criteria . '_quality', + ] + ); + $migration->addField( + $table, + $criteria . '_quality', + 'int unsigned NOT NULL DEFAULT \'0\'', + [ + 'comment' => 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + 'after' => $criteria, + ] + ); + // $migration->dropField($table, $criteria); + // $migration->dropField($table, $criteria . '_quality'); + $previous_criteria = $criteria; +} + +// Uniformize existing impact : make floats signed +$old_criterias = [ + 'gwp' => '(unit g CO2 eq) Global warming potential', + 'adp' => '(unit g Sb eq) Abiotic depletion potential', + 'pe' => '(unit J) Primary energy', +]; +foreach ($old_criterias as $criteria => $comment) { + $migration->changeField($table, $criteria, $criteria, 'float DEFAULT \'0\'', [ + 'comment' => $comment, + ]); +} + +// Rename cards for the report +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_report_embodied_gwp_impact' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_report_embodied_gwp_impact', + ]); +} + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_report_embodied_abiotic_depletion' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_report_embodied_adp_impact', + ]); +} diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index 3dc30e84..879990a6 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -35,6 +35,7 @@ use Computer; use Glpi\Dashboard\Filter; use GlpiPlugin\Carbon\Config; +use GlpiPlugin\Carbon\Impact\Type; use Session; class Grid @@ -153,6 +154,22 @@ protected static function getStandardCards(): array ]; } + foreach (Type::getImpactTypes() as $impact_type) { + $key = "plugin_carbon_embodied_{$impact_type}_impact"; + if (isset($new_cards[$key])) { + trigger_error("The card $key already exists", E_USER_WARNING); + } + $new_cards[$key] = [ + 'widgettype' => ['bigNumber'], + 'group' => $group, + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'provider' => Provider::class . '::getImpactOfEmbodiedCriteria', + 'args' => [ + 'impact_type' => $impact_type + ] + ]; + } + $new_cards += [ // Usage impact 'plugin_carbon_total_usage_power' => [ @@ -175,24 +192,24 @@ protected static function getStandardCards(): array ], // Embodied impact - 'plugin_carbon_embodied_gwp_impact' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Embodied global warming potential', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedGlobalWarming', - ], - 'plugin_carbon_embodied_pe_impact' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Embodied primary energy consumed', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', - ], - 'plugin_carbon_embodied_adp_impact' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Embodied abiotic depletion potential', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', - ], + // 'plugin_carbon_embodied_gwp_impact' => [ + // 'widgettype' => ['bigNumber'], + // 'group' => $group, + // 'label' => __('Embodied global warming potential', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedGlobalWarming', + // ], + // 'plugin_carbon_embodied_pe_impact' => [ + // 'widgettype' => ['bigNumber'], + // 'group' => $group, + // 'label' => __('Embodied primary energy consumed', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', + // ], + // 'plugin_carbon_embodied_adp_impact' => [ + // 'widgettype' => ['bigNumber'], + // 'group' => $group, + // 'label' => __('Embodied abiotic depletion potential', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', + // ], // embodied + usage impact 'plugin_carbon_total_gwp_impact' => [ @@ -238,8 +255,8 @@ protected static function getReportCards(): array ]; } - // Usage impact $new_cards += [ + // Usage impact 'plugin_carbon_report_usage_carbon_emission_ytd' => [ 'widgettype' => ['usage_carbon_emission_ytd'], 'group' => $group, @@ -278,25 +295,40 @@ protected static function getReportCards(): array ], // Embodied impact - 'plugin_carbon_report_embodied_global_warming' => [ - 'widgettype' => ['embodied_global_warming'], - 'group' => $group, - 'label' => __('Embodied global warming potential', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedGlobalWarming', - ], - 'plugin_carbon_report_embodied_abiotic_depletion' => [ - 'widgettype' => ['embodied_abiotic_depletion'], - 'group' => $group, - 'label' => __('Embodied abiotic depletion potential', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', - ], - 'plugin_carbon_report_embodied_pe_impact' => [ - 'widgettype' => ['embodied_primary_energy'], - 'group' => $group, - 'label' => __('Embodied primary energy consumed', 'carbon'), - 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', - ], + // 'plugin_carbon_report_embodied_global_warming' => [ + // 'widgettype' => ['embodied_global_warming'], + // 'group' => $group, + // 'label' => __('Embodied global warming potential', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedGlobalWarming', + // ], + // 'plugin_carbon_report_embodied_abiotic_depletion' => [ + // 'widgettype' => ['embodied_abiotic_depletion'], + // 'group' => $group, + // 'label' => __('Embodied abiotic depletion potential', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', + // ], + // 'plugin_carbon_report_embodied_pe_impact' => [ + // 'widgettype' => ['embodied_primary_energy'], + // 'group' => $group, + // 'label' => __('Embodied primary energy consumed', 'carbon'), + // 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', + // ], ]; + foreach (Type::getImpactTypes() as $impact_type) { + $key = "plugin_carbon_report_embodied_{$impact_type}_impact"; + if (isset($new_cards[$key])) { + trigger_error("The card $key already exists", E_USER_WARNING); + } + $new_cards[$key] = [ + 'widgettype' => [self::getWidgetForImpact(true, $impact_type)], + 'group' => $group, + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'provider' => Provider::class . '::getImpactOfEmbodiedCriteria', + 'args' => [ + 'impact_type' => $impact_type + ] + ]; + } // Informational content $new_cards += [ @@ -314,4 +346,21 @@ protected static function getReportCards(): array return $new_cards; } + + private static function getWidgetForImpact(bool $embodied, string $type): string + { + switch (($embodied ? 'embodied' : 'usage') . ' ' . $type) { + case 'embodied gwp': + return 'embodied_global_warming'; + break; + case 'embodied adp': + return 'embodied_abiotic_depletion'; + break; + case 'embodied pe': + return 'embodied_primary_energy'; + break; + } + + return ''; + } } diff --git a/src/Dashboard/Provider.php b/src/Dashboard/Provider.php index 1e8f188e..16d830c3 100644 --- a/src/Dashboard/Provider.php +++ b/src/Dashboard/Provider.php @@ -53,6 +53,7 @@ use GlpiPlugin\Carbon\UsageImpact; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; +use GlpiPlugin\Carbon\Impact\Type; use Search; use Session; use Toolbox as GlpiToolbox; @@ -950,6 +951,44 @@ public static function getEmbodiedAbioticDepletion(array $params = [], array $cr ]; } + /** + * Total embodied abiotic depletion potential in antimony equivalent + * + * @param array $params + * @param array $crit + * @return array + */ + public static function getImpactOfEmbodiedCriteria(string $impact_type, array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => 'fa-solid fa-temperature-arrow-up', + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::getSum(EmbodiedImpact::getTable(), $impact_type, $params, $crit); + if ($value === null) { + $value = 'N/A'; + } else { + // $value = Toolbox::getWeight($value) . Type::getImpactUnit($impact_type); + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + ]; + } + /** * Get usage abiotic depletion potential in antimony equivalent * diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 13271feb..190ece8f 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -90,7 +90,7 @@ public function rawSearchOptions() 'name' => __('Global Warming Potential', 'carbon'), 'massiveaction' => false, 'datatype' => 'number', - 'unit' => 'gCO2eq', + 'unit' => 'g CO2 eq', ]; $tab[] = [ @@ -100,7 +100,7 @@ public function rawSearchOptions() 'name' => __('Abiotic Depletion Potential', 'carbon'), 'massiveaction' => false, 'datatype' => 'number', - 'unit' => 'KgSbeq', + 'unit' => 'g Sb eq', ]; $tab[] = [ diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index f215f3dc..e7f9685d 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -55,7 +55,8 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte /** @var Client instance of the HTTP client */ protected ?Client $client = null; - protected array $criterias = [ + /** @var array Supported impact criterias and the multiplier unit of the value returned by Boaviztapi */ + protected array $criteria_units = [ 'gwp' => 1000, // Kg 'adp' => 1000, // Kg 'pe' => 1000000, // MJ @@ -73,7 +74,8 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte 'adpf' => 1000000, // MJ 'ap' => 1, // mol 'ctue' => 1, // CTUe - // 'ctuh_c' => '', // CTUh request fails when this criteria is added, not a URL encoding issue + // 'ctuh_c' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue + // 'ctuh_nc' => 1, // CTUh request fails when this criteria is added, not a URL encoding issue 'epf' => 1000, // Kg 'epm' => 1000, // Kg 'ept' => 1, // mol @@ -129,7 +131,7 @@ protected function getVersion(): string */ protected function getCriteriasQueryString(): string { - return 'criteria=' . implode('&criteria=', array_keys($this->criterias)); + return 'criteria=' . implode('&criteria=', array_keys($this->criteria_units)); } /** @@ -172,51 +174,6 @@ protected function parseResponse(array $response): array continue; } $impacts[$impact_id] = $this->parseCriteria($type, $response['impacts'][$type]); - // switch ($type) { - // case 'gwp': - // $impacts[$impact_id] = $this->parseGwp($response['impacts'][$type]); - // break; - // case 'adp': - // $impacts[$impact_id] = $this->parseAdp($response['impacts'][$type]); - // break; - // case 'pe': - // $impacts[$impact_id] = $this->parsePe($response['impacts'][$type]); - // break; - // case 'gwppb': - // break; - // case 'gwppf': - // break; - // case 'gwpplu': - // break; - // case 'ir': - // break; - // case 'lu': - // break; - // case 'odp': - // break; - // case 'pm': - // break; - // case 'pocp': - // break; - // case 'wu': - // break; - // case 'mips': - // break; - // case 'adpe': - // break; - // case 'adpf': - // break; - // case 'ap': - // break; - // case 'ctue': - // break; - // case 'epf': - // break; - // case 'epm': - // break; - // case 'ept': - // break; - // } } return $impacts; @@ -228,7 +185,7 @@ protected function parseCriteria(string $name, array $impact): ?TrackedFloat return null; } - $unit_multiplier = $this->criterias[$name]; + $unit_multiplier = $this->criteria_units[$name]; $value = new TrackedFloat( $impact['embedded']['value'] * $unit_multiplier, null, diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 7a9de684..78c7bd0a 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -53,9 +53,10 @@ class Type const IMPACT_AP = 16; const IMPACT_CTUE = 17; // const IMPACT_CTUHC = 18; - const IMPACT_EPF = 19; - const IMPACT_EPM = 20; - const IMPACT_EPT = 21; + // const IMPACT_CTUHNC = 19; + const IMPACT_EPF = 20; + const IMPACT_EPM = 21; + const IMPACT_EPT = 22; private static array $impact_types = [ self::IMPACT_GWP => 'gwp', @@ -76,11 +77,42 @@ class Type self::IMPACT_AP => 'ap', self::IMPACT_CTUE => 'ctue', // self::IMPACT_CTUHC => 'ctuh_c', + // self::IMPACT_CTUHNC => 'ctuh_nc', self::IMPACT_EPF => 'epf', self::IMPACT_EPM => 'epm', self::IMPACT_EPT => 'ept', ]; + /** + * Unit of impact criterias + * + * @var array + */ + private static array $impact_units = [ + 'gwp' => ['g', 'CO2 eq'], + 'adp' => ['g', 'SB eq'], + 'pe' => ['J', ''], + 'gwppb' => ['g', 'CO2 eq'], + 'gwppf' => ['g', 'CO2 eq'], + 'gwpplu' => ['g', 'CO2 eq'], + 'ir' => ['g', 'U235 eq'], + 'lu' => null, + 'odp' => ['g', 'CFC-11 eq'], + 'pm' => null, + 'pocp' => ['g', 'U235 eq'], + 'wu' => ['m³', ''], + 'mips' => ['g', ''], + 'adpe' => ['g', 'SB eq'], + 'adpf' => ['J', ''], + 'ap' => ['mol', 'H+ eq'], + 'ctue' => ['J', ''], + // 'ctuh_c' => [null, 'CTUh'], + // 'ctuh_nc' => [null, 'CTUh'], + 'epf' => ['g', 'P eq'], + 'epm' => ['g', 'N eq'], + 'ept' => ['mol', 'N eq'], + ]; + public static function getImpactTypes(): array { return self::$impact_types; @@ -91,9 +123,90 @@ public static function getImpactTypes(): array * * @param string $type * @return int|string|false - */ + **/ public static function getImpactId(string $type) { return array_search($type, self::$impact_types); } + + /** + * Get the unit of an impact type + * + * @param string $type impact type name + * @return array + **/ + public static function getImpactUnit(string $type): array + { + return self::$impact_units[$type] ?? ['', '']; + } + + /** + * Get the unit of an impact type + * + * @param string $type impact type name + * @return string + **/ + public static function getEmbodiedImpactLabel(string $type): string + { + $embodied_impact_label = [ + 'gwp' => __('Embodied Global warming potential', 'carbon'), + 'adp' => __('Embodied Abiotic depletion potential', 'carbon'), + 'pe' => __('Embodied Primary energy consumed', 'carbon'), + 'gwppb' => __('Embodied Climate change - Contribution of biogenic emissions', 'carbon'), + 'gwppf' => __('Embodied Climate change - Contribution of fossil fuel emissions', 'carbon'), + 'gwpplu' => __('Embodied Climate change - Contribution of emissions from land use change', 'carbon'), + 'ir' => __('Embodied Emissions of radionizing substances', 'carbon'), + 'lu' => __('Embodied Land use', 'carbon'), + 'odp' => __('Embodied Depletion of the ozone layer', 'carbon'), + 'pm' => __('Embodied Fine particle emissions', 'carbon'), + 'pocp' => __('Embodied Photochemical ozone formation', 'carbon'), + 'wu' => __('Embodied Use of water resources', 'carbon'), + 'mips' => __('Embodied Material input per unit of service', 'carbon'), + 'adpe' => __('Embodied Use of mineral and metal resources', 'carbon'), + 'adpf' => __('Embodied Use of fossil resources (including nuclear)', 'carbon'), + 'ap' => __('Embodied Acidification', 'carbon'), + 'ctue' => __('Embodied Human Toxicity - Carcinogenic Effects', 'carbon'), + // 'ctuh_c' => __('Embodied Human Toxicity - Carcinogenic Effects', 'carbon'), + // 'ctuh_nc' => __('Embodied Human toxicity - non-carcinogenic effects', 'carbon'), + 'epf' => __('Embodied Eutrophication of freshwater', 'carbon'), + 'epm' => __('Embodied Eutrophication of marine waters', 'carbon'), + 'ept' => __('Embodied Terrestrial eutrophication', 'carbon'), + ]; + return $embodied_impact_label[$type] ?? ''; + } + + /** + * Get the unit of an impact type + * + * @param string $type impact type name + * @return string + **/ + public static function getUsageImpactLabel(string $type): string + { + static $usage_impact_label = [ + 'gwp' => __('Usage Global warming potential', 'carbon'), + 'adp' => __('Usage Abiotic depletion potential', 'carbon'), + 'pe' => __('Usage Primary energy consumed', 'carbon'), + 'gwppb' => __('Usage Climate change - Contribution of biogenic emissions', 'carbon'), + 'gwppf' => __('Usage Climate change - Contribution of fossil fuel emissions', 'carbon'), + 'gwpplu' => __('Usage Climate change - Contribution of emissions from land use change', 'carbon'), + 'ir' => __('Usage Emissions of radionizing substances', 'carbon'), + 'lu' => __('Usage Land use', 'carbon'), + 'odp' => __('Usage Depletion of the ozone layer', 'carbon'), + 'pm' => __('Usage Fine particle emissions', 'carbon'), + 'pocp' => __('Usage Photochemical ozone formation', 'carbon'), + 'wu' => __('Usage Use of water resources', 'carbon'), + 'mips' => __('Usage Material input per unit of service', 'carbon'), + 'adpe' => __('Usage Use of mineral and metal resources', 'carbon'), + 'adpf' => __('Usage Use of fossil resources (including nuclear)', 'carbon'), + 'ap' => __('Usage Acidification', 'carbon'), + 'ctue' => __('Usage Human Toxicity - Carcinogenic Effects', 'carbon'), + // 'ctuh_c' => __('Usage Human Toxicity - Carcinogenic Effects', 'carbon'), + // 'ctuh_nc' => __('Usage Human toxicity - non-carcinogenic effects', 'carbon'), + 'epf' => __('Usage Eutrophication of freshwater', 'carbon'), + 'epm' => __('Usage Eutrophication of marine waters', 'carbon'), + 'ept' => __('Usage Terrestrial eutrophication', 'carbon'), + ]; + return $usage_impact_label[$type] ?? ''; + } } diff --git a/src/Toolbox.php b/src/Toolbox.php index 328e34cf..5dfc8b64 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -201,9 +201,9 @@ public static function getWeight(float $weight): string { //TRANS: list of unit (o for octet) $units = [ - __('g', 'carbon'), + __('g', 'carbon'), __('Kg', 'carbon'), - __('t', 'carbon'), + __('t', 'carbon'), __('Kt', 'carbon'), __('Mt', 'carbon'), __('Gt', 'carbon'), @@ -224,20 +224,7 @@ public static function getWeight(float $weight): string $weight = self::dynamicRound($weight); //TRANS: %1$s is a number maybe float or string and %2$s the unit - return sprintf(__('%1$s %2$s'), $weight, $human_readable_unit); - } - - public static function dynamicRound(float $number): float - { - if ($number < 10) { - $number = round($number, 2); - } else if ($number < 100) { - $number = round($number, 1); - } else { - $number = round($number, 0); - } - - return $number; + return sprintf(__('%1$s %2$s'), $weight, $human_readable_unit); } /** @@ -272,19 +259,19 @@ public static function getPower(float $p): string $p = self::dynamicRound($p); //TRANS: %1$s is a number maybe float or string and %2$s the unit - return sprintf(__('%1$s %2$s'), $p, $human_readable_unit); + return sprintf(__('%1$s %2$s'), $p, $human_readable_unit); } /** - * Format a power passing a power in grams + * Format a energy in watt.hour * - * @param float $p Power in Watt + * @param float $p Energy in watt.hour * - * @return string formatted power + * @return string formatted energy **/ public static function getEnergy(float $p): string { - //TRANS: list of unit (W for watt) + //TRANS: list of unit (Wh for watt.hour) $units = [ __('Wh', 'carbon'), __('KWh', 'carbon'), @@ -307,7 +294,53 @@ public static function getEnergy(float $p): string $p = self::dynamicRound($p); //TRANS: %1$s is a number maybe float or string and %2$s the unit - return sprintf(__('%1$s %2$s'), $p, $human_readable_unit); + return sprintf(__('%1$s %2$s'), $p, $human_readable_unit); + } + + public static function dynamicRound(float $number): float + { + if ($number < 10) { + $number = round($number, 2); + } else if ($number < 100) { + $number = round($number, 1); + } else { + $number = round($number, 0); + } + + return $number; + } + + /** + * Convert a value and its unit into a human readable value + * + * Unit is an array i.e. ['g', 'CO2 eq'] for grams of carbondioxyde equivalent + * + * @param float $value value of the quantity to convert + * @param array $unit unit splitted into a standard unit and a qualification. + * @return string + */ + public static function getHumanReadableValue(float $value, array $unit): string + { + switch ($unit[0]) { + case 'g': + return self::getWeight($value) . $unit[1]; + break; + case 'J': + // To be converted into watt.hour + return self::getEnergy($value / 3600) . $unit[1]; + break; + case 'Wh': + return self::getEnergy($value) . $unit[1]; + break; + case 'm³': + // Value is in m^3 + return sprintf(__('%1$s %2$s', 'carbon'), $value * 1000, 'L'); + break; + case 'mol': + break; + } + + return sprintf(__('%1$s %2$s', 'carbon'), $value, implode(' ', $unit)); } /** From 1dd0625d5097d6f2dbc183a65adc6a7bed032fcd Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 09:34:35 +0100 Subject: [PATCH 04/25] feat(Embodied\Impact): add widgets for new impacts --- install/mysql/plugin_carbon_empty.sql | 40 +++++++++++++-------------- src/Impact/Type.php | 14 ++++++---- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/install/mysql/plugin_carbon_empty.sql b/install/mysql/plugin_carbon_empty.sql index 03eacbe1..53f8c35b 100644 --- a/install/mysql/plugin_carbon_empty.sql +++ b/install/mysql/plugin_carbon_empty.sql @@ -137,45 +137,45 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_embodiedimpacts` ( `engine` varchar(255) DEFAULT NULL, `engine_version` varchar(255) DEFAULT NULL, `date_mod` timestamp NULL DEFAULT NULL, - `gwp` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Global warming potential', + `gwp` float DEFAULT '0' COMMENT '(unit g CO2 eq) Global warming potential', `gwp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adp` float unsigned DEFAULT '0' COMMENT '(unit g Sb eq) Abiotic depletion potential', + `adp` float DEFAULT '0' COMMENT '(unit g Sb eq) Abiotic depletion potential', `adp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pe` float unsigned DEFAULT '0' COMMENT '(unit J) Primary energy', + `pe` float DEFAULT '0' COMMENT '(unit J) Primary energy', `pe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `gwppb` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of biogenic emissions', + `gwppb` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of biogenic emissions', `gwppb_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `gwppf` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of fossil fuel emissions', + `gwppf` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of fossil fuel emissions', `gwppf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `gwpplu` float unsigned DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of emissions from land use change', + `gwpplu` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of emissions from land use change', `gwpplu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `ir` float unsigned DEFAULT '0' COMMENT '(unit g U235 eq) Emissions of radionizing substances', + `ir` float DEFAULT '0' COMMENT '(unit g U235 eq) Emissions of radionizing substances', `ir_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `lu` float unsigned DEFAULT '0' COMMENT '(unit none) Land use', + `lu` float DEFAULT '0' COMMENT '(unit none) Land use', `lu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `odp` float unsigned DEFAULT '0' COMMENT '(unit g CFC-11 eq) Depletion of the ozone layer', + `odp` float DEFAULT '0' COMMENT '(unit g CFC-11 eq) Depletion of the ozone layer', `odp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pm` float unsigned DEFAULT '0' COMMENT '(unit Disease occurrence) Fine particle emissions', + `pm` float DEFAULT '0' COMMENT '(unit Disease occurrence) Fine particle emissions', `pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pocp` float unsigned DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', + `pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', `pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `wu` float unsigned DEFAULT '0' COMMENT '(unit L) Use of water resources', + `wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources', `wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `mips` float unsigned DEFAULT '0' COMMENT '(unit g) Material input per unit of service', + `mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service', `mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adpe` float unsigned DEFAULT '0' COMMENT '(unit g SB eq) Use of mineral and metal resources', + `adpe` float DEFAULT '0' COMMENT '(unit g SB eq) Use of mineral and metal resources', `adpe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adpf` float unsigned DEFAULT '0' COMMENT '(unit J) Use of fossil resources (including nuclear)', + `adpf` float DEFAULT '0' COMMENT '(unit J) Use of fossil resources (including nuclear)', `adpf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `ap` float unsigned DEFAULT '0' COMMENT '(unit mol H+ eq) Acidification', + `ap` float DEFAULT '0' COMMENT '(unit mol H+ eq) Acidification', `ap_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `ctue` float unsigned DEFAULT '0' COMMENT '(unit CTUe) Freshwater ecotoxicity', + `ctue` float DEFAULT '0' COMMENT '(unit CTUe) Freshwater ecotoxicity', `ctue_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `epf` float unsigned DEFAULT '0' COMMENT '(unit g P eq) Eutrophication of freshwater', + `epf` float DEFAULT '0' COMMENT '(unit g P eq) Eutrophication of freshwater', `epf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `epm` float unsigned DEFAULT '0' COMMENT '(unit g N eq) Eutrophication of marine waters', + `epm` float DEFAULT '0' COMMENT '(unit g N eq) Eutrophication of marine waters', `epm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `ept` float unsigned DEFAULT '0' COMMENT '(unit mol N eq) Terrestrial eutrophication', + `ept` float DEFAULT '0' COMMENT '(unit mol N eq) Terrestrial eutrophication', `ept_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', PRIMARY KEY (`id`), UNIQUE KEY `unicity` (`itemtype`, `items_id`) diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 78c7bd0a..059aa5b6 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -148,7 +148,7 @@ public static function getImpactUnit(string $type): array **/ public static function getEmbodiedImpactLabel(string $type): string { - $embodied_impact_label = [ + $label = match ($type) { 'gwp' => __('Embodied Global warming potential', 'carbon'), 'adp' => __('Embodied Abiotic depletion potential', 'carbon'), 'pe' => __('Embodied Primary energy consumed', 'carbon'), @@ -171,8 +171,9 @@ public static function getEmbodiedImpactLabel(string $type): string 'epf' => __('Embodied Eutrophication of freshwater', 'carbon'), 'epm' => __('Embodied Eutrophication of marine waters', 'carbon'), 'ept' => __('Embodied Terrestrial eutrophication', 'carbon'), - ]; - return $embodied_impact_label[$type] ?? ''; + default => '', + }; + return $label; } /** @@ -183,7 +184,7 @@ public static function getEmbodiedImpactLabel(string $type): string **/ public static function getUsageImpactLabel(string $type): string { - static $usage_impact_label = [ + $label = match ($type) { 'gwp' => __('Usage Global warming potential', 'carbon'), 'adp' => __('Usage Abiotic depletion potential', 'carbon'), 'pe' => __('Usage Primary energy consumed', 'carbon'), @@ -206,7 +207,8 @@ public static function getUsageImpactLabel(string $type): string 'epf' => __('Usage Eutrophication of freshwater', 'carbon'), 'epm' => __('Usage Eutrophication of marine waters', 'carbon'), 'ept' => __('Usage Terrestrial eutrophication', 'carbon'), - ]; - return $usage_impact_label[$type] ?? ''; + default => '' + }; + return $label; } } From e53b243b8c2d89f7c109b1cce80c77322340d23a Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 10:17:33 +0100 Subject: [PATCH 05/25] style: code style --- src/Toolbox.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Toolbox.php b/src/Toolbox.php index 5dfc8b64..eb944174 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -201,9 +201,9 @@ public static function getWeight(float $weight): string { //TRANS: list of unit (o for octet) $units = [ - __('g', 'carbon'), + __('g', 'carbon'), __('Kg', 'carbon'), - __('t', 'carbon'), + __('t', 'carbon'), __('Kt', 'carbon'), __('Mt', 'carbon'), __('Gt', 'carbon'), @@ -320,7 +320,7 @@ public static function dynamicRound(float $number): float * @return string */ public static function getHumanReadableValue(float $value, array $unit): string - { + { switch ($unit[0]) { case 'g': return self::getWeight($value) . $unit[1]; From dd0c6073c94b5c278940fe186c62baab6135ff48 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 10:24:53 +0100 Subject: [PATCH 06/25] fix(Install): method always return true but may throw an exception on error --- install/Install.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/install/Install.php b/install/Install.php index 34c1cebb..1ba40627 100644 --- a/install/Install.php +++ b/install/Install.php @@ -105,11 +105,18 @@ public function install(array $args = []): bool global $DB; $dbFile = plugin_carbon_getSchemaPath(); - if ($dbFile === null || !$DB->runFile($dbFile)) { + if ($dbFile === null) { $this->migration->addWarningMessage("Error creating tables : " . $DB->error()); return false; } + try { + $DB->runFile($dbFile); + } catch (\RuntimeException $e) { + $this->migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + return false; + } + // Execute all install sub tasks $install_dir = __DIR__ . '/install/'; $update_scripts = scandir($install_dir); From c2a7925d4e69c082dbbed37a30916a7ac0ba39ae Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 10:31:58 +0100 Subject: [PATCH 07/25] fix(Install): follow stricter lint checks --- install/install/init_datasources.php | 40 ++++++++++----------- install/migration/update_1.0.0_to_1.0.1.php | 8 ++++- install/migration/update_1.0.1_to_1.1.0.php | 9 ++++- install/migration/update_1.1.1_to_1.2.0.php | 9 ++++- install/migration/update_x.x.x_to_y.y.y.php | 9 ++++- src/Dashboard/Grid.php | 3 -- src/Impact/Embodied/Engine.php | 2 ++ src/Toolbox.php | 4 --- 8 files changed, 53 insertions(+), 31 deletions(-) diff --git a/install/install/init_datasources.php b/install/install/init_datasources.php index b7e18c64..dd4bf17a 100644 --- a/install/install/init_datasources.php +++ b/install/install/init_datasources.php @@ -104,16 +104,16 @@ Install::linkSourceZone($source_id, $zone_id, $code); // Insert into the database - $success = $DB->updateOrInsert($table, [ - 'intensity' => $intensity, - 'data_quality' => 2 // constant GlpiPlugin\Carbon\DataTracking::DATA_QUALITY_ESTIMATED - ], [ - 'date' => "$year-01-01 00:00:00", - 'plugin_carbon_sources_id' => $source_id, - 'plugin_carbon_zones_id' => $zone_id, - ]); - - if ($success === false) { + try { + $DB->updateOrInsert($table, [ + 'intensity' => $intensity, + 'data_quality' => 2 // constant GlpiPlugin\Carbon\DataTracking::DATA_QUALITY_ESTIMATED + ], [ + 'date' => "$year-01-01 00:00:00", + 'plugin_carbon_sources_id' => $source_id, + 'plugin_carbon_zones_id' => $zone_id, + ]); + } catch (\RuntimeException $e) { $file = null; // close the file throw new \RuntimeException("Failed to insert data for year $year"); } @@ -129,16 +129,16 @@ $quebec_carbon_intensity = include(dirname(__DIR__) . '/data/carbon_intensity/quebec.php'); foreach ($quebec_carbon_intensity as $year => $intensity) { - $success = $DB->updateOrInsert($table, [ - 'intensity' => $intensity, - 'data_quality' => 2 // constant GlpiPlugin\Carbon\DataTracking::DATA_QUALITY_ESTIMATED - ], [ - 'date' => "$year-01-01 00:00:00", - 'plugin_carbon_sources_id' => $source_id, - 'plugin_carbon_zones_id' => $zone_id_quebec, - ]); - - if ($success === false) { + try { + $DB->updateOrInsert($table, [ + 'intensity' => $intensity, + 'data_quality' => 2 // constant GlpiPlugin\Carbon\DataTracking::DATA_QUALITY_ESTIMATED + ], [ + 'date' => "$year-01-01 00:00:00", + 'plugin_carbon_sources_id' => $source_id, + 'plugin_carbon_zones_id' => $zone_id_quebec, + ]); + } catch (\RuntimeException $e) { $file = null; // close the file throw new \RuntimeException("Failed to insert data for year $year"); } 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 975bf076..9d2ff61a 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 @@ -57,10 +57,16 @@ function update100to101(Migration $migration) } $dbFile = plugin_carbon_getSchemaPath($to_version); - if ($dbFile === null || !$DB->runFile($dbFile)) { + if ($dbFile === null) { $migration->addWarningMessage("Error creating tables : " . $DB->error()); $updateresult = false; } + try { + $DB->runFile($dbFile); + } catch (\RuntimeException $e) { + $migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + $updateresult = false; + } // ************ Keep it at the end ************** $migration->executeMigration(); 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 c32d549a..6998fce6 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 @@ -57,10 +57,17 @@ function update101to110(Migration $migration) } $dbFile = plugin_carbon_getSchemaPath($to_version); - if ($dbFile === null || !$DB->runFile($dbFile)) { + if ($dbFile === null) { $migration->addWarningMessage("Error creating tables : " . $DB->error()); $updateresult = false; } + try { + $DB->runFile($dbFile); + } catch (\RuntimeException $e) { + $migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + $updateresult = false; + } + // ************ Keep it at the end ************** $migration->executeMigration(); 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 4e62e6d3..d5ca343a 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 @@ -57,10 +57,17 @@ function update111to120(Migration $migration) } $dbFile = plugin_carbon_getSchemaPath($to_version); - if ($dbFile === null || !$DB->runFile($dbFile)) { + if ($dbFile === null) { $migration->addWarningMessage("Error creating tables : " . $DB->error()); $updateresult = false; } + try { + $DB->runFile($dbFile); + } catch (\RuntimeException $e) { + $migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + $updateresult = false; + } + // ************ Keep it at the end ************** $migration->executeMigration(); 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 cb9599dc..2b416877 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 @@ -57,10 +57,17 @@ function update001to100(Migration $migration) } $dbFile = plugin_carbon_getSchemaPath($to_version); - if ($dbFile === null || !$DB->runFile($dbFile)) { + if ($dbFile === null) { $migration->addWarningMessage("Error creating tables : " . $DB->error()); $updateresult = false; } + try { + $DB->runFile($dbFile); + } catch (\RuntimeException $e) { + $migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + $updateresult = false; + } + // ************ Keep it at the end ************** $migration->executeMigration(); diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index 879990a6..e005fb5c 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -352,13 +352,10 @@ private static function getWidgetForImpact(bool $embodied, string $type): string switch (($embodied ? 'embodied' : 'usage') . ' ' . $type) { case 'embodied gwp': return 'embodied_global_warming'; - break; case 'embodied adp': return 'embodied_abiotic_depletion'; - break; case 'embodied pe': return 'embodied_primary_energy'; - break; } return ''; diff --git a/src/Impact/Embodied/Engine.php b/src/Impact/Embodied/Engine.php index c033983f..a66b2b70 100644 --- a/src/Impact/Embodied/Engine.php +++ b/src/Impact/Embodied/Engine.php @@ -34,6 +34,7 @@ use CommonGLPI; use CommonDBTM; +use DBmysql; use GlpiPlugin\Carbon\AbstractModel; use GlpiPlugin\Carbon\Config; use GlpiPlugin\Carbon\DataSource\Lca\Boaviztapi\Client; @@ -131,6 +132,7 @@ protected static function configureEngine(EmbodiedImpactInterface $engine): Embo */ private static function hasModelData(CommonDBTM $item): bool { + /** @var DBmysql $DB */ global $DB; $itemtype = get_class($item); diff --git a/src/Toolbox.php b/src/Toolbox.php index eb944174..67517d2d 100644 --- a/src/Toolbox.php +++ b/src/Toolbox.php @@ -324,18 +324,14 @@ public static function getHumanReadableValue(float $value, array $unit): string switch ($unit[0]) { case 'g': return self::getWeight($value) . $unit[1]; - break; case 'J': // To be converted into watt.hour return self::getEnergy($value / 3600) . $unit[1]; - break; case 'Wh': return self::getEnergy($value) . $unit[1]; - break; case 'm³': // Value is in m^3 return sprintf(__('%1$s %2$s', 'carbon'), $value * 1000, 'L'); - break; case 'mol': break; } From afae034a942e2fafea1d7e4db2a2eec2a8393816 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 10:43:26 +0100 Subject: [PATCH 08/25] feat(EmbodiedImpact): add search options --- src/EmbodiedImpact.php | 43 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 190ece8f..9cb30c91 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -36,6 +36,7 @@ use DBmysql; use DBmysqlIterator; use Glpi\DBAL\QuerySubQuery; +use GlpiPlugin\Carbon\Impact\Type; /** * Embodied impact of assets @@ -83,35 +84,19 @@ public function rawSearchOptions() 'datatype' => 'itemtypename', ]; - $tab[] = [ - 'id' => '5', - 'table' => $this->getTable(), - 'field' => 'gwp', - 'name' => __('Global Warming Potential', 'carbon'), - 'massiveaction' => false, - 'datatype' => 'number', - 'unit' => 'g CO2 eq', - ]; - - $tab[] = [ - 'id' => '6', - 'table' => $this->getTable(), - 'field' => 'adp', - 'name' => __('Abiotic Depletion Potential', 'carbon'), - 'massiveaction' => false, - 'datatype' => 'number', - 'unit' => 'g Sb eq', - ]; - - $tab[] = [ - 'id' => '7', - 'table' => $this->getTable(), - 'field' => 'pe', - 'name' => __('Primary energy', 'carbon'), - 'massiveaction' => false, - 'datatype' => 'number', - 'unit' => 'J', - ]; + $id = 5; + foreach (Type::getImpactTypes() as $type => $unit) { + $tab[] = [ + 'id' => $id, + 'table' => $this->getTable(), + 'field' => $type, + 'name' => Type::getEmbodiedImpactLabel($type), + 'massiveaction' => false, + 'datatype' => 'number', + 'unit' => implode(' ', $unit), + ]; + $id++; + } $tab[] = [ 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, From 860c6c3e1550a7c8fd4eb05b186e4b7dd85c1d81 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 10:44:19 +0100 Subject: [PATCH 09/25] fix(Impact\Type): better unit notation --- src/Impact/Type.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 059aa5b6..2a7b3a30 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -89,12 +89,12 @@ class Type * @var array */ private static array $impact_units = [ - 'gwp' => ['g', 'CO2 eq'], + 'gwp' => ['g', 'CO₂ eq'], 'adp' => ['g', 'SB eq'], 'pe' => ['J', ''], - 'gwppb' => ['g', 'CO2 eq'], - 'gwppf' => ['g', 'CO2 eq'], - 'gwpplu' => ['g', 'CO2 eq'], + 'gwppb' => ['g', 'CO₂ eq'], + 'gwppf' => ['g', 'CO₂ eq'], + 'gwpplu' => ['g', 'CO₂ eq'], 'ir' => ['g', 'U235 eq'], 'lu' => null, 'odp' => ['g', 'CFC-11 eq'], From c22ae21d1c9392afd6116520d4b33b6b9ac8ffa3 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 11:29:04 +0100 Subject: [PATCH 10/25] fix: code lint --- install/install/init_datasources.php | 4 ++-- src/Impact/Type.php | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/install/install/init_datasources.php b/install/install/init_datasources.php index dd4bf17a..cff83fc3 100644 --- a/install/install/init_datasources.php +++ b/install/install/init_datasources.php @@ -115,7 +115,7 @@ ]); } catch (\RuntimeException $e) { $file = null; // close the file - throw new \RuntimeException("Failed to insert data for year $year"); + throw new \RuntimeException("Failed to insert data for year $year; reason: " . $e->getMessage(), $e->getCode(), $e); } } if ($progress_bar) { @@ -140,6 +140,6 @@ ]); } catch (\RuntimeException $e) { $file = null; // close the file - throw new \RuntimeException("Failed to insert data for year $year"); + throw new \RuntimeException("Failed to insert data for year $year; reason: " . $e->getMessage(), $e->getCode(), $e); } } diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 2a7b3a30..9802b39f 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -113,6 +113,11 @@ class Type 'ept' => ['mol', 'N eq'], ]; + /** + * Undocumented function + * + * @return array + */ public static function getImpactTypes(): array { return self::$impact_types; From 6c76cf8908a19e5464033306c9e6131917d25ede Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 16 Feb 2026 11:44:11 +0100 Subject: [PATCH 11/25] fix(Type): search option construction --- src/EmbodiedImpact.php | 4 ++-- src/Impact/Type.php | 10 +++++----- tests/integration/SearchOptionTest.php | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 9cb30c91..968e3530 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -85,7 +85,7 @@ public function rawSearchOptions() ]; $id = 5; - foreach (Type::getImpactTypes() as $type => $unit) { + foreach (Type::getImpactTypes() as $type) { $tab[] = [ 'id' => $id, 'table' => $this->getTable(), @@ -93,7 +93,7 @@ public function rawSearchOptions() 'name' => Type::getEmbodiedImpactLabel($type), 'massiveaction' => false, 'datatype' => 'number', - 'unit' => implode(' ', $unit), + 'unit' => implode(' ', Type::getImpactUnit($type)), ]; $id++; } diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 9802b39f..b127512d 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -35,8 +35,8 @@ class Type { - const IMPACT_GWP = 0; // Global warming potential - const IMPACT_ADP = 1; // Abiotic Depletion Potential + const IMPACT_GWP = 1; // Global warming potential + const IMPACT_ADP = 2; // Abiotic Depletion Potential const IMPACT_PE = 3; // Primary Energy const IMPACT_GWPPB = 4; const IMPACT_GWPPF = 5; @@ -114,9 +114,9 @@ class Type ]; /** - * Undocumented function + * get an array of impact types * - * @return array + * @return array */ public static function getImpactTypes(): array { @@ -138,7 +138,7 @@ public static function getImpactId(string $type) * Get the unit of an impact type * * @param string $type impact type name - * @return array + * @return array **/ public static function getImpactUnit(string $type): array { diff --git a/tests/integration/SearchOptionTest.php b/tests/integration/SearchOptionTest.php index e8e979b9..6a45a523 100644 --- a/tests/integration/SearchOptionTest.php +++ b/tests/integration/SearchOptionTest.php @@ -81,6 +81,25 @@ class SearchOptionTest extends CommonTestCase 'gwp_quality', 'adp_quality', 'pe_quality', + 'gwppb_quality', + 'gwppf_quality', + 'gwpplu_quality', + 'ir_quality', + 'lu_quality', + 'odp_quality', + 'pm_quality', + 'pocp_quality', + 'wu_quality', + 'mips_quality', + 'adpe_quality', + 'adpf_quality', + 'ap_quality', + 'ctue_quality', + 'ctuh_c_quality', + 'ctuh_nc_quality', + 'epf_quality', + 'epm_quality', + 'ept_quality', ], Location::class => [], Zone::class => [ From 0d65672e3b2a1fc70f1764b876e0a89949a82009 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 20 Feb 2026 22:51:06 +0100 Subject: [PATCH 12/25] refactor: unify embodied impact cards for report page --- src/Dashboard/Grid.php | 53 +----- src/Dashboard/Provider.php | 7 +- src/Dashboard/Widget.php | 133 ++++----------- src/Documentation.php | 63 ------- src/Impact/Type.php | 156 +++++++++++++++++- .../embodied-abiotic-depletion.html.twig | 102 ------------ ...wig => embodied-impact-criteria.html.twig} | 22 +-- .../embodied-primary-energy.html.twig | 102 ------------ 8 files changed, 214 insertions(+), 424 deletions(-) delete mode 100644 src/Documentation.php delete mode 100644 templates/dashboard/embodied-abiotic-depletion.html.twig rename templates/dashboard/{embodied-carbon-emission.html.twig => embodied-impact-criteria.html.twig} (85%) delete mode 100644 templates/dashboard/embodied-primary-energy.html.twig diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index e005fb5c..c9284c5e 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -154,6 +154,7 @@ protected static function getStandardCards(): array ]; } + // Embodied impact foreach (Type::getImpactTypes() as $impact_type) { $key = "plugin_carbon_embodied_{$impact_type}_impact"; if (isset($new_cards[$key])) { @@ -191,26 +192,6 @@ protected static function getStandardCards(): array 'provider' => Provider::class . '::getUsageAbioticDepletion', ], - // Embodied impact - // 'plugin_carbon_embodied_gwp_impact' => [ - // 'widgettype' => ['bigNumber'], - // 'group' => $group, - // 'label' => __('Embodied global warming potential', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedGlobalWarming', - // ], - // 'plugin_carbon_embodied_pe_impact' => [ - // 'widgettype' => ['bigNumber'], - // 'group' => $group, - // 'label' => __('Embodied primary energy consumed', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', - // ], - // 'plugin_carbon_embodied_adp_impact' => [ - // 'widgettype' => ['bigNumber'], - // 'group' => $group, - // 'label' => __('Embodied abiotic depletion potential', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', - // ], - // embodied + usage impact 'plugin_carbon_total_gwp_impact' => [ 'widgettype' => ['bigNumber'], @@ -293,34 +274,16 @@ protected static function getReportCards(): array 'label' => __('Usage abiotic depletion potential', 'carbon'), 'provider' => Provider::class . '::getUsageAbioticDepletion', ], - - // Embodied impact - // 'plugin_carbon_report_embodied_global_warming' => [ - // 'widgettype' => ['embodied_global_warming'], - // 'group' => $group, - // 'label' => __('Embodied global warming potential', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedGlobalWarming', - // ], - // 'plugin_carbon_report_embodied_abiotic_depletion' => [ - // 'widgettype' => ['embodied_abiotic_depletion'], - // 'group' => $group, - // 'label' => __('Embodied abiotic depletion potential', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedAbioticDepletion', - // ], - // 'plugin_carbon_report_embodied_pe_impact' => [ - // 'widgettype' => ['embodied_primary_energy'], - // 'group' => $group, - // 'label' => __('Embodied primary energy consumed', 'carbon'), - // 'provider' => Provider::class . '::getEmbodiedPrimaryEnergy', - // ], ]; + + // Embodied impact foreach (Type::getImpactTypes() as $impact_type) { $key = "plugin_carbon_report_embodied_{$impact_type}_impact"; if (isset($new_cards[$key])) { trigger_error("The card $key already exists", E_USER_WARNING); } $new_cards[$key] = [ - 'widgettype' => [self::getWidgetForImpact(true, $impact_type)], + 'widgettype' => ['impact_criteria_number'], 'group' => $group, 'label' => Type::getEmbodiedImpactLabel($impact_type), 'provider' => Provider::class . '::getImpactOfEmbodiedCriteria', @@ -351,13 +314,13 @@ private static function getWidgetForImpact(bool $embodied, string $type): string { switch (($embodied ? 'embodied' : 'usage') . ' ' . $type) { case 'embodied gwp': - return 'embodied_global_warming'; + return 'impact_criteria_number'; case 'embodied adp': - return 'embodied_abiotic_depletion'; + return 'impact_criteria_number'; case 'embodied pe': - return 'embodied_primary_energy'; + return 'impact_criteria_number'; } - return ''; + return 'bigNumber'; } } diff --git a/src/Dashboard/Provider.php b/src/Dashboard/Provider.php index 16d830c3..75df4264 100644 --- a/src/Dashboard/Provider.php +++ b/src/Dashboard/Provider.php @@ -53,6 +53,7 @@ use GlpiPlugin\Carbon\UsageImpact; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; +use GlpiPlugin\Carbon\Documentation; use GlpiPlugin\Carbon\Impact\Type; use Search; use Session; @@ -962,7 +963,7 @@ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $p { $default_params = [ 'label' => Type::getEmbodiedImpactLabel($impact_type), - 'icon' => 'fa-solid fa-temperature-arrow-up', + 'icon' => Type::getCriteriaIcon($impact_type), ]; $params = array_merge($default_params, $params); if (count($crit['itemtype'] ?? []) === 0) { @@ -975,7 +976,6 @@ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $p if ($value === null) { $value = 'N/A'; } else { - // $value = Toolbox::getWeight($value) . Type::getImpactUnit($impact_type); $value = Toolbox::getHumanReadableValue( $value, Type::getImpactUnit($impact_type) @@ -986,6 +986,9 @@ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $p 'number' => $value, 'label' => $params['label'], 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), ]; } diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index dd639208..c73eb0bd 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -107,27 +107,34 @@ public static function WidgetTypes(): array ], // Embodied impact - 'embodied_global_warming' => [ - 'label' => __('Embodied carbon emission', 'carbon'), - 'function' => self::class . '::displayEmbodiedCarbonEmission', - 'image' => '', - 'width' => 6, - 'height' => 3, - ], - 'embodied_abiotic_depletion' => [ - 'label' => __('Embodied abiotic depletion potential', 'carbon'), - 'function' => self::class . '::displayEmbodiedAbioticDepletion', - 'image' => '', - 'width' => 6, - 'height' => 3, - ], - 'embodied_primary_energy' => [ - 'label' => __('Embodied consumed primary energy', 'carbon'), - 'function' => self::class . '::displayEmbodiedPrimaryEnergy', + // 'embodied_global_warming' => [ + // 'label' => __('Embodied carbon emission', 'carbon'), + // 'function' => self::class . '::displayEmbodiedCarbonEmission', + // 'image' => '', + // 'width' => 6, + // 'height' => 3, + // ], + // 'embodied_abiotic_depletion' => [ + // 'label' => __('Embodied abiotic depletion potential', 'carbon'), + // 'function' => self::class . '::displayEmbodiedAbioticDepletion', + // 'image' => '', + // 'width' => 6, + // 'height' => 3, + // ], + // 'embodied_primary_energy' => [ + // 'label' => __('Embodied consumed primary energy', 'carbon'), + // 'function' => self::class . '::displayEmbodiedPrimaryEnergy', + // 'image' => '', + // 'width' => 6, + // 'height' => 3, + // ], + 'impact_criteria_number' => [ + 'label' => __('Impact criteria', 'carbon'), + 'function' => self::class . '::displayImpactCriteriaNumber', 'image' => '', 'width' => 6, 'height' => 3, - ], + ] ]; // Data diagnostic @@ -830,59 +837,25 @@ public static function displayUsageCarbonEmissionYearToDate(array $params = []): ]); } - public static function displayEmbodiedCarbonEmission(array $params = []): string + public static function displayImpactCriteriaNumber(array $params = []): string { $default = [ - 'number' => 0, 'url' => '', 'label' => '', 'alt' => '', 'color' => '', 'icon' => '', - 'id' => 'plugin_carbon_embodied_carbon_emission_' . mt_rand(), + 'id' => 'plugin_carbon_embodied_primary_energy_' . mt_rand(), 'filters' => [], // TODO: Not implemented yet (is this useful ?) ]; $p = array_merge($default, $params); - $url = Documentation::getInfoLink('carbon_emission'); - $tooltip = __('Evaluates the carbon emission in CO₂ equivalent. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); - $tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => $p['id'] . '_tip', - ]); - - $label_color = '#626976'; - $fg_color = GlpiToolbox::getFgColor($p['color']); - return TemplateRenderer::getInstance()->render('@carbon/dashboard/embodied-carbon-emission.html.twig', [ - 'id' => $p['id'], - 'color' => $p['color'], - 'fg_color' => $fg_color, - 'fg_hover_color' => GlpiToolbox::getFgColor($p['color'], 15), - 'fg_hover_border' => GlpiToolbox::getFgColor($p['color'], 30), - 'label_color' => Toolbox::getAdaptedFgColor($p['color'], $label_color, 4), - 'dark_label_color' => Toolbox::getAdaptedFgColor($fg_color, $label_color, 4), - 'number' => $p['number'], - 'tooltip_html' => $tooltip_html, - ]); - } - - public static function displayEmbodiedAbioticDepletion(array $params = []): string - { - $default = [ - 'number' => 0, - 'url' => '', - 'label' => '', - 'alt' => '', - 'color' => '', - 'icon' => '', - 'id' => 'plugin_carbon_embodied_abiotic_depletion_' . mt_rand(), - 'filters' => [], // TODO: Not implemented yet (is this useful ?) - ]; - $p = array_merge($default, $params); - $url = Documentation::getInfoLink('abiotic_depletion_impact'); - $tooltip = __('Evaluates the consumption of non renewable resources in Antimony equivalent. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); + $url = Documentation::getInfoLink('primary_energy_impact'); + $url = $p['doc_url']; + $tooltip = $p['tooltip']; + $tooltip .= '
' + . __('More information', 'carbon') + . ''; $tooltip_html = Html::showToolTip($tooltip, [ 'display' => false, 'applyto' => $p['id'] . '_tip', @@ -890,7 +863,7 @@ public static function displayEmbodiedAbioticDepletion(array $params = []): stri $label_color = '#626976'; $fg_color = GlpiToolbox::getFgColor($p['color']); - return TemplateRenderer::getInstance()->render('@carbon/dashboard/embodied-abiotic-depletion.html.twig', [ + return TemplateRenderer::getInstance()->render('@carbon/dashboard/embodied-impact-criteria.html.twig', [ 'id' => $p['id'], 'color' => $p['color'], 'fg_color' => $fg_color, @@ -898,8 +871,10 @@ public static function displayEmbodiedAbioticDepletion(array $params = []): stri 'fg_hover_border' => GlpiToolbox::getFgColor($p['color'], 30), 'label_color' => Toolbox::getAdaptedFgColor($p['color'], $label_color, 4), 'dark_label_color' => Toolbox::getAdaptedFgColor($fg_color, $label_color, 4), + 'label' => $p['label'], 'number' => $p['number'], 'tooltip_html' => $tooltip_html, + 'pictogram_file' => $p['pictogram_file'], ]); } @@ -1085,42 +1060,6 @@ public static function displayInformationMethodology(array $params = []): string ]); } - public static function displayEmbodiedPrimaryEnergy(array $params = []): string - { - $default = [ - 'url' => '', - 'label' => __('Total embodied primary energy', 'carbon'), - 'alt' => '', - 'color' => '', - 'icon' => '', - 'id' => 'plugin_carbon_embodied_primary_energy_' . mt_rand(), - 'filters' => [], // TODO: Not implemented yet (is this useful ?) - ]; - $p = array_merge($default, $params); - - $url = Documentation::getInfoLink('primary_energy_impact'); - $tooltip = __('Evaluates the primary energy consumed. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); - $tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => $p['id'] . '_tip', - ]); - - $label_color = '#626976'; - $fg_color = GlpiToolbox::getFgColor($p['color']); - return TemplateRenderer::getInstance()->render('@carbon/dashboard/embodied-primary-energy.html.twig', [ - 'id' => $p['id'], - 'color' => $p['color'], - 'fg_color' => $fg_color, - 'fg_hover_color' => GlpiToolbox::getFgColor($p['color'], 15), - 'fg_hover_border' => GlpiToolbox::getFgColor($p['color'], 30), - 'label_color' => Toolbox::getAdaptedFgColor($p['color'], $label_color, 4), - 'dark_label_color' => Toolbox::getAdaptedFgColor($fg_color, $label_color, 4), - 'number' => $p['number'], - 'tooltip_html' => $tooltip_html, - ]); - } - /** * Displays a widget with a radar (or web) chart * diff --git a/src/Documentation.php b/src/Documentation.php deleted file mode 100644 index 6b32cbff..00000000 --- a/src/Documentation.php +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * ------------------------------------------------------------------------- - */ - -namespace GlpiPlugin\Carbon; - -class Documentation -{ - private const BASE_URL = 'https://glpi-plugins.readthedocs.io/%s/latest/carbon'; - /** - * Get external URL to a detailed description of the given path - * - * @param string $object_descriptor - * @return string - */ - public static function getInfoLink(string $object_descriptor): string - { - // $lang = substr($_SESSION['glpilanguage'], 0, 2); - $lang = 'en'; - $base_url = sprintf( - self::BASE_URL, - $lang - ); - switch ($object_descriptor) { - case 'abiotic_depletion_impact': - return "$base_url/types_of_impact.html#antimony-equivalent"; - case 'primary_energy_impact': - return "$base_url/carbon/types_of_impact.html#primary-energy"; - case 'carbon_emission': - return "$base_url/carbon/types_of_impact.html#carbon-dioxyde-equivalent"; - } - - return ''; - } -} diff --git a/src/Impact/Type.php b/src/Impact/Type.php index b127512d..6ec59dbc 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -35,6 +35,9 @@ class Type { + + private const BASE_URL = 'https://glpi-plugins.readthedocs.io/%s/latest/carbon'; + const IMPACT_GWP = 1; // Global warming potential const IMPACT_ADP = 2; // Abiotic Depletion Potential const IMPACT_PE = 3; // Primary Energy @@ -105,7 +108,7 @@ class Type 'adpe' => ['g', 'SB eq'], 'adpf' => ['J', ''], 'ap' => ['mol', 'H+ eq'], - 'ctue' => ['J', ''], + 'ctue' => null, // 'ctuh_c' => [null, 'CTUh'], // 'ctuh_nc' => [null, 'CTUh'], 'epf' => ['g', 'P eq'], @@ -170,7 +173,7 @@ public static function getEmbodiedImpactLabel(string $type): string 'adpe' => __('Embodied Use of mineral and metal resources', 'carbon'), 'adpf' => __('Embodied Use of fossil resources (including nuclear)', 'carbon'), 'ap' => __('Embodied Acidification', 'carbon'), - 'ctue' => __('Embodied Human Toxicity - Carcinogenic Effects', 'carbon'), + 'ctue' => __('Embodied Freshwater ecotoxicity', 'carbon'), // 'ctuh_c' => __('Embodied Human Toxicity - Carcinogenic Effects', 'carbon'), // 'ctuh_nc' => __('Embodied Human toxicity - non-carcinogenic effects', 'carbon'), 'epf' => __('Embodied Eutrophication of freshwater', 'carbon'), @@ -206,7 +209,7 @@ public static function getUsageImpactLabel(string $type): string 'adpe' => __('Usage Use of mineral and metal resources', 'carbon'), 'adpf' => __('Usage Use of fossil resources (including nuclear)', 'carbon'), 'ap' => __('Usage Acidification', 'carbon'), - 'ctue' => __('Usage Human Toxicity - Carcinogenic Effects', 'carbon'), + 'ctue' => __('Usage Freshwater ecotoxicity', 'carbon'), // 'ctuh_c' => __('Usage Human Toxicity - Carcinogenic Effects', 'carbon'), // 'ctuh_nc' => __('Usage Human toxicity - non-carcinogenic effects', 'carbon'), 'epf' => __('Usage Eutrophication of freshwater', 'carbon'), @@ -216,4 +219,151 @@ public static function getUsageImpactLabel(string $type): string }; return $label; } + + public static function getCriteriaIcon(string $type): string + { + return match ($type) { + // Global Warming Potential + 'gwp', 'gwppb', 'gwppf', 'gwpplu' + => 'fa-solid fa-temperature-high', + + // Abiotic depletion (minerals / fossil) + 'adp', 'adpe' + => 'fa-solid fa-gem', + 'adpf' + => 'fa-solid fa-oil-can', + + // Primary energy + 'pe' + => 'fa-solid fa-bolt', + + // Ionising radiation + 'ir' + => 'fa-solid fa-radiation', + + // Land use + 'lu' + => 'fa-solid fa-tree', + + // Ozone depletion + 'odp' + => 'fa-solid fa-cloud', + + // Particulate matter + 'pm' + => 'fa-solid fa-smog', + + // Photochemical ozone creation + 'pocp' + => 'fa-solid fa-sun', + + // Water use + 'wu' + => 'fa-solid fa-droplet', + + // Material input per service unit + 'mips' + => 'fa-solid fa-boxes-stacked', + + // Acidification + 'ap' + => 'fa-solid fa-flask', + + // Ecotoxicity (freshwater, marine, terrestrial) + 'epf', 'epm', 'ept' + => 'fa-solid fa-fish', + + // Human toxicity / ecotoxicity + 'ctue', 'ctuh_c', 'ctuh_nc' + => 'fa-solid fa-skull-crossbones', + + default + => '', // or 'fa-solid fa-circle-question', + }; + } + + public static function getCriteriaTooltip(string $type): string + { + return match ($type) { + 'gwp' => __('Carbon emission in CO₂ equivalent', 'carbon'), + 'adp' => __('Consumption of non renewable resources in Antimony equivalent.', 'carbon'), + 'pe' => __('Primary energy consumed.', 'carbon'), + 'gwppb' => __('', 'carbon'), + 'gwppf' => __('', 'carbon'), + 'gwpplu' => __('', 'carbon'), + 'ir' => __('', 'carbon'), + 'lu' => __('', 'carbon'), + 'odp' => __('', 'carbon'), + 'pm' => __('', 'carbon'), + 'pocp' => __('', 'carbon'), + 'wu' => __('', 'carbon'), + 'mips' => __('', 'carbon'), + 'adpe' => __('', 'carbon'), + 'adpf' => __('', 'carbon'), + 'ap' => __('', 'carbon'), + 'ctue' => __('', 'carbon'), + // 'ctuh_c' => __('', 'carbon'), + // 'ctuh_nc' => __('', 'carbon'), + 'epf' => __('Usage Eutrophication of freshwater', 'carbon'), + 'epm' => __('Usage Eutrophication of marine waters', 'carbon'), + 'ept' => __('Usage Terrestrial eutrophication', 'carbon'), + default => '' + }; + } + + public static function getCriteriaPictogram(string $type): string + { + $pictogram_file = match ($type) { + 'gwp' => 'icon-carbon-emission.svg', + 'adp' => 'icon-fossil-primary-energy.svg', + 'pe' => 'icon-pickaxe.svg', + 'gwppb' => '', + 'gwppf' => '', + 'gwpplu' => '', + 'ir' => '', + 'lu' => '', + 'odp' => '', + 'pm' => '', + 'pocp' => '', + 'wu' => '', + 'mips' => '', + 'adpe' => '', + 'adpf' => '', + 'ap' => '', + 'ctue' => '', + // 'ctuh_c' => '', + // 'ctuh_nc' => '', + 'epf' => '', + 'epm' => '', + 'ept' => '', + default => '' + }; + return $pictogram_file; + } + + /** + * Get external URL to a detailed description of the given path + * + * @param string $object_descriptor + * @return string + */ + public static function getCriteriaInfoLink(string $impact_type): string + { + // $lang = substr($_SESSION['glpilanguage'], 0, 2); + $lang = 'en'; + $base_url = sprintf( + self::BASE_URL, + $lang + ); + switch ($impact_type) { + case 'gwp': + return "$base_url/carbon/types_of_impact.html#carbon-dioxyde-equivalent"; + case 'adp': + return "$base_url/types_of_impact.html#antimony-equivalent"; + case 'pe': + return "$base_url/carbon/types_of_impact.html#primary-energy"; + } + + return ''; + } } diff --git a/templates/dashboard/embodied-abiotic-depletion.html.twig b/templates/dashboard/embodied-abiotic-depletion.html.twig deleted file mode 100644 index 71201be5..00000000 --- a/templates/dashboard/embodied-abiotic-depletion.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -{# - # ------------------------------------------------------------------------- - # Carbon plugin for GLPI - # - # @copyright Copyright (C) 2024-2025 Teclib' and contributors. - # @license https://www.gnu.org/licenses/gpl-3.0.txt GPLv3+ - # @license MIT https://opensource.org/licenses/mit-license.php - # @link https://github.com/pluginsGLPI/carbon - # - # ------------------------------------------------------------------------- - # - # LICENSE - # - # This file is part of Carbon plugin for GLPI. - # - # This program is free software: you can redistribute it and/or modify - # it under the terms of the GNU General Public License as published by - # the Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # This program is distributed in the hope that it will be useful, - # but WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - # GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License - # along with this program. If not, see . - # - # ------------------------------------------------------------------------- - #} - - -
-
-
-
- - {{ source('@carbon/dashboard/icon-pickaxe.svg') }} - -
-
-
{{ __('Embodied abiotic depletion potential', 'carbon') }}
-
-
{{ number|raw }}
-
- - - -
-
-
-
- -{{ tooltip_html|raw }} diff --git a/templates/dashboard/embodied-carbon-emission.html.twig b/templates/dashboard/embodied-impact-criteria.html.twig similarity index 85% rename from templates/dashboard/embodied-carbon-emission.html.twig rename to templates/dashboard/embodied-impact-criteria.html.twig index e530f997..32c803b2 100644 --- a/templates/dashboard/embodied-carbon-emission.html.twig +++ b/templates/dashboard/embodied-impact-criteria.html.twig @@ -74,11 +74,13 @@
- {{ source('@carbon/dashboard/icon-carbon-emission.svg') }} + {% if pictogram_file != '' %} + {{ source('@carbon/dashboard/' ~ pictogram_file) }} + {% endif %}
-
{{ __('Embodied carbon emission', 'carbon') }}
+
{{ label }}
{{ number|raw }}
@@ -90,13 +92,13 @@
{{ tooltip_html|raw }} diff --git a/templates/dashboard/embodied-primary-energy.html.twig b/templates/dashboard/embodied-primary-energy.html.twig deleted file mode 100644 index cf201e70..00000000 --- a/templates/dashboard/embodied-primary-energy.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -{# - # ------------------------------------------------------------------------- - # Carbon plugin for GLPI - # - # @copyright Copyright (C) 2024-2025 Teclib' and contributors. - # @license https://www.gnu.org/licenses/gpl-3.0.txt GPLv3+ - # @license MIT https://opensource.org/licenses/mit-license.php - # @link https://github.com/pluginsGLPI/carbon - # - # ------------------------------------------------------------------------- - # - # LICENSE - # - # This file is part of Carbon plugin for GLPI. - # - # This program is free software: you can redistribute it and/or modify - # it under the terms of the GNU General Public License as published by - # the Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # This program is distributed in the hope that it will be useful, - # but WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - # GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License - # along with this program. If not, see . - # - # ------------------------------------------------------------------------- - #} - - -
-
-
-
- - {{ source('@carbon/dashboard/icon-fossil-primary-energy.svg') }} - -
-
-
{{ __('Embodied primary energy', 'carbon') }}
-
-
{{ number|raw }}
-
- - - -
-
-
-
- -{{ tooltip_html|raw }} From 133165658f75e5734bff8253d4dabd53d3938e7d Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Tue, 24 Feb 2026 10:32:17 +0100 Subject: [PATCH 13/25] Install: update reporting page configuration --- install/data/report_dashboard.json | 10 +++---- .../09_add_impact_criterias.php | 28 ++++++++++++++++++- src/Dashboard/Grid.php | 14 ---------- src/Dashboard/Provider.php | 1 - src/Dashboard/Widget.php | 10 +++---- src/Impact/Type.php | 3 +- src/UsageInfo.php | 7 +++-- 7 files changed, 42 insertions(+), 31 deletions(-) diff --git a/install/data/report_dashboard.json b/install/data/report_dashboard.json index 0c561512..bbfd65f6 100644 --- a/install/data/report_dashboard.json +++ b/install/data/report_dashboard.json @@ -77,27 +77,27 @@ "point_labels": "0" } }, - "plugin_carbon_report_embodied_abiotic_depletion": { + "plugin_carbon_report_embodied_adp_impact": { "x": 5, "y": 3, "width": 5, "height": 3, "card_options": { "color": "#ffd966", - "widgettype": "embodied_abiotic_depletion", + "widgettype": "impact_criteria_number", "use_gradient": "0", "point_labels": "0", "limit": "7" } }, - "plugin_carbon_report_embodied_global_warming": { + "plugin_carbon_report_embodied_gwp_impact": { "x": 10, "y": 0, "width": 5, "height": 3, "card_options": { "color": "#7a941e", - "widgettype": "embodied_global_warming", + "widgettype": "impact_criteria_number", "use_gradient": "0", "point_labels": "0", "limit": "7" @@ -110,7 +110,7 @@ "height": 3, "card_options": { "color": "#326319", - "widgettype": "embodied_primary_energy", + "widgettype": "impact_criteria_number", "use_gradient": "0", "point_labels": "0", "limit": "7" 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 4d718d4b..5da8e804 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 @@ -97,12 +97,17 @@ // Rename cards for the report $dashboard_item = new DashboardItem(); $rows = $dashboard_item->find([ - 'card_id' => 'plugin_carbon_report_embodied_gwp_impact' + 'card_id' => 'plugin_carbon_report_embodied_global_warming' ]); foreach ($rows as $row) { + $card_options = json_decode($row['card_options'], true); + if ($card_options['widgettype'] === 'embodied_global_warming') { + $card_options['widgettype'] = 'impact_criteria_number'; + } $dashboard_item->update([ 'id' => $row['id'], 'card_id' => 'plugin_carbon_report_embodied_gwp_impact', + 'card_options' => json_encode($card_options), ]); } @@ -111,8 +116,29 @@ 'card_id' => 'plugin_carbon_report_embodied_abiotic_depletion' ]); foreach ($rows as $row) { + $card_options = json_decode($row['card_options'], true); + if ($card_options['widgettype'] === 'embodied_abiotic_depletion') { + $card_options['widgettype'] = 'impact_criteria_number'; + } $dashboard_item->update([ 'id' => $row['id'], 'card_id' => 'plugin_carbon_report_embodied_adp_impact', + 'card_options' => json_encode($card_options), + ]); +} + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_report_embodied_pe_impact' +]); +foreach ($rows as $row) { + $card_options = json_decode($row['card_options'], true); + if ($card_options['widgettype'] === 'embodied_primary_energy') { + $card_options['widgettype'] = 'impact_criteria_number'; + } + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_report_embodied_pe_impact', + 'card_options' => json_encode($card_options), ]); } diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index c9284c5e..a8056a65 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -309,18 +309,4 @@ protected static function getReportCards(): array return $new_cards; } - - private static function getWidgetForImpact(bool $embodied, string $type): string - { - switch (($embodied ? 'embodied' : 'usage') . ' ' . $type) { - case 'embodied gwp': - return 'impact_criteria_number'; - case 'embodied adp': - return 'impact_criteria_number'; - case 'embodied pe': - return 'impact_criteria_number'; - } - - return 'bigNumber'; - } } diff --git a/src/Dashboard/Provider.php b/src/Dashboard/Provider.php index 75df4264..ee84bd93 100644 --- a/src/Dashboard/Provider.php +++ b/src/Dashboard/Provider.php @@ -53,7 +53,6 @@ use GlpiPlugin\Carbon\UsageImpact; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; -use GlpiPlugin\Carbon\Documentation; use GlpiPlugin\Carbon\Impact\Type; use Search; use Session; diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index c73eb0bd..103287db 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -40,6 +40,7 @@ use Glpi\Application\View\TemplateRenderer; use Glpi\Dashboard\Widget as GlpiDashboardWidget; use GlpiPlugin\Carbon\Documentation; +use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Report; use GlpiPlugin\Carbon\Toolbox; use Monitor; @@ -744,7 +745,7 @@ public static function displayMonthlyCarbonEmission(array $params = []): string $last_month['series'][0]['unit'] ); - $url = Documentation::getInfoLink('carbon_emission'); + $url = Type::getCriteriaInfoLink('gwp'); $tooltip = __('Evaluates the usage carbon emission in CO₂ equivalent during the last 2 months. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $tooltip_html = Html::showToolTip($tooltip, [ @@ -810,7 +811,7 @@ public static function displayUsageCarbonEmissionYearToDate(array $params = []): break; } - $url = Documentation::getInfoLink('carbon_emission'); + $url = Type::getCriteriaInfoLink('gwp'); $tooltip = __('Evaluates the usage carbon emission in CO₂ equivalent during the last 12 elapsed months. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $tooltip_html = Html::showToolTip($tooltip, [ @@ -845,12 +846,11 @@ public static function displayImpactCriteriaNumber(array $params = []): string 'alt' => '', 'color' => '', 'icon' => '', - 'id' => 'plugin_carbon_embodied_primary_energy_' . mt_rand(), + 'id' => 'plugin_carbon_impact_criteria_' . mt_rand(), 'filters' => [], // TODO: Not implemented yet (is this useful ?) ]; $p = array_merge($default, $params); - $url = Documentation::getInfoLink('primary_energy_impact'); $url = $p['doc_url']; $tooltip = $p['tooltip']; $tooltip .= '
' @@ -892,7 +892,7 @@ public static function displayUsageAbioticDepletion(array $params = []): string ]; $p = array_merge($default, $params); - $url = Documentation::getInfoLink('abiotic_depletion_impact'); + $url = Type::getCriteriaInfoLink('adp'); $tooltip = __('Evaluates the consumption of non renewable resources in Antimony equivalent. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $tooltip_html = Html::showToolTip($tooltip, [ diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 6ec59dbc..106c7e41 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -35,7 +35,6 @@ class Type { - private const BASE_URL = 'https://glpi-plugins.readthedocs.io/%s/latest/carbon'; const IMPACT_GWP = 1; // Global warming potential @@ -344,7 +343,7 @@ public static function getCriteriaPictogram(string $type): string /** * Get external URL to a detailed description of the given path * - * @param string $object_descriptor + * @param string $impact_type * @return string */ public static function getCriteriaInfoLink(string $impact_type): string diff --git a/src/UsageInfo.php b/src/UsageInfo.php index b88f1728..3f5c2c6e 100644 --- a/src/UsageInfo.php +++ b/src/UsageInfo.php @@ -39,6 +39,7 @@ use Glpi\Application\View\TemplateRenderer; use GlpiPlugin\Carbon\Dashboard\Provider; use GlpiPlugin\Carbon\Dashboard\Widget; +use GlpiPlugin\Carbon\Impact\Type; use Html; use Monitor; use NetworkEquipment; @@ -214,7 +215,7 @@ public static function showCharts(CommonDBTM $asset) 'items_id' => $asset->getID(), ]); - $url = Documentation::getInfoLink('carbon_emission'); + $url = Type::getCriteriaInfoLink('gwp'); $tooltip = __('Evaluates the carbon emission in CO₂ equivalent. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $carbon_emission_tooltip_html = Html::showToolTip($tooltip, [ @@ -222,7 +223,7 @@ public static function showCharts(CommonDBTM $asset) 'applyto' => 'carbon_emission_tip', ]); - $url = Documentation::getInfoLink('abiotic_depletion_impact'); + $url = Type::getCriteriaInfoLink('adp'); $tooltip = __('Evaluates the consumption of non renewable resources in Antimony equivalent. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $usage_abiotic_depletion_tooltip_html = Html::showToolTip($tooltip, [ @@ -234,7 +235,7 @@ public static function showCharts(CommonDBTM $asset) 'applyto' => 'embodied_abiotic_depletion_tip', ]); - $url = Documentation::getInfoLink('primary_energy'); + $url = Type::getCriteriaInfoLink('pe'); $tooltip = __('Evaluates the primary energy consumed. %s More information %s', 'carbon'); $tooltip = sprintf($tooltip, '
', ''); $embodied_primary_energy_tooltip_html = Html::showToolTip($tooltip, [ From 6846248830be9841c3f134ef52e6ac306e3e70d8 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Tue, 24 Feb 2026 11:36:36 +0100 Subject: [PATCH 14/25] feat(Dashboard\DemoProvider): update --- src/Dashboard/DemoProvider.php | 110 ++++++++++++++---------- tests/uninstall/PluginUninstallTest.php | 2 +- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index e8ca167d..144cd2f6 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -36,6 +36,7 @@ use DateInterval; use DateTime; use DateTimeImmutable; +use GlpiPlugin\Carbon\Impact\Type; use GlpiPlugin\Carbon\Toolbox; use Monitor; use NetworkEquipment; @@ -43,51 +44,30 @@ class DemoProvider { - public static function getEmbodiedGlobalWarming(array $params = []): array - { - $value = 616000000; - $value = Toolbox::getWeight($value) . __('CO₂eq', 'carbon'); - - $params['icon'] = 'fa-solid fa-temperature-arrow-up'; - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - - public static function getEmbodiedPrimaryEnergy(array $params = []): array - { - $value = 491000000; - $value = Toolbox::getEnergy($value / 3600); - - $params['icon'] = 'fa-solid fa-fire-flame-simple'; - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - - public static function getEmbodiedAbioticDepletion(array $params = [], array $crit = []): array - { - $default_params = [ - 'label' => __('Embodied abiotic depletion potential', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', - ]; - $params = array_merge($default_params, $params); - - $value = 12.748; - $value = Toolbox::getWeight($value) . __('Sbeq', 'carbon'); - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } + private static array $impact_values = [ + 'gwp' => 616000000, + 'adp' => 12.748, + 'pe' => 491000000, + 'gwppb' => null, + 'gwppf' => null, + 'gwpplu' => null, + 'ir' => null, + 'lu' => -101, + 'odp' => null, + 'pm' => null, + 'pocp' => null, + 'wu' => null, + 'mips' => null, + 'adpe' => null, + 'adpf' => null, + 'ap' => null, + 'ctue' => null, + // 'ctuh_c' => null, + // 'ctuh_nc' => null, + 'epf' => null, + 'epm' => null, + 'ept' => null, + ]; public static function getUsageAbioticDepletion(array $params = [], array $crit = []): array { @@ -419,4 +399,44 @@ public static function getUsageCarbonEmission(array $params = []): array 'icon' => $params['icon'], ]; } + + /** + * Total embodied abiotic depletion potential in antimony equivalent + * + * @param array $params + * @param array $crit + * @return array + */ + public static function getImpactOfEmbodiedCriteria(string $impact_type, array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => Type::getCriteriaIcon($impact_type), + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::$impact_values[$impact_type]; + if ($value === null) { + $value = 'N/A'; + } else { + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), + ]; + } } diff --git a/tests/uninstall/PluginUninstallTest.php b/tests/uninstall/PluginUninstallTest.php index ad99ad5c..241679c3 100644 --- a/tests/uninstall/PluginUninstallTest.php +++ b/tests/uninstall/PluginUninstallTest.php @@ -36,6 +36,7 @@ use CronTask; use DisplayPreference; use Glpi\Dashboard\Dashboard; +use Glpi\Dashboard\Dashboard_Item; use GlpiPlugin\Carbon\CarbonEmission; use GlpiPlugin\Carbon\CarbonIntensity; use GlpiPlugin\Carbon\Uninstall; @@ -75,7 +76,6 @@ public function testUninstallPlugin() $this->checkConfig(); // $this->checkRequestType(); $this->checkAutomaticAction(); - // $this->checkDashboard(); $this->checkRights(); $this->checkDisplayPrefs(); $this->checkDashboard(); From 4fbf453b5f788c95fbe1155da0b91fde6cb32f51 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 25 Feb 2026 08:59:03 +0100 Subject: [PATCH 15/25] feat(Dashboard\Grid): extend usage and total widgets to all criteria --- .../09_add_impact_criterias.php | 58 ++++++ src/Dashboard/DemoProvider.php | 131 +++++++++--- src/Dashboard/Grid.php | 60 +++--- src/Dashboard/Provider.php | 192 +++++++++--------- src/Impact/Type.php | 36 ++++ src/Report.php | 4 +- 6 files changed, 325 insertions(+), 156 deletions(-) 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 5da8e804..29a6b083 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 @@ -142,3 +142,61 @@ 'card_options' => json_encode($card_options), ]); } + +// Rename cards for the standard dashboard : usage indicators + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_total_usage_power' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_usage_pe_impact', + ]); +} + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_total_usage_carbon_emission' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_usage_gwp_impact', + ]); +} + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_total_usage_adp_impact' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_usage_adp_impact', + ]); +} + +// Rename cards for the standard dashboard : Embodied + usage indicators +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_total_gwp_impact' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_all_scopes_gwp_impact', + ]); +} + +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_total_adp_impact' +]); +foreach ($rows as $row) { + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_all_scopes_adp_impact', + ]); +} diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index 144cd2f6..8c5f68fe 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -44,29 +44,30 @@ class DemoProvider { + /** @var array $impact_values Embodied and usage impact values*/ private static array $impact_values = [ - 'gwp' => 616000000, - 'adp' => 12.748, - 'pe' => 491000000, - 'gwppb' => null, - 'gwppf' => null, - 'gwpplu' => null, - 'ir' => null, - 'lu' => -101, - 'odp' => null, - 'pm' => null, - 'pocp' => null, - 'wu' => null, - 'mips' => null, - 'adpe' => null, - 'adpf' => null, - 'ap' => null, - 'ctue' => null, - // 'ctuh_c' => null, - // 'ctuh_nc' => null, - 'epf' => null, - 'epm' => null, - 'ept' => null, + 'gwp' => [616000000, null], + 'adp' => [12.748, null], + 'pe' => [491000000, null], + 'gwppb' => [null, null], + 'gwppf' => [null, null], + 'gwpplu' => [null, null], + 'ir' => [null, null], + 'lu' => [-101, null], + 'odp' => [null, null], + 'pm' => [null, null], + 'pocp' => [null, null], + 'wu' => [null, null], + 'mips' => [null, null], + 'adpe' => [null, null], + 'adpf' => [null, null], + 'ap' => [null, null], + 'ctue' => [null, null], + // 'ctuh_c' => [null, null], + // 'ctuh_nc' => [null, null], + 'epf' => [null, null], + 'epm' => [null, null], + 'ept' => [null, null], ]; public static function getUsageAbioticDepletion(array $params = [], array $crit = []): array @@ -400,8 +401,8 @@ public static function getUsageCarbonEmission(array $params = []): array ]; } - /** - * Total embodied abiotic depletion potential in antimony equivalent + /** + * Get the value of an impact criteria for the embodied scope * * @param array $params * @param array $crit @@ -420,7 +421,87 @@ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $p $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); } - $value = self::$impact_values[$impact_type]; + $value = self::$impact_values[$impact_type][0]; + if ($value === null) { + $value = 'N/A'; + } else { + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), + ]; + } + + /** + * Get the value of an impact criteria for the usage scope + * + * @param array $params + * @param array $crit + * @return array + */ + public static function getImpactOfUsageCriteria(string $impact_type, array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => Type::getCriteriaIcon($impact_type), + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::$impact_values[$impact_type][1]; + if ($value === null) { + $value = 'N/A'; + } else { + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), + ]; + } + + /** + * Get the value of an impact criteria for the embodied + usage scopes + * + * @param array $params + * @param array $crit + * @return array + */ + public static function getImpactOfEmbodiedAndUsageCriteria(string $impact_type, array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => Type::getCriteriaIcon($impact_type), + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::$impact_values[$impact_type][1]; if ($value === null) { $value = 'N/A'; } else { diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index a8056a65..80e88879 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -171,47 +171,45 @@ protected static function getStandardCards(): array ]; } - $new_cards += [ - // Usage impact - 'plugin_carbon_total_usage_power' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Usage power consumption', 'carbon'), - 'provider' => Provider::class . '::getUsagePower', - ], - 'plugin_carbon_total_usage_carbon_emission' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Usage carbon emission', 'carbon'), - 'provider' => Provider::class . '::getUsageCarbonEmission', - ], - 'plugin_carbon_total_usage_adp_impact' => [ + // Usage impact + foreach (Type::getImpactTypes() as $impact_type) { + $key = "plugin_carbon_usage_{$impact_type}_impact"; + if (isset($new_cards[$key])) { + trigger_error("The card $key already exists", E_USER_WARNING); + } + $new_cards[$key] = [ 'widgettype' => ['bigNumber'], 'group' => $group, - 'label' => __('Usage abiotic depletion potential', 'carbon'), - 'provider' => Provider::class . '::getUsageAbioticDepletion', - ], + 'label' => Type::getUsageImpactLabel($impact_type), + 'provider' => Provider::class . '::getImpactOfUsageCriteria', + 'args' => [ + 'impact_type' => $impact_type + ] + ]; + } - // embodied + usage impact - 'plugin_carbon_total_gwp_impact' => [ - 'widgettype' => ['bigNumber'], - 'group' => $group, - 'label' => __('Global warming potential', 'carbon'), - 'provider' => Provider::class . '::getTotalGlobalWarming', - ], - 'plugin_carbon_total_adp_impact' => [ + // embodied + usage impact + foreach (Type::getImpactTypes() as $impact_type) { + $key = "plugin_carbon_all_scopes_{$impact_type}_impact"; + if (isset($new_cards[$key])) { + trigger_error("The card $key already exists", E_USER_WARNING); + } + $new_cards[$key] = [ 'widgettype' => ['bigNumber'], 'group' => $group, - 'label' => __('Abiotic depletion potential', 'carbon'), - 'provider' => Provider::class . '::getTotalAbioticDepletion', - ], - ]; + 'label' => Type::getEmbodiedAndUsageImpactLabel($impact_type), + 'provider' => Provider::class . '::getImpactOfEmbodiedAndUsageCriteria', + 'args' => [ + 'impact_type' => $impact_type + ] + ]; + } return $new_cards; } /** - * getdescription of cards for dashboard in the reporting page of the plugin + * Get description of cards for dashboard in the reporting page of the plugin * * @return array */ diff --git a/src/Dashboard/Provider.php b/src/Dashboard/Provider.php index ee84bd93..1df8619a 100644 --- a/src/Dashboard/Provider.php +++ b/src/Dashboard/Provider.php @@ -719,6 +719,81 @@ public static function getUsageCarbonEmission(array $params = []): array ]; } + /** + * Get usage abiotic depletion potential in antimony equivalent + * + * @param array $params + * @param array $crit + * @return array + */ + public static function getUsageAbioticDepletion(array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => __('Usage abiotic depletion potential', 'carbon'), + 'icon' => 'fa-solid fa-temperature-arrow-up', + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::getSum(UsageImpact::getTable(), 'adp', $params, $crit); + if ($value === null) { + $value = 'N/A'; + } else { + $value = Toolbox::getWeight($value) . __('Sbeq', 'carbon'); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + ]; + } + + /** + * Get usage impact for the given criteria + * + * @param string $impact_type Impact type identifier + * @param array $params + * @param array $crit + * @return array + */ + public static function getImpactOfUsageCriteria(string $impact_type, array $params = [], array $crit = []): array + { + $default_params = [ + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => Type::getCriteriaIcon($impact_type), + ]; + $params = array_merge($default_params, $params); + if (count($crit['itemtype'] ?? []) === 0) { + $crit['itemtype'] = PLUGIN_CARBON_TYPES; + } else { + $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); + } + + $value = self::getSum(UsageImpact::getTable(), 'adp', $params, $crit); + if ($value === null) { + $value = 'N/A'; + } else { + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); + } + + return [ + 'number' => $value, + 'label' => $params['label'], + 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), + ]; + } + /** * Get the usage carbon emission of the 12 last elapsed months * @@ -864,98 +939,12 @@ public static function getFiltersCriteria(string $table = "", array $apply_filte return $criteria; } - public static function getEmbodiedGlobalWarming(array $params = []): array - { - $default_params = [ - 'label' => __('Total embodied global warming potential', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', - ]; - $params = array_merge($default_params, $params); - - $crit = [ - 'itemtype' => PLUGIN_CARBON_TYPES, - ]; - $value = self::getSum(EmbodiedImpact::getTable(), 'gwp', $params, $crit); - if ($value === null) { - $value = 'N/A'; - } else { - $value = Toolbox::getWeight($value) . __('CO₂eq', 'carbon'); - } - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - - /** - * Get the primary energy consumed to build assets - * - * @param array $params - * @return array - */ - public static function getEmbodiedPrimaryEnergy(array $params = []): array - { - $crit = [ - 'itemtype' => PLUGIN_CARBON_TYPES, - ]; - $value = self::getSum(EmbodiedImpact::getTable(), 'pe', $params, $crit); - if ($value === null) { - $value = 'N/A'; - } else { - // Convert into Watt.hour - $value = Toolbox::getEnergy($value / 3600); - } - - $params['icon'] = 'fa-solid fa-fire-flame-simple'; - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - - /** - * Total embodied abiotic depletion potential in antimony equivalent - * - * @param array $params - * @param array $crit - * @return array - */ - public static function getEmbodiedAbioticDepletion(array $params = [], array $crit = []): array - { - $default_params = [ - 'label' => __('Embodied abiotic depletion potential', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', - ]; - $params = array_merge($default_params, $params); - - if (count($crit['itemtype'] ?? []) === 0) { - $crit['itemtype'] = PLUGIN_CARBON_TYPES; - } else { - $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); - } - $value = self::getSum(EmbodiedImpact::getTable(), 'adp', $params, $crit); - if ($value === null) { - $value = 'N/A'; - } else { - $value = Toolbox::getWeight($value) . __('Sbeq', 'carbon'); - } - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - /** - * Total embodied abiotic depletion potential in antimony equivalent + * Total embodied impact for the given criteria * - * @param array $params - * @param array $crit + * @param string $impact_type Impact type identifier + * @param array $params + * @param array $crit * @return array */ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $params = [], array $crit = []): array @@ -992,17 +981,18 @@ public static function getImpactOfEmbodiedCriteria(string $impact_type, array $p } /** - * Get usage abiotic depletion potential in antimony equivalent + * Get the value of an impact criteria for the embodied + usage scopes * - * @param array $params - * @param array $crit + * @param string $impact_type Impact type identifier + * @param array $params + * @param array $crit * @return array */ - public static function getUsageAbioticDepletion(array $params = [], array $crit = []): array + public static function getImpactOfEmbodiedAndUsageCriteria(string $impact_type, array $params = [], array $crit = []): array { $default_params = [ - 'label' => __('Usage abiotic depletion potential', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', + 'label' => Type::getEmbodiedImpactLabel($impact_type), + 'icon' => Type::getCriteriaIcon($impact_type), ]; $params = array_merge($default_params, $params); if (count($crit['itemtype'] ?? []) === 0) { @@ -1011,17 +1001,23 @@ public static function getUsageAbioticDepletion(array $params = [], array $crit $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); } - $value = self::getSum(UsageImpact::getTable(), 'adp', $params, $crit); + $value = self::getSum(EmbodiedImpact::getTable(), $impact_type, $params, $crit); if ($value === null) { $value = 'N/A'; } else { - $value = Toolbox::getWeight($value) . __('Sbeq', 'carbon'); + $value = Toolbox::getHumanReadableValue( + $value, + Type::getImpactUnit($impact_type) + ); } return [ 'number' => $value, 'label' => $params['label'], 'icon' => $params['icon'], + 'tooltip' => Type::getCriteriaTooltip($impact_type), + 'pictogram_file' => Type::getCriteriaPictogram($impact_type), + 'doc_url' => Type::getCriteriaInfoLink($impact_type), ]; } diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 106c7e41..58d9a1af 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -219,6 +219,42 @@ public static function getUsageImpactLabel(string $type): string return $label; } + /** + * Get the unit of an impact type + * + * @param string $type impact type name + * @return string + **/ + public static function getEmbodiedAndUsageImpactLabel(string $type): string + { + $label = match ($type) { + 'gwp' => __('Total Global warming potential', 'carbon'), + 'adp' => __('Total Abiotic depletion potential', 'carbon'), + 'pe' => __('Total Primary energy consumed', 'carbon'), + 'gwppb' => __('Total Climate change - Contribution of biogenic emissions', 'carbon'), + 'gwppf' => __('Total Climate change - Contribution of fossil fuel emissions', 'carbon'), + 'gwpplu' => __('Total Climate change - Contribution of emissions from land use change', 'carbon'), + 'ir' => __('Total Emissions of radionizing substances', 'carbon'), + 'lu' => __('Total Land use', 'carbon'), + 'odp' => __('Total Depletion of the ozone layer', 'carbon'), + 'pm' => __('Total Fine particle emissions', 'carbon'), + 'pocp' => __('Total Photochemical ozone formation', 'carbon'), + 'wu' => __('Total Use of water resources', 'carbon'), + 'mips' => __('Total Material input per unit of service', 'carbon'), + 'adpe' => __('Total Use of mineral and metal resources', 'carbon'), + 'adpf' => __('Total Use of fossil resources (including nuclear)', 'carbon'), + 'ap' => __('Total Acidification', 'carbon'), + 'ctue' => __('Total Freshwater ecotoxicity', 'carbon'), + // 'ctuh_c' => __('Total Human Toxicity - Carcinogenic Effects', 'carbon'), + // 'ctuh_nc' => __('Total Human toxicity - non-carcinogenic effects', 'carbon'), + 'epf' => __('Total Eutrophication of freshwater', 'carbon'), + 'epm' => __('Total Eutrophication of marine waters', 'carbon'), + 'ept' => __('Total Terrestrial eutrophication', 'carbon'), + default => '', + }; + return $label; + } + public static function getCriteriaIcon(string $type): string { return match ($type) { diff --git a/src/Report.php b/src/Report.php index bbb6f8ed..8b5861a9 100644 --- a/src/Report.php +++ b/src/Report.php @@ -129,7 +129,7 @@ public static function getUsageCarbonEmission(array $params = []): array $end_date = DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $params['args']['apply_filters'][1]); } - $value = Provider::getUsageCarbonEmission($params)['number']; + $value = Provider::getImpactOfUsageCriteria('gwp', $params)['number']; // Prepare date format $date_format = 'Y F'; @@ -166,7 +166,7 @@ public static function getTotalEmbodiedCarbonEmission(array $params = []): array $end_date = DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $params['args']['apply_filters'][1]); } - $value = Provider::getEmbodiedGlobalWarming($params); + $value = Provider::getImpactOfEmbodiedCriteria('gwp', $params); // Prepare date format $date_format = 'Y F'; From 18550ce7bea221746b4cef757788882a7fe253d1 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 25 Feb 2026 13:42:01 +0100 Subject: [PATCH 16/25] refactor(Dashboard): mutualize template for number widget --- .../09_add_impact_criterias.php | 16 +++ src/Dashboard/DemoProvider.php | 45 +------- src/Dashboard/Grid.php | 9 +- src/Dashboard/Widget.php | 2 +- ...ia.html.twig => impact-criteria.html.twig} | 2 +- .../usage-abiotic-depletion.html.twig | 102 ------------------ 6 files changed, 27 insertions(+), 149 deletions(-) rename templates/dashboard/{embodied-impact-criteria.html.twig => impact-criteria.html.twig} (97%) delete mode 100644 templates/dashboard/usage-abiotic-depletion.html.twig 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 29a6b083..f8c8640d 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 @@ -111,6 +111,22 @@ ]); } +$dashboard_item = new DashboardItem(); +$rows = $dashboard_item->find([ + 'card_id' => 'plugin_carbon_report_usage_abiotic_depletion' +]); +foreach ($rows as $row) { + $card_options = json_decode($row['card_options'], true); + if ($card_options['widgettype'] === 'usage_abiotic_depletion') { + $card_options['widgettype'] = 'impact_criteria_number'; + } + $dashboard_item->update([ + 'id' => $row['id'], + 'card_id' => 'plugin_carbon_report_usage_adp_impact', + 'card_options' => json_encode($card_options), + ]); +} + $dashboard_item = new DashboardItem(); $rows = $dashboard_item->find([ 'card_id' => 'plugin_carbon_report_embodied_abiotic_depletion' diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index 8c5f68fe..97f93677 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -46,8 +46,8 @@ class DemoProvider { /** @var array $impact_values Embodied and usage impact values*/ private static array $impact_values = [ - 'gwp' => [616000000, null], - 'adp' => [12.748, null], + 'gwp' => [616000000, 176483], + 'adp' => [12.748, 2.86], 'pe' => [491000000, null], 'gwppb' => [null, null], 'gwppf' => [null, null], @@ -70,24 +70,6 @@ class DemoProvider 'ept' => [null, null], ]; - public static function getUsageAbioticDepletion(array $params = [], array $crit = []): array - { - $default_params = [ - 'label' => __('Usage abiotic depletion potential', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', - ]; - $params = array_merge($default_params, $params); - - $value = 2.86; - $value = Toolbox::getWeight($value) . __('Sbeq', 'carbon'); - - return [ - 'number' => $value, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - public static function getUsageCarbonEmissionPerMonth(array $params = [], array $crit = []): array { $default_params = [ @@ -380,27 +362,6 @@ public static function getSumUsageEmissionsPerModel(array $params = [], array $w ]; } - /** - * Get usage CO2 emissions - * - * @param array $params - * @return array - */ - public static function getUsageCarbonEmission(array $params = []): array - { - $default_params = [ - 'label' => __('plugin carbon - Usage carbon emission', 'carbon'), - 'icon' => 'fa-solid fa-temperature-arrow-up', - ]; - $params = array_merge($default_params, $params); - $gwp = Toolbox::getWeight(176483) . __('CO₂eq', 'carbon'); - return [ - 'number' => $gwp, - 'label' => $params['label'], - 'icon' => $params['icon'], - ]; - } - /** * Get the value of an impact criteria for the embodied scope * @@ -501,7 +462,7 @@ public static function getImpactOfEmbodiedAndUsageCriteria(string $impact_type, $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); } - $value = self::$impact_values[$impact_type][1]; + $value = self::$impact_values[$impact_type][0] ?? 0 + self::$impact_values[$impact_type][1] ?? 0; if ($value === null) { $value = 'N/A'; } else { diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index 80e88879..dc26287d 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -266,11 +266,14 @@ protected static function getReportCards(): array 'label' => __('Biggest monthly averaged carbon emission per model', 'carbon'), 'provider' => Provider::class . '::getSumUsageEmissionsPerModel', ], - 'plugin_carbon_report_usage_abiotic_depletion' => [ - 'widgettype' => ['usage_abiotic_depletion'], + 'plugin_carbon_report_usage_adp_impact' => [ + 'widgettype' => ['impact_criteria_number'], 'group' => $group, 'label' => __('Usage abiotic depletion potential', 'carbon'), - 'provider' => Provider::class . '::getUsageAbioticDepletion', + 'provider' => Provider::class . '::getImpactOfUsageCriteria', + 'args' => [ + 'impact_type' => 'adp', + ] ], ]; diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index 103287db..cddc3f97 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -863,7 +863,7 @@ public static function displayImpactCriteriaNumber(array $params = []): string $label_color = '#626976'; $fg_color = GlpiToolbox::getFgColor($p['color']); - return TemplateRenderer::getInstance()->render('@carbon/dashboard/embodied-impact-criteria.html.twig', [ + return TemplateRenderer::getInstance()->render('@carbon/dashboard/impact-criteria.html.twig', [ 'id' => $p['id'], 'color' => $p['color'], 'fg_color' => $fg_color, diff --git a/templates/dashboard/embodied-impact-criteria.html.twig b/templates/dashboard/impact-criteria.html.twig similarity index 97% rename from templates/dashboard/embodied-impact-criteria.html.twig rename to templates/dashboard/impact-criteria.html.twig index 32c803b2..a9672f65 100644 --- a/templates/dashboard/embodied-impact-criteria.html.twig +++ b/templates/dashboard/impact-criteria.html.twig @@ -72,7 +72,7 @@
-
+
{% if pictogram_file != '' %} {{ source('@carbon/dashboard/' ~ pictogram_file) }} diff --git a/templates/dashboard/usage-abiotic-depletion.html.twig b/templates/dashboard/usage-abiotic-depletion.html.twig deleted file mode 100644 index ec47aa10..00000000 --- a/templates/dashboard/usage-abiotic-depletion.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -{# - # ------------------------------------------------------------------------- - # Carbon plugin for GLPI - # - # @copyright Copyright (C) 2024-2025 Teclib' and contributors. - # @license https://www.gnu.org/licenses/gpl-3.0.txt GPLv3+ - # @license MIT https://opensource.org/licenses/mit-license.php - # @link https://github.com/pluginsGLPI/carbon - # - # ------------------------------------------------------------------------- - # - # LICENSE - # - # This file is part of Carbon plugin for GLPI. - # - # This program is free software: you can redistribute it and/or modify - # it under the terms of the GNU General Public License as published by - # the Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # This program is distributed in the hope that it will be useful, - # but WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - # GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License - # along with this program. If not, see . - # - # ------------------------------------------------------------------------- - #} - - -
-
-
-
- - {{ source('@carbon/dashboard/icon-pickaxe.svg') }} - -
-
-
{{ __('Usage abiotic depletion potential', 'carbon') }}
-
-
{{ number|raw }}
-
- - - -
-
-
-
- -{{ tooltip_html|raw }} From 0bbbf82d1fa5b117fb0806a4980728d92bb711f7 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 25 Feb 2026 15:23:32 +0100 Subject: [PATCH 17/25] refactor(Dashboard\Grid): code optimization --- src/Dashboard/Grid.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index dc26287d..e1914b6c 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -154,8 +154,10 @@ protected static function getStandardCards(): array ]; } + $impact_types = Type::getImpactTypes(); + // Embodied impact - foreach (Type::getImpactTypes() as $impact_type) { + foreach ($impact_types as $impact_type) { $key = "plugin_carbon_embodied_{$impact_type}_impact"; if (isset($new_cards[$key])) { trigger_error("The card $key already exists", E_USER_WARNING); @@ -172,7 +174,7 @@ protected static function getStandardCards(): array } // Usage impact - foreach (Type::getImpactTypes() as $impact_type) { + foreach ($impact_types as $impact_type) { $key = "plugin_carbon_usage_{$impact_type}_impact"; if (isset($new_cards[$key])) { trigger_error("The card $key already exists", E_USER_WARNING); @@ -189,7 +191,7 @@ protected static function getStandardCards(): array } // embodied + usage impact - foreach (Type::getImpactTypes() as $impact_type) { + foreach ($impact_types as $impact_type) { $key = "plugin_carbon_all_scopes_{$impact_type}_impact"; if (isset($new_cards[$key])) { trigger_error("The card $key already exists", E_USER_WARNING); From 20a95ca6f074b78c9f57043565e1e04a5c2230d0 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Wed, 25 Feb 2026 15:44:49 +0100 Subject: [PATCH 18/25] feat(Install): add new criterias for usage impact --- .../09_add_impact_criterias.php | 75 ++++++++++--------- install/mysql/plugin_carbon_empty.sql | 50 +++++++++++-- 2 files changed, 84 insertions(+), 41 deletions(-) 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 f8c8640d..382f707a 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 @@ -50,47 +50,54 @@ 'adpf' => '(unit J) Use of fossil resources (including nuclear)', 'ap' => '(unit mol H+ eq) Acidification', 'ctue' => '(unit CTUe) Freshwater ecotoxicity', - // ctuh_c => '(unit CTUh) Human toxicity - non-carcinogenic effects', + // ctuh_c => '(unit CTUh) Human toxicity - Carcinogenic effects', + // ctuh_nc => (unit CTUh) Human toxicity - non-carcinogenic effects', 'epf' => '(unit g P eq) Eutrophication of freshwater', 'epm' => '(unit g N eq) Eutrophication of marine waters', 'ept' => '(unit mol N eq) Terrestrial eutrophication', ]; -$table = 'glpi_plugin_carbon_embodiedimpacts'; -$previous_criteria = 'pe'; -foreach ($new_criterias as $criteria => $comment) { - $migration->addField( - $table, - $criteria, - 'float DEFAULT \'0\'', - [ +$tables = ['glpi_plugin_carbon_embodiedimpacts', 'glpi_plugin_carbon_usageimpacts']; +foreach ($tables as $table) { + $previous_criteria = 'pe'; + foreach ($new_criterias as $criteria => $comment) { + $migration->addField( + $table, + $criteria, + 'float DEFAULT \'0\'', + [ + 'comment' => $comment, + 'after' => $previous_criteria . '_quality', + ] + ); + $migration->addField( + $table, + $criteria . '_quality', + 'int unsigned NOT NULL DEFAULT \'0\'', + [ + 'comment' => 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + 'after' => $criteria, + ] + ); + $previous_criteria = $criteria; + } + + // Uniformize existing impact : make floats signed + $old_criterias = [ + 'gwp' => '(unit g CO2 eq) Global warming potential', + 'adp' => '(unit g Sb eq) Abiotic depletion potential', + 'pe' => '(unit J) Primary energy', + ]; + foreach ($old_criterias as $criteria => $comment) { + $migration->changeField($table, $criteria, $criteria, 'float DEFAULT \'0\'', [ 'comment' => $comment, - 'after' => $previous_criteria . '_quality', - ] - ); - $migration->addField( - $table, - $criteria . '_quality', - 'int unsigned NOT NULL DEFAULT \'0\'', - [ - 'comment' => 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - 'after' => $criteria, - ] - ); - // $migration->dropField($table, $criteria); - // $migration->dropField($table, $criteria . '_quality'); - $previous_criteria = $criteria; -} + ]); + } -// Uniformize existing impact : make floats signed -$old_criterias = [ - 'gwp' => '(unit g CO2 eq) Global warming potential', - 'adp' => '(unit g Sb eq) Abiotic depletion potential', - 'pe' => '(unit J) Primary energy', -]; -foreach ($old_criterias as $criteria => $comment) { - $migration->changeField($table, $criteria, $criteria, 'float DEFAULT \'0\'', [ - 'comment' => $comment, + // Add a recalculate boolean + $migration->addField($table, 'recalculate', 'bool', [ + 'after' => 'date_mod', + 'update' => 1 ]); } diff --git a/install/mysql/plugin_carbon_empty.sql b/install/mysql/plugin_carbon_empty.sql index 53f8c35b..25f37f5c 100644 --- a/install/mysql/plugin_carbon_empty.sql +++ b/install/mysql/plugin_carbon_empty.sql @@ -137,6 +137,7 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_embodiedimpacts` ( `engine` varchar(255) DEFAULT NULL, `engine_version` varchar(255) DEFAULT NULL, `date_mod` timestamp NULL DEFAULT NULL, + `recalculate` tinyint NOT NULL DEFAULT '0', `gwp` float DEFAULT '0' COMMENT '(unit g CO2 eq) Global warming potential', `gwp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', `adp` float DEFAULT '0' COMMENT '(unit g Sb eq) Abiotic depletion potential', @@ -260,17 +261,52 @@ CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_usageinfos` ( CREATE TABLE IF NOT EXISTS `glpi_plugin_carbon_usageimpacts` ( `id` int unsigned NOT NULL AUTO_INCREMENT, - `itemtype` varchar(255) DEFAULT NULL, + `itemtype` varchar(255) DEFAULT NULL, `items_id` int unsigned NOT NULL DEFAULT '0', - `engine` varchar(255) DEFAULT NULL, - `engine_version` varchar(255) DEFAULT NULL, - `date_mod` timestamp NULL DEFAULT NULL, - `gwp` float unsigned DEFAULT '0' COMMENT '(unit gCO2eq) Global warming potential', + `engine` varchar(255) DEFAULT NULL, + `engine_version` varchar(255) DEFAULT NULL, + `date_mod` timestamp NULL DEFAULT NULL, + `recalculate` tinyint NOT NULL DEFAULT '0', + `gwp` float DEFAULT '0' COMMENT '(unit g CO2 eq) Global warming potential', `gwp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `adp` float unsigned DEFAULT '0' COMMENT '(unit gSbeq) Abiotic depletion potential', + `adp` float DEFAULT '0' COMMENT '(unit g Sb eq) Abiotic depletion potential', `adp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', - `pe` float unsigned DEFAULT '0' COMMENT '(unit J) Primary energy', + `pe` float DEFAULT '0' COMMENT '(unit J) Primary energy', `pe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwppb` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of biogenic emissions', + `gwppb_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwppf` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of fossil fuel emissions', + `gwppf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `gwpplu` float DEFAULT '0' COMMENT '(unit g CO2 eq) Climate change - Contribution of emissions from land use change', + `gwpplu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ir` float DEFAULT '0' COMMENT '(unit g U235 eq) Emissions of radionizing substances', + `ir_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `lu` float DEFAULT '0' COMMENT '(unit none) Land use', + `lu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `odp` float DEFAULT '0' COMMENT '(unit g CFC-11 eq) Depletion of the ozone layer', + `odp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `pm` float DEFAULT '0' COMMENT '(unit Disease occurrence) Fine particle emissions', + `pm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `pocp` float DEFAULT '0' COMMENT '(unit g NMVOC eq) Photochemical ozone formation', + `pocp_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `wu` float DEFAULT '0' COMMENT '(unit L) Use of water resources', + `wu_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `mips` float DEFAULT '0' COMMENT '(unit g) Material input per unit of service', + `mips_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `adpe` float DEFAULT '0' COMMENT '(unit g SB eq) Use of mineral and metal resources', + `adpe_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `adpf` float DEFAULT '0' COMMENT '(unit J) Use of fossil resources (including nuclear)', + `adpf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ap` float DEFAULT '0' COMMENT '(unit mol H+ eq) Acidification', + `ap_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ctue` float DEFAULT '0' COMMENT '(unit CTUe) Freshwater ecotoxicity', + `ctue_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `epf` float DEFAULT '0' COMMENT '(unit g P eq) Eutrophication of freshwater', + `epf_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `epm` float DEFAULT '0' COMMENT '(unit g N eq) Eutrophication of marine waters', + `epm_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', + `ept` float DEFAULT '0' COMMENT '(unit mol N eq) Terrestrial eutrophication', + `ept_quality` int unsigned NOT NULL DEFAULT '0' COMMENT 'DataTtacking\\AbstractTracked::DATA_QUALITY_* constants', PRIMARY KEY (`id`), UNIQUE KEY `unicity` (`itemtype`, `items_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; From 20e31d520bbcb3525f301bb3be76499865c828fc Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Thu, 26 Feb 2026 10:20:31 +0100 Subject: [PATCH 19/25] feat(Impact\Embodied): handle recalculate flag --- .../Embodied/AbstractEmbodiedImpact.php | 9 +++---- src/Impact/Usage/AbstractUsageImpact.php | 9 +++---- .../AbstractEmbodiedImpactTest.php | 26 ++++++++++++++++--- .../Impact/Embodied/Boavizta/ComputerTest.php | 4 +-- .../Impact/Embodied/Boavizta/MonitorTest.php | 4 +-- 5 files changed, 33 insertions(+), 19 deletions(-) rename tests/src/Impact/{Engine => Embodied}/AbstractEmbodiedImpactTest.php (83%) diff --git a/src/Impact/Embodied/AbstractEmbodiedImpact.php b/src/Impact/Embodied/AbstractEmbodiedImpact.php index 03f94fd4..1290ce7d 100644 --- a/src/Impact/Embodied/AbstractEmbodiedImpact.php +++ b/src/Impact/Embodied/AbstractEmbodiedImpact.php @@ -108,11 +108,10 @@ public static function getItemsToEvaluate(string $itemtype, array $crit = []): D /** @var DBmysql $DB */ global $DB; - // if (!is_subclass_of($itemtype, CommonDBTM::class)) { - // throw new \LogicException('Itemtype does not inherits from ' . CommonDBTM::class); - // } - - $crit[EmbodiedImpact::getTableField('id')] = null; + $crit[] = ['OR' => [ + EmbodiedImpact::getTableField('id') => null, + EmbodiedImpact::getTableField('recalculate') => 1, + ]]; $iterator = $DB->request(self::getEvaluableQuery($itemtype, $crit, false)); return $iterator; diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 9d6101d7..05ddc7de 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -114,6 +114,10 @@ public function getItemsToEvaluate(array $crit = []): DBmysqlIterator throw new \LogicException('Itemtype does not inherits from ' . CommonDBTM::class); } + $crit[] = ['OR' => [ + UsageImpact::getTableField('id') => null, + UsageImpact::getTableField('recalculate') => 1, + ]]; $crit[UsageImpact::getTableField('id')] = null; $iterator = $DB->request($this->getEvaluableQuery($crit, false)); @@ -226,11 +230,6 @@ public function getEvaluableQuery(array $crit = [], bool $entity_restrict = true $glpi_location_table = GlpiLocation::getTable(); $usage_impact_table = UsageImpact::getTable(); - // $where = []; - // if (!$recalculate) { - // $where = [UsageImpact::getTableField('id') => null]; - // } - $request = [ 'SELECT' => [ $itemtype::getTableField('id'), diff --git a/tests/src/Impact/Engine/AbstractEmbodiedImpactTest.php b/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php similarity index 83% rename from tests/src/Impact/Engine/AbstractEmbodiedImpactTest.php rename to tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php index 1ca380aa..4045a261 100644 --- a/tests/src/Impact/Engine/AbstractEmbodiedImpactTest.php +++ b/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php @@ -30,7 +30,7 @@ * ------------------------------------------------------------------------- */ -namespace GlpiPlugin\Carbon\Tests\Impact\Engine; +namespace GlpiPlugin\Carbon\Tests\Impact\Embodied; use DBmysql; use GlpiPlugin\Carbon\EmbodiedImpact; @@ -45,7 +45,7 @@ class AbstractEmbodiedImpactTest extends DbTestCase protected static string $itemtype_type = ''; protected static string $itemtype_model = ''; - public function testGetEvaluableItems() + public function testGetItemsToEvaluate() { if (static::$itemtype === '' || static::$itemtype_type === '' || static::$itemtype_model === '') { // Ensure that the inherited test class is properly implemented for this test @@ -65,7 +65,7 @@ public function testGetEvaluableItems() ]); $this->assertEquals(1, $iterator->count()); - // Test the asset is no longer evaluable when no embodied impact is in the DB + // Test the asset is no longer evaluable when there is embodied impact in the DB $glpi_asset_type = $this->createItem(static::$itemtype_type); $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), @@ -76,11 +76,31 @@ public function testGetEvaluableItems() $embodied_impact = $this->createItem(EmbodiedImpact::class, [ 'itemtype' => $asset->getType(), 'items_id' => $asset->getID(), + 'recalculate' => 0, ]); $iterator = AbstractEmbodiedImpact::getItemsToEvaluate(static::$itemtype, [ $asset::getTableField('id') => $asset->getID(), ]); $this->assertEquals(0, $iterator->count()); + + // Test the asset is evaluable when there is embodied impact in the DB but recamculate is set + $glpi_asset_type = $this->createItem(static::$itemtype_type); + $asset_type = $this->createItem('GlpiPlugin\\Carbon\\' . static::$itemtype_type, [ + getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), + ]); + $asset = $this->createItem(static::$itemtype, [ + getForeignKeyFieldForItemType(static::$itemtype_type) => $glpi_asset_type->getID(), + ]); + $embodied_impact = $this->createItem(EmbodiedImpact::class, [ + 'itemtype' => $asset->getType(), + 'items_id' => $asset->getID(), + 'recalculate' => 1, + ]); + $iterator = AbstractEmbodiedImpact::getItemsToEvaluate(static::$itemtype, [ + $asset::getTableField('id') => $asset->getID(), + ]); + $this->assertEquals(1, $iterator->count()); + } public function testGetEvaluableQuery() diff --git a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php index 292fc130..a49a0bc2 100644 --- a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php +++ b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php @@ -35,10 +35,8 @@ use Computer as GlpiComputer; use ComputerType as GlpiComputerType; use ComputerModel as GlpiComputerModel; -use DBmysql; -use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; use GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Computer as BoaviztaComputer; -use GlpiPlugin\Carbon\Tests\Impact\Engine\AbstractEmbodiedImpactTest; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractEmbodiedImpactTest; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(BoaviztaComputer::class)] diff --git a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php index 3fa1a550..142c6779 100644 --- a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php +++ b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php @@ -35,10 +35,8 @@ use Monitor as GlpiMonitor; use MonitorType as GlpiMonitorType; use MonitorModel as glpiMonitorModel; -use DBmysql; -use GlpiPlugin\Carbon\Impact\Embodied\AbstractEmbodiedImpact; use GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Monitor as BoaviztaMonitor; -use GlpiPlugin\Carbon\Tests\Impact\Engine\AbstractEmbodiedImpactTest; +use GlpiPlugin\Carbon\Tests\Impact\Embodied\AbstractEmbodiedImpactTest; use PHPUnit\Framework\Attributes\CoversClass; #[CoversClass(BoaviztaMonitor::class)] From f356f2b470fce3a60013073a33b93d8aeb131195 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Thu, 26 Feb 2026 15:56:35 +0100 Subject: [PATCH 20/25] fix(Impact): engine and version of calculations --- src/CronTask.php | 1 + src/Impact/Embodied/AbstractEmbodiedImpact.php | 7 +++++-- src/Impact/Embodied/Boavizta/AbstractAsset.php | 2 +- src/Impact/Usage/AbstractUsageImpact.php | 8 ++++++-- src/Impact/Usage/Boavizta/AbstractAsset.php | 11 +++++++---- src/Impact/Usage/Boavizta/Computer.php | 2 +- tests/units/Impact/Embodied/Boavizta/ComputerTest.php | 2 +- tests/units/Impact/Embodied/Boavizta/MonitorTest.php | 2 +- 8 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/CronTask.php b/src/CronTask.php index 7baa822b..7016cc0f 100644 --- a/src/CronTask.php +++ b/src/CronTask.php @@ -146,6 +146,7 @@ public static function cronUsageImpact(GlpiCronTask $task): int $task->setVolume(0); // start with zero $usage_impacts = Toolbox::getGwpUsageImpactClasses(); + $task->setVolume(0); // start with zero $remaining = $task->fields['param']; $limit_per_type = (int) floor(($remaining) / count($usage_impacts)); // Half of job for GWP, the other half for other impacts diff --git a/src/Impact/Embodied/AbstractEmbodiedImpact.php b/src/Impact/Embodied/AbstractEmbodiedImpact.php index 1290ce7d..45dc8838 100644 --- a/src/Impact/Embodied/AbstractEmbodiedImpact.php +++ b/src/Impact/Embodied/AbstractEmbodiedImpact.php @@ -121,9 +121,8 @@ public function evaluateItem(): bool { $itemtype = get_class($this->item); - $input['engine'] = $this->engine; try { - $input['engine_version'] = $this->getVersion(); + $this->getVersion(); $impacts = $this->doEvaluation(); } catch (ConnectException $e) { Session::addMessageAfterRedirect(__('Connection to Boavizta failed.', 'carbon'), false, ERROR); @@ -147,6 +146,10 @@ public function evaluateItem(): bool $embodied_impact->getFromDBByCrit($input); $impact_types = Type::getImpactTypes(); + $input['recalculate'] = 0; + $input['engine'] = $this->engine; + $input['engine_version'] = self::$engine_version; + // Prepare inputs for add or update foreach ($impacts as $type => $value) { $key = $impact_types[$type]; diff --git a/src/Impact/Embodied/Boavizta/AbstractAsset.php b/src/Impact/Embodied/Boavizta/AbstractAsset.php index e7f9685d..c1490d04 100644 --- a/src/Impact/Embodied/Boavizta/AbstractAsset.php +++ b/src/Impact/Embodied/Boavizta/AbstractAsset.php @@ -44,7 +44,7 @@ abstract class AbstractAsset extends AbstractEmbodiedImpact implements AssetInte protected string $engine = 'Boavizta'; /** @var string $engine_version Version of the calculation engine */ - protected static string $engine_version = 'unknown'; + // protected static string $engine_version = 'unknown'; /** @var string Endpoint to query for the itemtype, to be filled in child class */ protected string $endpoint = ''; diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 05ddc7de..136c9696 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -58,7 +58,7 @@ abstract class AbstractUsageImpact implements UsageImpactInterface protected string $engine = 'undefined'; /** @var string $engine_version Version of the calculation engine */ - protected string $engine_version = 'unknown'; + protected static string $engine_version = 'unknown'; /** @var array of TrackedFloat */ protected array $impacts = []; @@ -70,6 +70,8 @@ public function __construct() } } + abstract protected function getVersion(): string; + /** * Get the unit of an impact * @@ -173,6 +175,7 @@ public function evaluateItem(int $id): bool } try { + $this->getVersion(); $impacts = $this->doEvaluation($item); } catch (\RuntimeException $e) { return false; @@ -192,8 +195,9 @@ public function evaluateItem(int $id): bool $usage_impact->getFromDBByCrit($input); $impact_types = Type::getImpactTypes(); + $input['recalculate'] = 0; $input['engine'] = $this->engine; - $input['engine_version'] = $this->engine_version; + $input['engine_version'] = self::$engine_version; // Prepare inputs for add or update foreach ($impacts as $type => $value) { diff --git a/src/Impact/Usage/Boavizta/AbstractAsset.php b/src/Impact/Usage/Boavizta/AbstractAsset.php index 1d89e986..417fb7f8 100644 --- a/src/Impact/Usage/Boavizta/AbstractAsset.php +++ b/src/Impact/Usage/Boavizta/AbstractAsset.php @@ -53,7 +53,7 @@ abstract class AbstractAsset extends AbstractUsageImpact implements AssetInterfa protected string $engine = 'Boavizta'; /** @var string $engine_version Version of the calculation engine */ - protected string $engine_version = 'unknown'; + // protected static string $engine_version = 'unknown'; /** @var string Endpoint to query for the itemtype, to be filled in child class */ protected string $endpoint = ''; @@ -91,11 +91,14 @@ abstract protected function getAveragePower(int $id): ?int; public function setClient(Client $client) { $this->client = $client; - // $this->engine_version = $this->getVersion(); } protected function getVersion(): string { + if (self::$engine_version !== 'unknown') { + return self::$engine_version; + } + try { $response = $this->client->get('utils/version'); } catch (\RuntimeException $e) { @@ -109,8 +112,8 @@ protected function getVersion(): string ), E_USER_WARNING); throw new \RuntimeException('Invalid response from Boavizta API'); } - - return $response[0]; + self::$engine_version = $response[0]; + return self::$engine_version; } protected function query($description): array diff --git a/src/Impact/Usage/Boavizta/Computer.php b/src/Impact/Usage/Boavizta/Computer.php index cb1d328c..69f3b35b 100644 --- a/src/Impact/Usage/Boavizta/Computer.php +++ b/src/Impact/Usage/Boavizta/Computer.php @@ -183,7 +183,7 @@ protected function doEvaluation(CommonDBTM $item): ?array $average_power = $this->getAveragePower($item->getID()); - // Ask for embodied impact only + // Ask for usage impact only $configuration = $this->analyzeHardware($item); if (count($configuration) === 0) { return null; diff --git a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php index a49a0bc2..3d664042 100644 --- a/tests/units/Impact/Embodied/Boavizta/ComputerTest.php +++ b/tests/units/Impact/Embodied/Boavizta/ComputerTest.php @@ -30,7 +30,7 @@ * ------------------------------------------------------------------------- */ -namespace GlpiPlugin\Carbon\Impact\Engine\Boavizta\Tests; +namespace GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Tests; use Computer as GlpiComputer; use ComputerType as GlpiComputerType; diff --git a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php index 142c6779..c5101e5d 100644 --- a/tests/units/Impact/Embodied/Boavizta/MonitorTest.php +++ b/tests/units/Impact/Embodied/Boavizta/MonitorTest.php @@ -30,7 +30,7 @@ * ------------------------------------------------------------------------- */ -namespace GlpiPlugin\Carbon\Impact\Engine\Boavizta\Tests; +namespace GlpiPlugin\Carbon\Impact\Embodied\Boavizta\Tests; use Monitor as GlpiMonitor; use MonitorType as GlpiMonitorType; From 89a88e35bab7a5b296d2b417f4118cae41328fb7 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 27 Feb 2026 14:58:48 +0100 Subject: [PATCH 21/25] feat: show all individual impacts --- src/AbstractImpact.php | 54 +++++++++++++++ src/EmbodiedImpact.php | 21 +----- src/Impact/Type.php | 2 +- src/UsageImpact.php | 27 +------- src/UsageInfo.php | 27 ++++++++ templates/environmentalimpact-item.html.twig | 72 +++++++++++--------- 6 files changed, 126 insertions(+), 77 deletions(-) create mode 100644 src/AbstractImpact.php diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php new file mode 100644 index 00000000..e6fcc9a1 --- /dev/null +++ b/src/AbstractImpact.php @@ -0,0 +1,54 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon; + +use CommonDBChild; +use GlpiPlugin\Carbon\Impact\Type; + +abstract class AbstractImpact extends CommonDBChild +{ + public static $rightname = 'carbon:report'; + + /** + * Get impact value in a human r eadable format, selecting the best unit + * + * @param string $field the field of the impact + */ + public function getHumanReadableImpact(string $field): string + { + return Toolbox::getHumanReadableValue( + $this->fields[$field], + Type::getImpactUnit($field) + ); + } +} \ No newline at end of file diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 968e3530..8c449f05 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -43,10 +43,8 @@ * * Embodied impact is the impact of the asset while it is built and destroyed or recycled */ -class EmbodiedImpact extends CommonDBTM +class EmbodiedImpact extends AbstractImpact { - public static $rightname = 'carbon:report'; - public function canEdit($ID): bool { return false; @@ -193,21 +191,4 @@ public function calculateImpact(string $lca_type, int $limit = 0): int return $iterator->count(); } - - /** - * Get impact value in a human r eadable format, selecting the best unit - */ - public function getHumanReadableImpact(string $field): string - { - switch ($field) { - case 'gwp': - return Toolbox::getWeight($this->fields[$field]) . 'CO2eq'; - case 'adp': - return Toolbox::getWeight($this->fields[$field]) . 'Sbeq'; - case 'pe': - return Toolbox::getEnergy($this->fields[$field] / 3600); // Convert J into Wh - } - - return ''; - } } diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 58d9a1af..8fb6ac68 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -392,7 +392,7 @@ public static function getCriteriaInfoLink(string $impact_type): string ); switch ($impact_type) { case 'gwp': - return "$base_url/carbon/types_of_impact.html#carbon-dioxyde-equivalent"; + return "$base_url/types_of_impact.html#carbon-dioxyde-equivalent"; case 'adp': return "$base_url/types_of_impact.html#antimony-equivalent"; case 'pe': diff --git a/src/UsageImpact.php b/src/UsageImpact.php index 5ee94260..a0b24c7c 100644 --- a/src/UsageImpact.php +++ b/src/UsageImpact.php @@ -32,19 +32,13 @@ namespace GlpiPlugin\Carbon; -use CommonDBChild; -use DateInterval; -use DateTimeInterface; -use Entity; -use Location; +use GlpiPlugin\Carbon\Impact\Type; -class UsageImpact extends CommonDBChild +class UsageImpact extends AbstractImpact { public static $itemtype = 'itemtype'; public static $items_id = 'items_id'; - public static $rightname = 'carbon:report'; - public static function getTypeName($nb = 0) { return _n("Usage impact", "Usage impacts", $nb, 'carbon'); @@ -165,21 +159,4 @@ public function rawSearchOptions() return $tab; } - - /** - * Get impact value in a human r eadable format, selecting the best unit - */ - public function getHumanReadableImpact(string $field): string - { - switch ($field) { - case 'gwp': - return Toolbox::getWeight($this->fields[$field]) . 'CO2eq'; - case 'adp': - return Toolbox::getWeight($this->fields[$field]) . 'Sbeq'; - case 'pe': - return Toolbox::getEnergy($this->fields[$field] / 3600); // Convert J into Wh - } - - return ''; - } } diff --git a/src/UsageInfo.php b/src/UsageInfo.php index 3f5c2c6e..3fee0b6a 100644 --- a/src/UsageInfo.php +++ b/src/UsageInfo.php @@ -243,6 +243,30 @@ public static function showCharts(CommonDBTM $asset) 'applyto' => 'embodied_primary_energy_tip', ]); + $tooltips = []; + $embodied_labels = []; + $usage_labels = []; + foreach (Type::getImpactTypes() as $impact_type) { + $tooltip = Type::getCriteriaTooltip($impact_type); + $url = Type::getCriteriaInfoLink($impact_type); + if ($url !== '') { + $tooltip = sprintf( + $tooltip . __('%s More information %s', 'carbon'), + '
', + '' + ); + } + $tooltips[$impact_type] = ''; + if ($tooltip !== '') { + $tooltips[$impact_type] = Html::showToolTip($tooltip, [ + 'display' => false, + 'applyto' => 'embodied_' . $impact_type . '_tip', + ]); + } + $embodied_labels[$impact_type] = Type::getEmbodiedImpactLabel($impact_type); + $usage_labels[$impact_type] = Type::getUsageImpactLabel($impact_type); + } + $usage_imapct_action_url = $CFG_GLPI['root_doc'] . '/plugins/carbon/front/usageimpact.form.php'; $embodied_impact_action_url = $CFG_GLPI['root_doc'] . '/plugins/carbon/front/embodiedimpact.form.php'; TemplateRenderer::getInstance()->display('@carbon/environmentalimpact-item.html.twig', [ @@ -251,6 +275,9 @@ public static function showCharts(CommonDBTM $asset) 'usage_carbon_emission_count' => $usage_carbon_emission_count, 'embodied_impact' => $embodied_impact, 'usage_impact' => $usage_impact, + 'embodied_labels' => $embodied_labels, + 'usage_labels' => $usage_labels, + 'tooltips' => $tooltips, 'usage_carbon_emission_graph' => Widget::DisplayGraphUsageCarbonEmissionPerMonth($data), 'carbon_emission_tooltip_html' => $carbon_emission_tooltip_html, 'usage_abiotic_depletion_tooltip_html' => $usage_abiotic_depletion_tooltip_html, diff --git a/templates/environmentalimpact-item.html.twig b/templates/environmentalimpact-item.html.twig index f5f9fd01..6210e01d 100644 --- a/templates/environmentalimpact-item.html.twig +++ b/templates/environmentalimpact-item.html.twig @@ -37,7 +37,27 @@ ) }} {{ usage_carbon_emission_graph|raw }} -
+
+
+
+
+ {% for criteria, label in usage_labels %} + {% if usage_impact.fields[criteria] ?? null is not null %} +
+ {{ label }} +
+
+ + {{ usage_impact.getHumanReadableImpact(criteria)|raw }} + {# #} + + {{ tooltips[criteria]|raw }} +
+ {% endif %} + {% endfor %} +
+
+
{% if usage_impact.fields.gwp ?? null is not null %}
{{ usage_impact.getHumanReadableImpact('gwp')|raw }}
{% endif %} @@ -71,37 +91,27 @@ 'fas fa-chart-pie' ) }} -
- {% if embodied_impact.fields.gwp ?? null is not null %} -
- - {{ embodied_impact.getHumanReadableImpact('gwp')|raw }} - {# #} - - {{ carbon_emission_tooltip_html|raw }} -
- {% endif %} - - {% if embodied_impact.fields.adp ?? null is not null %} -
- - {{ embodied_impact.getHumanReadableImpact('adp')|raw }} - {# #} - - {{ embodied_abiotic_depletion_tooltip_html|raw }} -
- {% endif %} - - {% if embodied_impact.fields.pe ?? null is not null %} -
- - {{ embodied_impact.getHumanReadableImpact('pe')|raw }} - {# #} - - {{ embodied_primary_energy_tooltip_html|raw }} +
+
+
+
+ {% for criteria, label in embodied_labels %} + {% if embodied_impact.fields[criteria] ?? null is not null %} +
+ {{ label }} +
+
+ + {{ embodied_impact.getHumanReadableImpact(criteria)|raw }} + {# #} + + {{ tooltips[criteria]|raw }} +
+ {% endif %} + {% endfor %} +
- {% endif %} - +
{% if not embodied_impact.isNewItem() %} {% set reset_args = "{_glpi_csrf_token: '" ~ csrf_token() ~ "', reset: '', id: '" ~ embodied_impact.getID() ~ "'}" %} From 6a0e19a2d103610d9435021124ec948501881b70 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 27 Feb 2026 15:03:40 +0100 Subject: [PATCH 22/25] refactor(AbstractImpact): factorize code from embodied and usage impact --- src/AbstractImpact.php | 70 +++++++++++++++++++++++++++ src/EmbodiedImpact.php | 70 --------------------------- src/UsageImpact.php | 107 ----------------------------------------- 3 files changed, 70 insertions(+), 177 deletions(-) diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index e6fcc9a1..65bcb09b 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -39,6 +39,76 @@ abstract class AbstractImpact extends CommonDBChild { public static $rightname = 'carbon:report'; + public function rawSearchOptions() + { + $tab = parent::rawSearchOptions(); + + $tab[] = [ + 'id' => '2', + 'table' => $this->getTable(), + 'field' => 'id', + 'name' => __('ID'), + 'massiveaction' => false, // implicit field is id + 'datatype' => 'number' + ]; + + $tab[] = [ + 'id' => '3', + 'table' => $this->getTable(), + 'field' => 'items_id', + 'name' => __('Associated item ID'), + 'massiveaction' => false, + 'datatype' => 'specific', + 'additionalfields' => ['itemtype'] + ]; + + $tab[] = [ + 'id' => '4', + 'table' => $this->getTable(), + 'field' => 'itemtype', + 'name' => _n('Type', 'Types', 1), + 'massiveaction' => false, + 'datatype' => 'itemtypename', + ]; + + $id = 5; + foreach (Type::getImpactTypes() as $type) { + $tab[] = [ + 'id' => $id, + 'table' => $this->getTable(), + 'field' => $type, + 'name' => Type::getEmbodiedImpactLabel($type), + 'massiveaction' => false, + 'datatype' => 'number', + 'unit' => implode(' ', Type::getImpactUnit($type)), + ]; + $id++; + } + + $tab[] = [ + 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, + 'table' => self::getTable(), + 'field' => 'date_mod', + 'name' => __('Date of evaluation', 'carbon') + ]; + + $tab[] = [ + 'id' => SearchOptions::CARBON_EMISSION_ENGINE, + 'table' => self::getTable(), + 'field' => 'engine', + 'name' => __('Engine', 'carbon') + ]; + + $tab[] = [ + 'id' => SearchOptions::CARBON_EMISSION_ENGINE_VER, + 'table' => self::getTable(), + 'field' => 'engine_version', + 'name' => __('Engine version', 'carbon') + ]; + + return $tab; + } + /** * Get impact value in a human r eadable format, selecting the best unit * diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index 8c449f05..dbf79ae3 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -50,76 +50,6 @@ public function canEdit($ID): bool return false; } - public function rawSearchOptions() - { - $tab = parent::rawSearchOptions(); - - $tab[] = [ - 'id' => '2', - 'table' => $this->getTable(), - 'field' => 'id', - 'name' => __('ID'), - 'massiveaction' => false, // implicit field is id - 'datatype' => 'number' - ]; - - $tab[] = [ - 'id' => '3', - 'table' => $this->getTable(), - 'field' => 'items_id', - 'name' => __('Associated item ID'), - 'massiveaction' => false, - 'datatype' => 'specific', - 'additionalfields' => ['itemtype'] - ]; - - $tab[] = [ - 'id' => '4', - 'table' => $this->getTable(), - 'field' => 'itemtype', - 'name' => _n('Type', 'Types', 1), - 'massiveaction' => false, - 'datatype' => 'itemtypename', - ]; - - $id = 5; - foreach (Type::getImpactTypes() as $type) { - $tab[] = [ - 'id' => $id, - 'table' => $this->getTable(), - 'field' => $type, - 'name' => Type::getEmbodiedImpactLabel($type), - 'massiveaction' => false, - 'datatype' => 'number', - 'unit' => implode(' ', Type::getImpactUnit($type)), - ]; - $id++; - } - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, - 'table' => self::getTable(), - 'field' => 'date_mod', - 'name' => __('Date of evaluation', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE, - 'table' => self::getTable(), - 'field' => 'engine', - 'name' => __('Engine', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE_VER, - 'table' => self::getTable(), - 'field' => 'engine_version', - 'name' => __('Engine version', 'carbon') - ]; - - return $tab; - } - /** * Get iterator of items without known embodied impact for a specified itemtype * diff --git a/src/UsageImpact.php b/src/UsageImpact.php index a0b24c7c..95d869a5 100644 --- a/src/UsageImpact.php +++ b/src/UsageImpact.php @@ -52,111 +52,4 @@ public function prepareInputForAdd($input) } return $input; } - - public function rawSearchOptions() - { - $tab = parent::rawSearchOptions(); - - $tab[] = [ - 'id' => '2', - 'table' => $this->getTable(), - 'field' => 'id', - 'name' => __('ID'), - 'massiveaction' => false, // implicit field is id - 'datatype' => 'number' - ]; - - $tab[] = [ - 'id' => '3', - 'table' => $this->getTable(), - 'field' => 'items_id', - 'name' => __('Associated item ID'), - 'massiveaction' => false, - 'datatype' => 'specific', - 'additionalfields' => ['itemtype'] - ]; - - $tab[] = [ - 'id' => '4', - 'table' => $this->getTable(), - 'field' => 'itemtype', - 'name' => _n('Type', 'Types', 1), - 'massiveaction' => false, - 'datatype' => 'itemtypename', - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_DATE, - 'table' => self::getTable(), - 'field' => 'date', - 'name' => __('Date') - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_GWP, - 'table' => self::getTable(), - 'field' => 'gwp', - 'name' => __('Global warming potential', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_GWP_QUALITY, - 'table' => self::getTable(), - 'field' => 'gwp_quality', - 'name' => __('Global warming potential quality', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_ADP, - 'table' => self::getTable(), - 'field' => 'adp', - 'name' => __('Abiotic depletion potential', 'carbon') - - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_ADP_QUALITY, - 'table' => self::getTable(), - 'field' => 'adp_quality', - 'name' => __('Abiotic depletion potential quality', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_PE, - 'table' => self::getTable(), - 'field' => 'pe', - 'name' => __('Primary energy quality', 'carbon') - - ]; - - $tab[] = [ - 'id' => SearchOptions::USAGE_IMPACT_PE_QUALITY, - 'table' => self::getTable(), - 'field' => 'pe_quality', - 'name' => __('Primary energy quality', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, - 'table' => self::getTable(), - 'field' => 'date_mod', - 'name' => __('Date of evaluation', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE, - 'table' => self::getTable(), - 'field' => 'engine', - 'name' => __('Engine', 'carbon') - ]; - - $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE_VER, - 'table' => self::getTable(), - 'field' => 'engine_version', - 'name' => __('Engine version', 'carbon') - ]; - - return $tab; - } } From 62acf6477f6ec248ff5445cf16fa8cc64e4ecc74 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 27 Feb 2026 15:16:10 +0100 Subject: [PATCH 23/25] style: various code style fixes --- src/AbstractImpact.php | 9 ++++++--- src/Dashboard/DemoProvider.php | 4 ++-- src/EmbodiedImpact.php | 1 - src/Impact/Embodied/AbstractEmbodiedImpact.php | 10 ++++++---- src/Impact/Usage/AbstractUsageImpact.php | 10 ++++++---- src/UsageImpact.php | 3 --- templates/environmentalimpact-item.html.twig | 4 ++-- .../src/Impact/Embodied/AbstractEmbodiedImpactTest.php | 1 - 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index 65bcb09b..29577891 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -37,6 +37,9 @@ abstract class AbstractImpact extends CommonDBChild { + public static $itemtype = 'itemtype'; + public static $items_id = 'items_id'; + public static $rightname = 'carbon:report'; public function rawSearchOptions() @@ -117,8 +120,8 @@ public function rawSearchOptions() public function getHumanReadableImpact(string $field): string { return Toolbox::getHumanReadableValue( - $this->fields[$field], - Type::getImpactUnit($field) + $this->fields[$field], + Type::getImpactUnit($field) ); } -} \ No newline at end of file +} diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index 97f93677..6fa60037 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -462,10 +462,10 @@ public static function getImpactOfEmbodiedAndUsageCriteria(string $impact_type, $crit['itemtype'] = array_intersect($crit['itemtype'], PLUGIN_CARBON_TYPES); } - $value = self::$impact_values[$impact_type][0] ?? 0 + self::$impact_values[$impact_type][1] ?? 0; - if ($value === null) { + if (self::$impact_values[$impact_type][0] === null && self::$impact_values[$impact_type][1] === null) { $value = 'N/A'; } else { + $value = (self::$impact_values[$impact_type][0] ?? 0) + (self::$impact_values[$impact_type][1] ?? 0); $value = Toolbox::getHumanReadableValue( $value, Type::getImpactUnit($impact_type) diff --git a/src/EmbodiedImpact.php b/src/EmbodiedImpact.php index dbf79ae3..d8c38537 100644 --- a/src/EmbodiedImpact.php +++ b/src/EmbodiedImpact.php @@ -36,7 +36,6 @@ use DBmysql; use DBmysqlIterator; use Glpi\DBAL\QuerySubQuery; -use GlpiPlugin\Carbon\Impact\Type; /** * Embodied impact of assets diff --git a/src/Impact/Embodied/AbstractEmbodiedImpact.php b/src/Impact/Embodied/AbstractEmbodiedImpact.php index 45dc8838..2cbe9979 100644 --- a/src/Impact/Embodied/AbstractEmbodiedImpact.php +++ b/src/Impact/Embodied/AbstractEmbodiedImpact.php @@ -108,10 +108,12 @@ public static function getItemsToEvaluate(string $itemtype, array $crit = []): D /** @var DBmysql $DB */ global $DB; - $crit[] = ['OR' => [ - EmbodiedImpact::getTableField('id') => null, - EmbodiedImpact::getTableField('recalculate') => 1, - ]]; + $crit[] = [ + 'OR' => [ + EmbodiedImpact::getTableField('id') => null, + EmbodiedImpact::getTableField('recalculate') => 1, + ] + ]; $iterator = $DB->request(self::getEvaluableQuery($itemtype, $crit, false)); return $iterator; diff --git a/src/Impact/Usage/AbstractUsageImpact.php b/src/Impact/Usage/AbstractUsageImpact.php index 136c9696..38414f36 100644 --- a/src/Impact/Usage/AbstractUsageImpact.php +++ b/src/Impact/Usage/AbstractUsageImpact.php @@ -116,10 +116,12 @@ public function getItemsToEvaluate(array $crit = []): DBmysqlIterator throw new \LogicException('Itemtype does not inherits from ' . CommonDBTM::class); } - $crit[] = ['OR' => [ - UsageImpact::getTableField('id') => null, - UsageImpact::getTableField('recalculate') => 1, - ]]; + $crit[] = [ + 'OR' => [ + UsageImpact::getTableField('id') => null, + UsageImpact::getTableField('recalculate') => 1, + ] + ]; $crit[UsageImpact::getTableField('id')] = null; $iterator = $DB->request($this->getEvaluableQuery($crit, false)); diff --git a/src/UsageImpact.php b/src/UsageImpact.php index 95d869a5..de422c6f 100644 --- a/src/UsageImpact.php +++ b/src/UsageImpact.php @@ -36,9 +36,6 @@ class UsageImpact extends AbstractImpact { - public static $itemtype = 'itemtype'; - public static $items_id = 'items_id'; - public static function getTypeName($nb = 0) { return _n("Usage impact", "Usage impacts", $nb, 'carbon'); diff --git a/templates/environmentalimpact-item.html.twig b/templates/environmentalimpact-item.html.twig index 6210e01d..45b4dd52 100644 --- a/templates/environmentalimpact-item.html.twig +++ b/templates/environmentalimpact-item.html.twig @@ -47,7 +47,7 @@ {{ label }}
- + {{ usage_impact.getHumanReadableImpact(criteria)|raw }} {# #} @@ -101,7 +101,7 @@ {{ label }}
- + {{ embodied_impact.getHumanReadableImpact(criteria)|raw }} {# #} diff --git a/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php b/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php index 4045a261..cda8aae2 100644 --- a/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php +++ b/tests/src/Impact/Embodied/AbstractEmbodiedImpactTest.php @@ -100,7 +100,6 @@ public function testGetItemsToEvaluate() $asset::getTableField('id') => $asset->getID(), ]); $this->assertEquals(1, $iterator->count()); - } public function testGetEvaluableQuery() From 21276309cc42e71d7b81a444646e8f4890e119f5 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Mon, 2 Mar 2026 15:36:24 +0100 Subject: [PATCH 24/25] refactor(UsageInfo): code cleanup --- src/UsageInfo.php | 47 +++++--------------- templates/environmentalimpact-item.html.twig | 20 ++------- tests/units/UsageInfoTest.php | 6 +-- 3 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/UsageInfo.php b/src/UsageInfo.php index 3fee0b6a..1449493d 100644 --- a/src/UsageInfo.php +++ b/src/UsageInfo.php @@ -215,35 +215,8 @@ public static function showCharts(CommonDBTM $asset) 'items_id' => $asset->getID(), ]); - $url = Type::getCriteriaInfoLink('gwp'); - $tooltip = __('Evaluates the carbon emission in CO₂ equivalent. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); - $carbon_emission_tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => 'carbon_emission_tip', - ]); - - $url = Type::getCriteriaInfoLink('adp'); - $tooltip = __('Evaluates the consumption of non renewable resources in Antimony equivalent. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); - $usage_abiotic_depletion_tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => 'usage_abiotic_depletion_tip', - ]); - $embodied_abiotic_depletion_tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => 'embodied_abiotic_depletion_tip', - ]); - - $url = Type::getCriteriaInfoLink('pe'); - $tooltip = __('Evaluates the primary energy consumed. %s More information %s', 'carbon'); - $tooltip = sprintf($tooltip, '
', ''); - $embodied_primary_energy_tooltip_html = Html::showToolTip($tooltip, [ - 'display' => false, - 'applyto' => 'embodied_primary_energy_tip', - ]); - - $tooltips = []; + $embodied_tooltips = []; + $usage_tooltips = []; $embodied_labels = []; $usage_labels = []; foreach (Type::getImpactTypes() as $impact_type) { @@ -256,12 +229,17 @@ public static function showCharts(CommonDBTM $asset) '' ); } - $tooltips[$impact_type] = ''; + $embodied_tooltips[$impact_type] = ''; + $usage_tooltips[$impact_type] = ''; if ($tooltip !== '') { - $tooltips[$impact_type] = Html::showToolTip($tooltip, [ + $embodied_tooltips[$impact_type] = Html::showToolTip($tooltip, [ 'display' => false, 'applyto' => 'embodied_' . $impact_type . '_tip', ]); + $usage_tooltips[$impact_type] = Html::showToolTip($tooltip, [ + 'display' => false, + 'applyto' => 'usage_' . $impact_type . '_tip', + ]); } $embodied_labels[$impact_type] = Type::getEmbodiedImpactLabel($impact_type); $usage_labels[$impact_type] = Type::getUsageImpactLabel($impact_type); @@ -277,12 +255,9 @@ public static function showCharts(CommonDBTM $asset) 'usage_impact' => $usage_impact, 'embodied_labels' => $embodied_labels, 'usage_labels' => $usage_labels, - 'tooltips' => $tooltips, + 'embodied_tooltips' => $embodied_tooltips, + 'usage_tooltips' => $usage_tooltips, 'usage_carbon_emission_graph' => Widget::DisplayGraphUsageCarbonEmissionPerMonth($data), - 'carbon_emission_tooltip_html' => $carbon_emission_tooltip_html, - 'usage_abiotic_depletion_tooltip_html' => $usage_abiotic_depletion_tooltip_html, - 'embodied_abiotic_depletion_tooltip_html' => $embodied_abiotic_depletion_tooltip_html, - 'embodied_primary_energy_tooltip_html' => $embodied_primary_energy_tooltip_html, 'usage_impact_action_url' => $usage_imapct_action_url, 'embodied_impact_action_url' => $embodied_impact_action_url, ]); diff --git a/templates/environmentalimpact-item.html.twig b/templates/environmentalimpact-item.html.twig index 45b4dd52..b4f31b31 100644 --- a/templates/environmentalimpact-item.html.twig +++ b/templates/environmentalimpact-item.html.twig @@ -47,11 +47,11 @@ {{ label }}
- + {{ usage_impact.getHumanReadableImpact(criteria)|raw }} {# #} - {{ tooltips[criteria]|raw }} + {{ usage_tooltips[criteria]|raw }}
{% endif %} {% endfor %} @@ -61,20 +61,6 @@ {% if usage_impact.fields.gwp ?? null is not null %}
{{ usage_impact.getHumanReadableImpact('gwp')|raw }}
{% endif %} - - {% if usage_impact.fields.adp ?? null is not null %} -
- - {{ usage_impact.getHumanReadableImpact('adp')|raw }} - {# #} - - {{ usage_abiotic_depletion_tooltip_html|raw }} -
- {% endif %} - - {% if usage_impact.fields.pe ?? null is not null %} -
{{ usage_impact.getHumanReadableImpact('pe')|raw }}
- {% endif %}
@@ -105,7 +91,7 @@ {{ embodied_impact.getHumanReadableImpact(criteria)|raw }} {# #} - {{ tooltips[criteria]|raw }} + {{ embodied_tooltips[criteria]|raw }}
{% endif %} {% endfor %} diff --git a/tests/units/UsageInfoTest.php b/tests/units/UsageInfoTest.php index 90bbe459..cc905236 100644 --- a/tests/units/UsageInfoTest.php +++ b/tests/units/UsageInfoTest.php @@ -247,19 +247,19 @@ public function testShowcharts() private function testEmbodiedGwp(Crawler $crawler): bool { - $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_carbon_emission_tip'); + $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_gwp_tip'); return $items->count() === 1; } private function testEmbodiedAdp(Crawler $crawler): bool { - $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_abiotic_depletion_tip'); + $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_adp_tip'); return $items->count() === 1; } private function testEmbodiedPe(Crawler $crawler): bool { - $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_primary_energy_tip'); + $items = $crawler->filter('#plugin_carbon_embodied_impacts #embodied_pe_tip'); return $items->count() === 1; } } From 18ddde94b0f8864187b6bcbb7640f64d36caedd3 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Tue, 3 Mar 2026 14:23:41 +0100 Subject: [PATCH 25/25] feat: update and reorganize search options --- .../10_update_display_preferences.php | 138 ++++++++++++++++++ src/AbstractImpact.php | 30 +++- src/CarbonEmission.php | 6 +- src/Impact/Type.php | 44 +++--- src/SearchOptions.php | 21 ++- 5 files changed, 198 insertions(+), 41 deletions(-) create mode 100644 install/migration/update_1.1.1_to_1.2.0/10_update_display_preferences.php diff --git a/install/migration/update_1.1.1_to_1.2.0/10_update_display_preferences.php b/install/migration/update_1.1.1_to_1.2.0/10_update_display_preferences.php new file mode 100644 index 00000000..b38067c6 --- /dev/null +++ b/install/migration/update_1.1.1_to_1.2.0/10_update_display_preferences.php @@ -0,0 +1,138 @@ +. + * + * ------------------------------------------------------------------------- + */ + +use GlpiPlugin\Carbon\EmbodiedImpact; + +/** @var DBmysql $DB */ +/** @var Migration $migration */ + +$display_pref = new DisplayPreference(); + +$plugin_so_base = 128000; +$group_base = 1200; + +// +// EmbodiedImpact search options +// + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 5 // Was GWP, now recalculate flag +]); +$id = $plugin_so_base + $group_base; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$id = $plugin_so_base + $group_base + 2; +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 6 // Was ADP, now recalculate flag +]); +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$id = $plugin_so_base + $group_base + 4; +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 7 // Was PE, now recalculate flag +]); +foreach ($rows as $row) { + $row['num'] = 1204; + $display_pref->update($row); +} + +// +// UsageImpact search options +// +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128701 // usage GWP +]); +$id = $plugin_so_base + $group_base; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128702 // usage GWP qiality +]); +$id = $plugin_so_base + $group_base + 1; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128703 // usage ADP +]); +$id = $plugin_so_base + $group_base + 2; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128704 // usage ADP qiality +]); +$id = $plugin_so_base + $group_base + 3; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128705 // usage PE +]); +$id = $plugin_so_base + $group_base + 4; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} + +$rows = $display_pref->find([ + 'itemtype' => EmbodiedImpact::class, + 'num' => 128706 // usage PE qiality +]); +$id = $plugin_so_base + $group_base + 5; +foreach ($rows as $row) { + $row['num'] = $id; + $display_pref->update($row); +} diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php index 29577891..82137a11 100644 --- a/src/AbstractImpact.php +++ b/src/AbstractImpact.php @@ -74,8 +74,18 @@ public function rawSearchOptions() 'datatype' => 'itemtypename', ]; - $id = 5; - foreach (Type::getImpactTypes() as $type) { + $tab[] = [ + 'id' => '5', + 'table' => $this->getTable(), + 'field' => 'recalculate', + 'name' => __('', 'carbon'), + 'massiveaction' => false, + 'datatype' => 'bool', + ]; + + $id = SearchOptions::IMPACT_BASE; + foreach (Type::getImpactTypes() as $type_id => $type) { + $id = SearchOptions::IMPACT_BASE + $type_id * 2; $tab[] = [ 'id' => $id, 'table' => $this->getTable(), @@ -86,24 +96,34 @@ public function rawSearchOptions() 'unit' => implode(' ', Type::getImpactUnit($type)), ]; $id++; + + $tab[] = [ + 'id' => $id, + 'table' => $this->getTable(), + 'field' => "{$type}_quality", + 'name' => Type::getEmbodiedImpactLabel($type), + 'massiveaction' => false, + 'datatype' => 'number', + 'unit' => implode(' ', Type::getImpactUnit($type)), + ]; } $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, + 'id' => SearchOptions::CALCULATION_DATE, 'table' => self::getTable(), 'field' => 'date_mod', 'name' => __('Date of evaluation', 'carbon') ]; $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE, + 'id' => SearchOptions::CALCULATION_ENGINE, 'table' => self::getTable(), 'field' => 'engine', 'name' => __('Engine', 'carbon') ]; $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE_VER, + 'id' => SearchOptions::CALCULATION_ENGINE_VERSION, 'table' => self::getTable(), 'field' => 'engine_version', 'name' => __('Engine version', 'carbon') diff --git a/src/CarbonEmission.php b/src/CarbonEmission.php index b8dabee7..dbc5dcbb 100644 --- a/src/CarbonEmission.php +++ b/src/CarbonEmission.php @@ -145,21 +145,21 @@ public function rawSearchOptions() ]; $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_CALC_DATE, + 'id' => SearchOptions::CALCULATION_DATE, 'table' => self::getTable(), 'field' => 'date_mod', 'name' => __('Date of evaluation', 'carbon') ]; $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE, + 'id' => SearchOptions::CALCULATION_ENGINE, 'table' => self::getTable(), 'field' => 'engine', 'name' => __('Engine', 'carbon') ]; $tab[] = [ - 'id' => SearchOptions::CARBON_EMISSION_ENGINE_VER, + 'id' => SearchOptions::CALCULATION_ENGINE_VERSION, 'table' => self::getTable(), 'field' => 'engine_version', 'name' => __('Engine version', 'carbon') diff --git a/src/Impact/Type.php b/src/Impact/Type.php index 8fb6ac68..bc817a96 100644 --- a/src/Impact/Type.php +++ b/src/Impact/Type.php @@ -37,28 +37,28 @@ class Type { private const BASE_URL = 'https://glpi-plugins.readthedocs.io/%s/latest/carbon'; - const IMPACT_GWP = 1; // Global warming potential - const IMPACT_ADP = 2; // Abiotic Depletion Potential - const IMPACT_PE = 3; // Primary Energy - const IMPACT_GWPPB = 4; - const IMPACT_GWPPF = 5; - const IMPACT_GWPPLU = 6; - const IMPACT_IR = 7; - const IMPACT_LU = 8; - const IMPACT_ODP = 9; - const IMPACT_PM = 10; - const IMPACT_POCP = 11; - const IMPACT_WU = 12; - const IMPACT_MIPS = 13; - const IMPACT_ADPE = 14; - const IMPACT_ADPF = 15; - const IMPACT_AP = 16; - const IMPACT_CTUE = 17; - // const IMPACT_CTUHC = 18; - // const IMPACT_CTUHNC = 19; - const IMPACT_EPF = 20; - const IMPACT_EPM = 21; - const IMPACT_EPT = 22; + const IMPACT_GWP = 0; // Global warming potential + const IMPACT_ADP = 1; // Abiotic Depletion Potential + const IMPACT_PE = 2; // Primary Energy + const IMPACT_GWPPB = 3; + const IMPACT_GWPPF = 4; + const IMPACT_GWPPLU = 5; + const IMPACT_IR = 6; + const IMPACT_LU = 7; + const IMPACT_ODP = 8; + const IMPACT_PM = 9; + const IMPACT_POCP = 10; + const IMPACT_WU = 11; + const IMPACT_MIPS = 12; + const IMPACT_ADPE = 13; + const IMPACT_ADPF = 14; + const IMPACT_AP = 15; + const IMPACT_CTUE = 16; + // const IMPACT_CTUHC = 17; + // const IMPACT_CTUHNC = 18; + const IMPACT_EPF = 19; + const IMPACT_EPM = 20; + const IMPACT_EPT = 21; private static array $impact_types = [ self::IMPACT_GWP => 'gwp', diff --git a/src/SearchOptions.php b/src/SearchOptions.php index 1d2dc9b4..b416bad3 100644 --- a/src/SearchOptions.php +++ b/src/SearchOptions.php @@ -89,17 +89,9 @@ class SearchOptions public const CARBON_EMISSION_PER_DAY = self::SEARCH_OPTION_BASE + 602; public const CARBON_EMISSION_ENERGY_QUALITY = self::SEARCH_OPTION_BASE + 603; public const CARBON_EMISSION_EMISSION_QUALITY = self::SEARCH_OPTION_BASE + 604; - public const CARBON_EMISSION_CALC_DATE = self::SEARCH_OPTION_BASE + 605; - public const CARBON_EMISSION_ENGINE = self::SEARCH_OPTION_BASE + 606; - public const CARBON_EMISSION_ENGINE_VER = self::SEARCH_OPTION_BASE + 607; - - public const USAGE_IMPACT_DATE = self::SEARCH_OPTION_BASE + 700; - public const USAGE_IMPACT_GWP = self::SEARCH_OPTION_BASE + 701; - public const USAGE_IMPACT_GWP_QUALITY = self::SEARCH_OPTION_BASE + 702; - public const USAGE_IMPACT_ADP = self::SEARCH_OPTION_BASE + 703; - public const USAGE_IMPACT_ADP_QUALITY = self::SEARCH_OPTION_BASE + 704; - public const USAGE_IMPACT_PE = self::SEARCH_OPTION_BASE + 705; - public const USAGE_IMPACT_PE_QUALITY = self::SEARCH_OPTION_BASE + 706; + public const CALCULATION_DATE = self::SEARCH_OPTION_BASE + 605; + public const CALCULATION_ENGINE = self::SEARCH_OPTION_BASE + 606; + public const CALCULATION_ENGINE_VERSION = self::SEARCH_OPTION_BASE + 607; public const COMPUTER_TYPE_CATEGORY = self::SEARCH_OPTION_BASE + 800; public const COMPUTER_TYPE_IS_IGNORED = self::SEARCH_OPTION_BASE + 801; @@ -110,6 +102,13 @@ class SearchOptions public const NETEQUIP_TYPE_IS_IGNORED = self::SEARCH_OPTION_BASE + 1100; + // First search option ID for all imapcts + // Impacts are numbered accross several consecutive IDs + // - impact type + // - impact quality + // @see AbstractImpact::rawSearchOption + public const IMPACT_BASE = self::SEARCH_OPTION_BASE + 1200; + public const EMBODIED_IMPACT_GWP = self::SEARCH_OPTION_BASE + 1200; public const EMBODIED_IMPACT_GWP_SOURCE = self::SEARCH_OPTION_BASE + 1201; public const EMBODIED_IMPACT_GWP_QUALITY = self::SEARCH_OPTION_BASE + 1202;