From 9faaf41615026adfe1dfc0e1fb5421a360347c55 Mon Sep 17 00:00:00 2001 From: Rom1-B <8530352+Rom1-B@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:48:25 +0200 Subject: [PATCH 1/2] Fix: correct minor bugs in the import/export workflow --- CHANGELOG.md | 3 ++- ajax/inject_batch.php | 2 +- ajax/injection.php | 2 +- ajax/results.php | 2 +- front/info.form.php | 2 ++ front/popup.php | 2 ++ inc/clientinjection.class.php | 14 ++++++++++--- inc/info.class.php | 5 ++++- inc/softwarelicenseinjection.class.php | 28 +++++++++++++------------- inc/softwareversioninjection.class.php | 28 +++++++++++++------------- inc/userinjection.class.php | 13 +++++------- 11 files changed, 57 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a43be598..635162b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## [Unreleased] ### Fixed +- Fix various minor bugs in the import/export workflow - Fix an issue where data are not formatted when coming from a field plugin's custom field. ## [2.15.10] - 2026-08-07 diff --git a/ajax/inject_batch.php b/ajax/inject_batch.php index 2096cf47..d86e647d 100644 --- a/ajax/inject_batch.php +++ b/ajax/inject_batch.php @@ -32,7 +32,7 @@ header("Content-Type: application/json; charset=UTF-8"); Html::header_nocache(); -Session::checkCentralAccess(); +Session::checkRight(PluginDatainjectionClientInjection::$rightname, READ); $offset = (int) ($_POST['offset'] ?? 0); $batch_size = (int) ($_POST['batch_size'] ?? 10); diff --git a/ajax/injection.php b/ajax/injection.php index 4ad9be62..76581502 100644 --- a/ajax/injection.php +++ b/ajax/injection.php @@ -34,6 +34,6 @@ Html::header_nocache(); } -Session::checkCentralAccess(); +Session::checkRight(PluginDatainjectionClientInjection::$rightname, READ); $model = PluginDatainjectionSession::unserialize($_SESSION['datainjection']['currentmodel']); PluginDatainjectionClientInjection::showInjectionForm($model, $_SESSION['glpiactive_entity']); diff --git a/ajax/results.php b/ajax/results.php index c08ed9a9..1d64dfb7 100644 --- a/ajax/results.php +++ b/ajax/results.php @@ -34,6 +34,6 @@ Html::header_nocache(); } -Session::checkCentralAccess(); +Session::checkRight(PluginDatainjectionClientInjection::$rightname, READ); $model = PluginDatainjectionSession::unserialize($_SESSION['datainjection']['currentmodel']); PluginDatainjectionClientInjection::showResultsForm($model); diff --git a/front/info.form.php b/front/info.form.php index 5cbdffaf..9c0f1f53 100644 --- a/front/info.form.php +++ b/front/info.form.php @@ -32,6 +32,8 @@ /* Update mappings */ if (isset($_POST["update"])) { + $model = new PluginDatainjectionModel(); + $model->check($_POST['models_id'], UPDATE); PluginDatainjectionInfo::manageInfos($_POST['models_id'], $_POST); } elseif (isset($_POST["delete"])) { $info = new PluginDatainjectionInfo(); diff --git a/front/popup.php b/front/popup.php index 8683018a..63999660 100644 --- a/front/popup.php +++ b/front/popup.php @@ -32,6 +32,8 @@ switch ($_GET["popup"]) { case "preview": + $model = new PluginDatainjectionModel(); + $model->check($_GET['models_id'], READ); Html::popHeader(__('See the file', 'datainjection'), $_SERVER['PHP_SELF']); PluginDatainjectionModel::showPreviewMappings($_GET['models_id']); Html::popFooter(); diff --git a/inc/clientinjection.class.php b/inc/clientinjection.class.php index ca94ea53..0d638f37 100644 --- a/inc/clientinjection.class.php +++ b/inc/clientinjection.class.php @@ -320,6 +320,14 @@ public static function showResultsForm(PluginDatainjectionModel $model) TemplateRenderer::getInstance()->display('@datainjection/clientinjection_result.html.twig', $data); } + private static function escapeCsvFormula($value) + { + if (is_string($value) && isset($value[0]) && str_contains('=+-@', $value[0])) { + return "'" . $value; + } + return $value; + } + public static function exportErrorsInCSV() { @@ -328,7 +336,7 @@ public static function exportErrorsInCSV() if (!empty($error_lines)) { $model = PluginDatainjectionSession::unserialize(PluginDatainjectionSession::getParam('currentmodel')); - $file = PLUGIN_DATAINJECTION_UPLOAD_DIR . PluginDatainjectionSession::getParam('file_name'); + $file = PLUGIN_DATAINJECTION_UPLOAD_DIR . basename(PluginDatainjectionSession::getParam('file_name')); $mappings = $model->getMappings(); $tmpfile = fopen($file, 'w'); @@ -341,11 +349,11 @@ public static function exportErrorsInCSV() //Write lines foreach ($error_lines as $line) { - fputcsv($tmpfile, $line, $model->getBackend()->getDelimiter()); + fputcsv($tmpfile, array_map([self::class, 'escapeCsvFormula'], $line), $model->getBackend()->getDelimiter()); } fclose($tmpfile); - $name = "Error-" . PluginDatainjectionSession::getParam('file_name'); + $name = "Error-" . basename(PluginDatainjectionSession::getParam('file_name')); $name = str_replace(' ', '', $name); header('Content-disposition: attachment; filename=' . $name); header('Content-Type: application/octet-stream'); diff --git a/inc/info.class.php b/inc/info.class.php index 2eaff495..92150701 100644 --- a/inc/info.class.php +++ b/inc/info.class.php @@ -226,7 +226,10 @@ public static function manageInfos($models_id, $infos = []) } if ($id > 0) { - $info->update($info_infos); + $existing = new self(); + if ($existing->getFromDB($id) && $existing->fields['models_id'] == $models_id) { + $info->update($info_infos); + } } else { $info_infos['models_id'] = $models_id; unset($info_infos['id']); diff --git a/inc/softwarelicenseinjection.class.php b/inc/softwarelicenseinjection.class.php index 450b55be..a5b08f9c 100644 --- a/inc/softwarelicenseinjection.class.php +++ b/inc/softwarelicenseinjection.class.php @@ -157,20 +157,20 @@ public function getValueForAdditionalMandatoryFields($fields_toinject = []) return $fields_toinject; } - $query = "SELECT `id` - FROM `glpi_softwares` - WHERE `name` = '" . $fields_toinject['SoftwareLicense']['softwares_id'] . "'" . - getEntitiesRestrictRequest( - " AND", - "glpi_softwares", - "entities_id", - $fields_toinject['SoftwareLicense']['entities_id'], - true, - ); - $result = $DB->doQuery($query); - - if ($DB->numrows($result) > 0) { - $id = $DB->result($result, 0, 'id'); + $where = ['name' => $fields_toinject['SoftwareLicense']['softwares_id']] + getEntitiesRestrictCriteria( + "glpi_softwares", + "entities_id", + $fields_toinject['SoftwareLicense']['entities_id'], + true, + ); + $result = $DB->request([ + 'SELECT' => 'id', + 'FROM' => 'glpi_softwares', + 'WHERE' => $where, + ]); + + if (count($result) > 0) { + $id = $result->current()['id']; //Add softwares_id to the array $fields_toinject['SoftwareLicense']['softwares_id'] = $id; } else { diff --git a/inc/softwareversioninjection.class.php b/inc/softwareversioninjection.class.php index 4e391791..fc9f2755 100644 --- a/inc/softwareversioninjection.class.php +++ b/inc/softwareversioninjection.class.php @@ -152,20 +152,20 @@ public function getValueForAdditionalMandatoryFields($fields_toinject = []) return $fields_toinject; } - $query = "SELECT `id` - FROM `glpi_softwares` - WHERE `name` = '" . $fields_toinject['SoftwareVersion']['softwares_id'] . "'" . - getEntitiesRestrictRequest( - " AND", - "glpi_softwares", - "entities_id", - $fields_toinject['SoftwareVersion']['entities_id'], - true, - ); - $result = $DB->doQuery($query); - - if ($DB->numrows($result) > 0) { - $id = $DB->result($result, 0, 'id'); + $where = ['name' => $fields_toinject['SoftwareVersion']['softwares_id']] + getEntitiesRestrictCriteria( + "glpi_softwares", + "entities_id", + $fields_toinject['SoftwareVersion']['entities_id'], + true, + ); + $result = $DB->request([ + 'SELECT' => 'id', + 'FROM' => 'glpi_softwares', + 'WHERE' => $where, + ]); + + if (count($result) > 0) { + $id = $result->current()['id']; //Add softwares_id to the array $fields_toinject['SoftwareVersion']['softwares_id'] = $id; } else { diff --git a/inc/userinjection.class.php b/inc/userinjection.class.php index f5ab1fc7..f40cb56e 100644 --- a/inc/userinjection.class.php +++ b/inc/userinjection.class.php @@ -213,14 +213,11 @@ public function processAfterInsertOrUpdate($values, $add = true, $rights = []) } if (isset($values['User']['password']) && ($values['User']['password'] != '')) { - //We use an SQL request because updating the password is unesasy - //(self reset password process in $user->prepareInputForUpdate()) - $password = sha1($values['User']["password"]); - - $query = "UPDATE `glpi_users` - SET `password` = '" . $password . "' - WHERE `id` = '" . $values['User']['id'] . "'"; - $DB->doQuery($query); + $DB->update( + 'glpi_users', + ['password' => Auth::getPasswordHash($values['User']['password'])], + ['id' => $values['User']['id']], + ); } } From 5c73832ee7101b90053e26fac0bdd4c33271fd11 Mon Sep 17 00:00:00 2001 From: Rom1-B <8530352+Rom1-B@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:13:40 +0200 Subject: [PATCH 2/2] stan --- inc/clientinjection.class.php | 4 +- .../ClientInjectionEscapeCsvFormulaTest.php | 71 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/unit/ClientInjectionEscapeCsvFormulaTest.php diff --git a/inc/clientinjection.class.php b/inc/clientinjection.class.php index 0d638f37..3531b5da 100644 --- a/inc/clientinjection.class.php +++ b/inc/clientinjection.class.php @@ -320,9 +320,9 @@ public static function showResultsForm(PluginDatainjectionModel $model) TemplateRenderer::getInstance()->display('@datainjection/clientinjection_result.html.twig', $data); } - private static function escapeCsvFormula($value) + private static function escapeCsvFormula(mixed $value): mixed { - if (is_string($value) && isset($value[0]) && str_contains('=+-@', $value[0])) { + if (is_string($value) && isset($value[0]) && in_array($value[0], ['=', '+', '-', '@'], true)) { return "'" . $value; } return $value; diff --git a/tests/unit/ClientInjectionEscapeCsvFormulaTest.php b/tests/unit/ClientInjectionEscapeCsvFormulaTest.php new file mode 100644 index 00000000..03ebaed3 --- /dev/null +++ b/tests/unit/ClientInjectionEscapeCsvFormulaTest.php @@ -0,0 +1,71 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2007-2023 by DataInjection plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/datainjection + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Datainjection\Tests\Unit; + +use Glpi\Tests\DbTestCase; +use PluginDatainjectionClientInjection; +use ReflectionMethod; + +require_once dirname(__DIR__, 2) . '/inc/clientinjection.class.php'; + +/** + * Covers escapeCsvFormula(), which prefixes values starting with a CSV + * formula-injection trigger character with a single quote before they are + * written out by exportErrorsInCSV(). + */ +final class ClientInjectionEscapeCsvFormulaTest extends DbTestCase +{ + public static function escapeCsvFormulaProvider(): array + { + return [ + 'empty string' => ['', ''], + 'equals trigger' => ['=SUM(A1:A2)', "'=SUM(A1:A2)"], + 'plus trigger' => ['+1234', "'+1234"], + 'minus trigger' => ['-1234', "'-1234"], + 'at trigger' => ['@SUM(A1:A2)', "'@SUM(A1:A2)"], + 'safe value passthrough' => ['normal value', 'normal value'], + 'non-string passthrough' => [42, 42], + ]; + } + + /** + * @dataProvider escapeCsvFormulaProvider + */ + public function testEscapeCsvFormula(mixed $value, mixed $expected): void + { + $escape_csv_formula = new ReflectionMethod( + PluginDatainjectionClientInjection::class, + 'escapeCsvFormula', + ); + + self::assertSame($expected, $escape_csv_formula->invoke(null, $value)); + } +}