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
23 changes: 21 additions & 2 deletions src/Glpi/Form/Destination/AbstractCommonITILFormDestination.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
use Glpi\Form\Destination\CommonITILField\AssociatedItemsField;
use Glpi\Form\Destination\CommonITILField\ContentField;
use Glpi\Form\Destination\CommonITILField\EntityField;
use Glpi\Form\Destination\CommonITILField\EntityFieldConfig;
use Glpi\Form\Destination\CommonITILField\EntityFieldStrategy;
use Glpi\Form\Destination\CommonITILField\ITILCategoryField;
use Glpi\Form\Destination\CommonITILField\ITILFollowupField;
use Glpi\Form\Destination\CommonITILField\ITILTaskField;
Expand Down Expand Up @@ -129,10 +131,27 @@ final public function createDestinationItems(
'content' => '',
];

// Entity must be computed first as it will be used to pick the correct template
// If entity is set to be computed from the requester, then we need to
// compute the requesters before going any further.
$entity_field = new EntityField();
$entity_config = $entity_field->getConfig($form, $config);
if (
$entity_config instanceof EntityFieldConfig
&& $entity_config->getStrategy() === EntityFieldStrategy::REQUESTER_ENTITY
) {
// Compute requester field
$requester_field = new RequesterField();
$input = $requester_field->applyConfiguratedValueToInputUsingAnswers(
$requester_field->getConfig($form, $config),
$input,
$answers_set
);
$already_applied_fields[] = RequesterField::class;
}

