From 7489a9110ed4a5984a300f6321e6e9ae47c2b3d7 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 27 Aug 2026 16:36:47 +0200 Subject: [PATCH] Add links for uninstall templates from item form --- CHANGELOG.md | 4 + front/config.form.php | 2 + inc/state.class.php | 41 +--- inc/uninstall.class.php | 229 ++++++++++++++++++----- public/scripts/uninstall.js | 73 ++++++++ rector.php | 2 +- setup.php | 9 + templates/form_uninstallation.html.twig | 54 ++++++ templates/links_uninstallation.html.twig | 38 ++++ templates/state_replace.html.twig | 35 ++++ 10 files changed, 408 insertions(+), 79 deletions(-) create mode 100644 public/scripts/uninstall.js create mode 100644 templates/form_uninstallation.html.twig create mode 100644 templates/links_uninstallation.html.twig create mode 100644 templates/state_replace.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a7402..5ac4156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Add Links for uninstall templates From Item Form + ### Fixed - Fix uninstall/replace actions processing items without checking item type or user rights on the item diff --git a/front/config.form.php b/front/config.form.php index 5c6dad1..ed15f50 100644 --- a/front/config.form.php +++ b/front/config.form.php @@ -28,6 +28,8 @@ * ------------------------------------------------------------------------- */ +declare(strict_types=1); + Session::checkRight('config', UPDATE); /** @var array $CFG_GLPI */ diff --git a/inc/state.class.php b/inc/state.class.php index be19111..0b5857e 100644 --- a/inc/state.class.php +++ b/inc/state.class.php @@ -28,9 +28,7 @@ * ------------------------------------------------------------------------- */ -use function Safe\json_encode; -use function Safe\ob_end_clean; -use function Safe\ob_start; +use Glpi\Application\View\TemplateRenderer; class PluginUninstallState { @@ -58,36 +56,17 @@ public static function replaceState($params = []) 'complete' => true, ]); - // get form for uninstall actions - ob_start(); - PluginUninstallUninstall::showFormUninstallation($items_id, $item, $users_id); - $html_modal = ob_get_contents(); - ob_end_clean(); + // Get the uninstall actions form as a string (no output buffering). + $html_modal = PluginUninstallUninstall::showFormUninstallation($items_id, $item, $users_id, 0, false); - // we json encore to pass it to js (auto-escaping) - $html = json_encode(" - {$states_name} - " - . __s("Update") - . ""); - $modal_body = json_encode($html_modal); - - $JS = <<display('@uninstall/state_replace.html.twig', [ + 'rand' => mt_rand(), + 'states_name' => $states_name, + 'modal' => $html_modal, + ]); - glpi_html_dialog({ - body: {$modal_body} - }) - }); - }); -JAVASCRIPT; - echo Html::scriptBlock($JS); return null; } } diff --git a/inc/uninstall.class.php b/inc/uninstall.class.php index 2757eb0..e0e6afe 100644 --- a/inc/uninstall.class.php +++ b/inc/uninstall.class.php @@ -29,8 +29,7 @@ */ use Glpi\Asset\Asset_PeripheralAsset; - -use function Safe\preg_grep; +use Glpi\Application\View\TemplateRenderer; /** * ------------------------------------------------------------------------- @@ -60,6 +59,8 @@ * ------------------------------------------------------------------------- */ +use function Safe\preg_grep; + class PluginUninstallUninstall extends CommonDBTM { public const PLUGIN_UNINSTALL_TRANSFER_NAME = "plugin_uninstall"; @@ -926,61 +927,192 @@ public static function getInfocomPresentForDevice($type, $ID) /** - * @param $ID - * @param $item - * @param $user_id - **/ - public static function showFormUninstallation($ID, $item, $user_id) + * Render the "apply uninstall/replace model" form. + * + * @param int|string $ID id of the item the form applies to + * @param CommonGLPI $item item the form applies to + * @param int $user_id current user id (for stored location preference) + * @param int $templates_id 0 for a fresh model selector, otherwise the model to apply + * @param bool $display true echoes the form, false returns it as a string + * + * @return string|null the HTML when $display is false + **/ + public static function showFormUninstallation($ID, $item, $user_id, $templates_id = 0, $display = true, $used = null) { /** * @var array $CFG_GLPI */ - global $CFG_GLPI; + global $CFG_GLPI, $DB; $type = $item->getType(); - echo "
"; - echo Html::hidden('device_type', ['value' => $type]); - echo ""; - echo ""; - - echo ""; - $params = ['templates_id' => '__VALUE__', - 'entity' => $item->fields["entities_id"], - 'users_id' => $_SESSION["glpiID"], - ]; + $supported = $item instanceof CommonDBTM; + + $model_dropdown = ''; + $location_html = ''; + $onselect_js = ''; + + if ($supported) { + // The caller already passes a loaded item; only (re)load when the + // given $ID does not match it, to avoid a redundant getFromDB on + // every call inside the showLinksUninstallation() loop. + if ($item->isNewItem() || (int) $item->getID() !== (int) $ID) { + $item->getFromDB($ID); + } + + $entities_id = $item->fields["entities_id"]; + + if ($templates_id == 0) { + $rand = mt_rand(); + $model_dropdown = self::dropdownUninstallModels( + "model_id", + $_SESSION["glpiID"], + $entities_id, + ['display' => false, 'rand' => $rand], + ); + + $params = [ + 'templates_id' => '__VALUE__', + 'entity' => $entities_id, + 'users_id' => $_SESSION["glpiID"], + ]; + + $onselect_js = Ajax::updateItemOnSelectEvent( + 'dropdown_model_id' . $rand, + "show_objects", + $CFG_GLPI['root_doc'] . "/plugins/uninstall/ajax/locations.php", + $params, + false, + ); + + $location_html = "" . Dropdown::EMPTY_VALUE . ""; + } else { + // $used may be supplied by the caller (see showLinksUninstallation) + // to avoid recomputing this query once per link; fall back to + // computing it here when the method is called on its own. + if ($used === null) { + $used = []; + if (!PluginUninstallModel::canReplace()) { + $used = array_column( + iterator_to_array( + $DB->request([ + 'SELECT' => ['id'], + 'FROM' => 'glpi_plugin_uninstall_models', + 'WHERE' => [ + 'types_id' => [2, 3], + ], + ]), + ), + 'id', + ); + } + } + + $model_dropdown = PluginUninstallModel::dropdown([ + 'name' => "model_id", + 'value' => $templates_id, + 'entity' => $entities_id, + 'used' => $used, + 'display' => false, + ]); - Ajax::updateItemOnSelectEvent( - 'dropdown_model_id' . $rand, - "show_objects", - $CFG_GLPI['root_doc'] . "/plugins/uninstall/ajax/locations.php", - $params, + $location = PluginUninstallPreference::getLocationByUserByEntity( + $user_id, + $templates_id, + $entities_id, + ); + $location_html = Location::dropdown([ + 'value' => ($location == '' ? 0 : $location), + 'comments' => 1, + 'entity' => $entities_id, + 'toadd' => [ + -1 => __s('Keep previous location', 'uninstall'), + 0 => __s('Empty location', 'uninstall'), + ], + 'display' => false, + ]); + } + } + + $html = TemplateRenderer::getInstance()->render('@uninstall/form_uninstallation.html.twig', [ + 'supported' => $supported, + 'action_url' => $CFG_GLPI['root_doc'] . "/plugins/uninstall/front/action.php", + 'device_type' => $type, + 'item_id' => $ID, + 'model_dropdown' => $model_dropdown, + 'location_html' => $location_html, + 'onselect_js' => $onselect_js, + ]); + + if ($display) { + echo $html; + return null; + } + + return $html; + } + + public static function showLinksUninstallation( + $params, + ) { + /** + * @var array $UNINSTALL_TYPES + */ + global $DB, $UNINSTALL_TYPES; + + $right = Session::haveRight(self::$rightname, READ); + + $users_id = Session::getLoginUserID(); + $item = $params['item']; + if ( + !$right + || !in_array($item->getType(), $UNINSTALL_TYPES) + || $item->getID() <= 0 + ) { + return null; + } + + $criteria = [ + 'FROM' => 'glpi_plugin_uninstall_models', + 'WHERE' => getEntitiesRestrictCriteria('glpi_plugin_uninstall_models', '', $item->fields['entities_id'], true), + ]; + + // Compute the excluded/used model ids once, before the loop: + // showFormUninstallation() would otherwise re-run this same + // canReplace() query for every link (2N redundant round-trips). + $used = []; + if (!PluginUninstallModel::canReplace()) { + $criteria['WHERE']['NOT'] = ['types_id' => [2, 3]]; + $used = array_column( + iterator_to_array( + $DB->request([ + 'SELECT' => ['id'], + 'FROM' => 'glpi_plugin_uninstall_models', + 'WHERE' => [ + 'types_id' => [2, 3], + ], + ]), + ), + 'id', ); + } + $links = []; + foreach ($DB->request($criteria) as $data) { + $links[] = [ + 'id' => (int) $data['id'], + 'name' => $data['name'], + // The modal body is captured as a string (no output buffering) + // and rendered inside an inert
" . __s("Apply model", 'uninstall') . "
" . __s("Model") . ""; - if (class_exists($type) && is_a($type, CommonDBTM::class, true)) { - - $item = new $type(); - $item->getFromDB($ID); - $rand = self::dropdownUninstallModels( - "model_id", - $_SESSION["glpiID"], - $item->fields["entities_id"], - ); - echo "