Skip to content

Commit 7afc632

Browse files
authored
feat(forms): Form Delegation
1 parent 2dd5df6 commit 7afc632

File tree

23 files changed

+932
-58
lines changed

23 files changed

+932
-58
lines changed

css/includes/components/form/_form-renderer.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,24 @@
9191
[data-glpi-form-renderer-hidden-by-condition] {
9292
display: none !important;
9393
}
94+
95+
.input-group {
96+
> div:not(.d-none) {
97+
&:has(> span.select2-container) {
98+
&:not(:first-child) {
99+
.select2-selection {
100+
border-top-left-radius: 0 !important;
101+
border-bottom-left-radius: 0 !important;
102+
}
103+
}
104+
105+
&:not(:last-of-type) {
106+
.select2-selection {
107+
border-top-right-radius: 0 !important;
108+
border-bottom-right-radius: 0 !important;
109+
}
110+
}
111+
}
112+
}
113+
}
94114
}

install/migrations/update_10.0.x_to_11.0.0/group_user.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,19 @@
3333
*/
3434

3535
/**
36+
* @var \DBmysql $DB
3637
* @var \Migration $migration
37-
* @var array $DELFROMDISPLAYPREF
3838
*/
3939

40-
$migration->dropField('glpi_groups_users', 'is_userdelegate');
41-
42-
$DELFROMDISPLAYPREF['Group'] = [71];
43-
$DELFROMDISPLAYPREF['Group_User'] = [7];
40+
if (!$DB->fieldExists('glpi_groups_users', 'is_userdelegate')) {
41+
$migration->addField(
42+
'glpi_groups_users',
43+
'is_userdelegate',
44+
"tinyint NOT NULL DEFAULT '0'",
45+
['after' => 'is_manager']
46+
);
47+
$migration->addKey(
48+
'glpi_groups_users',
49+
'is_userdelegate'
50+
);
51+
}

install/mysql/glpi-empty.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3156,11 +3156,13 @@ CREATE TABLE `glpi_groups_users` (
31563156
`groups_id` int unsigned NOT NULL DEFAULT '0',
31573157
`is_dynamic` tinyint NOT NULL DEFAULT '0',
31583158
`is_manager` tinyint NOT NULL DEFAULT '0',
3159+
`is_userdelegate` tinyint NOT NULL DEFAULT '0',
31593160
PRIMARY KEY (`id`),
31603161
UNIQUE KEY `unicity` (`users_id`,`groups_id`),
31613162
KEY `groups_id` (`groups_id`),
31623163
KEY `is_dynamic` (`is_dynamic`),
3163-
KEY `is_manager` (`is_manager`)
3164+
KEY `is_manager` (`is_manager`),
3165+
KEY `is_userdelegate` (`is_userdelegate`)
31643166
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
31653167

31663168
### Dump table glpi_helpdesks_tiles_profiles_tiles

js/modules/Forms/RendererController.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ export class GlpiFormRendererController
123123
debouncedComputeItemsVisibilities();
124124
});
125125

