Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ajax/inject_batch.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion ajax/injection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
2 changes: 1 addition & 1 deletion ajax/results.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@
Html::header_nocache();
}

Session::checkCentralAccess();
Session::checkRight(PluginDatainjectionClientInjection::$rightname, READ);
$model = PluginDatainjectionSession::unserialize($_SESSION['datainjection']['currentmodel']);
PluginDatainjectionClientInjection::showResultsForm($model);
2 changes: 2 additions & 0 deletions front/info.form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions front/popup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 11 additions & 3 deletions inc/clientinjection.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@ public static function showResultsForm(PluginDatainjectionModel $model)
TemplateRenderer::getInstance()->display('@datainjection/clientinjection_result.html.twig', $data);
}

private static function escapeCsvFormula($value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security function with no unit test. It is a pure function with several edge cases worth pinning (empty string, non-string passthrough, each of the four trigger characters, a value starting with a safe character). Without a test, a future refactor or PHP upgrade could silently break the protection and let formula-injection payloads through.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing PHP 8.2 type declarations. The function accepts any type (non-strings are passed through unchanged) and returns the same type. Project rules require strict typing everywhere.

Suggested change
private static function escapeCsvFormula($value)
private static function escapeCsvFormula(mixed $value): mixed

{
if (is_string($value) && isset($value[0]) && str_contains('=+-@', $value[0])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguments are in an unconventional order: str_contains reads as "does haystack contain needle", so str_contains('=+-@', $value[0]) means "does '=+-@' contain the first char?"
which is correct but non-obvious. in_array makes the intent explicit and self-documents the set of trigger characters.

Suggested change
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;
}

public static function exportErrorsInCSV()
{

Expand All @@ -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');
Expand All @@ -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');
Expand Down
5 changes: 4 additions & 1 deletion inc/info.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
28 changes: 14 additions & 14 deletions inc/softwarelicenseinjection.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 14 additions & 14 deletions inc/softwareversioninjection.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 5 additions & 8 deletions inc/userinjection.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']],
);
}
}

Expand Down