// Entity must be computed first as it will be used to pick the correct template
$input = $entity_field->applyConfiguratedValueToInputUsingAnswers(
$entity_field->getConfig($form, $config),
$entity_config,
$input,
$answers_set
);
Expand Down
2 changes: 1 addition & 1 deletion src/Glpi/Form/Destination/CommonITILField/EntityField.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public function applyConfiguratedValueToInputUsingAnswers(
$strategy = current($config->getStrategies());

// Compute value according to strategy
$entity_id = $strategy->getEntityID($config, $answers_set);
$entity_id = $strategy->getEntityID($config, $answers_set, $input);

// We always need a valid value for entities
if (Entity::getById($entity_id) === false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,18 @@ public static function getStrategiesInputName(): string
return self::STRATEGY;
}

public function getStrategy(): EntityFieldStrategy
{
return $this->strategy;
}

/**
* @return array<EntityFieldStrategy>
*/
public function getStrategies(): array
{
// TODO: why does this field seems to support multiple stategies?
// It doesn't make sense IMO, can you take a look @ccailly?
Comment on lines +100 to +101
Copy link
Contributor

Choose a reason for hiding this comment

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

ping @ccailly

Copy link
Contributor

Choose a reason for hiding this comment

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

@ccailly did not respond to the ping, maybe could you @AdrienClairembault open an issue and assign him?

return [$this->strategy];
}

Expand Down
70 changes: 65 additions & 5 deletions src/Glpi/Form/Destination/CommonITILField/EntityFieldStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@
namespace Glpi\Form\Destination\CommonITILField;

use Entity;
use Glpi\DBAL\QuerySubQuery;
use Glpi\Form\AnswersSet;
use Glpi\Form\QuestionType\QuestionTypeItem;
use Profile;
use Profile_User;
use Session;
use User;

enum EntityFieldStrategy: string
{
Expand All @@ -47,21 +51,25 @@ enum EntityFieldStrategy: string
case SPECIFIC_VALUE = 'specific_value';
case SPECIFIC_ANSWER = 'specific_answer';
case LAST_VALID_ANSWER = 'last_valid_answer';
case REQUESTER_ENTITY = 'requester_entity';

public function getLabel(): string
{
return match ($this) {
self::FORM_FILLER => __("Active entity of the form filler"),
self::FROM_FORM => __("From form"),
self::SPECIFIC_VALUE => __("Specific entity"),
self::SPECIFIC_ANSWER => __("Answer from a specific question"),
self::LAST_VALID_ANSWER => __('Answer to last "Entity" item question'),
self::FORM_FILLER => __("Active entity of the form filler"),
self::FROM_FORM => __("From form"),
self::SPECIFIC_VALUE => __("Specific entity"),
self::SPECIFIC_ANSWER => __("Answer from a specific question"),
self::LAST_VALID_ANSWER => __('Answer to last "Entity" item question'),
self::REQUESTER_ENTITY => __('Entity of the requester'),
};
}

public function getEntityID(
EntityFieldConfig $config,
AnswersSet $answers_set,
// TODO: remove default value on main, it was only added to prevent BC breaks
array $input = [],
): int {
return match ($this) {
self::FORM_FILLER => $this->getFormFillerEntityID(),
Expand All @@ -72,6 +80,7 @@ public function getEntityID(
$answers_set
),
self::LAST_VALID_ANSWER => $this->getEntityIDForLastValidAnswer($answers_set),
self::REQUESTER_ENTITY => $this->getEntityIdFromRequester($input) ?? $this->getFormFillerEntityID(),
};
}

Expand Down Expand Up @@ -127,4 +136,55 @@ public function getEntityIDForLastValidAnswer(

return (int) $value['items_id'];
}

public function getEntityIdFromRequester(
array $input,
): ?int {
// Load requesters from input
$requesters = $input['_users_id_requester'] ?? null;
if (!$requesters) {
return null;
}

// Get ID of the first requester
$requester_id = current($requesters);
$requester = User::getById($requester_id);
if (!$requester) {
return null;
}

// Get profiles of the requester
$profiles = (new Profile_User())->find([
'users_id' => $requester->getID(),
]);

// User has no entity
if (count($profiles) == 0) {
return null;
}
$profile = current($profiles);

// If only one profile, use its entity
if (count($profiles) == 1) {
return $profile['entities_id'];
}

$helpdesk_profiles = (new Profile_User())->find([
'users_id' => $requester->getID(),
'profiles_id' => new QuerySubQuery([
'SELECT' => 'id',
'FROM' => Profile::getTable(),
'WHERE' => ['interface' => 'helpdesk'],
]),
]);

// If one or more helpdesk profiles, use entity of the first profile
if (count($helpdesk_profiles) > 0) {
$helpdesk_profile = current($helpdesk_profiles);
return $helpdesk_profile['entities_id'];
}

// Fallback to first profile
return $profile['entities_id'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,21 @@
use Glpi\Form\Destination\CommonITILField\EntityField;
use Glpi\Form\Destination\CommonITILField\EntityFieldConfig;
use Glpi\Form\Destination\CommonITILField\EntityFieldStrategy;
use Glpi\Form\Destination\CommonITILField\ITILActorFieldStrategy;
use Glpi\Form\Destination\CommonITILField\RequesterField;
use Glpi\Form\Destination\CommonITILField\RequesterFieldConfig;
use Glpi\Form\Form;
use Glpi\Form\QuestionType\QuestionTypeItem;
use Glpi\Form\QuestionType\QuestionTypeRequester;
use Glpi\Tests\AbstractDestinationFieldTest;
use Glpi\Tests\FormBuilder;
use Glpi\Tests\FormTesterTrait;
use Override;
use PHPUnit\Framework\Attributes\DataProvider;
use Profile_User;
use User;

use function Safe\json_encode;

final class EntityFieldTest extends AbstractDestinationFieldTest
{
Expand Down Expand Up @@ -335,6 +344,122 @@ public static function provideConvertFieldConfigFromFormCreator(): iterable
];
}

public static function entityFromRequesterProvider(): iterable
{
yield 'user with single profile' => [
'profiles' => [
// Super admin
['profiles_id' => 4, 'entities_id' => "_test_child_3"],
],
// The only found profile is used
'expected_entity' => "_test_child_3",
];

yield 'users with two profiles, one is helpdesk' => [
'profiles' => [
// Super admin
['profiles_id' => 4, 'entities_id' => "_test_child_3"],
// Self-service
['profiles_id' => 1, 'entities_id' => "_test_child_2"],
],
// First helpdesk profile is used
'expected_entity' => "_test_child_2",
];

yield 'users with two helpdesk profiles' => [
'profiles' => [
// Self-service
['profiles_id' => 1, 'entities_id' => "_test_child_3"],
// Self-service
['profiles_id' => 1, 'entities_id' => "_test_child_2"],
],
// First profile is used
'expected_entity' => "_test_child_3",
];

yield 'users with two central profiles' => [
'profiles' => [
// Super admin
['profiles_id' => 4, 'entities_id' => "_test_child_1"],
// Super admin
['profiles_id' => 4, 'entities_id' => "_test_child_2"],
],
// First profile is used
'expected_entity' => "_test_child_1",
];
}

#[DataProvider('entityFromRequesterProvider')]
public function testEntityFromRequester(
array $profiles,
string $expected_entity,
): void {
// Arrange: create a form with an actor question used as the requester
$builder = new FormBuilder();
$builder->addQuestion("Requester", QuestionTypeRequester::class);
$form = $this->createForm($builder);

// The form requester will be picked from the "Requester" question
$requester_config = new RequesterFieldConfig(
strategies: [ITILActorFieldStrategy::SPECIFIC_ANSWERS],
specific_question_ids: [$this->getQuestionId($form, "Requester")]
);
$this->setDestinationFieldConfig(
form: $form,
key: RequesterField::getKey(),
config: $requester_config,
);

// The entity will be taken from the requester
$form = Form::getById($form->getId());
$entity_config = new EntityFieldConfig(
strategy: EntityFieldStrategy::REQUESTER_ENTITY,
);
$this->setDestinationFieldConfig(
form: $form,
key: EntityField::getKey(),
config: $entity_config,
);

// Create an user to use as a requester
$default_profile = array_shift($profiles);
$user = $this->createItem(User::class, [
'name' => 'My_user',
'_profiles_id' => $default_profile['profiles_id'],
'_entities_id' => getItemByTypeName(
Entity::class,
$default_profile['entities_id'],
true,
),
'_is_recursive' => true,
]);

// Add others profiles if needed
foreach ($profiles as $profile) {
$this->createItem(Profile_User::class, [
'users_id' => $user->getID(),
'profiles_id' => $profile['profiles_id'],
'entities_id' => getItemByTypeName(
Entity::class,
$profile['entities_id'],
true,
),
'is_recursive' => true,
]);
}

// Act: submit an answer to the form
$ticket = $this->sendFormAndGetCreatedTicket($form, [
"Requester" => "users_id-{$user->getID()}",
]);

// Assert: the ticket entity should be "_test_child_3"
$this->assertEquals(
getItemByTypeName(Entity::class, $expected_entity, true),
$ticket->fields['entities_id'],
);
}

private function sendFormAndAssertTicketEntity(
Form $form,
EntityFieldConfig $config,
Expand Down Expand Up @@ -365,7 +490,7 @@ private function sendFormAndAssertTicketEntity(
$answers = $answers_handler->saveAnswers(
$form,
$formatted_answers,
getItemByTypeName(\User::class, TU_USER, true)
getItemByTypeName(User::class, TU_USER, true)
);

// Get created ticket
Expand Down
7 changes: 7 additions & 0 deletions tests/src/AbstractDestinationFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public static function setUpBeforeClass(): void

parent::setUpBeforeClass();

// Clean up data in case execution was stopped before tearDownAfterClass
// could run.
$tables = $DB->listTables('glpi\_plugin\_formcreator\_%');
foreach ($tables as $table) {
$DB->dropTable($table['TABLE_NAME']);
}

$queries = $DB->getQueriesFromFile(sprintf('%s/tests/glpi-formcreator-migration-data.sql', GLPI_ROOT));
foreach ($queries as $query) {
$DB->doQuery($query);
Expand Down
17 changes: 12 additions & 5 deletions tests/src/FormTesterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

use Change;
use CommonDBTM;
use Glpi\DBAL\JsonFieldInterface;
use Glpi\Form\AccessControl\FormAccessControl;
use Glpi\Form\AnswersHandler\AnswersHandler;
use Glpi\Form\AnswersSet;
Expand Down Expand Up @@ -849,21 +850,27 @@ private function createAndGetFormWithFirstAndLastNameQuestions(
private function setDestinationFieldConfig(
Form $form,
string $key,
string $config
string|JsonFieldInterface $config
): void {
$config = new SimpleValueConfig($config);
if (is_string($config)) {
$config = new SimpleValueConfig($config);
}

$destinations = $form->getDestinations();
$this->assertCount(1, $destinations);

$destination = current($destinations);
$original_config = json_decode($destination->fields['config'], true);
$added_config = [
$key => $config->jsonSerialize(),
];
$new_config = array_merge($original_config, $added_config);

$this->updateItem(
$destination::getType(),
$destination->getId(),
[
'config' => [
$key => $config->jsonSerialize(),
],
'config' => $new_config,
],
["config"],
);
Expand Down