126+
// Handle delegation form update
127+
$(this.#target).on(
128+
'change',
129+
'[data-glpi-form-renderer-delegation-container] select[name="delegation_users_id"]',
130+
(e) => this.#renderDelegation(e)
131+
);
132+
126133
// Enable actions
127134
$(this.#target).removeClass('pointer-events-none');
128135
}
@@ -192,6 +199,7 @@ export class GlpiFormRendererController
192199
$(this.#target)
193200
.find(`
194201
[data-glpi-form-renderer-form-header],
202+
[data-glpi-form-renderer-delegation-container],
195203
[data-glpi-form-renderer-section=${this.#section_index}],
196204
[data-glpi-form-renderer-parent-section=${this.#section_index}],
197205
[data-glpi-form-renderer-actions]
@@ -491,4 +499,21 @@ export class GlpiFormRendererController
491499
.removeClass("pointer-events-none")
492500
;
493501
}
502+
503+
async #renderDelegation()
504+
{
505+
const selected_user_id = $(this.#target)
506+
.find('[data-glpi-form-renderer-delegation-container]')
507+
.find('select[name="delegation_users_id"]')
508+
.val();
509+
510+
const response = await $.get('/Form/Delegation', {
511+
'selected_user_id': selected_user_id,
512+
});
513+
514+
// Replace only the inner content of the delegation container
515+
$(this.#target)
516+
.find('[data-glpi-form-renderer-delegation-container]')
517+
.html(response);
518+
}
494519
}

phpunit/functional/Glpi/Form/AnswersSetTest.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,22 @@
3434

3535
namespace tests\units\Glpi\Form;
3636

37+
use CommonITILActor;
3738
use DbTestCase;
3839
use Glpi\Form\Answer;
3940
use Glpi\Form\AnswersHandler\AnswersHandler;
41+
use Glpi\Form\DelegationData;
4042
use Glpi\Form\Destination\FormDestinationProblem;
4143
use Glpi\Form\Form;
4244
use Glpi\Form\Question;
4345
use Glpi\Form\Destination\FormDestinationTicket;
4446
use Glpi\Form\QuestionType\QuestionTypeShortText;
4547
use Glpi\Tests\FormBuilder;
4648
use Glpi\Tests\FormTesterTrait;
49+
use Group;
50+
use Group_User;
4751
use Ticket;
52+
use User;
4853

4954
class AnswersSetTest extends DbTestCase
5055
{
@@ -133,6 +138,113 @@ public function testGetLinksToCreatedItemssForEndUser()
133138
$this->assertCount(1, $answers->getLinksToCreatedItems());
134139
}
135140

141+
public function testGetDelegationWihoutRights()
142+
{
143+
$this->login("post-only", "postonly");
144+
$form = $this->createForm(new FormBuilder());
145+
146+
$answers_handler = AnswersHandler::getInstance();
147+
$answers = $answers_handler->saveAnswers(
148+
$form,
149+
[],
150+
\Session::getLoginUserID(),
151+
[],
152+
new DelegationData(
153+
getitemByTypeName(User::class, 'glpi')->getID(),
154+
true,
155+
''
156+
)
157+
);
158+
159+
/** @var Ticket $ticket */
160+
$ticket = current($answers->getCreatedItems());
161+
$this->assertInstanceOf(Ticket::class, $ticket);
162+
$requesters = $ticket->getActorsForType(CommonITILActor::REQUESTER);
163+
$this->assertCount(1, $requesters);
164+
$this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(
165+
[
166+
'itemtype' => User::class,
167+
'items_id' => getitemByTypeName(User::class, 'post-only')->getID(),
168+
'use_notification' => 1,
169+
'alternative_email' => '',
170+
],
171+
current($requesters),
172+
[
173+
'itemtype',
174+
'items_id',
175+
'use_notification',
176+
'alternative_email',
177+
]
178+
);
179+
}
180+
181+
public function testGetDelegation()
182+
{
183+
$this->login("post-only", "postonly");
184+
185+
// Create a group
186+
$group = $this->createItem(
187+
Group::class,
188+
[
189+
'name' => 'Test group',
190+
'entities_id' => 0,
191+
]
192+
);
193+
194+
// Add users to the group
195+
$this->createItem(
196+
Group_User::class,
197+
[
198+
'groups_id' => $group->getID(),
199+
'users_id' => getItemByTypeName(User::class, 'post-only')->getID(),
200+
'is_userdelegate' => 1,
201+
]
202+
);
203+
$this->createItem(
204+
Group_User::class,
205+
[
206+
'groups_id' => $group->getID(),
207+
'users_id' => getItemByTypeName(User::class, 'glpi')->getID(),
208+
]
209+
);
210+
211+
$form = $this->createForm(new FormBuilder());
212+
213+
$answers_handler = AnswersHandler::getInstance();
214+
$answers = $answers_handler->saveAnswers(
215+
$form,
216+
[],
217+
\Session::getLoginUserID(),
218+
[],
219+
new DelegationData(
220+
getitemByTypeName(User::class, 'glpi')->getID(),
221+
true,
222+
''
223+
),
224+
);
225+
226+
/** @var Ticket $ticket */
227+
$ticket = current($answers->getCreatedItems());
228+
$this->assertInstanceOf(Ticket::class, $ticket);
229+
$requesters = $ticket->getActorsForType(CommonITILActor::REQUESTER);
230+
$this->assertCount(1, $requesters);
231+
$this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(
232+
[
233+
'itemtype' => User::class,
234+
'items_id' => getitemByTypeName(User::class, 'glpi')->getID(),
235+
'use_notification' => 1,
236+
'alternative_email' => '',
237+
],
238+
current($requesters),
239+
[
240+
'itemtype',
241+
'items_id',
242+
'use_notification',
243+
'alternative_email',
244+
]
245+
);
246+
}
247+
136248
private function createAndGetFormWithTwoAnswers(): Form
137249
{
138250
$form = $this->createForm(

phpunit/functional/Group_UserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public function testgetListForItemParams()
185185

186186
$this->assertArrayHasKey('linkid', $list_items[$user->getID()]);
187187
$this->assertArrayHasKey('is_manager', $list_items[$user->getID()]);
188+
$this->assertArrayHasKey('is_userdelegate', $list_items[$user->getID()]);
188189
$this->assertSame(TU_USER, $list_items[$user->getID()]['name']);
189190

190191
$this->assertSame(2, $group_user->countForItem($user));
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 Glpi\Controller\Form;
36+
37+
use Glpi\Controller\AbstractController;
38+
use Glpi\Exception\Http\BadRequestHttpException;
39+
use Glpi\Exception\Http\NotFoundHttpException;
40+
use Glpi\Http\Firewall;
41+
use Glpi\Security\Attribute\SecurityStrategy;
42+
use Symfony\Component\HttpFoundation\Request;
43+
use Symfony\Component\HttpFoundation\Response;
44+
use Symfony\Component\Routing\Attribute\Route;
45+
use User;
46+
47+
final class DelegationController extends AbstractController
48+
{
49+
#[Route(
50+
"/Form/Delegation",
51+
name: "glpi_form_delegation",
52+
methods: "GET",
53+
)]
54+
#[SecurityStrategy(Firewall::STRATEGY_AUTHENTICATED)]
55+
public function __invoke(Request $request): Response
56+
{
57+
$selected_user_id = $request->query->get('selected_user_id');
58+
if (empty($selected_user_id)) {
59+
throw new BadRequestHttpException('Missing selected_user_id parameter');
60+
}
61+
62+
$selected_user = new User();
63+
if (!$selected_user->getFromDB($selected_user_id)) {
64+
throw new NotFoundHttpException('Selected user not found');
65+
}
66+
return $this->render('components/helpdesk_forms/delegation_alert.html.twig', [
67+
'selected_user' => $selected_user,
68+
]);
69+
}
70+
}

src/Glpi/Controller/Form/SubmitAnswerController.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Glpi\Exception\Http\NotFoundHttpException;
4242
use Glpi\Form\AnswersHandler\AnswersHandler;
4343
use Glpi\Form\AnswersSet;
44+
use Glpi\Form\DelegationData;
4445
use Glpi\Form\EndUserInputNameProvider;
4546
use Glpi\Form\Form;
4647
use Glpi\Http\Firewall;
@@ -96,8 +97,13 @@ private function saveSubmittedAnswers(
9697
$post = $request->request->all();
9798
$provider = new EndUserInputNameProvider();
9899

99-
$answers = $provider->getAnswers($post);
100-
$files = $provider->getFiles($post, $answers);
100+
$delegation = new DelegationData(
101+
$request->request->getInt('delegation_users_id', 0) ?: null,
102+
$request->request->getBoolean('delegation_use_notification', false) ?: null,
103+
$request->request->getString('delegation_alternative_email', '') ?: null
104+
);
105+
$answers = $provider->getAnswers($post);
106+
$files = $provider->getFiles($post, $answers);
101107
if (empty($answers)) {
102108
throw new BadRequestHttpException();
103109
}
@@ -108,6 +114,7 @@ private function saveSubmittedAnswers(
108114
$answers,
109115
Session::getLoginUserID(),
110116
$files,
117+
$delegation
111118
);
112119

113120
return $answers;

0 commit comments

Comments
 (0)