Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Fixed

- Fixed the `Tree cascade Dropdown` field so that the subtree depth limit is enforced when loading children via AJAX

## [1.1.1] - 2026-05-27

### Fixed
Expand Down
3 changes: 0 additions & 3 deletions src/Controller/TreeDropdownChildrenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ public function __invoke(Request $request): Response
$foreign_key = $itemtype::getForeignKeyField();
$table = $itemtype::getTable();

$level_key = $table . '.level';

$where = [];

$item_check = getItemForItemtype($itemtype);
Expand All @@ -100,7 +98,6 @@ public function __invoke(Request $request): Response
}

if (!empty($condition_param) && is_array($condition_param)) {
unset($condition_param[$level_key]);
$where = array_merge($where, $condition_param);
}

Expand Down
105 changes: 105 additions & 0 deletions tests/Model/QuestionType/TreeCascadeDropdownQuestionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
use Glpi\Form\QuestionType\QuestionTypeItemDropdownExtraDataConfig;
use Glpi\Tests\FormBuilder;
use Glpi\Tests\FormTesterTrait;
use GlpiPlugin\Advancedforms\Controller\TreeDropdownChildrenController;
use GlpiPlugin\Advancedforms\Model\Config\ConfigurableItemInterface;
use GlpiPlugin\Advancedforms\Model\QuestionType\TreeCascadeDropdownQuestion;
use GlpiPlugin\Advancedforms\Tests\QuestionType\QuestionTypeTestCase;
use Symfony\Component\HttpFoundation\Request;
use Location;
use Override;
use Session;
Expand Down Expand Up @@ -502,6 +504,109 @@ public function testDropdownShowsNameNotCompletename(): void
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The new test only exercises the children query path (verifying Level3 is excluded once depth is exceeded). It never exercises the parent_is_selectable path, which is fed by the same $condition_param that this fix restores. Can you add a test case where the controller is invoked with parent_id set to an item whose own level already exceeds subtree_depth (e.g. level3), and assert the response no longer renders the selectable placeholder (value="0") that parent_is_selectable controls in the twig template?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

/**
* Verify that the subtree depth limit prevents children beyond the configured depth from being returned by the children controller.
*/
public function testSubtreeDepthIsEnforcedInChildrenController(): void
Comment thread
Rom1-B marked this conversation as resolved.
{
$this->login();
$this->enableConfigurableItem(TreeCascadeDropdownQuestion::class);

$entity_id = Session::getActiveEntity();
$level1 = $this->createItem(Location::class, [
'name' => 'Level1',
'locations_id' => 0,
'entities_id' => $entity_id,
]);
$level2 = $this->createItem(Location::class, [
'name' => 'Level2',
'locations_id' => $level1->getID(),
'entities_id' => $entity_id,
]);
$level3 = $this->createItem(Location::class, [
'name' => 'Level3',
'locations_id' => $level2->getID(),
'entities_id' => $entity_id,
]);

$extra_data = json_encode(new QuestionTypeItemDropdownExtraDataConfig(
itemtype: Location::class,
subtree_depth: 2,
));

$builder = new FormBuilder("Depth limit test");
$builder->addQuestion("Location", TreeCascadeDropdownQuestion::class, '', $extra_data);
$form = $this->createForm($builder);

$questions = $form->getQuestions();
$question = array_values($questions)[0];

$controller = new TreeDropdownChildrenController();

$response_level1_children = $controller->__invoke(
Request::create('', 'GET', ['questions_id' => $question->getID(), 'parent_id' => $level1->getID()]),
);
$this->assertStringContainsString('Level2', $response_level1_children->getContent());

$response_level2_children = $controller->__invoke(
Request::create('', 'GET', ['questions_id' => $question->getID(), 'parent_id' => $level2->getID()]),
);
$this->assertStringNotContainsString('Level3', $response_level2_children->getContent());
}

/**
* Verify that when the parent itself exceeds the subtree depth limit, it is not selectable and no children are returned.
*/
public function testParentExceedingDepthLimitIsNotSelectableAndHasNoChildren(): void
{
$this->login();
$this->enableConfigurableItem(TreeCascadeDropdownQuestion::class);

$entity_id = Session::getActiveEntity();
$level1 = $this->createItem(Location::class, [
'name' => 'L1',
'locations_id' => 0,
'entities_id' => $entity_id,
]);
$level2 = $this->createItem(Location::class, [
'name' => 'L2',
'locations_id' => $level1->getID(),
'entities_id' => $entity_id,
]);
$level3 = $this->createItem(Location::class, [
'name' => 'L3',
'locations_id' => $level2->getID(),
'entities_id' => $entity_id,
]);
$this->createItem(Location::class, [
'name' => 'L4',
'locations_id' => $level3->getID(),
'entities_id' => $entity_id,
]);

$extra_data = json_encode(new QuestionTypeItemDropdownExtraDataConfig(
itemtype: Location::class,
subtree_depth: 2,
));

$builder = new FormBuilder("Depth selectable test");
$builder->addQuestion("Location", TreeCascadeDropdownQuestion::class, '', $extra_data);
$form = $this->createForm($builder);

$questions = $form->getQuestions();
$question = array_values($questions)[0];

$controller = new TreeDropdownChildrenController();

// Level3 exceeds subtree_depth=2; its child L4 must not be returned and
// the parent_is_selectable placeholder (value="0") must not appear.
$response = $controller->__invoke(
Request::create('', 'GET', ['questions_id' => $question->getID(), 'parent_id' => $level3->getID()]),
);
$this->assertStringNotContainsString('value="0"', $response->getContent());
$this->assertStringNotContainsString('L4', $response->getContent());
}

private function renderHelpdeskForm(\Glpi\Form\Form $form): Crawler
{
$this->login();
Expand Down