Skip to content

Commit

Permalink
feat(forms): Add export and import functionality for questions
Browse files Browse the repository at this point in the history
  • Loading branch information
ccailly committed Oct 28, 2024
1 parent b475d9c commit 6faf2d9
Show file tree
Hide file tree
Showing 25 changed files with 843 additions and 58 deletions.
134 changes: 134 additions & 0 deletions phpunit/functional/Glpi/Form/Export/FormSerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,20 @@
use Glpi\Form\Export\Result\ImportError;
use Glpi\Form\Export\Serializer\FormSerializer;
use Glpi\Form\Form;
use Glpi\Form\Question;
use Glpi\Form\QuestionType\QuestionTypeActorsExtraDataConfig;
use Glpi\Form\QuestionType\QuestionTypeActorsDefaultValueConfig;
use Glpi\Form\QuestionType\QuestionTypeDropdown;
use Glpi\Form\QuestionType\QuestionTypeDropdownExtraDataConfig;
use Glpi\Form\QuestionType\QuestionTypeItemDefaultValueConfig;
use Glpi\Form\QuestionType\QuestionTypeItemExtraDataConfig;
use Glpi\Form\QuestionType\QuestionTypeItemDropdown;
use Glpi\Form\QuestionType\QuestionTypeRequester;
use Glpi\Form\QuestionType\QuestionTypeShortText;
use Glpi\Form\Section;
use Glpi\Tests\FormBuilder;
use Glpi\Tests\FormTesterTrait;
use Location;
use Session;

final class FormSerializerTest extends \DbTestCase
Expand Down Expand Up @@ -327,6 +338,129 @@ public function testExportAndImportComments(): void
], $comments_data);
}

public function testExportAndImportQuestions(): void
{
$this->login();

$user = $this->createItem('User', ['name' => 'John Doe']);
$location = $this->createItem(
Location::class,
[
'name' => 'My location',
'entities_id' => $this->getTestRootEntity(only_id: true)
]
);

// Arrange: create a form with multiple sections and questions
$dropdown_config = new QuestionTypeDropdownExtraDataConfig([
'123456789' => 'Option 1',
'987654321' => 'Option 2',
true,
]);
$item_default_value_config = new QuestionTypeItemDefaultValueConfig($location->getID());
$item_extra_data_config = new QuestionTypeItemExtraDataConfig(Location::class);
$actors_default_value_config = new QuestionTypeActorsDefaultValueConfig(
users_ids: [$user->getID()],
);
$actors_extra_data_config = new QuestionTypeActorsExtraDataConfig(
is_multiple_actors: true,
);

$builder = new FormBuilder();
$builder->addSection("My first section")
->addQuestion(
"My text question",
QuestionTypeShortText::class,
'Test default value',
'',
'My text question description'
)
->addQuestion(
"My dropdown question",
QuestionTypeDropdown::class,
'123456789',
json_encode($dropdown_config->jsonSerialize()),
'My dropdown question description'
)
->addSection("My second section")
->addQuestion(
"My item dropdown question",
QuestionTypeItemDropdown::class,
$location->getID(),
json_encode($item_extra_data_config->jsonSerialize()),
'My item dropdown question description',
true
)
->addQuestion(
"My requester question",
QuestionTypeRequester::class,
['users_id-' . $user->getID()],
json_encode($actors_extra_data_config->jsonSerialize()),
);
$form = $this->createForm($builder);

// Act: export and import the form
$form_copy = $this->exportAndImportForm($form);

// Assert: validate questions fields
$questions = array_values($form_copy->getQuestions());
$questions_data = array_map(function (Question $question) {
return [
'name' => $question->fields['name'],
'type' => $question->fields['type'],
'is_mandatory' => $question->fields['is_mandatory'],
'rank' => $question->fields['rank'],
'description' => $question->fields['description'],
'default_value' => $question->fields['default_value'],
'extra_data' => $question->fields['extra_data'],
'forms_sections_id' => $question->fields['forms_sections_id'],
];
}, $questions);

$this->assertEquals([
[
'name' => 'My text question',
'type' => QuestionTypeShortText::class,
'is_mandatory' => (int) false,
'rank' => 0,
'description' => 'My text question description',
'default_value' => 'Test default value',
'extra_data' => "",
'forms_sections_id' => array_values($form_copy->getSections())[0]->fields['id'],
],
[
'name' => 'My dropdown question',
'type' => QuestionTypeDropdown::class,
'is_mandatory' => (int) false,
'rank' => 1,
'description' => 'My dropdown question description',
'default_value' => '123456789',
'extra_data' => json_encode($dropdown_config->jsonSerialize()),
'forms_sections_id' => array_values($form_copy->getSections())[0]->fields['id'],
],
[
'name' => 'My item dropdown question',
'type' => QuestionTypeItemDropdown::class,
'is_mandatory' => (int) true,
'rank' => 0,
'description' => 'My item dropdown question description',
'default_value' => json_encode($item_default_value_config->jsonSerialize()),
'extra_data' => json_encode($item_extra_data_config->jsonSerialize()),
'forms_sections_id' => array_values($form_copy->getSections())[1]->fields['id'],
],
[
'name' => 'My requester question',
'type' => QuestionTypeRequester::class,
'is_mandatory' => (int) false,
'rank' => 1,
'description' => '',
'default_value' => json_encode($actors_default_value_config->jsonSerialize()),
'extra_data' => json_encode($actors_extra_data_config->jsonSerialize()),
'forms_sections_id' => array_values($form_copy->getSections())[1]->fields['id'],
]
], $questions_data);
}

