Skip to content

Commit dcab162

Browse files
committed
feat(forms): Form Delegation
1 parent 440d2cf commit dcab162

File tree

22 files changed

+831
-97
lines changed

22 files changed

+831
-97
lines changed

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

+20
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,24 @@
9191
[data-glpi-form-renderer-hidden-by-condition] {
9292
display: none;
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

-43
This file was deleted.

install/mysql/glpi-empty.sql

+3-1
Original file line numberDiff line numberDiff line change
@@ -3154,11 +3154,13 @@ CREATE TABLE `glpi_groups_users` (
31543154
`groups_id` int unsigned NOT NULL DEFAULT '0',
31553155
`is_dynamic` tinyint NOT NULL DEFAULT '0',
31563156
`is_manager` tinyint NOT NULL DEFAULT '0',
3157+
`is_userdelegate` tinyint NOT NULL DEFAULT '0',
31573158
PRIMARY KEY (`id`),
31583159
UNIQUE KEY `unicity` (`users_id`,`groups_id`),
31593160
KEY `groups_id` (`groups_id`),
31603161
KEY `is_dynamic` (`is_dynamic`),
3161-
KEY `is_manager` (`is_manager`)
3162+
KEY `is_manager` (`is_manager`),
3163+
KEY `is_userdelegate` (`is_userdelegate`)
31623164
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
31633165

31643166
### Dump table glpi_helpdesks_tiles_profiles_tiles

js/modules/Forms/RendererController.js

+38
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ export class GlpiFormRendererController
122122

123123
debouncedComputeItemsVisibilities();
124124
});
125+
126+
// Handle delegation form update
127+
this.#initDelegationEventHandlers();
125128
}
126129

