diff --git a/src/Glpi/Features/AssignableItem.php b/src/Glpi/Features/AssignableItem.php index 817a4f0a02f..095fae1a4c1 100644 --- a/src/Glpi/Features/AssignableItem.php +++ b/src/Glpi/Features/AssignableItem.php @@ -35,6 +35,7 @@ namespace Glpi\Features; +use CommonITILObject; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; use Group_Item; @@ -105,8 +106,26 @@ public function canUpdateItem(): bool * @return array[]|QueryExpression[] * @see AssignableItemInterface::getAssignableVisiblityCriteria() */ - public static function getAssignableVisiblityCriteria(?string $item_table_reference = null): array - { + public static function getAssignableVisiblityCriteria( + ?string $item_table_reference = null + ): array { + $criteria = Session::getCurrentInterface() === "central" + ? self::getAssignableVisiblityCriteriaForCentral($item_table_reference) + : self::getAssignableVisiblityCriteriaForHelpdesk($item_table_reference) + ; + + // Add another layer to the array to prevent losing duplicates keys if the + // result of the function is merged with another array + return [crc32(serialize($criteria)) => $criteria]; + } + + /** + * @param string|null $item_table_reference + * @return array[]|QueryExpression[] + */ + private static function getAssignableVisiblityCriteriaForCentral( + ?string $item_table_reference = null + ): array { if (!Session::haveRightsOr(static::$rightname, [READ, READ_ASSIGNED, READ_OWNED])) { return [new QueryExpression('0')]; } @@ -138,29 +157,69 @@ public static function getAssignableVisiblityCriteria(?string $item_table_refere } } if (Session::haveRight(static::$rightname, READ_OWNED)) { + $or += self::getOwnAssetsCriteria($item_table, $relation_table); + } + + return ['OR' => $or]; + } + + /** + * @param string|null $item_table_reference + * @return array[]|QueryExpression[] + */ + private static function getAssignableVisiblityCriteriaForHelpdesk( + ?string $item_table_reference = null + ): array { + // Helpdesk doesn't support READ, READ_ASSIGNED, READ_OWNED rights. + // Instead, we will directly check the helpdesk_hardware and + // helpdesk_item_type properties from the profile + $profile = Session::getCurrentProfile(); + + $raw_allowed_itemtypes = $profile->fields['helpdesk_item_type']; + $allowed_itemtypes = importArrayFromDB($raw_allowed_itemtypes); + if (!in_array(static::class, $allowed_itemtypes)) { + return [new QueryExpression('0')]; + } + + $raw_rights = $profile->fields['helpdesk_hardware']; + $all_assets = $raw_rights & (2 ** CommonITILObject::HELPDESK_ALL_HARDWARE); + if ($all_assets) { + return [new QueryExpression('1')]; + } + + $my_assets = $raw_rights & (2 ** CommonITILObject::HELPDESK_MY_HARDWARE); + if ($my_assets) { + $item_table = $item_table_reference ?? static::getTable(); + $relation_table = Group_Item::getTable(); + $or = self::getOwnAssetsCriteria($item_table, $relation_table); + + return ['OR' => $or]; + } + + // User can't see any assets + return [new QueryExpression('0')]; + } + + private static function getOwnAssetsCriteria( + string $item_table, + string $relation_table, + ): array { + $or = [$item_table . '.users_id' => $_SESSION['glpiID']]; + if (count($_SESSION['glpigroups']) > 0) { $or[] = [ - $item_table . '.users_id' => $_SESSION['glpiID'], + $item_table . '.id' => new QuerySubQuery([ + 'SELECT' => $relation_table . '.items_id', + 'FROM' => $relation_table, + 'WHERE' => [ + 'itemtype' => static::class, + 'groups_id' => $_SESSION['glpigroups'], + 'type' => Group_Item::GROUP_TYPE_NORMAL, + ], + ]), ]; - if (count($_SESSION['glpigroups']) > 0) { - $or[] = [ - $item_table . '.id' => new QuerySubQuery([ - 'SELECT' => $relation_table . '.items_id', - 'FROM' => $relation_table, - 'WHERE' => [ - 'itemtype' => static::class, - 'groups_id' => $_SESSION['glpigroups'], - 'type' => Group_Item::GROUP_TYPE_NORMAL, - ], - ]), - ]; - } } - // Add another layer to the array to prevent losing duplicates keys if the - // result of the function is merged with another array - $criteria = [crc32(serialize($or)) => ['OR' => $or]]; - - return $criteria; + return $or; } /** @see AssignableItemInterface::getRights() */ diff --git a/tests/functional/DropdownTest.php b/tests/functional/DropdownTest.php index d0134b6866a..175e258f026 100644 --- a/tests/functional/DropdownTest.php +++ b/tests/functional/DropdownTest.php @@ -35,8 +35,10 @@ namespace tests\units; use CommonDBTM; +use CommonITILObject; use Computer; use DbTestCase; +use Entity; use Generator; use Glpi\Asset\Asset_PeripheralAsset; use Glpi\Features\AssignableItem; @@ -45,9 +47,11 @@ use Item_DeviceSimcard; use Monitor; use PHPUnit\Framework\Attributes\DataProvider; +use Profile; use Session; use State; use Symfony\Component\DomCrawler\Crawler; +use Ticket; use User; /* Test for inc/dropdown.class.php */ @@ -2537,12 +2541,12 @@ public function testSupplierActorDropdownOnlyActive() 'is_recursive' => 1, ]); $params = [ - 'itemtype' => \Ticket::class, + 'itemtype' => Ticket::class, 'actortype' => 'assign', 'returned_itemtypes' => [\Supplier::class], 'searchText' => '', ]; - $results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(\Ticket::class, $params)], false); + $results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(Ticket::class, $params)], false); $this->assertNotEmpty($results['results'][0]['children']); $this->assertCount(0, array_filter($results['results'][0]['children'], function ($result) use ($inactive_supplier) { return $result['id'] === \Supplier::class . '_' . $inactive_supplier->getID(); @@ -2550,7 +2554,7 @@ public function testSupplierActorDropdownOnlyActive() // If asking for inactive_deleted, it should return the inactive supplier $params['inactive_deleted'] = 1; - $results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(\Ticket::class, $params)], false); + $results = \Dropdown::getDropdownActors($params + ['_idor_token' => Session::getNewIDORToken(Ticket::class, $params)], false); $this->assertNotEmpty($results['results'][0]['children']); $this->assertCount(1, array_filter($results['results'][0]['children'], function ($result) use ($inactive_supplier) { return $result['id'] === \Supplier::class . '_' . $inactive_supplier->getID(); @@ -2653,7 +2657,7 @@ public function testGetDropdownMyDevices() ]); // Ensure proper permissions and helpdesk types - $_SESSION["glpiactiveprofile"]["helpdesk_hardware"] = pow(2, \Ticket::HELPDESK_MY_HARDWARE); + $_SESSION["glpiactiveprofile"]["helpdesk_hardware"] = pow(2, Ticket::HELPDESK_MY_HARDWARE); $_SESSION["glpiactiveprofile"]["helpdesk_item_type"] = ['Computer', 'Monitor', 'Printer']; $post = [ @@ -2736,4 +2740,82 @@ public function testGetDropdownMyDevices() // Test that count is accurate $this->assertGreaterThan(0, $result['count']); } + + public static function assetsDropdownForHelpdeskProvider(): iterable + { + yield 'no rights' => [ + 'can_view' => 0, + 'itemtypes' => [Computer::class], + 'expected' => [], + ]; + yield 'see his own computers' => [ + 'can_view' => 2 ** CommonITILObject::HELPDESK_MY_HARDWARE, + 'itemtypes' => [Computer::class], + 'expected' => ['My computer'], + ]; + yield 'see all computers' => [ + 'can_view' => 2 ** CommonITILObject::HELPDESK_ALL_HARDWARE, + 'itemtypes' => [Computer::class], + 'expected' => ['My computer', 'Not my computer'], + ]; + yield 'see all monitors' => [ + 'can_view' => 2 ** CommonITILObject::HELPDESK_ALL_HARDWARE, + 'itemtypes' => [Monitor::class], + 'expected' => [], + ]; + } + + #[DataProvider('assetsDropdownForHelpdeskProvider')] + public function testAssetsDropdownForHelpdesk( + int $can_view, + array $itemtypes, + array $expected, + ): void { + // Arrange: assign a computer to a self-service user and set up the + // profile with the given rights. + // Wrap items in an entity for better test isolation + $this->login(); // Need to be logged in to create an entity + $entity = $this->createItem(Entity::class, [ + 'name' => 'My entity', + 'entities_id' => $this->getTestRootEntity(only_id: true), + ]); + $this->logOut(); + $this->createItem(Computer::class, [ + 'name' => 'My computer', + 'entities_id' => $entity->getID(), + 'users_id' => getItemByTypeName(User::class, "post-only", true), + ]); + $this->createItem(Computer::class, [ + 'name' => 'Not my computer', + 'entities_id' => $entity->getID(), + ]); + $this->updateItem( + Profile::class, + getItemByTypeName(Profile::class, 'Self-Service', onlyid: true), + [ + 'helpdesk_hardware' => $can_view, + 'helpdesk_item_type' => $itemtypes, + ], + ['helpdesk_item_type'], + ); + + // Act: get dropdown values for this user + $this->login('post-only'); + $this->setEntity("My entity", false); + $params = [ + 'itemtype' => Computer::class, + ]; + $params['_idor_token'] = Session::getNewIDORToken(Computer::class, $params); + $results = \Dropdown::getDropdownValue($params, false); + + // Assert: only one computer should be count + $this->assertEquals(count($expected), $results["count"]); + if (!empty($expected)) { + $found_items = array_map( + fn($data) => $data['text'], + $results["results"][1]["children"], + ); + $this->assertEquals($expected, $found_items); + } + } }