From 97ce6151b4e56788dfad4c83c1d69a24f27ce7c5 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 16:10:39 +0200 Subject: [PATCH 1/7] fix(Location): fail to massively update locations - unable to call entry point with official GLPI 11 docker image because glpi FS root is not a parent of plugi's directory, when installed via the marketplace - a bug which makes the massive update being ignored => convert the entrypoint into a controller --- ajax/dropdownZone.php | 56 ------------------------ src/Controller/AjaxController.php | 73 +++++++++++++++++++++++++++++++ src/Location.php | 2 +- 3 files changed, 74 insertions(+), 57 deletions(-) delete mode 100644 ajax/dropdownZone.php create mode 100644 src/Controller/AjaxController.php diff --git a/ajax/dropdownZone.php b/ajax/dropdownZone.php deleted file mode 100644 index 1ed765d2..00000000 --- a/ajax/dropdownZone.php +++ /dev/null @@ -1,56 +0,0 @@ -. - * - * ------------------------------------------------------------------------- - */ - -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(); -} - -$source_zone_table = Source_Zone::getTable(); -$zone_table = Zone::getTable(); -$source_id = (int) $_POST['plugin_carbon_sources_id']; -Zone::dropdown([ - 'rand' => (int) $_POST['dom_id'], - 'condition' => Zone::getRestrictBySourceCondition($source_id), - 'specific_tags' => ($source_id === 0 ? ['disabled' => 'disabled'] : []), -]); diff --git a/src/Controller/AjaxController.php b/src/Controller/AjaxController.php new file mode 100644 index 00000000..61e65c29 --- /dev/null +++ b/src/Controller/AjaxController.php @@ -0,0 +1,73 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Controller; + +use Config as GlpiConfig; +use Glpi\Controller\AbstractController; +use Glpi\Http\Firewall; +use Glpi\Security\Attribute\SecurityStrategy; +use GlpiPlugin\Carbon\Source; +use GlpiPlugin\Carbon\Source_Zone; +use GlpiPlugin\Carbon\Zone; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +class AjaxController extends AbstractController +{ + #[SecurityStrategy(Firewall::STRATEGY_AUTHENTICATED)] + #[Route( + path: 'ajax/dropdownZone.php', + name: 'ajax dropdownZone', + methods: ['GET', 'POST'])] + public function showDropdownBySourceCondition(Request $request): Response + { + // if method is GET, then throw an exception, workaround bug in GLPI up to 11.0.7 + if ($request->isMethod('GET')) { + return new Response('', 403); + } + + if (!Zone::canView()) { + return new Response('', 403); + } + + $source_id = (int) $_POST['plugin_carbon_sources_id']; + $html = Zone::dropdown([ + 'display' => false, + 'rand' => (int) $_POST['dom_id'], + 'condition' => Zone::getRestrictBySourceCondition($source_id), + 'specific_tags' => ($source_id === 0 ? ['disabled' => 'disabled'] : []), + ]); + return new Response($html); + } +} diff --git a/src/Location.php b/src/Location.php index 8d3af569..735311c5 100644 --- a/src/Location.php +++ b/src/Location.php @@ -124,7 +124,7 @@ public function prepareInputForUpdate($input) } } - if (($input['plugin_carbon_sources_id'] ?? 0) == 0) { + if (isset($input['plugin_carbon_sources_id']) && $input['plugin_carbon_sources_id'] == 0) { $input['plugin_carbon_sources_zones_id'] = 0; } From 7288aa445de36fb156486b2c197100fc830a664f Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:27:57 +0200 Subject: [PATCH 2/7] fix(Source_Zone): broken download toggle --- ajax/toggleZoneDownload.php | 69 ------------------------------- src/Controller/AjaxController.php | 32 ++++++++++++++ src/Source_Zone.php | 38 ++++++++--------- 3 files changed, 50 insertions(+), 89 deletions(-) delete mode 100644 ajax/toggleZoneDownload.php diff --git a/ajax/toggleZoneDownload.php b/ajax/toggleZoneDownload.php deleted file mode 100644 index b3ce51cc..00000000 --- a/ajax/toggleZoneDownload.php +++ /dev/null @@ -1,69 +0,0 @@ -. - * - * ------------------------------------------------------------------------- - */ - -use Config as GlpiConfig; -use GlpiPlugin\Carbon\Source; -use GlpiPlugin\Carbon\Source_Zone; - -include(__DIR__ . '/../../../inc/includes.php'); - -// Check if plugin is activated... -if (!Plugin::isPluginActive('carbon')) { - echo __('Not found.', 'carbon'); - http_response_code(404); - die(); -} - -if (!Source::canView() || ! GlpiConfig::canUpdate()) { - // Will die - echo __('Access denied.', 'carbon'); - http_response_code(403); - die(); -} - -if (!isset($_POST['id'])) { - echo __('Bad request.', 'carbon'); - http_response_code(400); - die(); -} - -$source_zone = new Source_Zone(); -if (!$source_zone->getFromDB($_POST['id'])) { - echo __('Item not found.', 'carbon'); - http_response_code(403); - die(); -} -if (!$source_zone->toggleZone()) { - echo __('Update failed.', 'carbon'); - http_response_code(500); - die(); -} diff --git a/src/Controller/AjaxController.php b/src/Controller/AjaxController.php index 61e65c29..3524982f 100644 --- a/src/Controller/AjaxController.php +++ b/src/Controller/AjaxController.php @@ -39,6 +39,7 @@ use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Zone; +use Session; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -70,4 +71,35 @@ public function showDropdownBySourceCondition(Request $request): Response ]); return new Response($html); } + + #[SecurityStrategy(Firewall::STRATEGY_AUTHENTICATED)] + #[Route( + path: 'ajax/toggleZoneDownload.php', + name: 'ajax toggle zone download flag', + methods: ['GET', 'POST'])] + public function toggleZoneDownload(Request $request): Response + { + // if method is GET, then throw an exception, workaround bug in GLPI up to 11.0.7 + if ($request->isMethod('GET')) { + return new Response('', 403); + } + + if (!Source::canView() || ! GlpiConfig::canUpdate()) { + return new Response(__('Access denied.', 'carbon'), 403); + } + + if (!isset($_POST['id'])) { + return new Response(__('Bad request.', 'carbon'), 400); + } + + $source_zone = new Source_Zone(); + if (!$source_zone->getFromDB($_POST['id'])) { + return new Response(__('Item not found.', 'carbon'), 403); + } + if (!$source_zone->toggleZone()) { + return new Response( __('Update failed.', 'carbon'), 500); + } + + return new Response(); + } } diff --git a/src/Source_Zone.php b/src/Source_Zone.php index a3422d6f..a3d7fd07 100644 --- a/src/Source_Zone.php +++ b/src/Source_Zone.php @@ -197,19 +197,18 @@ public static function showForSource(CommonDBTM $item) // At least 1 entry then add JS to toggle the state of zones echo Html::scriptBlock(' var plugin_carbon_toggleZone = function (id) { - fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php?id=" + id, { + var csrfToken = "' . Session::getNewCSRFToken() . '"; + var formData = new FormData(); + formData.append("_glpi_csrf_token", csrfToken); + formData.append("id", id); + fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php", { method: "POST", headers: { - "X-Glpi-Csrf-Token": "' . Session::getNewCSRFToken() . '" - } + "X-Glpi-Csrf-Token": csrfToken + }, + body: formData, }).then(response => { - if (response.status === 200) { - reloadTab(); - } else { - response.text().then(function (text) { - glpi_toast_error(text) - }); - } + reloadTab(); }); }; '); @@ -301,19 +300,18 @@ public static function showForZone(CommonDBTM $item) // At least 1 entry then add JS to toggle the state of zones echo Html::scriptBlock(' var plugin_carbon_toggleZone = function (id) { - fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php?id=" + id, { + var csrfToken = "' . Session::getNewCSRFToken() . '"; + var formData = new FormData(); + formData.append("_glpi_csrf_token", csrfToken); + formData.append("id", id); + fetch(CFG_GLPI["root_doc"] + "/plugins/carbon/ajax/toggleZoneDownload.php", { method: "POST", headers: { - "X-Glpi-Csrf-Token": "' . Session::getNewCSRFToken() . '" - } + "X-Glpi-Csrf-Token": csrfToken + }, + body: formData, }).then(response => { - if (response.status === 200) { - reloadTab(); - } else { - response.text().then(function (text) { - glpi_toast_error(text) - }); - } + reloadTab(); }); }; '); From 810f94cb497a32551b816b1ceb6dd40f50b8bc24 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:40:18 +0200 Subject: [PATCH 3/7] style: fix code style --- src/Controller/AjaxController.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Controller/AjaxController.php b/src/Controller/AjaxController.php index 3524982f..a825149a 100644 --- a/src/Controller/AjaxController.php +++ b/src/Controller/AjaxController.php @@ -39,7 +39,6 @@ use GlpiPlugin\Carbon\Source; use GlpiPlugin\Carbon\Source_Zone; use GlpiPlugin\Carbon\Zone; -use Session; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -50,7 +49,8 @@ class AjaxController extends AbstractController #[Route( path: 'ajax/dropdownZone.php', name: 'ajax dropdownZone', - methods: ['GET', 'POST'])] + methods: ['GET', 'POST'] + )] public function showDropdownBySourceCondition(Request $request): Response { // if method is GET, then throw an exception, workaround bug in GLPI up to 11.0.7 @@ -76,7 +76,8 @@ public function showDropdownBySourceCondition(Request $request): Response #[Route( path: 'ajax/toggleZoneDownload.php', name: 'ajax toggle zone download flag', - methods: ['GET', 'POST'])] + methods: ['GET', 'POST'] + )] public function toggleZoneDownload(Request $request): Response { // if method is GET, then throw an exception, workaround bug in GLPI up to 11.0.7 @@ -97,7 +98,7 @@ public function toggleZoneDownload(Request $request): Response return new Response(__('Item not found.', 'carbon'), 403); } if (!$source_zone->toggleZone()) { - return new Response( __('Update failed.', 'carbon'), 500); + return new Response(__('Update failed.', 'carbon'), 500); } return new Response(); From b3762c25eb1c0cebc12024877e2eb2b7922b341c Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:41:11 +0200 Subject: [PATCH 4/7] chore: update rector path list --- rector.php | 1 - 1 file changed, 1 deletion(-) diff --git a/rector.php b/rector.php index 69b3bb42..8ca924aa 100644 --- a/rector.php +++ b/rector.php @@ -40,7 +40,6 @@ return RectorConfig::configure() ->withPaths([ - __DIR__ . '/ajax', __DIR__ . '/front', __DIR__ . '/install', __DIR__ . '/public', From b9170569870859580673b05a40be1ce466530bd7 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:42:36 +0200 Subject: [PATCH 5/7] chore: remove php conventionnal changelog --- composer.json | 3 +- composer.lock | 80 +-------------------------------------------------- 2 files changed, 2 insertions(+), 81 deletions(-) diff --git a/composer.json b/composer.json index 33e9c7fc..55cff10d 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,7 @@ "php-http/message-factory": "^1.1" }, "require-dev": { - "glpi-project/tools": "^0.8.1", - "marcocesarato/php-conventional-changelog": "^1.17" + "glpi-project/tools": "^0.8.1" }, "config": { "optimize-autoloader": true, diff --git a/composer.lock b/composer.lock index 3a534dbe..a39801f4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a821ef4c2ae32d2c6dce28a1a3b4ceb9", + "content-hash": "e9d627568b1c399e0361e80bb7cdef54", "packages": [ { "name": "clue/stream-filter", @@ -693,84 +693,6 @@ }, "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", From 3c547bb9697f3d3047cd7673b662153f03a2e349 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:37:17 +0200 Subject: [PATCH 6/7] chore: update phpstan paths --- phpstan.neon | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index c02769b3..55b0307c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,7 +8,6 @@ parameters: paths: - src - front - - ajax - install - hook.php - setup.php From 6a39ad232d2b1b9022771fb854628f1f58b81a76 Mon Sep 17 00:00:00 2001 From: Thierry Bugier Date: Fri, 25 Sep 2026 17:49:29 +0200 Subject: [PATCH 7/7] refactor(Controller\AjaxController): use request object instead of global var --- src/Controller/AjaxController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controller/AjaxController.php b/src/Controller/AjaxController.php index a825149a..c8f9faa2 100644 --- a/src/Controller/AjaxController.php +++ b/src/Controller/AjaxController.php @@ -65,7 +65,7 @@ public function showDropdownBySourceCondition(Request $request): Response $source_id = (int) $_POST['plugin_carbon_sources_id']; $html = Zone::dropdown([ 'display' => false, - 'rand' => (int) $_POST['dom_id'], + 'rand' => (int) $request->request->get('dom_id'), 'condition' => Zone::getRestrictBySourceCondition($source_id), 'specific_tags' => ($source_id === 0 ? ['disabled' => 'disabled'] : []), ]); @@ -94,7 +94,7 @@ public function toggleZoneDownload(Request $request): Response } $source_zone = new Source_Zone(); - if (!$source_zone->getFromDB($_POST['id'])) { + if (!$source_zone->getFromDB($request->request->get('id'))) { return new Response(__('Item not found.', 'carbon'), 403); } if (!$source_zone->toggleZone()) {