127130
/**
@@ -160,6 +163,7 @@ export class GlpiFormRendererController
160163
$(this.#target)
161164
.find(`
162165
[data-glpi-form-renderer-form-header],
166+
[data-glpi-form-renderer-delegation-container],
163167
[data-glpi-form-renderer-section=${this.#section_index}],
164168
[data-glpi-form-renderer-parent-section=${this.#section_index}],
165169
[data-glpi-form-renderer-actions]
@@ -447,4 +451,38 @@ export class GlpiFormRendererController
447451
.removeClass("pointer-events-none")
448452
;
449453
}
454+
455+
#initDelegationEventHandlers()
456+
{
457+
$(this.#target)
458+
.find('[data-glpi-form-renderer-delegation-container]')
459+
.find('select[name="delegation_users_id"]')
460+
.on('change', (e) => this.#renderDelegation(e))
461+
;
462+
}
463+
464+
async #renderDelegation()
465+
{
466+
const selected_user_id = $(this.#target)
467+
.find('[data-glpi-form-renderer-delegation-container]')
468+
.find('select[name="delegation_users_id"]')
469+
.val();
470+
471+
const response = await $.get('/Form/Delegation', {
472+
'selected_user_id': selected_user_id,
473+
});
474+
475+
// Create a temporary element to parse the AJAX response
476+
const $temp = $('<div>').html(response);
477+
// Extract the inner content of the delegation container from the response
478+
const innerContent = $temp.find('[data-glpi-form-renderer-delegation-container]').html();
479+
480+
// Replace only the inner content of the delegation container
481+
$(this.#target)
482+
.find('[data-glpi-form-renderer-delegation-container]')
483+
.html(innerContent);
484+
485+
// Re-init event handlers
486+
this.#initDelegationEventHandlers();
487+
}
450488
}

phpunit/functional/Glpi/Form/AnswersSetTest.php

+111
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
namespace tests\units\Glpi\Form;
3636

3737
use CommonGLPI;
38+
use CommonITILActor;
3839
use DbTestCase;
3940
use Glpi\Form\Answer;
4041
use Glpi\Form\AnswersHandler\AnswersHandler;
@@ -46,7 +47,10 @@
4647
use Glpi\Form\QuestionType\QuestionTypeShortText;
4748
use Glpi\Tests\FormBuilder;
4849
use Glpi\Tests\FormTesterTrait;
50+
use Group;
51+
use Group_User;
4952
use Ticket;
53+
use User;
5054

5155
class AnswersSetTest extends DbTestCase
5256
{
@@ -135,6 +139,113 @@ public function testGetLinksToCreatedItemssForEndUser()
135139
$this->assertCount(1, $answers->getLinksToCreatedItems());
136140
}
137141

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

phpunit/functional/Glpi/Form/Destination/CommonITILField/RequesterFieldTest.php

-2
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,9 @@
4242
use Glpi\Form\Destination\CommonITILField\RequesterFieldConfig;
4343
use Glpi\Form\Destination\CommonITILField\ITILActorFieldStrategy;
4444
use Glpi\Form\Destination\CommonITILField\RequesterField;
45-
use Glpi\Form\Destination\FormDestinationTicket;
4645
use Glpi\Form\Form;
4746
use Glpi\Form\QuestionType\QuestionTypeActorsExtraDataConfig;
4847
use Glpi\Form\QuestionType\QuestionTypeRequester;
49-
use Glpi\PHPUnit\Tests\Glpi\Form\Destination\CommonITILField\AbstractDestinationFieldTest;
5048
use Glpi\Tests\FormBuilder;
5149
use Group;
5250
use Override;

phpunit/functional/Group_UserTest.php

+1
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));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
* @copyright 2003-2014 by the INDEPNET Development Team.
12+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
13+
*
14+
* ---------------------------------------------------------------------
15+
*
16+
* LICENSE
17+
*
18+
* This file is part of GLPI.
19+
*
20+
* This program is free software: you can redistribute it and/or modify
21+
* it under the terms of the GNU General Public License as published by
22+
* the Free Software Foundation, either version 3 of the License, or
23+
* (at your option) any later version.
24+
*
25+
* This program is distributed in the hope that it will be useful,
26+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
27+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28+
* GNU General Public License for more details.
29+
*
30+
* You should have received a copy of the GNU General Public License
31+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
32+
*
33+
* ---------------------------------------------------------------------
34+
*/
35+
36+
namespace Glpi\Controller\Form;
37+
38+
use Glpi\Controller\AbstractController;
39+
use Glpi\Exception\Http\BadRequestHttpException;
40+
use Glpi\Exception\Http\NotFoundHttpException;
41+
use Symfony\Component\HttpFoundation\Request;
42+
use Symfony\Component\HttpFoundation\Response;
43+
use Symfony\Component\Routing\Attribute\Route;
44+
use User;
45+
46+
final class DelegationController extends AbstractController
47+
{
48+
#[Route(
49+
"/Form/Delegation",
50+
name: "glpi_form_delegation",
51+
methods: "GET",
52+
)]
53+
public function __invoke(Request $request): Response
54+
{
55+
$selected_user_id = $request->query->get('selected_user_id');
56+
if (empty($selected_user_id)) {
57+
throw new BadRequestHttpException('Missing selected_user_id parameter');
58+
}
59+
60+
$selected_user = new User();
61+
if (!$selected_user->getFromDB($selected_user_id)) {
62+
throw new NotFoundHttpException('Selected user not found');
63+
}
64+
return $this->render('components/helpdesk_forms/delegation_alert.html.twig', [
65+
'selected_user' => $selected_user
66+
]);
67+
}
68+
}

src/Glpi/Controller/Form/SubmitAnswerController.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,13 @@ private function saveSubmittedAnswers(
9696
$post = $request->request->all();
9797
$provider = new EndUserInputNameProvider();
9898

99-
$answers = $provider->getAnswers($post);
100-
$files = $provider->getFiles($post, $answers);
99+
$delegation = [
100+
'users_id' => $post['delegation_users_id'] ?? null,
101+
'use_notification' => $post['delegation_use_notification'] ?? null,
102+
'alternative_email' => $post['delegation_alternative_email'] ?? null,
103+
];
104+
$answers = $provider->getAnswers($post);
105+
$files = $provider->getFiles($post, $answers);
101106
if (empty($answers)) {
102107
throw new BadRequestHttpException();
103108
}
@@ -108,6 +113,7 @@ private function saveSubmittedAnswers(
108113
$answers,
109114
Session::getLoginUserID(),
110115
$files,
116+
$delegation
111117
);
112118

113119
return $answers;

0 commit comments

Comments
 (0)