Skip to content

Commit fc4e409

Browse files
authored
fix(itil): Ensure readonly logic is applied on backend for ITILObjects (#21578)
1 parent 859a8a4 commit fc4e409

10 files changed

+550
-19
lines changed

front/change.form.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
if (isset($_POST["add"])) {
5454
$change->check(-1, CREATE, $_POST);
5555

56+
$_POST = $change->enforceReadonlyFields($_POST, true);
5657
$newID = $change->add($_POST);
5758
Event::log(
5859
$newID,
@@ -109,6 +110,7 @@
109110
} elseif (isset($_POST["update"])) {
110111
$change->check($_POST["id"], UPDATE);
111112

113+
$_POST = $change->enforceReadonlyFields($_POST);
112114
$change->update($_POST);
113115
Event::log(
114116
$_POST["id"],

front/problem.form.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
if (isset($_POST["add"])) {
5454
$problem->check(-1, CREATE, $_POST);
5555

56+
$_POST = $problem->enforceReadonlyFields($_POST, true);
5657
if ($newID = $problem->add($_POST)) {
5758
Event::log(
5859
$newID,
@@ -108,6 +109,7 @@
108109
} elseif (isset($_POST["update"])) {
109110
$problem->check($_POST["id"], UPDATE);
110111

112+
$_POST = $problem->enforceReadonlyFields($_POST);
111113
$problem->update($_POST);
112114
Event::log(
113115
$_POST["id"],

front/ticket.form.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575
if (isset($_POST["add"])) {
7676
$track->check(-1, CREATE, $_POST);
77+
$_POST = $track->enforceReadonlyFields($_POST, true);
7778

7879
if ($track->add($_POST)) {
7980
if ($_SESSION['glpibackcreated']) {
@@ -85,6 +86,7 @@
8586
if (!$track::canUpdate()) {
8687
throw new AccessDeniedHttpException();
8788
}
89+
$_POST = $track->enforceReadonlyFields($_POST);
8890
$track->update($_POST);
8991

9092
if (isset($_POST['kb_linked_id'])) {

src/CommonITILObject.php

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
use Glpi\RichText\UserMention;
5353
use Glpi\Search\Output\HTMLSearchOutput;
5454
use Glpi\Team\Team;
55+
use Glpi\Urgency;
5556
use Safe\Exceptions\DatetimeException;
5657

5758
use function Safe\getimagesize;
@@ -1768,17 +1769,6 @@ public function cleanDBonPurge()
17681769
protected function handleTemplateFields(array $input, bool $show_error_message = true)
17691770
{
17701771
//// check mandatory fields
1771-
// First get ticket template associated: entity and type/category
1772-
$entid = $input['entities_id'] ?? $this->fields['entities_id'];
1773-
1774-
$type = null;
1775-
if (isset($input['type'])) {
1776-
$type = $input['type'];
1777-
} elseif (isset($this->fields['type'])) {
1778-
$type = $this->fields['type'];
1779-
}
1780-
1781-
$categid = $input['itilcategories_id'] ?? $this->fields['itilcategories_id'];
17821772

17831773
$check_allowed_fields_for_template = false;
17841774
$allowed_fields = [];
@@ -1865,9 +1855,9 @@ class_exists($validation_class)
18651855
}
18661856
}
18671857

1868-
$tt = $this->getITILTemplateToUse(0, $type, $categid, $entid);
1869-
1870-
if (count($tt->mandatory)) {
1858+
// First get ticket template associated: entity and type/category
1859+
$tt = $this->getITILTemplateFromInput($input);
1860+
if ($tt && count($tt->mandatory)) {
18711861
$mandatory_missing = [];
18721862
$fieldsname = $tt->getAllowedFieldsNames(true);
18731863
foreach ($tt->mandatory as $key => $val) {
@@ -2341,6 +2331,40 @@ public function prepareInputForUpdate($input)
23412331
return $input;
23422332
}
23432333

2334+
/**
2335+
* Processes readonly fields in the input array based on the ITIL template data.
2336+
*
2337+
* @param array $input The user input data to process (often $_POST).
2338+
* @param bool $isAdd true if we are in a creation, will force to apply the template predefined field.
2339+
*
2340+
* @return array The modified user input array after processing readonly fields.
2341+
*
2342+
* @since 11.0.2
2343+
*/
2344+
public function enforceReadonlyFields(array $input, bool $isAdd = false): array
2345+
{
2346+
$tt = $this->getITILTemplateFromInput($input);
2347+
if (!$tt) {
2348+
return $input;
2349+
}
2350+
2351+
$tt->getFromDBWithData($tt->getID()); // We load the fields (predefined and readonly)
2352+
2353+
foreach (array_keys($tt->readonly) as $read_only_field) {
2354+
if ($isAdd && array_key_exists($read_only_field, $tt->predefined)) {
2355+
$input[$read_only_field] = $tt->predefined[$read_only_field];
2356+
continue;
2357+
}
2358+
2359+
if (array_key_exists($read_only_field, $this->fields)) {
2360+
$input[$read_only_field] = $this->fields[$read_only_field];
2361+
} else {
2362+
unset($input[$read_only_field]);
2363+
}
2364+
}
2365+
return $input;
2366+
}
2367+
23442368
public function post_updateItem($history = true)
23452369
{
23462370
// Handle rich-text images and uploaded documents
@@ -2824,7 +2848,7 @@ public function prepareInputForAdd($input)
28242848
}
28252849

28262850
// save value before clean;
2827-
$title = ltrim($input['name']);
2851+
$title = ltrim($input['name'] ?? '');
28282852

28292853
// Set default status to avoid notice
28302854
if (!isset($input["status"])) {
@@ -2835,7 +2859,7 @@ public function prepareInputForAdd($input)
28352859
!isset($input["urgency"])
28362860
|| !($CFG_GLPI['urgency_mask'] & (1 << $input["urgency"]))
28372861
) {
2838-
$input["urgency"] = 3;
2862+
$input["urgency"] = Urgency::MEDIUM->value;
28392863
}
28402864
if (
28412865
!isset($input["impact"])
@@ -2886,8 +2910,8 @@ public function prepareInputForAdd($input)
28862910
}
28872911

28882912
// No name set name
2889-
$input["name"] = ltrim($input["name"]);
2890-
$input['content'] = ltrim($input['content']);
2913+
$input["name"] = ltrim($input["name"] ?? '');
2914+
$input['content'] = ltrim($input['content'] ?? '');
28912915
if (empty($input["name"])) {
28922916
// Build name based on content
28932917

@@ -8252,6 +8276,33 @@ public function getITILTemplateToUse(
82528276
return $tt;
82538277
}
82548278

8279+
/**
8280+
* Get the template to use
8281+
* If the input is not defined, it will get it from the object fields datas
8282+
*
8283+
* @param array $input
8284+
* @return ITILTemplate|null
8285+
*
8286+
* @since 11.0.2
8287+
*/
8288+
public function getITILTemplateFromInput(array $input = []): ?ITILTemplate
8289+
{
8290+
$entid = $input['entities_id'] ?? $this->fields['entities_id'];
8291+
8292+
$type = null;
8293+
if (isset($input['type'])) {
8294+
$type = $input['type'];
8295+
} elseif (isset($this->fields['type'])) {
8296+
$type = $this->fields['type'];
8297+
}
8298+
8299+
$categid = $input['itilcategories_id'] ?? $this->fields['itilcategories_id'] ?? null;
8300+
if (is_null($categid)) {
8301+
return null;
8302+
}
8303+
return $this->getITILTemplateToUse(0, $type, $categid, $entid);
8304+
}
8305+
82558306
/**
82568307
* Get template field name
82578308
*

src/Ticket.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use Glpi\RichText\RichText;
4646
use Glpi\RichText\UserMention;
4747
use Glpi\Search\DefaultSearchRequestInterface;
48+
use Glpi\Urgency;
4849
use Safe\DateTime;
4950

5051
use function Safe\preg_match;
@@ -3489,7 +3490,7 @@ public static function getDefaultValues($entity = 0)
34893490
'name' => '',
34903491
'content' => '',
34913492
'itilcategories_id' => 0,
3492-
'urgency' => 3,
3493+
'urgency' => Urgency::MEDIUM->value,
34933494
'impact' => 3,
34943495
'priority' => self::computePriority(3, 3),
34953496
'requesttypes_id' => $requesttype,

tests/cypress/e2e/ITILObject/ticket_form.cy.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,52 @@ describe("Ticket Form", () => {
334334
cy.findByRole('cell').should('contain.text', 'No results found');
335335
});
336336
});
337+
338+
it('Create/update a ticket using a template with readonly fields', () => {
339+
const ticket_template_name = `test template ${rand}`;
340+
cy.createWithAPI('TicketTemplate', {
341+
'name': ticket_template_name,
342+
}).as('ticket_template_id');
343+
344+
cy.get('@ticket_template_id').then((ticket_template_id) => {
345+
cy.createWithAPI('TicketTemplatePredefinedField', {
346+
'tickettemplates_id': ticket_template_id, // Default template
347+
'num': 10, // Urgency
348+
'value': 4, // High
349+
});
350+
351+
cy.createWithAPI('TicketTemplateReadonlyField', {
352+
'tickettemplates_id': ticket_template_id,
353+
'num': 10,
354+
});
355+
356+
cy.createWithAPI('ITILCategory', {
357+
'name':ticket_template_name,
358+
'tickettemplates_id': ticket_template_id,
359+
'tickettemplates_id_incident': ticket_template_id,
360+
'tickettemplates_id_demand': ticket_template_id,
361+
'changetemplates_id': ticket_template_id,
362+
'problemtemplates_id': ticket_template_id,
363+
});
364+
});
365+
366+
// Create form
367+
cy.visit(`/front/ticket.form.php`);
368+
369+
// intercept form submit
370+
cy.intercept('POST', '/front/ticket.form.php').as('submit');
371+
372+
cy.getDropdownByLabelText('Category').selectDropdownValue(${ticket_template_name}`);
373+
374+
// We change the value of a readonly field, it should be ignored
375+
cy.get('input[name="urgency"]').invoke('val', '1');
376+
cy.findByRole('button', {'name': 'Add'}).click();
377+
cy.wait('@submit').its('response.statusCode').should('eq', 200);
378+
cy.get('input[name="urgency"]').should('have.value', '4'); // Should be the template 4 value
379+
380+
// We try updating it
381+
cy.get('input[name="urgency"]').invoke('val', '1');
382+
cy.findByRole('button', {'name': 'Save'}).click();
383+
cy.get('input[name="urgency"]').should('have.value', '4'); // Should be the template 4 value
384+
});
337385
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/**
4+
* ---------------------------------------------------------------------
5+
*
6+
* GLPI - Gestionnaire Libre de Parc Informatique
7+
*
8+
* http://glpi-project.org
9+
*
10+
* @copyright 2015-2025 Teclib' and contributors.
11+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
12+
*
13+
* ---------------------------------------------------------------------
14+
*
15+
* LICENSE
16+
*
17+
* This file is part of GLPI.
18+
*
19+
* This program is free software: you can redistribute it and/or modify
20+
* it under the terms of the GNU General Public License as published by
21+
* the Free Software Foundation, either version 3 of the License, or
22+
* (at your option) any later version.
23+
*
24+
* This program is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+
* GNU General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU General Public License
30+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
31+
*
32+
* ---------------------------------------------------------------------
33+
*/
34+
35+
namespace functional;
36+
37+
use Change;
38+
use Glpi\Tests\AbstractITILTemplateReadonlyFieldTest;
39+
40+
class ChangeITILTemplateReadonlyFieldTest extends AbstractITILTemplateReadonlyFieldTest
41+
{
42+
public function getITILClass(): Change
43+
{
44+
return new Change();
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/**
4+
* ---------------------------------------------------------------------
5+
*
6+
* GLPI - Gestionnaire Libre de Parc Informatique
7+
*
8+
* http://glpi-project.org
9+
*
10+
* @copyright 2015-2025 Teclib' and contributors.
11+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
12+
*
13+
* ---------------------------------------------------------------------
14+
*
15+
* LICENSE
16+
*
17+
* This file is part of GLPI.
18+
*
19+
* This program is free software: you can redistribute it and/or modify
20+
* it under the terms of the GNU General Public License as published by
21+
* the Free Software Foundation, either version 3 of the License, or
22+
* (at your option) any later version.
23+
*
24+
* This program is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+
* GNU General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU General Public License
30+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
31+
*
32+
* ---------------------------------------------------------------------
33+
*/
34+
35+
namespace functional;
36+
37+
use Glpi\Tests\AbstractITILTemplateReadonlyFieldTest;
38+
use Problem;
39+
40+
class ProblemITILTemplateReadonlyFieldTest extends AbstractITILTemplateReadonlyFieldTest
41+
{
42+
public function getITILClass(): Problem
43+
{
44+
return new Problem();
45+
}
46+
}

0 commit comments

Comments
 (0)