public function testPreviewImportWithValidForm(): void
{
// Arrange: create a valid form
Expand Down
3 changes: 2 additions & 1 deletion src/Glpi/Form/AccessControl/ControlType/AllowListConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use Glpi\DBAL\JsonFieldInterface;
use Glpi\Form\Export\Context\ForeignKey\ForeignKeyArrayHandler;
use Glpi\Form\Export\Context\ConfigWithForeignKeysInterface;
use Glpi\Form\Export\Specification\ContentSpecificationInterface;
use Group;
use Override;
use Profile;
Expand All @@ -61,7 +62,7 @@ public function __construct(
}

#[Override]
public static function listForeignKeysHandlers(): array
public static function listForeignKeysHandlers(ContentSpecificationInterface $content_spec): array
{
return [
new ForeignKeyArrayHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

namespace Glpi\Form\Export\Context;

use Glpi\Form\Export\Specification\ContentSpecificationInterface;

/**
* Must be implemented by all JsonFieldInterface objects that contains references
* foreign keys.
Expand All @@ -48,7 +50,8 @@ interface ConfigWithForeignKeysInterface
* Must return one JsonConfigForeignKeyHandlerInterface per serialized key that
* will contains foreign keys data.
*
* @param \Glpi\Form\Export\Specification\ContentSpecificationInterface $content_spec
* @return \Glpi\Form\Export\Context\ForeignKey\JsonConfigForeignKeyHandlerInterface[]
*/
public static function listForeignKeysHandlers(): array;
public static function listForeignKeysHandlers(ContentSpecificationInterface $content_spec): array;
}
112 changes: 112 additions & 0 deletions src/Glpi/Form/Export/Context/ForeignKey/ForeignKeyHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

namespace Glpi\Form\Export\Context\ForeignKey;

use Glpi\Form\Export\Context\DatabaseMapper;
use Glpi\Form\Export\Specification\DataRequirementSpecification;

/**
* Handle a foreign keys.
*/
final class ForeignKeyHandler implements JsonConfigForeignKeyHandlerInterface
{
/** @param class-string<\CommonDBTM> $itemtype */
public function __construct(
private string $key,
private string $itemtype,
) {
}

public function getDataRequirements(array $serialized_data): array
{
if (!$this->keyExistInSerializedData($serialized_data)) {
return [];
}

$requirements = [];
$foreign_key = $serialized_data[$this->key];

// Create a data requirement for the foreign key and load item
$item = new $this->itemtype();
if ($item->getFromDB($foreign_key)) {
$requirements[] = new DataRequirementSpecification(
$this->itemtype,
$item->getName(),
);
}

return $requirements;
}

public function replaceForeignKeysByNames(array $serialized_data): array
{
if (!$this->keyExistInSerializedData($serialized_data)) {
return [];
}

$foreign_key = $serialized_data[$this->key];

// Replace the foreign key by the name of the item it references and load item
$item = new $this->itemtype();
if ($item->getFromDB($foreign_key)) {
$serialized_data[$this->key] = $item->getName();
}

return $serialized_data;
}

public function replaceNamesByForeignKeys(
array $serialized_data,
DatabaseMapper $mapper,
): array {
if (!$this->keyExistInSerializedData($serialized_data)) {
return [];
}

// Replace name by its database id
$serialized_data[$this->key] = $mapper->getItemId(
$this->itemtype,
$serialized_data[$this->key]
);

return $serialized_data;
}

private function keyExistInSerializedData(array $serialized_data): bool
{
return isset($serialized_data[$this->key]);
}
}
Loading

0 comments on commit 6faf2d9

Please sign in to comment.