diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index dc3f4f9a..6530865b 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -22,7 +22,7 @@ jobs:
name: "Generate CI matrix"
uses: "glpi-project/plugin-ci-workflows/.github/workflows/generate-ci-matrix.yml@v1"
with:
- glpi-version: "11.0.x"
+ glpi-version: "12.0.x"
ci:
name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}"
needs: "generate-ci-matrix"
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..d4cfadbf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+include ../../PluginsMakefile.mk
+
diff --git a/SECURITY.md b/SECURITY.md
index 0484177c..32657117 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -13,12 +13,14 @@ Once the report will be handled, and if the issue is not yet fixed (or in progre
we'll add it to the GitHub security tab, and add you as observer. Meanwhile,
you will reserve a CVE for the issue.
-Thank you for improving the security of glpi-agent.
+Thank you for improving the security of the plugin Carbon.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
-| 1.0.x | :heavy_check_mark: |
+| 1.0.x | :x: |
| 1.2.x | :x: |
| 1.3.x | :heavy_check_mark: |
+| 1.3.x | :heavy_check_mark: |
+| 1.4.x | :heavy_check_mark: |
diff --git a/ajax/dropdownZone.php b/ajax/dropdownZone.php
index 1ed765d2..4488fa20 100644
--- a/ajax/dropdownZone.php
+++ b/ajax/dropdownZone.php
@@ -29,21 +29,16 @@
*
* -------------------------------------------------------------------------
*/
-
+use Glpi\Exception\Http\AccessDeniedHttpException;
+use Glpi\Exception\Http\NotFoundHttpException;
use GlpiPlugin\Carbon\Source_Zone;
use GlpiPlugin\Carbon\Zone;
-include(__DIR__ . '/../../../inc/includes.php');
-
// Check if plugin is activated...
if (!Plugin::isPluginActive('carbon')) {
- http_response_code(404);
- die();
-}
-
-if (!Zone::canView()) {
- http_response_code(403);
- die();
+ throw new NotFoundHttpException();
+} elseif (!Zone::canView()) {
+ throw new AccessDeniedHttpException();
}
$source_zone_table = Source_Zone::getTable();
diff --git a/composer.json b/composer.json
index e9a9a0b1..659812bf 100644
--- a/composer.json
+++ b/composer.json
@@ -3,13 +3,7 @@
"php": ">=8.2.0",
"geocoder-php/google-maps-provider": "^4.7",
"geocoder-php/nominatim-provider": "^5.7",
- "league/iso3166": "^4.3",
- "php-http/message": "^1.16",
- "php-http/message-factory": "^1.1"
- },
- "require-dev": {
- "glpi-project/tools": "^0.8.1",
- "marcocesarato/php-conventional-changelog": "^1.17"
+ "league/iso3166": "^4.3"
},
"config": {
"optimize-autoloader": true,
@@ -35,5 +29,5 @@
"license-headers": "vendor/bin/licence-headers-check --ansi --no-interaction",
"build-schema": "tools/build-db-schema.php carbon | plantuml -p -tpng > docs/db-schema.png"
},
- "version": "1.3.0-dev"
+ "version": "1.4.0-dev"
}
diff --git a/composer.lock b/composer.lock
index 3a534dbe..b5d2cf46 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,74 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a821ef4c2ae32d2c6dce28a1a3b4ceb9",
+ "content-hash": "20397c57834cf59058491e3259bd0375",
"packages": [
- {
- "name": "clue/stream-filter",
- "version": "v1.7.0",
- "source": {
- "type": "git",
- "url": "https://github.com/clue/stream-filter.git",
- "reference": "049509fef80032cb3f051595029ab75b49a3c2f7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7",
- "reference": "049509fef80032cb3f051595029ab75b49a3c2f7",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/functions_include.php"
- ],
- "psr-4": {
- "Clue\\StreamFilter\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Christian Lück",
- "email": "christian@clue.engineering"
- }
- ],
- "description": "A simple and modern approach to stream filtering in PHP",
- "homepage": "https://github.com/clue/stream-filter",
- "keywords": [
- "bucket brigade",
- "callback",
- "filter",
- "php_user_filter",
- "stream",
- "stream_filter_append",
- "stream_filter_register"
- ],
- "support": {
- "issues": "https://github.com/clue/stream-filter/issues",
- "source": "https://github.com/clue/stream-filter/tree/v1.7.0"
- },
- "funding": [
- {
- "url": "https://clue.engineering/support",
- "type": "custom"
- },
- {
- "url": "https://github.com/clue",
- "type": "github"
- }
- ],
- "time": "2023-12-20T15:40:13+00:00"
- },
{
"name": "geocoder-php/common-http",
"version": "4.7.0",
@@ -396,183 +330,6 @@
},
"time": "2024-10-02T11:20:13+00:00"
},
- {
- "name": "php-http/message",
- "version": "1.16.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/message.git",
- "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/message/zipball/06dd5e8562f84e641bf929bfe699ee0f5ce8080a",
- "reference": "06dd5e8562f84e641bf929bfe699ee0f5ce8080a",
- "shasum": ""
- },
- "require": {
- "clue/stream-filter": "^1.5",
- "php": "^7.2 || ^8.0",
- "psr/http-message": "^1.1 || ^2.0"
- },
- "provide": {
- "php-http/message-factory-implementation": "1.0"
- },
- "require-dev": {
- "ergebnis/composer-normalize": "^2.6",
- "ext-zlib": "*",
- "guzzlehttp/psr7": "^1.0 || ^2.0",
- "laminas/laminas-diactoros": "^2.0 || ^3.0",
- "php-http/message-factory": "^1.0.2",
- "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
- "slim/slim": "^3.0"
- },
- "suggest": {
- "ext-zlib": "Used with compressor/decompressor streams",
- "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
- "laminas/laminas-diactoros": "Used with Diactoros Factories",
- "slim/slim": "Used with Slim Framework PSR-7 implementation"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/filters.php"
- ],
- "psr-4": {
- "Http\\Message\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "HTTP Message related tools",
- "homepage": "http://php-http.org",
- "keywords": [
- "http",
- "message",
- "psr-7"
- ],
- "support": {
- "issues": "https://github.com/php-http/message/issues",
- "source": "https://github.com/php-http/message/tree/1.16.2"
- },
- "time": "2024-10-02T11:34:13+00:00"
- },
- {
- "name": "php-http/message-factory",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/message-factory.git",
- "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
- "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4",
- "psr/http-message": "^1.0 || ^2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Http\\Message\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "Factory interfaces for PSR-7 HTTP Message",
- "homepage": "http://php-http.org",
- "keywords": [
- "factory",
- "http",
- "message",
- "stream",
- "uri"
- ],
- "support": {
- "issues": "https://github.com/php-http/message-factory/issues",
- "source": "https://github.com/php-http/message-factory/tree/1.1.0"
- },
- "abandoned": "psr/http-factory",
- "time": "2023-04-14T14:16:17+00:00"
- },
- {
- "name": "psr/http-message",
- "version": "2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/http-message.git",
- "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
- "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Http\\Message\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common interface for HTTP messages",
- "homepage": "https://github.com/php-fig/http-message",
- "keywords": [
- "http",
- "http-message",
- "psr",
- "psr-7",
- "request",
- "response"
- ],
- "support": {
- "source": "https://github.com/php-fig/http-message/tree/2.0"
- },
- "time": "2023-04-04T09:54:51+00:00"
- },
{
"name": "willdurand/geocoder",
"version": "5.0.0",
@@ -636,957 +393,7 @@
"time": "2025-01-01T15:52:42+00:00"
}
],
- "packages-dev": [
- {
- "name": "glpi-project/tools",
- "version": "0.8.3",
- "source": {
- "type": "git",
- "url": "https://github.com/glpi-project/tools.git",
- "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/glpi-project/tools/zipball/8ea2a7d4702a858f4b0360ba7d4f1841a5e77026",
- "reference": "8ea2a7d4702a858f4b0360ba7d4f1841a5e77026",
- "shasum": ""
- },
- "require": {
- "symfony/console": "^5.4 || ^6.0",
- "twig/twig": "^3.3"
- },
- "require-dev": {
- "nikic/php-parser": "^4.13",
- "phpstan/phpstan-src": "^1.10"
- },
- "bin": [
- "bin/extract-locales",
- "bin/licence-headers-check",
- "tools/plugin-release"
- ],
- "type": "library",
- "autoload": {
- "psr-4": {
- "GlpiProject\\Tools\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Teclib'",
- "email": "glpi@teclib.com",
- "homepage": "http://teclib-group.com"
- }
- ],
- "description": "Various tools for GLPI and its plugins",
- "keywords": [
- "glpi",
- "plugins",
- "tools"
- ],
- "support": {
- "issues": "https://github.com/glpi-project/tools/issues",
- "source": "https://github.com/glpi-project/tools"
- },
- "time": "2025-10-14T10:26:06+00:00"
- },
- {
- "name": "marcocesarato/php-conventional-changelog",
- "version": "1.17.3",
- "source": {
- "type": "git",
- "url": "https://github.com/marcocesarato/php-conventional-changelog.git",
- "reference": "c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/marcocesarato/php-conventional-changelog/zipball/c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2",
- "reference": "c49b4a69ddf9ecbf055e8029c65e54a956d7ffa2",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "ext-mbstring": "*",
- "php": ">=7.1.3",
- "symfony/console": "^4 || ^5 || ^6 || ^7 || ^8"
- },
- "require-dev": {
- "brainmaestro/composer-git-hooks": "^2.8",
- "friendsofphp/php-cs-fixer": "^3.8",
- "php-mock/php-mock": "^2.3",
- "php-mock/php-mock-phpunit": "^2.6",
- "phpunit/phpunit": "^9.6"
- },
- "bin": [
- "conventional-changelog"
- ],
- "type": "library",
- "extra": {
- "hooks": {
- "pre-push": "composer check-cs",
- "post-merge": "composer install",
- "pre-commit": "composer fix-cs"
- }
- },
- "autoload": {
- "psr-4": {
- "ConventionalChangelog\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Marco Cesarato",
- "email": "cesarato.developer@gmail.com"
- }
- ],
- "description": "Generate changelogs and release notes from a project's commit messages and metadata and automate versioning with semver.org and conventionalcommits.org",
- "keywords": [
- "changelog",
- "commit",
- "commits",
- "convention",
- "conventional",
- "conventional-changelog",
- "conventional-changelog-preset",
- "conventional-commit",
- "conventional-commits",
- "conventionalcommits",
- "generation",
- "git",
- "history",
- "php",
- "readme",
- "tag"
- ],
- "support": {
- "issues": "https://github.com/marcocesarato/php-conventional-changelog/issues",
- "source": "https://github.com/marcocesarato/php-conventional-changelog/tree/v1.17.3"
- },
- "time": "2026-01-21T11:40:14+00:00"
- },
- {
- "name": "psr/container",
- "version": "2.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "shasum": ""
- },
- "require": {
- "php": ">=7.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
- "support": {
- "issues": "https://github.com/php-fig/container/issues",
- "source": "https://github.com/php-fig/container/tree/2.0.2"
- },
- "time": "2021-11-05T16:47:00+00:00"
- },
- {
- "name": "symfony/console",
- "version": "v6.4.44",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/60e3944c4859c487aa6ea2f0f7754917f70f7524",
- "reference": "60e3944c4859c487aa6ea2f0f7754917f70f7524",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
- "symfony/polyfill-mbstring": "~1.0",
- "symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^5.4|^6.0|^7.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<5.4",
- "symfony/dotenv": "<5.4",
- "symfony/event-dispatcher": "<5.4",
- "symfony/lock": "<5.4",
- "symfony/process": "<5.4"
- },
- "provide": {
- "psr/log-implementation": "1.0|2.0|3.0"
- },
- "require-dev": {
- "psr/log": "^1|^2|^3",
- "symfony/config": "^5.4|^6.0|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
- "symfony/http-foundation": "^6.4|^7.0",
- "symfony/http-kernel": "^6.4|^7.0",
- "symfony/lock": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Eases the creation of beautiful and testable command line interfaces",
- "homepage": "https://symfony.com",
- "keywords": [
- "cli",
- "command-line",
- "console",
- "terminal"
- ],
- "support": {
- "source": "https://github.com/symfony/console/tree/v6.4.44"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-08-21T07:42:12+00:00"
- },
- {
- "name": "symfony/deprecation-contracts",
- "version": "v3.7.1",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
- "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "3.7-dev"
- }
- },
- "autoload": {
- "files": [
- "function.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "A generic function and convention to trigger deprecation notices",
- "homepage": "https://symfony.com",
- "support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-06-05T06:23:12+00:00"
- },
- {
- "name": "symfony/polyfill-ctype",
- "version": "v1.37.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
- "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "provide": {
- "ext-ctype": "*"
- },
- "suggest": {
- "ext-ctype": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Ctype\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for ctype functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "ctype",
- "polyfill",
- "portable"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-04-10T16:19:22+00:00"
- },
- {
- "name": "symfony/polyfill-intl-grapheme",
- "version": "v1.41.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d",
- "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "suggest": {
- "ext-intl": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for intl's grapheme_* functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "grapheme",
- "intl",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-07-28T08:25:59+00:00"
- },
- {
- "name": "symfony/polyfill-intl-normalizer",
- "version": "v1.42.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502",
- "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2"
- },
- "suggest": {
- "ext-intl": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
- },
- "classmap": [
- "Resources/stubs"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for intl's Normalizer class and related functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "intl",
- "normalizer",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-08-07T06:33:24+00:00"
- },
- {
- "name": "symfony/polyfill-mbstring",
- "version": "v1.38.2",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
- "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
- "shasum": ""
- },
- "require": {
- "ext-iconv": "*",
- "php": ">=7.2"
- },
- "provide": {
- "ext-mbstring": "*"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/polyfill",
- "name": "symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-05-27T06:59:30+00:00"
- },
- {
- "name": "symfony/service-contracts",
- "version": "v3.7.1",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/service-contracts.git",
- "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0",
- "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1",
- "psr/container": "^1.1|^2.0",
- "symfony/deprecation-contracts": "^2.5|^3"
- },
- "conflict": {
- "ext-psr": "<1.1|>=2"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "3.7-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\Service\\": ""
- },
- "exclude-from-classmap": [
- "/Test/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Generic abstractions related to writing services",
- "homepage": "https://symfony.com",
- "keywords": [
- "abstractions",
- "contracts",
- "decoupling",
- "interfaces",
- "interoperability",
- "standards"
- ],
- "support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.7.1"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-06-16T09:55:08+00:00"
- },
- {
- "name": "symfony/string",
- "version": "v7.4.15",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/string.git",
- "reference": "e394af32256bf9e7bf80849d95e589167c10097b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b",
- "reference": "e394af32256bf9e7bf80849d95e589167c10097b",
- "shasum": ""
- },
- "require": {
- "php": ">=8.2",
- "symfony/deprecation-contracts": "^2.5|^3.0",
- "symfony/polyfill-ctype": "~1.8",
- "symfony/polyfill-intl-grapheme": "~1.33",
- "symfony/polyfill-intl-normalizer": "~1.0",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "conflict": {
- "symfony/translation-contracts": "<2.5"
- },
- "require-dev": {
- "symfony/emoji": "^7.1|^8.0",
- "symfony/http-client": "^6.4|^7.0|^8.0",
- "symfony/intl": "^6.4|^7.0|^8.0",
- "symfony/translation-contracts": "^2.5|^3.0",
- "symfony/var-exporter": "^6.4|^7.0|^8.0"
- },
- "type": "library",
- "autoload": {
- "files": [
- "Resources/functions.php"
- ],
- "psr-4": {
- "Symfony\\Component\\String\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
- "homepage": "https://symfony.com",
- "keywords": [
- "grapheme",
- "i18n",
- "string",
- "unicode",
- "utf-8",
- "utf8"
- ],
- "support": {
- "source": "https://github.com/symfony/string/tree/v7.4.15"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2026-07-28T07:33:02+00:00"
- },
- {
- "name": "twig/twig",
- "version": "v3.28.0",
- "source": {
- "type": "git",
- "url": "https://github.com/twigphp/Twig.git",
- "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b",
- "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1.0",
- "symfony/deprecation-contracts": "^2.5|^3",
- "symfony/polyfill-ctype": "^1.8",
- "symfony/polyfill-mbstring": "^1.3"
- },
- "require-dev": {
- "php-cs-fixer/shim": "^3.0@stable",
- "phpstan/phpstan": "^2.0@stable",
- "psr/container": "^1.0|^2.0",
- "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/Resources/core.php",
- "src/Resources/debug.php",
- "src/Resources/escaper.php",
- "src/Resources/string_loader.php"
- ],
- "psr-4": {
- "Twig\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com",
- "homepage": "http://fabien.potencier.org",
- "role": "Lead Developer"
- },
- {
- "name": "Twig Team",
- "role": "Contributors"
- },
- {
- "name": "Armin Ronacher",
- "email": "armin.ronacher@active-4.com",
- "role": "Project Founder"
- }
- ],
- "description": "Twig, the flexible, fast, and secure template language for PHP",
- "homepage": "https://twig.symfony.com",
- "keywords": [
- "templating"
- ],
- "support": {
- "issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v3.28.0"
- },
- "funding": [
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/twig/twig",
- "type": "tidelift"
- }
- ],
- "time": "2026-07-03T20:44:34+00:00"
- }
- ],
+ "packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
diff --git a/front/embodiedimpact.form.php b/front/embodiedimpact.form.php
index 9f402902..688a3d1a 100644
--- a/front/embodiedimpact.form.php
+++ b/front/embodiedimpact.form.php
@@ -75,6 +75,10 @@
$embodied_impact->check($_POST['id'], PURGE);
$itemtype = $embodied_impact->fields['itemtype'];
+ if (!is_a($itemtype, CommonDBTM::class, true)) {
+ Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR);
+ Html::back();
+ }
$item = new $itemtype();
$item->getFromDB($embodied_impact->fields['items_id']);
if (!$item->canUpdateItem()) {
@@ -92,7 +96,7 @@
}
$itemtype = $_POST['itemtype'];
- if (!Toolbox::isCommonDBTM($itemtype)) {
+ if (!is_a($itemtype, CommonDBTM::class, true)) {
Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR);
Html::back();
}
diff --git a/front/report.php b/front/report.php
index b309e0e8..ad519ab3 100644
--- a/front/report.php
+++ b/front/report.php
@@ -30,6 +30,7 @@
* -------------------------------------------------------------------------
*/
+use Config as GlpiConfig;
use Glpi\Exception\Http\AccessDeniedHttpException;
use Glpi\Exception\Http\NotFoundHttpException;
use GlpiPlugin\Carbon\Config;
@@ -48,7 +49,7 @@
}
if (isset($_GET['disable_demo'])) {
- Session::checkRight('config', UPDATE);
+ Session::checkRight(GlpiConfig::$rightname, UPDATE);
Config::exitDemoMode();
Html::back();
}
diff --git a/front/usageimpact.form.php b/front/usageimpact.form.php
index 94d3650b..97105cef 100644
--- a/front/usageimpact.form.php
+++ b/front/usageimpact.form.php
@@ -92,6 +92,10 @@
/** @var AbstractAsset $history */
$gwp_impact = new $gwp_impact_class();
$itemtype = $gwp_impact->getItemtype();
+ if (!is_a($itemtype, CommonDBTM::class, true)) {
+ Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR);
+ Html::back();
+ }
$item = new $itemtype();
$item->getFromDB($_POST['items_id']);
if (!$item->canUpdateItem()) {
@@ -110,7 +114,7 @@
}
$itemtype = $_POST['itemtype'];
- if (!Toolbox::isCommonDBTM($itemtype)) {
+ if (!is_a($itemtype, CommonDBTM::class, true)) {
Session::addMessageAfterRedirect(__('Bad arguments.', 'carbon'), false, ERROR);
Html::back();
}
diff --git a/hook.php b/hook.php
index 54d99277..c0578f0c 100644
--- a/hook.php
+++ b/hook.php
@@ -60,6 +60,9 @@
use Profile as GlpiProfile;
use Toolbox as GlpiToolbox;
+use function Safe\ob_end_clean;
+use function Safe\ob_start;
+
/**
* Plugin install process
* supported arguments for upgrade process
@@ -289,6 +292,9 @@ function plugin_carbon_hook_pre_purge_assettype(CommonDBTM $item)
}
$carbon_type_itemtype = 'GlpiPlugin\\Carbon\\' . $itemtype;
+ if (!is_a($carbon_type_itemtype, CommonDBTM::class, true)) {
+ return;
+ }
$carbon_type = new $carbon_type_itemtype();
$carbon_type->deleteByCriteria([
$item->getForeignKeyField() => $item->getID(),
diff --git a/install/Install.php b/install/Install.php
index bee40254..9e55c689 100644
--- a/install/Install.php
+++ b/install/Install.php
@@ -39,6 +39,11 @@
use Plugin;
use RuntimeException;
+use function Safe\ini_set;
+use function Safe\preg_match;
+use function Safe\preg_replace;
+use function Safe\scandir;
+
class Install
{
/**
diff --git a/install/install/create_automatic_actions.php b/install/install/create_automatic_actions.php
index edef8bb2..9d48cd74 100644
--- a/install/install/create_automatic_actions.php
+++ b/install/install/create_automatic_actions.php
@@ -34,7 +34,7 @@
use GlpiPlugin\Carbon\CronTask;
use GlpiPlugin\Carbon\DataSource\CronTaskProvider;
-$cron_task_classes = CronTaskProvider::getCronTaskTypes();
+$cron_task_classes = CronTaskProvider::getCronTaskTypes(CronTaskProvider::getCronTaskDirectories());
$automatic_actions = [];
foreach ($cron_task_classes as $cron_task_class) {
$automatic_actions = array_merge($automatic_actions, $cron_task_class::enumerateTasks());
diff --git a/install/install/create_dashboard.php b/install/install/create_dashboard.php
index ff2b2ec6..28e4287d 100644
--- a/install/install/create_dashboard.php
+++ b/install/install/create_dashboard.php
@@ -29,13 +29,15 @@
*
* -------------------------------------------------------------------------
*/
-
use Glpi\Dashboard\Dashboard;
use Glpi\Dashboard\Item as DashboardItem;
use Glpi\Dashboard\Right as DashboardRight;
use Glpi\DBAL\QueryExpression;
use GlpiPlugin\Carbon\Report;
+use function Safe\file_get_contents;
+use function Safe\json_decode;
+
/** @var DBmysql $DB */
global $DB;
diff --git a/install/install/init_datasources.php b/install/install/init_datasources.php
index a195c0fa..fd033c36 100644
--- a/install/install/init_datasources.php
+++ b/install/install/init_datasources.php
@@ -29,7 +29,6 @@
*
* -------------------------------------------------------------------------
*/
-
use GlpiPlugin\Carbon\CarbonIntensity;
use GlpiPlugin\Carbon\Config;
use GlpiPlugin\Carbon\Install;
@@ -39,6 +38,8 @@
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\StreamOutput;
+use function Safe\fopen;
+
/** @var DBmysql $DB */
global $DB;
@@ -125,7 +126,9 @@
$progress_bar->setProgress($rows_count);
}
$file = null; // close the file
-
+ if (isset($output)) {
+ $output->writeln("");
+ }
Config::setPluginConfigurationValues(['ember_dataset_date' => EMBER_DATASET_DATE]);
}
diff --git a/install/migration/update_1.0.0_to_1.0.1.php b/install/migration/update_1.0.0_to_1.0.1.php
index ba4ba692..c11f8b44 100644
--- a/install/migration/update_1.0.0_to_1.0.1.php
+++ b/install/migration/update_1.0.0_to_1.0.1.php
@@ -30,6 +30,9 @@
* -------------------------------------------------------------------------
*/
+use function Safe\preg_match;
+use function Safe\scandir;
+
function update100to101(Migration $migration)
{
/** @var DBmysql $DB */
diff --git a/install/migration/update_1.0.1_to_1.1.0.php b/install/migration/update_1.0.1_to_1.1.0.php
index 19714136..2b16446d 100644
--- a/install/migration/update_1.0.1_to_1.1.0.php
+++ b/install/migration/update_1.0.1_to_1.1.0.php
@@ -30,6 +30,9 @@
* -------------------------------------------------------------------------
*/
+use function Safe\preg_match;
+use function Safe\scandir;
+
function update101to110(Migration $migration)
{
/** @var DBmysql $DB */
diff --git a/install/migration/update_1.1.1_to_1.2.0.php b/install/migration/update_1.1.1_to_1.2.0.php
index fd3d0e80..9050252f 100644
--- a/install/migration/update_1.1.1_to_1.2.0.php
+++ b/install/migration/update_1.1.1_to_1.2.0.php
@@ -29,6 +29,8 @@
*
* -------------------------------------------------------------------------
*/
+use function Safe\preg_match;
+use function Safe\scandir;
function update111to120(Migration $migration)
{
diff --git a/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php b/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php
index 6b3d5a93..079261e8 100644
--- a/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php
+++ b/install/migration/update_1.1.1_to_1.2.0/09_add_impact_criterias.php
@@ -35,6 +35,9 @@
use Glpi\Dashboard\Item as DashboardItem;
+use function Safe\json_decode;
+use function Safe\json_encode;
+
$new_criteria = [
'gwppb' => '(unit g CO2 eq) Biogenic climate change potential',
'gwppf' => '(unit g CO2 eq) Fossil climate change potential',
diff --git a/install/migration/update_x.x.x_to_y.y.y.php b/install/migration/update_x.x.x_to_y.y.y.php
index 3c5899ff..8deb05c6 100644
--- a/install/migration/update_x.x.x_to_y.y.y.php
+++ b/install/migration/update_x.x.x_to_y.y.y.php
@@ -30,6 +30,9 @@
* -------------------------------------------------------------------------
*/
+use function Safe\preg_match;
+use function Safe\scandir;
+
function update001to100(Migration $migration)
{
/** @var DBmysql $DB */
diff --git a/package-lock.json b/package-lock.json
index ce930f9d..a5955b40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "carbon",
- "version": "1.3.0-dev",
+ "version": "1.4.0-dev",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "carbon",
- "version": "1.3.0-dev",
+ "version": "1.4.0-dev",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -1930,10 +1930,21 @@
"peer": true
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
"peer": true,
"dependencies": {
"argparse": "^2.0.1"
diff --git a/package.json b/package.json
index 831cb333..dcf9a0ed 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "carbon",
"description": "Measurement of carbon emissions of GLPI devices",
"license": "GPL-3.0-or-later",
- "version": "1.3.0-dev",
+ "version": "1.4.0-dev",
"engines": {
"node": ">= 18.12.0"
},
diff --git a/phpstan.neon b/phpstan.neon
index c02769b3..8580ab79 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,3 +1,8 @@
+includes:
+ - ../../vendor/glpi-project/phpstan-glpi/extension.neon
+ - ../../vendor/phpstan/phpstan-deprecation-rules/rules.neon
+ - ../../vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
+
parameters:
parallel:
maximumNumberOfProcesses: 2
@@ -5,6 +10,7 @@ parameters:
bootstrapFiles:
- ../../stubs/glpi_constants.php
- ../../vendor/autoload.php
+ - setup.php
paths:
- src
- front
@@ -17,5 +23,3 @@ parameters:
- ../../src
stubFiles:
- ../../stubs/glpi_constants.php
-rules:
- - GlpiProject\Tools\PHPStan\Rules\GlobalVarTypeRule
diff --git a/setup.php b/setup.php
index 5017da4b..6e35edff 100644
--- a/setup.php
+++ b/setup.php
@@ -32,7 +32,9 @@
use Config as GlpiConfig;
use CronTask as GlpiCronTask;
+use Glpi\Config\ProxyExclusion;
use Glpi\Plugin\Hooks;
+use GlpiPlugin\Carbon\CloudInventoryConnector;
use GlpiPlugin\Carbon\Config;
use GlpiPlugin\Carbon\CronTask;
use GlpiPlugin\Carbon\Dashboard\Grid;
@@ -46,10 +48,13 @@
use Location as GlpiLocation;
use Profile as GlpiProfile;
+use function Safe\define;
+use function Safe\preg_match;
+
// Version of the plugin (major.minor.bugfix)
-define('PLUGIN_CARBON_VERSION', '1.3.0-dev');
+define('PLUGIN_CARBON_VERSION', '1.4.0-dev');
// Schema version of this version (major.minor.bugfix)
-define('PLUGIN_CARBON_SCHEMA_VERSION', '1.3.0');
+define('PLUGIN_CARBON_SCHEMA_VERSION', '1.4.0');
// Version compatibility check -- from GLPI developer documentation
// > A bug in GLPI prior to 11.0.7 caused plugin routes with method constraints other than GET to never match.
@@ -63,9 +68,9 @@
// Watch it when adding new controllers.
// Minimal GLPI version, inclusive
-define('PLUGIN_CARBON_MIN_GLPI_VERSION', '11.0.0');
+define('PLUGIN_CARBON_MIN_GLPI_VERSION', '12.0.0');
// Maximum GLPI version, exclusive
-define('PLUGIN_CARBON_MAX_GLPI_VERSION', '12.0.0');
+define('PLUGIN_CARBON_MAX_GLPI_VERSION', '13.0.0');
define('PLUGIN_CARBON_DECIMALS', 3);
define('EMBER_DATASET_DATE', '2025-07-30');
@@ -102,6 +107,11 @@ function plugin_init_carbon()
plugin_carbon_registerClasses();
$CFG_GLPI['javascript']['tools'][strtolower(Report::class)] = ['dashboard'];
+ $proxy_exclusions = $CFG_GLPI['possible_proxy_exclusions'];
+ $proxy_exclusions->addExclusion(new ProxyExclusion(
+ Config::class,
+ plugin_carbon_getFriendlyName(),
+ ));
}
function plugin_carbon_setupHooks()
@@ -115,6 +125,8 @@ function plugin_carbon_setupHooks()
LcaClientFactory::getSecuredConfigs()
);
+ $PLUGIN_HOOKS[Hooks::POST_INIT]['carbon'] = [CloudInventoryConnector::class, 'checkPluginAvailability'];
+
// add new cards to the dashboard
$PLUGIN_HOOKS[Hooks::DASHBOARD_CARDS]['carbon'] = [Grid::class, 'getDashboardCards'];
$PLUGIN_HOOKS[Hooks::DASHBOARD_TYPES]['carbon'] = [Widget::class, 'WidgetTypes'];
@@ -123,7 +135,7 @@ function plugin_carbon_setupHooks()
$PLUGIN_HOOKS[Hooks::DASHBOARD_DEFAULTS]['carbon'] = [Grid::class, 'getDefaults'];
}
- if (Session::haveRight('config', UPDATE)) {
+ if (Session::haveRight(GlpiConfig::$rightname, UPDATE)) {
$PLUGIN_HOOKS['config_page']['carbon'] = 'front/config.form.php';
}
@@ -223,8 +235,9 @@ function plugin_carbon_check_prerequisites()
$prerequisitesSuccess = true;
+ // In case GLPI is so old that the modern version checker is not implemented
/** @phpstan-ignore if.alwaysFalse */
- if (version_compare(GLPI_VERSION, PLUGIN_CARBON_MIN_GLPI_VERSION, 'lt')) {
+ if (version_compare(GLPI_VERSION, "10.0.0", 'lt')) {
echo "This plugin requires GLPI >= " . PLUGIN_CARBON_MIN_GLPI_VERSION . " and GLPI < " . PLUGIN_CARBON_MAX_GLPI_VERSION . "
";
$prerequisitesSuccess = false;
}
@@ -239,22 +252,23 @@ function plugin_carbon_check_prerequisites()
$prerequisitesSuccess = false;
}
- if (getenv('CI') === false) {
- // only when not under test
- $version_string = $DB->getVersion();
-
- $server = preg_match('/-MariaDB/', $version_string) ? 'MariaDB' : 'MySQL';
- $version = preg_replace('/^((\d+\.?)+).*$/', '$1', $version_string);
- if ($server === 'MySQL' && version_compare($version, '8.0.0', '<')) {
- echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
';
- $prerequisitesSuccess = false;
- }
-
- if ($server === 'MariaDB' && version_compare($version, '10.2.0', '<')) {
- echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
';
- $prerequisitesSuccess = false;
- }
- }
+ // GLPI 12 requires MySQL >= 8.0 or MariaDB >= 10.2, then the check below can be disabled
+ // if (getenv('CI') === false) {
+ // // only when not under test
+ // $version_string = $DB->getVersion();
+
+ // $server = preg_match('/-MariaDB/', $version_string) ? 'MariaDB' : 'MySQL';
+ // $version = preg_replace('/^((\d+\.?)+).*$/', '$1', $version_string);
+ // if ($server === 'MySQL' && version_compare($version, '8.0.0', '<')) {
+ // echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
';
+ // $prerequisitesSuccess = false;
+ // }
+
+ // if ($server === 'MariaDB' && version_compare($version, '10.2.0', '<')) {
+ // echo 'This plugin requires MySQL >= 8.0 or MariaDB >= 10.2
';
+ // $prerequisitesSuccess = false;
+ // }
+ // }
return $prerequisitesSuccess;
}
diff --git a/src/AbstractChildDropdown.php b/src/AbstractChildDropdown.php
index 64675fb5..54994398 100644
--- a/src/AbstractChildDropdown.php
+++ b/src/AbstractChildDropdown.php
@@ -41,7 +41,7 @@
abstract class AbstractChildDropdown extends CommonDBChild
{
- public static $rightname = 'dropdown';
+ public static string $rightname = 'dropdown';
#[Override]
public static function getIcon(): string
diff --git a/src/AbstractImpact.php b/src/AbstractImpact.php
index 103ddc09..95a9fa71 100644
--- a/src/AbstractImpact.php
+++ b/src/AbstractImpact.php
@@ -44,10 +44,10 @@
abstract class AbstractImpact extends CommonDBChild
{
- public static $itemtype = 'itemtype';
- public static $items_id = 'items_id';
+ public static string $itemtype = 'itemtype';
+ public static string $items_id = 'items_id';
- public static $rightname = 'carbon:report';
+ public static string $rightname = 'carbon:report';
#[Override]
public function canEdit($ID): bool
@@ -255,6 +255,6 @@ public function truncate(): bool
return false;
}
- return $DB->delete(static::getTable(), [1]);
+ return $DB->delete(static::getTable(), ['NOT' => ['id' => null]]);
}
}
diff --git a/src/Application/View/Extension/DataHelpersExtension.php b/src/Application/View/Extension/DataHelpersExtension.php
deleted file mode 100644
index 2ccec211..00000000
--- a/src/Application/View/Extension/DataHelpersExtension.php
+++ /dev/null
@@ -1,303 +0,0 @@
-.
- *
- * -------------------------------------------------------------------------
- */
-
-namespace GlpiPlugin\Carbon\Application\View\Extension;
-
-use Glpi\RichText\RichText;
-use Glpi\Toolbox\Sanitizer;
-use Html;
-use Override;
-use Toolbox;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFilter;
-
-/**
- * @since 10.0.0
- */
-class DataHelpersExtension extends AbstractExtension
-{
- #[Override]
- public function getFilters(): array
- {
- return [
- new TwigFilter('formatted_datetime', [$this, 'getFormattedDatetime']),
- new TwigFilter('formatted_date', [$this, 'getFormattedDate']),
- new TwigFilter('formatted_duration', [$this, 'getFormattedDuration']),
- new TwigFilter('formatted_integer', [$this, 'getFormattedInteger']),
- new TwigFilter('formatted_number', [$this, 'getFormattedNumber']),
- new TwigFilter('formatted_size', [$this, 'getFormattedSize']),
- new TwigFilter('html_to_text', [$this, 'getTextFromHtml']),
- new TwigFilter('long2ip', 'long2ip'),
- new TwigFilter('picture_url', [$this, 'getPictureUrl']),
- new TwigFilter('relative_datetime', [$this, 'getRelativeDatetime']),
- new TwigFilter('safe_html', [$this, 'getSafeHtml'], ['is_safe' => ['html']]),
- new TwigFilter('verbatim_value', [$this, 'getVerbatimValue']),
- new TwigFilter('shortcut', [$this, 'underlineShortcutLetter'], ['is_safe' => ['html']]),
- new TwigFilter('enhanced_html', [$this, 'getEnhancedHtml'], ['is_safe' => ['html']]),
- new TwigFilter('truncate_left', [$this, 'truncateLeft']),
- ];
- }
-
- /**
- * Return date formatted to user preferred format.
- *
- * @param mixed $datetime
- * @param bool $with_seconds
- *
- * @return string|null
- */
- public function getFormattedDatetime($datetime, bool $with_seconds = false): ?string
- {
- if (!is_string($datetime)) {
- return null;
- }
- return Html::convDateTime($datetime, null, $with_seconds);
- }
-
- /**
- * Return date formatted to user preferred format.
- *
- * @param mixed $date
- *
- * @return string|null
- */
- public function getFormattedDate($date): ?string
- {
- if (!is_string($date)) {
- return null;
- }
- return Html::convDate($date);
- }
-
- /**
- * Return relative representation of given date.
- *
- * @param mixed $datetime
- *
- * @return string|null
- */
- public function getRelativeDatetime($datetime): ?string
- {
- if (!is_string($datetime)) {
- return null;
- }
- return Html::timestampToRelativeStr($datetime);
- }
-
- /**
- * Return human readable duration.
- *
- * @param mixed $duration
- * @param bool $display_seconds (default: true)
- *
- * @return string|null
- */
- public function getFormattedDuration(
- $duration,
- bool $display_seconds = true
- ): ?string {
- if (!is_numeric($duration)) {
- return null;
- }
- return Html::timestampToString($duration, $display_seconds);
- }
-
- /**
- * Return integer formatted to user preferred format.
- *
- * @param mixed $number Number to display
- *
- * @return string
- */
- public function getFormattedInteger($number): string
- {
- return Html::formatNumber($number, false, 0);
- }
-
- /**
- * Return number formatted to user preferred format.
- *
- * @param mixed $number Number to display
- *
- * @return string
- */
- public function getFormattedNumber($number): string
- {
- return Html::formatNumber($number);
- }
-
- /**
- * Return size formatted in a compact way (mo, ko, etc).
- *
- * @param mixed $number
- *
- * @return string
- */
- public function getFormattedSize($number): string
- {
- if (!is_numeric($number)) {
- return '';
- }
- return Toolbox::getSize($number);
- }
-
- /**
- * Return URL for given picture.
- *
- * @param mixed $path
- *
- * @return null|string
- */
- public function getPictureUrl($path): ?string
- {
- if (!is_string($path)) {
- return null;
- }
-
- return Toolbox::getPictureUrl($path, true);
- }
-
- /**
- * Return string having its shortcut letter underlined.
- *
- * @param string $string
- * @param string $shortcut_letter
- *
- * @return string
- */
- public function underlineShortcutLetter(string $string, string $shortcut_letter): string
- {
- if (empty($shortcut_letter)) {
- return $string;
- }
- return Toolbox::shortcut($string, $shortcut_letter);
- }
-
- /**
- * Return plain text from HTML (rich text).
- *
- * @param mixed $string HTML string to be made safe
- * @param bool $keep_presentation Indicates whether the presentation elements have to be replaced by plaintext equivalents
- * @param bool $compact Indicates whether the output should be compact (limited line length, no links URL, ...)
- *
- * @return mixed
- */
- public function getTextFromHtml($string, bool $keep_presentation = true, bool $compact = false)
- {
- if (!is_string($string)) {
- return $string;
- }
-
- return RichText::getTextFromHtml($string, $keep_presentation, $compact);
- }
-
- /**
- * Return safe HTML (rich text).
- * Value will be made safe, whenever it has been sanitize (value fetched from DB),
- * or not (value computed during runtime).
- * Result will not be escaped, to prevent having to use `|raw` filter.
- *
- * @param mixed $string
- *
- * @return mixed
- */
- public function getSafeHtml($string)
- {
- if (!is_string($string)) {
- return $string;
- }
-
- return RichText::getSafeHtml($string);
- }
-
- /**
- * Return enhanced HTML (rich text).
- * Value will be made safe, whenever it has been sanitize (value fetched from DB),
- * or not (value computed during runtime).
- * Result will not be escaped, to prevent having to use `|raw` filter.
- *
- * @param mixed $string
- *
- * @return mixed
- */
- public function getEnhancedHtml($string, array $params = [])
- {
- if (!is_string($string)) {
- return $string;
- }
-
- return RichText::getEnhancedHtml($string, $params);
- }
-
- /**
- * Return verbatim value for an itemtype field.
- * Returned value will be unsanitized if it has been transformed by GLPI sanitizing process (value fetched from DB).
- * Twig autoescaping system will then ensure that value is correctly escaped in rendered HTML.
- *
- * @param mixed $string
- *
- * @return mixed
- *
- * @deprecated 11.0
- */
- public function getVerbatimValue($string)
- {
- Toolbox::deprecated();
-
- if (!is_string($string)) {
- return $string;
- }
-
- return Sanitizer::unsanitize($string);
- }
-
-
- /**
- * return the provided string truncated on the left and prepend a prefix separator if length is reached
- *
- * @param string $string the string to left truncate
- * @param int $length number of char to preserve
- * @param string $separator prefix to prepend to the string
- *
- * @return string truncated string
- */
- public function truncateLeft(string $string = "", int $length = 30, string $separator = "...")
- {
- if (mb_strlen($string) <= $length) {
- return $string;
- }
-
- return $separator . mb_substr($string, -$length);
- }
-}
diff --git a/src/CarbonEmission.php b/src/CarbonEmission.php
index 95619e15..5c717a49 100644
--- a/src/CarbonEmission.php
+++ b/src/CarbonEmission.php
@@ -44,8 +44,8 @@
class CarbonEmission extends CommonDBChild
{
- public static $itemtype = 'itemtype';
- public static $items_id = 'items_id';
+ public static string $itemtype = 'itemtype';
+ public static string $items_id = 'items_id';
#[Override]
public static function getTypeName($nb = 0)
@@ -229,6 +229,6 @@ public function truncate(): bool
return false;
}
- return $DB->delete(static::getTable(), [1]);
+ return $DB->delete(static::getTable(), ['NOT' => ['id' => null]]);
}
}
diff --git a/src/CarbonIntensity.php b/src/CarbonIntensity.php
index 4c9fc7ec..e33992f2 100644
--- a/src/CarbonIntensity.php
+++ b/src/CarbonIntensity.php
@@ -34,14 +34,14 @@
use CommonDropdown;
use DateInterval;
-use DateTime;
-use DateTimeImmutable;
use DateTimeInterface;
use DBmysql;
use Exception;
use Glpi\DBAL\QueryParam;
use GlpiPlugin\Carbon\DataSource\CarbonIntensity\ClientInterface;
use Override;
+use Safe\DateTime;
+use Safe\DateTimeImmutable;
use Symfony\Component\Console\Helper\ProgressBar;
/**
@@ -59,7 +59,7 @@ class CarbonIntensity extends CommonDropdown
{
private const MIN_HISTORY_LENGTH = '13 months ago';
- public static $rightname = 'carbon:report';
+ public static string $rightname = 'carbon:report';
#[Override]
public static function getTypeName($nb = 0)
@@ -251,7 +251,7 @@ public function downloadOneZone(ClientInterface $data_source, Source_Zone $sourc
$gap_end = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $gap['end']);
$count = $data_source->fullDownload($source_zone, $gap_start, $gap_end, $this, $limit, $progress_bar);
$total_count += $count;
- if ($total_count >= $limit) {
+ if ($limit > 0 && $total_count >= $limit) {
return $total_count;
}
}
diff --git a/ajax/toggleZoneDownload.php b/src/CloudInventoryConnector.php
similarity index 58%
rename from ajax/toggleZoneDownload.php
rename to src/CloudInventoryConnector.php
index a3f8fe0a..491a8a73 100644
--- a/ajax/toggleZoneDownload.php
+++ b/src/CloudInventoryConnector.php
@@ -30,40 +30,28 @@
* -------------------------------------------------------------------------
*/
-use Config as GlpiConfig;
-use GlpiPlugin\Carbon\Source;
-use GlpiPlugin\Carbon\Source_Zone;
+namespace GlpiPlugin\Carbon;
-include(__DIR__ . '/../../../inc/includes.php');
+use Plugin;
-// Check if plugin is activated...
-if (!Plugin::isPluginActive('carbon')) {
- echo __('Not found.', 'carbon');
- http_response_code(404);
- die();
-}
+/**
+ * Establish the functional bridge between Carbon and the plugin CloudInventory
+ */
+class CloudInventoryConnector
+{
+ public static function checkPluginAvailability(): void
+ {
+ /** @var array $CFG_GLPI */
+ global $CFG_GLPI;
-if (!Source::canView() || ! GlpiConfig::canUpdate()) {
- // Will die
- echo __('Access denied.', 'carbon');
- http_response_code(403);
- die();
-}
+ $CFG_GLPI['plugin:carbon']['use_cloudinventory'] = Plugin::isPluginActive('cloudinventory');
+ }
-if (!isset($_GET['id'])) {
- echo __('Bad request.', 'carbon');
- http_response_code(400);
- die();
-}
+ public function pluginAvailable(): bool
+ {
+ /** @var array $CFG_GLPI */
+ global $CFG_GLPI;
-$source_zone = new Source_Zone();
-if (!$source_zone->getFromDB($_GET['id'])) {
- echo __('Item not found.', 'carbon');
- http_response_code(403);
- die();
-}
-if (!$source_zone->toggleZone()) {
- echo __('Update failed.', 'carbon');
- http_response_code(500);
- die();
+ return $CFG_GLPI['plugin:carbon']['use_cloudinventory'] ?? false;
+ }
}
diff --git a/src/Command/CollectCarbonIntensityCommand.php b/src/Command/CollectCarbonIntensityCommand.php
index 09941081..0b92bd2e 100644
--- a/src/Command/CollectCarbonIntensityCommand.php
+++ b/src/Command/CollectCarbonIntensityCommand.php
@@ -52,6 +52,8 @@
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
+use function Safe\define;
+
// 6 months
define('DATE_MIN', 'P6M');
@@ -117,7 +119,7 @@ protected function interact(InputInterface $input, OutputInterface $output)
}
#[Override]
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
if (count($this->zones) > 1 && $input->getArgument('zone') === null) {
// Null is not a valid key if there are several zones available for the source
diff --git a/src/Command/CreateFakeCarbonIntensityCommand.php b/src/Command/CreateFakeCarbonIntensityCommand.php
index 516b9d1e..339de6d7 100644
--- a/src/Command/CreateFakeCarbonIntensityCommand.php
+++ b/src/Command/CreateFakeCarbonIntensityCommand.php
@@ -33,12 +33,12 @@
namespace GlpiPlugin\Carbon\Command;
use DateInterval;
-use DateTime;
use DateTimeZone;
use GlpiPlugin\Carbon\CarbonIntensity;
use GlpiPlugin\Carbon\Source;
use GlpiPlugin\Carbon\Zone;
use Override;
+use Safe\DateTime;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
@@ -63,7 +63,7 @@ protected function configure()
}
#[Override]
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->output = $output;
diff --git a/src/Command/CreateTestInventoryCommand.php b/src/Command/CreateTestInventoryCommand.php
index 1ea9c7cf..bccd34c0 100644
--- a/src/Command/CreateTestInventoryCommand.php
+++ b/src/Command/CreateTestInventoryCommand.php
@@ -40,6 +40,7 @@
use GlpiPlugin\Carbon\ComputerType;
use GlpiPlugin\Carbon\ComputerUsageProfile;
use GlpiPlugin\Carbon\UsageInfo;
+use InvalidArgumentException;
use Location;
use Override;
use Symfony\Component\Console\Command\Command;
@@ -141,7 +142,7 @@ protected function configure()
}
#[Override]
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->output = $output;
@@ -161,6 +162,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
private function createItemIfNotExist(string $item_type, array $crit, ?array $input = null): CommonDBTM
{
+ if (!is_a($item_type, CommonDBTM::class, true)) {
+ throw new InvalidArgumentException("Item type $item_type is not a subclass of CommonDBTM");
+ }
$item = new $item_type();
$ret = $item->getFromDBByCrit($crit);
diff --git a/src/Command/ExportDashboardCommand.php b/src/Command/ExportDashboardCommand.php
index 9c36d94b..061e14a2 100644
--- a/src/Command/ExportDashboardCommand.php
+++ b/src/Command/ExportDashboardCommand.php
@@ -38,10 +38,15 @@
use Glpi\Dashboard\Item;
use Override;
use Plugin;
+use Safe\Exceptions\FilesystemException;
+use Safe\Exceptions\JsonException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+use function Safe\file_put_contents;
+use function Safe\json_encode;
+
class ExportDashboardCommand extends Command
{
private OutputInterface $output;
@@ -60,7 +65,7 @@ protected function configure()
}
#[Override]
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var DBmysql $DB */
global $DB;
@@ -93,10 +98,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->dashboard_description[] = $row;
}
- file_put_contents(
- $this->output_path,
- json_encode($this->dashboard_description, JSON_PRETTY_PRINT)
- );
+ try {
+ file_put_contents(
+ $this->output_path,
+ json_encode($this->dashboard_description, JSON_PRETTY_PRINT)
+ );
+ } catch (FilesystemException|JsonException $e) {
+ $message = __('Error while saving dashboard description', 'carbon');
+ $this->output->writeln("