From e176274ee9744653c2d072e77a5c5997465c4efa Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 17 Oct 2024 09:08:51 +0200 Subject: [PATCH 01/33] Fix Debug LDAP Informations --- src/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/User.php b/src/User.php index 3848bf62305..0a692bd51c9 100644 --- a/src/User.php +++ b/src/User.php @@ -5904,7 +5904,7 @@ private function showLdapInformation(): void $info = AuthLDAP::getUserByDn( $ds, $this->fields['user_dn'], - ['*', 'createTimeStamp', 'modifyTimestamp'] + ['*', '+', 'createTimeStamp', 'modifyTimestamp'] ); if (is_array($info)) { foreach ($info as $key => $values) { From b33c027b511fa7086f87a03ca59b26bdab07b725 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 24 Oct 2024 13:21:17 +0200 Subject: [PATCH 02/33] Fix --- src/User.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/User.php b/src/User.php index 183fdd40cc3..3d2921d655f 100644 --- a/src/User.php +++ b/src/User.php @@ -5965,7 +5965,11 @@ private function showLdapInformation(): void $info = AuthLDAP::getUserByDn( $ds, $this->fields['user_dn'], - ['*', '+', 'createTimeStamp', 'modifyTimestamp'] + [ + // see https://docs.ldap.com/ldap-sdk/docs/tool-usages/ldapsearch.html + '*', // all user attributes + '+', // all operational attributes + ] ); if (is_array($info)) { foreach ($info as $key => $values) { From 8184f7f441d71136934d54873dd8c385d1ad508a Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Tue, 8 Jul 2025 18:42:31 +0200 Subject: [PATCH 03/33] Feature : targeted private saved searches --- ajax/subvisibility.php | 4 + ajax/visibility.php | 22 +- front/savedsearch.php | 11 +- inc/relation.constant.php | 11 +- .../entity_savedsearch.php | 57 ++++ .../group_savedsearch.php | 60 ++++ .../update_10.0.x_to_11.0.0/savedsearch.php | 53 +++ .../savedsearch_usertarget.php | 55 +++ install/mysql/glpi-empty.sql | 44 ++- ...otificationTargetSavedSearch_AlertTest.php | 1 + phpunit/functional/SavedSearchTest.php | 168 +++++----- src/CommonDBVisible.php | 123 ++++++- src/Entity_SavedSearch.php | 73 ++++ .../Form/SavedSearchFormController.php | 4 +- src/Glpi/Controller/VisibilityController.php | 72 ++++ src/Glpi/Search/Provider/SQLProvider.php | 10 + src/Group_SavedSearch.php | 73 ++++ src/SavedSearch.php | 314 +++++++++++++++--- src/SavedSearch_UserTarget.php | 74 +++++ src/User.php | 65 +++- .../add_visibility_target.html.twig | 2 +- .../parts/saved_searches_list.html.twig | 4 +- .../pages/tools/savedsearch/form.html.twig | 17 +- 23 files changed, 1149 insertions(+), 168 deletions(-) create mode 100644 install/migrations/update_10.0.x_to_11.0.0/entity_savedsearch.php create mode 100644 install/migrations/update_10.0.x_to_11.0.0/group_savedsearch.php create mode 100644 install/migrations/update_10.0.x_to_11.0.0/savedsearch.php create mode 100644 install/migrations/update_10.0.x_to_11.0.0/savedsearch_usertarget.php create mode 100644 src/Entity_SavedSearch.php create mode 100644 src/Glpi/Controller/VisibilityController.php create mode 100644 src/Group_SavedSearch.php create mode 100644 src/SavedSearch_UserTarget.php diff --git a/ajax/subvisibility.php b/ajax/subvisibility.php index 11eb3163ea8..4bde9edb933 100644 --- a/ajax/subvisibility.php +++ b/ajax/subvisibility.php @@ -53,6 +53,10 @@ if (Session::canViewAllEntities()) { $params['toadd'] = [-1 => __('No restriction')]; } + if (isset($_POST['entity']) && $_POST['entity'] >= 0) { + $params['entity'] = $_POST['entity']; + $params['entity_sons'] = $_POST['is_recursive'] ?? false; + } echo "
"; echo htmlescape(Entity::getTypeName(1)); echo ""; diff --git a/ajax/visibility.php b/ajax/visibility.php index 789a2650c5f..554b71169d3 100644 --- a/ajax/visibility.php +++ b/ajax/visibility.php @@ -45,7 +45,6 @@ if ( isset($_POST['type']) && !empty($_POST['type']) - && isset($_POST['right']) ) { $display = false; $rand = mt_rand(); @@ -61,10 +60,16 @@ echo "
"; switch ($_POST['type']) { case 'User': - $params = [ - 'right' => isset($_POST['allusers']) ? 'all' : $_POST['right'], - 'name' => $prefix . 'users_id' . $suffix, - ]; + $params = ['name' => $prefix . 'users_id' . $suffix]; + if (isset($_POST['right'])) { + $params['right'] = isset($_POST['allusers']) ? 'all' : $_POST['right']; + } else { + $params['right'] = 'all'; + } + if (isset($_POST['entity']) && $_POST['entity'] >= 0) { + $params['entity'] = $_POST['entity']; + $params['entity_sons'] = $_POST['is_recursive'] ?? false; + } User::dropdown($params); $display = true; break; @@ -82,7 +87,12 @@ 'prefix' => $_POST['prefix'], ], ]; - + if (isset($_POST['entity']) && $_POST['entity'] >= 0) { + $params['entity'] = $_POST['entity']; + $params['toupdate']['moreparams']['entity'] = $_POST['entity']; + $params['entity_sons'] = $_POST['is_recursive'] ?? false; + $params['toupdate']['moreparams']['entity_sons'] = $_POST['is_recursive'] ?? false; + } Group::dropdown($params); echo ""; $display = true; diff --git a/front/savedsearch.php b/front/savedsearch.php index 72971a51082..bae7ac3eb7d 100644 --- a/front/savedsearch.php +++ b/front/savedsearch.php @@ -33,6 +33,8 @@ * --------------------------------------------------------------------- */ +use Glpi\Exception\Http\AccessDeniedHttpException; + require_once(__DIR__ . '/_check_webserver_config.php'); if (Session::getCurrentInterface() == "helpdesk") { @@ -47,8 +49,13 @@ isset($_GET['action']) && $_GET["action"] == "load" && isset($_GET["id"]) && ($_GET["id"] > 0) ) { - $savedsearch->check($_GET["id"], READ); - $savedsearch->load($_GET["id"]); + $savedsearch->getFromDB($_GET['id']); + if ($savedsearch->canViewItem()) { + $savedsearch->load($_GET["id"]); + } else { + $info = "User can not access the SavedSearch " . $_GET['id']; + throw new AccessDeniedHttpException($info); + } return; } diff --git a/inc/relation.constant.php b/inc/relation.constant.php index f48160abd12..7bc199f5f38 100644 --- a/inc/relation.constant.php +++ b/inc/relation.constant.php @@ -588,6 +588,7 @@ '_glpi_entities_knowbaseitems' => 'entities_id', '_glpi_entities_reminders' => 'entities_id', '_glpi_entities_rssfeeds' => 'entities_id', + '_glpi_entities_savedsearches' => 'entities_id', 'glpi_fieldblacklists' => 'entities_id', 'glpi_fieldunicities' => 'entities_id', 'glpi_forms_forms' => 'entities_id', @@ -597,6 +598,7 @@ 'glpi_groups_knowbaseitems' => 'entities_id', 'glpi_groups_reminders' => 'entities_id', 'glpi_groups_rssfeeds' => 'entities_id', + 'glpi_groups_savedsearches' => 'entities_id', 'glpi_holidays' => 'entities_id', 'glpi_imageformats' => 'entities_id', 'glpi_imageresolutions' => 'entities_id', @@ -740,6 +742,7 @@ '_glpi_groups_problems' => 'groups_id', '_glpi_groups_reminders' => 'groups_id', '_glpi_groups_rssfeeds' => 'groups_id', + '_glpi_groups_savedsearches' => 'groups_id', '_glpi_groups_tickets' => 'groups_id', '_glpi_groups_users' => 'groups_id', 'glpi_itilcategories' => 'groups_id', @@ -1275,8 +1278,11 @@ ], 'glpi_savedsearches' => [ - '_glpi_savedsearches_alerts' => 'savedsearches_id', - '_glpi_savedsearches_users' => 'savedsearches_id', + '_glpi_entities_savedsearches' => 'savedsearches_id', + '_glpi_groups_savedsearches' => 'savedsearches_id', + '_glpi_savedsearches_alerts' => 'savedsearches_id', + '_glpi_savedsearches_users' => 'savedsearches_id', + '_glpi_savedsearches_usertargets' => 'savedsearches_id', ], 'glpi_slalevels' => [ @@ -1638,6 +1644,7 @@ '_glpi_rssfeeds_users' => 'users_id', '_glpi_savedsearches' => 'users_id', '_glpi_savedsearches_users' => 'users_id', + '_glpi_savedsearches_usertargets' => 'users_id', 'glpi_softwarelicenses' => [ 'users_id_tech', 'users_id', diff --git a/install/migrations/update_10.0.x_to_11.0.0/entity_savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/entity_savedsearch.php new file mode 100644 index 00000000000..fae0e277efc --- /dev/null +++ b/install/migrations/update_10.0.x_to_11.0.0/entity_savedsearch.php @@ -0,0 +1,57 @@ +. + * + * --------------------------------------------------------------------- + */ + +/** + * @var \DBmysql $DB + * @var \Migration $migration + */ + +$default_charset = DBConnection::getDefaultCharset(); +$default_collation = DBConnection::getDefaultCollation(); +$default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + +if (!$DB->tableExists('glpi_entities_savedsearches')) { + $query = "CREATE TABLE `glpi_entities_savedsearches` ( + `id` int {$default_key_sign} NOT NULL AUTO_INCREMENT, + `savedsearches_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `entities_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `is_recursive` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `entities_id` (`entities_id`), + KEY `is_recursive` (`is_recursive`) + ) ENGINE = InnoDB ROW_FORMAT = DYNAMIC DEFAULT CHARSET = {$default_charset} COLLATE = {$default_collation};"; + $DB->doQuery($query); +} diff --git a/install/migrations/update_10.0.x_to_11.0.0/group_savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/group_savedsearch.php new file mode 100644 index 00000000000..33e9210ae64 --- /dev/null +++ b/install/migrations/update_10.0.x_to_11.0.0/group_savedsearch.php @@ -0,0 +1,60 @@ +. + * + * --------------------------------------------------------------------- + */ + +/** + * @var \DBmysql $DB + * @var \Migration $migration + */ + +$default_charset = DBConnection::getDefaultCharset(); +$default_collation = DBConnection::getDefaultCollation(); +$default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + +if (!$DB->tableExists('glpi_groups_savedsearches')) { + $query = "CREATE TABLE `glpi_groups_savedsearches` ( + `id` int {$default_key_sign} NOT NULL AUTO_INCREMENT, + `savedsearches_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `groups_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `entities_id` int {$default_key_sign} DEFAULT NULL, + `is_recursive` tinyint NOT NULL DEFAULT '0', + `no_entity_restriction` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `groups_id` (`groups_id`), + KEY `entities_id` (`entities_id`), + KEY `is_recursive` (`is_recursive`) + ) ENGINE = InnoDB ROW_FORMAT = DYNAMIC DEFAULT CHARSET = {$default_charset} COLLATE = {$default_collation};"; + $DB->doQuery($query); +} diff --git a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php new file mode 100644 index 00000000000..c4a39a45492 --- /dev/null +++ b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php @@ -0,0 +1,53 @@ +. + * + * --------------------------------------------------------------------- + */ + +/** + * @var \DBmysql $DB + * @var \Migration $migration + * @var array $DELFROMDISPLAYPREF + */ + +$table = SavedSearch::getTable(); +$field = 'is_private'; +if ($DB->fieldExists($table, $field)) { + $query = 'INSERT INTO glpi_entities_savedsearches (savedsearches_id, entities_id, is_recursive) +SELECT id, entities_id, is_recursive +FROM glpi_savedsearches WHERE is_private = 0;'; + $DB->doQuery($query); + + $migration->dropField($table, $field); + + $DELFROMDISPLAYPREF['SavedSearch'] = 4; +} diff --git a/install/migrations/update_10.0.x_to_11.0.0/savedsearch_usertarget.php b/install/migrations/update_10.0.x_to_11.0.0/savedsearch_usertarget.php new file mode 100644 index 00000000000..4033a3978e0 --- /dev/null +++ b/install/migrations/update_10.0.x_to_11.0.0/savedsearch_usertarget.php @@ -0,0 +1,55 @@ +. + * + * --------------------------------------------------------------------- + */ + +/** + * @var \DBmysql $DB + * @var \Migration $migration + */ + +$default_charset = DBConnection::getDefaultCharset(); +$default_collation = DBConnection::getDefaultCollation(); +$default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + +if (!$DB->tableExists('glpi_savedsearches_usertargets')) { + $query = "CREATE TABLE `glpi_savedsearches_usertargets` ( + `id` int {$default_key_sign} NOT NULL AUTO_INCREMENT, + `savedsearches_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `users_id` int {$default_key_sign} NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `users_id` (`users_id`) + ) ENGINE = InnoDB ROW_FORMAT = DYNAMIC DEFAULT CHARSET = {$default_charset} COLLATE = {$default_collation};"; + $DB->doQuery($query); +} diff --git a/install/mysql/glpi-empty.sql b/install/mysql/glpi-empty.sql index 5419306050e..16451f977bd 100644 --- a/install/mysql/glpi-empty.sql +++ b/install/mysql/glpi-empty.sql @@ -235,7 +235,6 @@ CREATE TABLE `glpi_savedsearches` ( `type` int NOT NULL DEFAULT '0', `itemtype` varchar(100) NOT NULL, `users_id` int unsigned NOT NULL DEFAULT '0', - `is_private` tinyint NOT NULL DEFAULT '1', `entities_id` int unsigned NOT NULL DEFAULT '0', `is_recursive` tinyint NOT NULL DEFAULT '0', `query` text, @@ -249,7 +248,6 @@ CREATE TABLE `glpi_savedsearches` ( KEY `itemtype` (`itemtype`), KEY `entities_id` (`entities_id`), KEY `users_id` (`users_id`), - KEY `is_private` (`is_private`), KEY `is_recursive` (`is_recursive`), KEY `last_execution_time` (`last_execution_time`), KEY `last_execution_date` (`last_execution_date`), @@ -292,6 +290,18 @@ CREATE TABLE `glpi_savedsearches_alerts` ( KEY `date_creation` (`date_creation`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; +### Dump table glpi_savedsearches_usertargets + +DROP TABLE IF EXISTS `glpi_savedsearches_usertargets`; +CREATE TABLE `glpi_savedsearches_usertargets` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `users_id` int unsigned NOT NULL DEFAULT '0', + `savedsearches_id` int unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `users_id` (`users_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + ### Dump table glpi_budgets @@ -2910,6 +2920,20 @@ CREATE TABLE `glpi_entities_rssfeeds` ( KEY `is_recursive` (`is_recursive`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; +### Dump table glpi_entities_savedsearches + +DROP TABLE IF EXISTS `glpi_entities_savedsearches`; +CREATE TABLE `glpi_entities_savedsearches` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `savedsearches_id` int unsigned NOT NULL DEFAULT '0', + `entities_id` int unsigned NOT NULL DEFAULT '0', + `is_recursive` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `entities_id` (`entities_id`), + KEY `is_recursive` (`is_recursive`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + ### Dump table glpi_events @@ -3134,6 +3158,22 @@ CREATE TABLE `glpi_groups_rssfeeds` ( KEY `is_recursive` (`is_recursive`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; +### Dump table glpi_groups_savedsearches + +DROP TABLE IF EXISTS `glpi_groups_savedsearches`; +CREATE TABLE `glpi_groups_savedsearches` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `savedsearches_id` int unsigned NOT NULL DEFAULT '0', + `groups_id` int unsigned NOT NULL DEFAULT '0', + `entities_id` int unsigned DEFAULT NULL, + `is_recursive` tinyint NOT NULL DEFAULT '0', + `no_entity_restriction` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `savedsearches_id` (`savedsearches_id`), + KEY `groups_id` (`groups_id`), + KEY `entities_id` (`entities_id`), + KEY `is_recursive` (`is_recursive`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; ### Dump table glpi_groups_tickets diff --git a/phpunit/functional/NotificationTargetSavedSearch_AlertTest.php b/phpunit/functional/NotificationTargetSavedSearch_AlertTest.php index 98165fae662..794a982348b 100644 --- a/phpunit/functional/NotificationTargetSavedSearch_AlertTest.php +++ b/phpunit/functional/NotificationTargetSavedSearch_AlertTest.php @@ -51,6 +51,7 @@ public function testAddDataForTemplate() 'entities_id' => getItemByTypeName('Entity', '_test_root_entity', true), 'users_id' => \Session::getLoginUserID(), 'itemtype' => 'Computer', + 'is_private' => 1, 'url' => 'http://localhost/front/computer.php?is_deleted=0&as_map=0&browse=0&criteria%5B0%5D%5Blink%5D=AND&criteria%5B0%5D%5Bfield%5D=view&criteria%5B0%5D%5Bsearchtype%5D=contains&criteria%5B0%5D%5Bvalue%5D=test&itemtype=Computer&start=0&_glpi_csrf_token=735e344f1f47545e5bea56aa4e75c15ca45d3628307937c3bf185e0a3bca39db&sort%5B%5D=1&order%5B%5D=ASC', ]); $this->assertGreaterThan(0, $saved_searches_id); diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index ae23dbc307c..3e6d6e44839 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -58,45 +58,50 @@ public function testGetVisibilityCriteria() public function testAddVisibilityRestrict() { - $test_root = getItemByTypeName('Entity', '_test_root_entity', true); - $test_child_1 = getItemByTypeName('Entity', '_test_child_1', true); - $test_child_2 = getItemByTypeName('Entity', '_test_child_2', true); - + global $DB; //first, as a super-admin $this->login(); + $visibility_restrict = "`glpi_savedsearches`.`users_id` = '5'"; $this->assertSame('', \SavedSearch::addVisibilityRestrict()); $this->login('normal', 'normal'); $this->assertSame( - "`glpi_savedsearches`.`is_private` = '1' AND `glpi_savedsearches`.`users_id` = '5' AND (true)", + $visibility_restrict, \SavedSearch::addVisibilityRestrict() ); - //add public saved searches read right for normal profile - global $DB; - $DB->update( - 'glpi_profilerights', - ['rights' => 1], - [ - 'profiles_id' => 2, - 'name' => 'bookmark_public', - ] - ); - //ACLs have changed: login again. + // temporarily add admin profile and switch to it to test can see public + $DB->insert('glpi_profiles_users', [ + 'users_id' => \Session::getLoginUserID(), + 'profiles_id' => 3, + 'entities_id' => 0, + 'is_recursive' => 1 + ]); + // logout -> login to be able to switch to new profile + $this->logOut(); $this->login('normal', 'normal'); + \Session::changeProfile(3); + $visibility_restrict2 = "((`glpi_savedsearches`.`users_id` = '5') OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; $this->assertSame( - "((`glpi_savedsearches`.`is_private` = '1' AND `glpi_savedsearches`.`users_id` = '5') OR (`glpi_savedsearches`.`is_private` = '0')) AND (true)", - \SavedSearch::addVisibilityRestrict() + $visibility_restrict2, + \SavedSearch::addVisibilityRestrict() ); + $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; // Check entity restriction $this->setEntity('_test_root_entity', true); $this->assertSame( - "((`glpi_savedsearches`.`is_private` = '1' AND `glpi_savedsearches`.`users_id` = '5') OR (`glpi_savedsearches`.`is_private` = '0')) AND ((`glpi_savedsearches`.`entities_id` IN ('$test_root', '$test_child_1', '$test_child_2') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))", + $visibility_restrict3, \SavedSearch::addVisibilityRestrict() ); + $DB->delete('glpi_profiles_users', [ + 'users_id' => \Session::getLoginUserID(), + 'profiles_id' => 3, + 'entities_id' => 0, + 'is_recursive' => 1 + ]); } public function testGetMine() @@ -104,7 +109,7 @@ public function testGetMine() global $DB; $root_entity_id = getItemByTypeName(\Entity::class, '_test_root_entity', true); - $child_entity_id = getItemByTypeName(\Entity::class, '_test_child_1', true); + $test_group_1_id = getItemByTypeName(\Group::class, '_test_group_1', true); // needs a user // let's use TU_USER @@ -116,72 +121,80 @@ public function testGetMine() $bk = new \SavedSearch(); $this->assertTrue( (bool) $bk->add([ - 'name' => 'public root recursive', + 'name' => 'private root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, - 'is_private' => 0, - 'entities_id' => $root_entity_id, + 'entities_id' => 0, 'is_recursive' => 1, 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, ]) ); + $bk_private_id = $bk->getID(); $this->assertTrue( (bool) $bk->add([ - 'name' => 'public root NOT recursive', + 'name' => 'target user root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, 'is_private' => 0, - 'entities_id' => $root_entity_id, - 'is_recursive' => 0, + 'entities_id' => 0, + 'is_recursive' => 1, 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, ]) ); + $bk_target_user_id = $bk->getID(); $this->assertTrue( (bool) $bk->add([ - 'name' => 'public child 1 recursive', + 'name' => 'target group root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, 'is_private' => 0, - 'entities_id' => $child_entity_id, + 'entities_id' => 0, 'is_recursive' => 1, 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, ]) ); + $bk_target_group_id = $bk->getID(); + // has is_private => 0 in inputs, so a target will be automatically created for the bookmark's entity $this->assertTrue( (bool) $bk->add([ - 'name' => 'private TU_USER', + 'name' => 'created public target entity root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, - 'is_private' => 1, + 'is_private' => 0, 'entities_id' => 0, 'is_recursive' => 1, 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, ]) ); + $bk_target_entity_id = $bk->getID(); + $bk2 = new \SavedSearch(); + $bk2->getFromDB($bk_target_entity_id); + $this->assertEquals(1, $bk2->countVisibilities()); + $this->assertTrue( (bool) $bk->add([ 'name' => 'private normal user', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $normal_id, - 'is_private' => 1, 'entities_id' => 0, 'is_recursive' => 1, 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, ]) ); - // With UPDATE 'config' right, we still shouldn't see other user's private searches + $bk_private_normal_id = $bk->getID(); + // With UPDATE 'config' right, we still shouldn't see other user's searches without targets $expected = [ - 'public root recursive', - 'public root NOT recursive', - 'public child 1 recursive', - 'private TU_USER', + 'private root recursive', + 'target user root recursive', + 'target group root recursive', + 'created public target entity root recursive', ]; $mine = $bk->getMine(); $this->assertCount(count($expected), $mine); @@ -196,13 +209,23 @@ public function testGetMine() array_column($mine, 'name') ); - // Normal user cannot see public saved searches by default + // test each type of targets so that normal will be able to see them + $bks_normal = [ + 'private normal user', + 'created public target entity root recursive' + ]; + // add normal to a group + $group_user = new \Group_User(); + $group_user->add([ + 'users_id' => 5, + 'groups_id' => $test_group_1_id + ]); $this->login('normal', 'normal'); $mine = $bk->getMine(); - $this->assertCount(1, $mine); + $this->assertCount(count($bks_normal), $mine); $this->assertEqualsCanonicalizing( - ['private normal user'], + $bks_normal, array_column($mine, 'name') ); @@ -211,61 +234,52 @@ public function testGetMine() 'glpi_profilerights', ['rights' => 1], [ - 'profiles_id' => 2, - 'name' => 'bookmark_public', + 'users_id' => 5, + 'savedsearches_id' => $bk_target_user_id ] ); - $this->login('normal', 'normal'); // ACLs have changed: login again. - $expected = [ - 'public root recursive', - 'public root NOT recursive', - 'public child 1 recursive', - 'private normal user', - ]; + $bks_normal[] = 'target user root recursive'; $mine = $bk->getMine('Ticket'); - $this->assertCount(count($expected), $mine); + $this->assertCount(count($bks_normal), $mine); $this->assertEqualsCanonicalizing( - $expected, + $bks_normal, array_column($mine, 'name') ); // Check entity restrictions - $this->setEntity('_test_root_entity', false); - $expected = [ - 'public root recursive', - 'public root NOT recursive', - 'private normal user', - ]; - $mine = $bk->getMine('Ticket'); - $this->assertCount(count($expected), $mine); - $this->assertEqualsCanonicalizing( - $expected, - array_column($mine, 'name') + // add the group as target for a bookmark + $DB->insert( + 'glpi_groups_savedsearches', + [ + 'savedsearches_id' => $bk_target_group_id, + 'groups_id' => $test_group_1_id, + 'entities_id' => 0, + 'is_recursive' => 1 + ] ); - $this->setEntity('_test_child_1', true); - $expected = [ - 'public root recursive', - 'public child 1 recursive', - 'private normal user', - ]; + $bks_normal[] = 'target group root recursive'; $mine = $bk->getMine('Ticket'); - $this->assertCount(count($expected), $mine); + $this->assertCount(count($bks_normal), $mine); $this->assertEqualsCanonicalizing( - $expected, + $bks_normal, array_column($mine, 'name') ); - $this->setEntity('_test_child_1', false); - $expected = [ - 'public root recursive', - 'public child 1 recursive', - 'private normal user', - ]; + // add an entity target for an entity at a level below the current one + $DB->insert( + 'glpi_entities_savedsearches', + [ + 'savedsearches_id' => $bk_private_id, + 'entities_id' => $root_entity_id, + 'is_recursive' => 1 + ] + ); + $bks_normal[] = 'private root recursive'; $mine = $bk->getMine('Ticket'); - $this->assertCount(count($expected), $mine); + $this->assertCount(count($bks_normal), $mine); $this->assertEqualsCanonicalizing( - $expected, + $bks_normal, array_column($mine, 'name') ); } diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index acb8d8875f0..b3c188ef2fe 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -34,12 +34,18 @@ */ use Glpi\Application\View\TemplateRenderer; - +use Glpi\Event; /** * Common DataBase visibility for items */ abstract class CommonDBVisible extends CommonDBTM { + /** + * Types of target available for the itemtype + * @var string[] + */ + public static $types = ['Entity', 'Group', 'Profile', 'User']; + /** * Entities on which item is visible. * Keys are ID, values are DB fields values. @@ -68,6 +74,58 @@ abstract class CommonDBVisible extends CommonDBTM */ protected $users = []; + /** + * Class defining relation to $users + * @var string + */ + protected $userClass; + + /** + * Class defining relation to $profiles + * @var string + */ + protected $profileClass; + + /** + * Class defining relation to $groups + * @var string + */ + protected $groupClass; + + /** + * Class defining relation to entities + * @var string + */ + protected $entityClass; + + /** + * Service for visibility target log + * @var string + */ + protected $service; + + public function __construct() + { + // define default values + if (!$this->userClass) { + $this->userClass = $this->getType() . '_User'; + } + if (!$this->groupClass) { + $this->groupClass = 'Group_' . $this->getType(); + } + if (!$this->entityClass) { + $this->entityClass = 'Entity_' . $this->getType(); + } + if (!$this->profileClass) { + $this->profileClass = 'Profile_' . $this->getType(); + } + if (!$this->service) { + $this->service = 'tools'; + } + + parent::__construct(); + } + public function __get(string $property) { // TODO Deprecate access to variables in GLPI 11.0. @@ -204,6 +262,16 @@ public function countVisibilities() + count($this->profiles)); } + + /** + * Get right which will be used to determine which users can be targeted + * @return string + */ + public function getVisibilityRight() + { + return strtolower($this::getType()) . '_public'; + } + /** * Show visibility configuration * @@ -220,9 +288,10 @@ public function showVisibility() if ($canedit) { TemplateRenderer::getInstance()->display('components/add_visibility_target.html.twig', [ - 'type' => static::class, - 'rand' => $rand, - 'id' => $ID, + 'type' => static::class, + 'types' => static::$types, + 'rand' => $rand, + 'id' => $ID, 'add_target_msg' => __('Add a target'), 'visiblity_dropdown_params' => $this->getShowVisibilityDropdownParams(), ]); @@ -233,7 +302,7 @@ public function showVisibility() foreach ($this->users as $val) { foreach ($val as $data) { $entries[] = [ - 'itemtype' => static::class . '_User', + 'itemtype' => $this instanceof SavedSearch ? SavedSearch_UserTarget::class : static::class . '_User', 'id' => $data['id'], 'type' => User::getTypeName(1), 'recipient' => htmlescape(getUserName($data['users_id'])), @@ -379,10 +448,10 @@ public function showVisibility() */ protected function getShowVisibilityDropdownParams() { - $params = [ - 'type' => '__VALUE__', - 'right' => strtolower($this::getType()) . '_public', - ]; + $params = ['type' => '__VALUE__']; + if ($right = $this->getVisibilityRight()) { + $params['right'] = $right; + } if (isset($this->fields['entities_id'])) { $params['entity'] = $this->fields['entities_id']; } @@ -391,4 +460,40 @@ protected function getShowVisibilityDropdownParams() } return $params; } + + /** + * Add a visibility target to the item + * @param array $inputs key '_type' determine the type of target + * @return void + */ + public function addVisibility(array $inputs) + { + $fkField = getForeignKeyFieldForItemType($this->getType()); + $item = null; + switch ($inputs['_type']) { + case 'User': + $item = new $this->userClass(); + break; + case 'Group': + $item = new $this->groupClass(); + break; + case 'Entity': + $item = new $this->entityClass(); + break; + case 'Profile': + $item = new $this->profileClass(); + break; + } + if (!is_null($item)) { + $item->add($inputs); + Event::log( + $inputs[$fkField], + $this->getType(), + 4, + $this->service, + //TRANS: %s is the user login + sprintf(__('%s adds a target'), $_SESSION["glpiname"]) + ); + } + } } diff --git a/src/Entity_SavedSearch.php b/src/Entity_SavedSearch.php new file mode 100644 index 00000000000..38337cc4ac4 --- /dev/null +++ b/src/Entity_SavedSearch.php @@ -0,0 +1,73 @@ +. + * + * --------------------------------------------------------------------- + */ + +class Entity_SavedSearch extends CommonDBRelation +{ + // From CommonDBRelation + public static $itemtype_1 = 'SavedSearch'; + public static $items_id_1 = 'savedsearches_id'; + public static $itemtype_2 = 'Entity'; + public static $items_id_2 = 'entities_id'; + + public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; + public static $logs_for_item_2 = false; + + + /** + * Get entities for a saved search + * + * @param SavedSearch $savedSearch SavedSearch instance + * + * @return array of entities linked to a saved search + **/ + public static function getEntities(SavedSearch $savedSearch) + { + /** @var \DBmysql $DB */ + global $DB; + + $results = []; + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + self::$items_id_1 => $savedSearch->getID() + ] + ]); + + foreach ($iterator as $data) { + $results[$data[self::$items_id_2]][] = $data; + } + return $results; + } +} \ No newline at end of file diff --git a/src/Glpi/Controller/ItemType/Form/SavedSearchFormController.php b/src/Glpi/Controller/ItemType/Form/SavedSearchFormController.php index 32e7416ee1c..c7d732f8322 100644 --- a/src/Glpi/Controller/ItemType/Form/SavedSearchFormController.php +++ b/src/Glpi/Controller/ItemType/Form/SavedSearchFormController.php @@ -34,7 +34,7 @@ namespace Glpi\Controller\ItemType\Form; -use Glpi\Controller\GenericFormController; +use Glpi\Controller\VisibilityController; use Glpi\Http\RedirectResponse; use Glpi\Routing\Attribute\ItemtypeFormRoute; use Html; @@ -42,7 +42,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -class SavedSearchFormController extends GenericFormController +class SavedSearchFormController extends VisibilityController { #[ItemtypeFormRoute(SavedSearch::class)] public function __invoke(Request $request): Response diff --git a/src/Glpi/Controller/VisibilityController.php b/src/Glpi/Controller/VisibilityController.php new file mode 100644 index 00000000000..01545213873 --- /dev/null +++ b/src/Glpi/Controller/VisibilityController.php @@ -0,0 +1,72 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Controller; + +use Glpi\Exception\Http\AccessDeniedHttpException; +use Glpi\Exception\Http\BadRequestHttpException; +use Html; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class VisibilityController extends GenericFormController +{ + public function __invoke(Request $request): Response + { + if ($request->request->has('addvisibility')) { + return $this->addVisibility($request); + } + + return parent::__invoke($request); + } + + public function addVisibility(Request $request): RedirectResponse + { + $class = $request->attributes->get('class'); + $item = getItemForItemtype($class); + $fk_field = getForeignKeyFieldForItemType($class); + if ($item instanceof \CommonDBVisible) { + if ($item->canEdit($request->request->get($fk_field))) { + $item->addVisibility($request->request->all()); + return new RedirectResponse(Html::getBackUrl()); + } else { + throw new AccessDeniedHttpException(); + } + } else { + throw new BadRequestHttpException("Invalid class"); + } + } +} \ No newline at end of file diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 81afc5755f9..65d1a2939e5 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -2319,6 +2319,16 @@ public static function getDefaultJoinCriteria(string $itemtype, string $ref_tabl } break; + case 'SavedSearch': + $criterias = \SavedSearch::getVisibilityCriteria(false); + if (isset($criterias['LEFT JOIN'])) { + $out = ['LEFT JOIN' => $criterias['LEFT JOIN']]; + foreach ($criterias['LEFT JOIN'] as $table => $criteria) { + $already_link_tables[] = $table; + } + } + break; + case ITILFollowup::class: foreach ($CFG_GLPI['itil_types'] as $itil_itemtype) { $out = array_merge_recursive($out, self::getLeftJoinCriteria( diff --git a/src/Group_SavedSearch.php b/src/Group_SavedSearch.php new file mode 100644 index 00000000000..52422e60fff --- /dev/null +++ b/src/Group_SavedSearch.php @@ -0,0 +1,73 @@ +. + * + * --------------------------------------------------------------------- + */ + +class Group_SavedSearch extends CommonDBRelation +{ + // From CommonDBRelation + public static $itemtype_1 = 'SavedSearch'; + public static $items_id_1 = 'savedsearches_id'; + public static $itemtype_2 = 'Group'; + public static $items_id_2 = 'groups_id'; + + public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; + public static $logs_for_item_2 = false; + + + /** + * Get groups for a saved search + * + * @param SavedSearch $savedSearch SavedSearch instance + * + * @return array of groups linked to a saved search + **/ + public static function getGroups(SavedSearch $savedSearch) + { + /** @var \DBmysql $DB */ + global $DB; + + $results = []; + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + self::$items_id_1 => $savedSearch->getID() + ] + ]); + + foreach ($iterator as $data) { + $results[$data[self::$items_id_2]][] = $data; + } + return $results; + } +} diff --git a/src/SavedSearch.php b/src/SavedSearch.php index 854f6192d65..8259da10709 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -45,12 +45,12 @@ * * @since 9.2 **/ -class SavedSearch extends CommonDBTM implements ExtraVisibilityCriteria +class SavedSearch extends CommonDBVisible implements ExtraVisibilityCriteria { use Clonable; public static $rightname = 'bookmark_public'; - + public static $types = ['Group', 'User', 'Entity']; public const SEARCH = 1; //SEARCH SYSTEM bookmark public const URI = 2; public const ALERT = 3; //SEARCH SYSTEM search alert @@ -59,6 +59,11 @@ class SavedSearch extends CommonDBTM implements ExtraVisibilityCriteria public const COUNT_YES = 1; public const COUNT_AUTO = 2; + protected $userClass = SavedSearch_UserTarget::class; + protected $groupClass = Group_SavedSearch::class; + protected $entityClass = Entity_SavedSearch::class; + protected $service = 'tools'; + public static function getForbiddenActionsForMenu() { return ['add']; @@ -254,33 +259,120 @@ public static function processMassiveActionsForOneItemtype( parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } - public function canCreateItem(): bool + public function haveVisibilityAccess() { + if (!self::canView()) { + return false; + } + + return parent::haveVisibilityAccess(); + } - if ($this->fields['is_private'] == 1) { - return (Session::haveRight('config', UPDATE) - || $this->fields['users_id'] == Session::getLoginUserID()); + public function canCreateItem(): bool + { + if (isset($this->input['is_private']) && $this->input['is_private'] == 0) { + return self::canCreatePublic(); } return parent::canCreateItem(); } + /** + * @return bool + */ + public static function canCreatePublic(): bool + { + return (Session::haveRight('config', UPDATE) || + Session::haveRight(self::$rightname, CREATE)); + } + public function canViewItem(): bool { - if ($this->fields['is_private'] == 1) { - return (Session::haveRight('config', READ) - || $this->fields['users_id'] == Session::getLoginUserID()); + if ( + Session::haveRight('config', READ) + || $this->fields['users_id'] == Session::getLoginUserID() + ) { + return true; + } + + if (array_key_exists($this->getID(), $this->getMine())) { + return true; + } + + return false; + } + + public function post_getFromDB() + { + // Group + $this->groups = Group_SavedSearch::getGroups($this); + + // Users + $this->users = SavedSearch_UserTarget::getUsers($this); + + // Entities + $this->entities = Entity_SavedSearch::getEntities($this); + } + + public function post_addItem() + { + // for search saved as public, automatically create a link with its entity + if (isset($this->input['is_private']) && !$this->input['is_private']) { + $item = new Entity_SavedSearch(); + $item->add([ + 'savedsearches_id' => $this->getID(), + 'entities_id' => $this->fields['entities_id'], + 'is_recursive' => $this->fields['is_recursive'], + ]); + } + parent::post_addItem(); + } + + public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + if (self::canView()) { + $nb = 0; + switch ($item::class) { + case SavedSearch::class: + if (self::canCreatePublic()) { + if ($_SESSION['glpishow_count_on_tabs']) { + $nb = $item->countVisibilities(); + } + return [ + 1 => self::createTabEntry( + _n( + 'Target', + 'Targets', + Session::getPluralNumber() + ), + $nb + ), + ]; + } + break; + } } - return parent::canViewItem(); + return ''; } public function defineTabs($options = []) { $ong = []; - $this->addDefaultFormTab($ong) - ->addStandardTab(SavedSearch_Alert::class, $ong, $options); + $this->addDefaultFormTab($ong); + $this->addStandardTab('SavedSearch', $ong, $options); + $this->addStandardTab('SavedSearch_Alert', $ong, $options); return $ong; } + public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) + { + switch ($item::class) { + case SavedSearch::class: + $item->showVisibility(); + return true; + } + return false; + } + public function rawSearchOptions() { $tab = parent::rawSearchOptions(); @@ -300,15 +392,6 @@ public function rawSearchOptions() 'datatype' => 'dropdown', ]; - $tab[] = [ - 'id' => 4, - 'table' => $this->getTable(), - 'field' => 'is_private', - 'name' => __('Is private'), - 'datatype' => 'bool', - 'massiveaction' => false, - ]; - $tab[] = ['id' => '8', 'table' => $this->getTable(), 'field' => 'itemtype', @@ -451,6 +534,8 @@ public function cleanDBonPurge() [ SavedSearch_Alert::class, SavedSearch_User::class, + SavedSearch_UserTarget::class, + Group_SavedSearch::class, ] ); } @@ -472,6 +557,7 @@ public function showForm($ID, array $options = []) TemplateRenderer::getInstance()->display('pages/tools/savedsearch/form.html.twig', [ 'item' => $this, 'can_create' => self::canCreate(), + 'can_create_public' => self::canCreatePublic(), 'params' => $options, ]); return true; @@ -665,6 +751,41 @@ public function unmarkDefaults(array $ids) return false; } + /** + * Get relations for targets + * @return array[][] key 'LEFT JOIN' for request + */ + public static function getDefaultJoin() + { + $table = self::getTable(); + $user_table = SavedSearch_UserTarget::getTable(); + $group_table = Group_SavedSearch::getTable(); + $entity_table = Entity_SavedSearch::getTable(); + return [ + 'LEFT JOIN' => [ + // relations for targets + $user_table => [ + 'ON' => [ + $user_table => 'savedsearches_id', + $table => 'id', + ], + ], + $group_table => [ + 'ON' => [ + $group_table => 'savedsearches_id', + $table => 'id', + ], + ], + $entity_table => [ + 'ON' => [ + $entity_table => 'savedsearches_id', + $table => 'id', + ], + ], + ], + ]; + } + /** * return an array of saved searches for a given itemtype * @@ -682,6 +803,10 @@ public function getMine(?string $itemtype = null, bool $inverse = false): array $table = $this->getTable(); $utable = 'glpi_savedsearches_users'; + $user_table = SavedSearch_UserTarget::getTable(); + $group_table = Group_SavedSearch::getTable(); + $entity_table = Entity_SavedSearch::getTable(); + $criteria = [ 'SELECT' => [ "$table.*", @@ -690,19 +815,59 @@ public function getMine(?string $itemtype = null, bool $inverse = false): array ), ], 'FROM' => $table, - 'LEFT JOIN' => [ - $utable => [ - 'ON' => [ - $utable => 'savedsearches_id', - $table => 'id', - ], - ], - ], 'ORDERBY' => [ 'itemtype', 'name', ], - ] + self::getVisibilityCriteriaForMine(); + ] + self::getDefaultJoin(); + + $criteria['LEFT JOIN'][$utable] = [ + 'ON' => [ + $utable => 'savedsearches_id', + $table => 'id', + ], + ]; + $owner_restrict = [ + $table . '.users_id' => Session::getLoginUserID(), + ]; + $entity_restrict = getEntitiesRestrictCriteria($table, '', '', true); + if (count($entity_restrict)) { + $owner_restrict += $entity_restrict; + } + $criteria['WHERE'] = [ + 'OR' => [ + $owner_restrict, + [ + 'OR' => [ + // directly targeted + $user_table . '.users_id' => Session::getLoginUserID(), + // targeted through groups + [ + $group_table . '.groups_id' => count($_SESSION["glpigroups"]) + ? $_SESSION["glpigroups"] + : [-1], + 'OR' => [ + [$group_table . '.no_entity_restriction' => 1], + getEntitiesRestrictCriteria( + $group_table, + '', + $_SESSION['glpiactiveentities'], + true + ), + ], + ], + // targeted through entities + getEntitiesRestrictCriteria( + $entity_table, + '', + '', + true, + true + ), + ], + ], + ], + ]; if ($itemtype != null) { if (!$inverse) { @@ -795,6 +960,7 @@ public function displayMine(?string $itemtype = null, bool $inverse = false) { TemplateRenderer::getInstance()->display('layout/parts/saved_searches_list.html.twig', [ 'active' => $_SESSION['glpi_loaded_savedsearch'] ?? "", + 'current_user' => Session::getLoginUserID(), 'saved_searches' => $this->getMine($itemtype, $inverse), ]); } @@ -1196,26 +1362,6 @@ public static function addVisibilityRestrict() return $sql; } - private static function getVisibilityCriteriaForMine(): array - { - $criteria = ['WHERE' => []]; - $restrict = [ - self::getTable() . '.is_private' => 1, - self::getTable() . '.users_id' => Session::getLoginUserID(), - ]; - - if (Session::haveRight(self::$rightname, READ)) { - $restrict = [ - 'OR' => [ - $restrict, - [self::getTable() . '.is_private' => 0], - ], - ]; - } - - $criteria['WHERE'] = $restrict + getEntitiesRestrictCriteria(self::getTable(), '', '', true); - return $criteria; - } /** * Return visibility joins to add to DBIterator parameters @@ -1232,7 +1378,64 @@ public static function getVisibilityCriteria(bool $forceall = false): array return ['WHERE' => []]; } - return self::getVisibilityCriteriaForMine(); + if (!Session::haveRight(self::$rightname, READ)) { + return [ + 'WHERE' => ['glpi_savedsearches.users_id' => Session::getLoginUserID()], + ]; + } + + $table = self::getTable(); + $user_table = SavedSearch_UserTarget::getTable(); + $group_table = Group_SavedSearch::getTable(); + $entity_table = Entity_SavedSearch::getTable(); + + $criteria = self::getDefaultJoin(); + + $owner_restrict = [ + $table . '.users_id' => Session::getLoginUserID(), + ]; + $entity_restrict = getEntitiesRestrictCriteria($table, '', '', true); + if (count($entity_restrict)) { + $owner_restrict += $entity_restrict; + } + + $restrict = [ + 'OR' => [ + $owner_restrict, + [ + 'OR' => [ + // directly targeted + $user_table . '.users_id' => Session::getLoginUserID(), + // targeted through groups + [ + $group_table . '.groups_id' => count($_SESSION["glpigroups"]) + ? $_SESSION["glpigroups"] + : [-1], + 'OR' => [ + [$group_table . '.no_entity_restriction' => 1], + getEntitiesRestrictCriteria( + $group_table, + '', + $_SESSION['glpiactiveentities'], + true + ), + ], + ], + // targeted through entities + getEntitiesRestrictCriteria( + $entity_table, + '', + '', + true, + true + ), + ], + ], + ], + ]; + $criteria['WHERE'] = $restrict; + + return $criteria; } public static function getIcon() @@ -1244,4 +1447,13 @@ public function getCloneRelations(): array { return []; } + + /** + * No specific right needed to be a target + * @return string + */ + public function getVisibilityRight() + { + return ''; + } } diff --git a/src/SavedSearch_UserTarget.php b/src/SavedSearch_UserTarget.php new file mode 100644 index 00000000000..a4aca3b2db6 --- /dev/null +++ b/src/SavedSearch_UserTarget.php @@ -0,0 +1,74 @@ +. + * + * --------------------------------------------------------------------- + */ + +class SavedSearch_UserTarget extends CommonDBRelation +{ + public $auto_message_on_action = false; + + public static $itemtype_1 = 'SavedSearch'; + public static $items_id_1 = 'savedsearches_id'; + + public static $itemtype_2 = 'User'; + public static $items_id_2 = 'users_id'; + + public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; + public static $logs_for_item_2 = false; + + /** + * Get users for a saved search + * + * @param SavedSearch $savedSearch SavedSearch instance + * + * @return array of users linked to a saved search + **/ + public static function getUsers(SavedSearch $savedSearch) + { + /** @var \DBmysql $DB */ + global $DB; + + $results = []; + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + self::$items_id_1 => $savedSearch->getID() + ] + ]); + + foreach ($iterator as $data) { + $results[$data[self::$items_id_2]][] = $data; + } + return $results; + } +} diff --git a/src/User.php b/src/User.php index 7e221ae570e..c0e0d3363b7 100644 --- a/src/User.php +++ b/src/User.php @@ -482,14 +482,64 @@ public function cleanDBonPurge() $reminder_translation = new ReminderTranslation(); $reminder_translation->deleteByCriteria(['users_id' => $this->fields['id']]); - // Delete private bookmark $ss = new SavedSearch(); - $ss->deleteByCriteria( - [ - 'users_id' => $this->fields['id'], - 'is_private' => 1, - ] - ); + $search_table = SavedSearch::getTable(); + $user_table = SavedSearch_UserTarget::getTable(); + $group_table = Group_SavedSearch::getTable(); + $entity_table = Entity_SavedSearch::getTable(); + // Retrieve all bookmarks created by the user which have at least 1 target + $publics = $ss->find([ + 'id' => new QuerySubQuery([ + 'SELECT' => $search_table . '.id', + 'FROM' => $ss->getTable(), + 'LEFT JOIN' => [ + $user_table => [ + 'ON' => [ + $user_table => 'savedsearches_id', + $search_table => 'id' + ] + ], + $group_table => [ + 'ON' => [ + $group_table => 'savedsearches_id', + $search_table => 'id' + ] + ], + $entity_table => [ + 'ON' => [ + $entity_table => 'savedsearches_id', + $search_table => 'id' + ] + ], + ], + 'WHERE' => [ + $search_table . '.users_id' => $this->fields['id'], + 'OR' => [ + ['NOT' => [$user_table . '.savedsearches_id' => null]], + ['NOT' => [$group_table . '.savedsearches_id' => null]], + ['NOT' => [$entity_table . '.savedsearches_id' => null]], + ] + ] + ]) + ]); + if (count($publics)) { + $publics = array_map(fn($e) => $e['id'], $publics); + // Delete private bookmark + $ss->deleteByCriteria( + [ + 'users_id' => $this->fields['id'], + 'NOT' => [ + 'id' => $publics + ] + ] + ); + } else { + $ss->deleteByCriteria( + [ + 'users_id' => $this->fields['id'] + ] + ); + } // Set no user to public bookmark $DB->update( @@ -529,6 +579,7 @@ public function cleanDBonPurge() Reminder_User::class, RSSFeed_User::class, SavedSearch_User::class, + SavedSearch_UserTarget::class, Ticket_User::class, UserEmail::class, ] diff --git a/templates/components/add_visibility_target.html.twig b/templates/components/add_visibility_target.html.twig index 6f331ca5c5c..77053c342f0 100644 --- a/templates/components/add_visibility_target.html.twig +++ b/templates/components/add_visibility_target.html.twig @@ -37,7 +37,7 @@
{{ fields.dropdownItemTypes('_type', '', add_target_msg, { - types: ['Entity', 'Group', 'Profile', 'User'], + types: types, rand: rand, inline_add_field_html: true, add_field_html: "", diff --git a/templates/layout/parts/saved_searches_list.html.twig b/templates/layout/parts/saved_searches_list.html.twig index a9b2e464d8c..d6b67e317ad 100644 --- a/templates/layout/parts/saved_searches_list.html.twig +++ b/templates/layout/parts/saved_searches_list.html.twig @@ -59,8 +59,8 @@ {% endif %}
- {% if search['is_private'] == 1 %} - {% endif %} diff --git a/templates/pages/tools/savedsearch/form.html.twig b/templates/pages/tools/savedsearch/form.html.twig index 2860c07ef5a..9be5a5c8bff 100644 --- a/templates/pages/tools/savedsearch/form.html.twig +++ b/templates/pages/tools/savedsearch/form.html.twig @@ -59,15 +59,18 @@ (constant('SavedSearch::COUNT_YES')): __('Yes'), (constant('SavedSearch::COUNT_NO')): __('No') }, __('Do count')) }} - {% if can_create %} - {{ fields.dropdownArrayField('is_private', item.fields['is_private'], { - 1: __('Private'), - 0: __('Public') - }, __('Visibility')) }} + {% if can_create and item.isNewItem() %} + {% if can_create_public %} + {{ fields.dropdownArrayField('is_private', item.fields['is_private'], { + 1: __('Private'), + 0: __('Public') + }, __('Visibility')) }} + {% endif %} {{ fields.dropdownField('Entity', 'entities_id', item.fields['entities_id'], 'Entity'|itemtype_name(1)) }} {{ fields.dropdownYesNo('is_recursive', item.fields['is_recursive'], __('Child entities')) }} - {% else %} - {{ fields.htmlField('', item.fields['is_private']|default(1) ? __('Private') : __('Public'), __('Visibility')) }} + {% if not can_create_public %} + {{ fields.hiddenField('is_private', 1) }} + {% endif %} {% endif %} {% endblock %} {% endblock %} From b7ce0cfe3217f2418baae7644c33ccd1b3533ac1 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Tue, 8 Jul 2025 19:00:29 +0200 Subject: [PATCH 04/33] CS Fixer --- phpunit/functional/SavedSearchTest.php | 16 ++++++------- src/CommonDBVisible.php | 1 + src/Entity_SavedSearch.php | 6 ++--- src/Glpi/Controller/VisibilityController.php | 2 +- src/Group_SavedSearch.php | 4 ++-- src/SavedSearch_UserTarget.php | 4 ++-- src/User.php | 24 ++++++++++---------- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 3e6d6e44839..658e6ffdc5b 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -76,7 +76,7 @@ public function testAddVisibilityRestrict() 'users_id' => \Session::getLoginUserID(), 'profiles_id' => 3, 'entities_id' => 0, - 'is_recursive' => 1 + 'is_recursive' => 1, ]); // logout -> login to be able to switch to new profile $this->logOut(); @@ -86,7 +86,7 @@ public function testAddVisibilityRestrict() $visibility_restrict2 = "((`glpi_savedsearches`.`users_id` = '5') OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; $this->assertSame( $visibility_restrict2, - \SavedSearch::addVisibilityRestrict() + \SavedSearch::addVisibilityRestrict() ); $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; @@ -100,7 +100,7 @@ public function testAddVisibilityRestrict() 'users_id' => \Session::getLoginUserID(), 'profiles_id' => 3, 'entities_id' => 0, - 'is_recursive' => 1 + 'is_recursive' => 1, ]); } @@ -212,13 +212,13 @@ public function testGetMine() // test each type of targets so that normal will be able to see them $bks_normal = [ 'private normal user', - 'created public target entity root recursive' + 'created public target entity root recursive', ]; // add normal to a group $group_user = new \Group_User(); $group_user->add([ 'users_id' => 5, - 'groups_id' => $test_group_1_id + 'groups_id' => $test_group_1_id, ]); $this->login('normal', 'normal'); @@ -235,7 +235,7 @@ public function testGetMine() ['rights' => 1], [ 'users_id' => 5, - 'savedsearches_id' => $bk_target_user_id + 'savedsearches_id' => $bk_target_user_id, ] ); $bks_normal[] = 'target user root recursive'; @@ -254,7 +254,7 @@ public function testGetMine() 'savedsearches_id' => $bk_target_group_id, 'groups_id' => $test_group_1_id, 'entities_id' => 0, - 'is_recursive' => 1 + 'is_recursive' => 1, ] ); @@ -272,7 +272,7 @@ public function testGetMine() [ 'savedsearches_id' => $bk_private_id, 'entities_id' => $root_entity_id, - 'is_recursive' => 1 + 'is_recursive' => 1, ] ); $bks_normal[] = 'private root recursive'; diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index b3c188ef2fe..5c90bd46a1c 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -35,6 +35,7 @@ use Glpi\Application\View\TemplateRenderer; use Glpi\Event; + /** * Common DataBase visibility for items */ diff --git a/src/Entity_SavedSearch.php b/src/Entity_SavedSearch.php index 38337cc4ac4..57b0e2285f7 100644 --- a/src/Entity_SavedSearch.php +++ b/src/Entity_SavedSearch.php @@ -61,8 +61,8 @@ public static function getEntities(SavedSearch $savedSearch) $iterator = $DB->request([ 'FROM' => self::getTable(), 'WHERE' => [ - self::$items_id_1 => $savedSearch->getID() - ] + self::$items_id_1 => $savedSearch->getID(), + ], ]); foreach ($iterator as $data) { @@ -70,4 +70,4 @@ public static function getEntities(SavedSearch $savedSearch) } return $results; } -} \ No newline at end of file +} diff --git a/src/Glpi/Controller/VisibilityController.php b/src/Glpi/Controller/VisibilityController.php index 01545213873..09934215493 100644 --- a/src/Glpi/Controller/VisibilityController.php +++ b/src/Glpi/Controller/VisibilityController.php @@ -69,4 +69,4 @@ public function addVisibility(Request $request): RedirectResponse throw new BadRequestHttpException("Invalid class"); } } -} \ No newline at end of file +} diff --git a/src/Group_SavedSearch.php b/src/Group_SavedSearch.php index 52422e60fff..9d5da8f8023 100644 --- a/src/Group_SavedSearch.php +++ b/src/Group_SavedSearch.php @@ -61,8 +61,8 @@ public static function getGroups(SavedSearch $savedSearch) $iterator = $DB->request([ 'FROM' => self::getTable(), 'WHERE' => [ - self::$items_id_1 => $savedSearch->getID() - ] + self::$items_id_1 => $savedSearch->getID(), + ], ]); foreach ($iterator as $data) { diff --git a/src/SavedSearch_UserTarget.php b/src/SavedSearch_UserTarget.php index a4aca3b2db6..a08abf4f5a2 100644 --- a/src/SavedSearch_UserTarget.php +++ b/src/SavedSearch_UserTarget.php @@ -62,8 +62,8 @@ public static function getUsers(SavedSearch $savedSearch) $iterator = $DB->request([ 'FROM' => self::getTable(), 'WHERE' => [ - self::$items_id_1 => $savedSearch->getID() - ] + self::$items_id_1 => $savedSearch->getID(), + ], ]); foreach ($iterator as $data) { diff --git a/src/User.php b/src/User.php index c0e0d3363b7..bd6811dfcc4 100644 --- a/src/User.php +++ b/src/User.php @@ -496,20 +496,20 @@ public function cleanDBonPurge() $user_table => [ 'ON' => [ $user_table => 'savedsearches_id', - $search_table => 'id' - ] + $search_table => 'id', + ], ], $group_table => [ 'ON' => [ $group_table => 'savedsearches_id', - $search_table => 'id' - ] + $search_table => 'id', + ], ], $entity_table => [ 'ON' => [ $entity_table => 'savedsearches_id', - $search_table => 'id' - ] + $search_table => 'id', + ], ], ], 'WHERE' => [ @@ -518,9 +518,9 @@ public function cleanDBonPurge() ['NOT' => [$user_table . '.savedsearches_id' => null]], ['NOT' => [$group_table . '.savedsearches_id' => null]], ['NOT' => [$entity_table . '.savedsearches_id' => null]], - ] - ] - ]) + ], + ], + ]), ]); if (count($publics)) { $publics = array_map(fn($e) => $e['id'], $publics); @@ -529,14 +529,14 @@ public function cleanDBonPurge() [ 'users_id' => $this->fields['id'], 'NOT' => [ - 'id' => $publics - ] + 'id' => $publics, + ], ] ); } else { $ss->deleteByCriteria( [ - 'users_id' => $this->fields['id'] + 'users_id' => $this->fields['id'], ] ); } From f00a4ef5f19cb447e71015b787a2e4dbb418bc85 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Tue, 8 Jul 2025 19:33:04 +0200 Subject: [PATCH 05/33] Fix ForbidDynamicInstantiationRule --- src/CommonDBVisible.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index 5c90bd46a1c..7f2dc4b2df2 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -473,16 +473,24 @@ public function addVisibility(array $inputs) $item = null; switch ($inputs['_type']) { case 'User': - $item = new $this->userClass(); + if (is_a($this->getType() . '_User', CommonDBTM::class, true)) { + $item = new $this->userClass(); + } break; case 'Group': - $item = new $this->groupClass(); + if (is_a('Group_' . $this->getType(), CommonDBTM::class, true)) { + $item = new $this->groupClass(); + } break; case 'Entity': - $item = new $this->entityClass(); + if (is_a('Entity_' . $this->getType(), CommonDBTM::class, true)) { + $item = new $this->entityClass(); + } break; case 'Profile': - $item = new $this->profileClass(); + if (is_a('Profile_' . $this->getType(), CommonDBTM::class, true)) { + $item = new $this->profileClass(); + } break; } if (!is_null($item)) { From 240c878aa9539ae323e74f8d33ba83737a9af753 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 08:36:39 +0200 Subject: [PATCH 06/33] Try fix tests --- phpunit/functional/SavedSearchTest.php | 84 ++++++++++---------------- 1 file changed, 31 insertions(+), 53 deletions(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 658e6ffdc5b..1d63d8bd73d 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -35,8 +35,8 @@ namespace tests\units; use DbTestCase; -use MassiveAction; -use SavedSearch; + +use function PHPUnit\Framework\assertContains; /* Test for inc/savedsearch.class.php */ @@ -59,39 +59,37 @@ public function testGetVisibilityCriteria() public function testAddVisibilityRestrict() { global $DB; - //first, as a super-admin + // super-admin $this->login(); - $visibility_restrict = "`glpi_savedsearches`.`users_id` = '5'"; $this->assertSame('', \SavedSearch::addVisibilityRestrict()); + // no rights on bookmark $this->login('normal', 'normal'); + $visibility_restrict = "`glpi_savedsearches`.`users_id` = '5'"; $this->assertSame( $visibility_restrict, \SavedSearch::addVisibilityRestrict() ); - //ACLs have changed: login again. // temporarily add admin profile and switch to it to test can see public $DB->insert('glpi_profiles_users', [ 'users_id' => \Session::getLoginUserID(), 'profiles_id' => 3, 'entities_id' => 0, - 'is_recursive' => 1, + 'is_recursive' => 1 ]); // logout -> login to be able to switch to new profile $this->logOut(); $this->login('normal', 'normal'); - \Session::changeProfile(3); $visibility_restrict2 = "((`glpi_savedsearches`.`users_id` = '5') OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; $this->assertSame( $visibility_restrict2, \SavedSearch::addVisibilityRestrict() ); - - $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; - // Check entity restriction + // can see public after moving entity $this->setEntity('_test_root_entity', true); + $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; $this->assertSame( $visibility_restrict3, \SavedSearch::addVisibilityRestrict() @@ -100,7 +98,7 @@ public function testAddVisibilityRestrict() 'users_id' => \Session::getLoginUserID(), 'profiles_id' => 3, 'entities_id' => 0, - 'is_recursive' => 1, + 'is_recursive' => 1 ]); } @@ -109,6 +107,7 @@ public function testGetMine() global $DB; $root_entity_id = getItemByTypeName(\Entity::class, '_test_root_entity', true); + $test_group_1_id = getItemByTypeName(\Group::class, '_test_group_1', true); // needs a user @@ -120,47 +119,44 @@ public function testGetMine() // now add a bookmark on Ticket view $bk = new \SavedSearch(); $this->assertTrue( - (bool) $bk->add([ + (bool)$bk->add([ 'name' => 'private root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, 'entities_id' => 0, 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id ]) ); $bk_private_id = $bk->getID(); $this->assertTrue( - (bool) $bk->add([ + (bool)$bk->add([ 'name' => 'target user root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, - 'is_private' => 0, 'entities_id' => 0, 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id ]) ); $bk_target_user_id = $bk->getID(); $this->assertTrue( - (bool) $bk->add([ + (bool)$bk->add([ 'name' => 'target group root recursive', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $tuuser_id, - 'is_private' => 0, 'entities_id' => 0, 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id ]) ); - $bk_target_group_id = $bk->getID(); // has is_private => 0 in inputs, so a target will be automatically created for the bookmark's entity $this->assertTrue( - (bool) $bk->add([ + (bool)$bk->add([ 'name' => 'created public target entity root recursive', 'type' => 1, 'itemtype' => 'Ticket', @@ -168,24 +164,23 @@ public function testGetMine() 'is_private' => 0, 'entities_id' => 0, 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id ]) ); - $bk_target_entity_id = $bk->getID(); $bk2 = new \SavedSearch(); $bk2->getFromDB($bk_target_entity_id); $this->assertEquals(1, $bk2->countVisibilities()); $this->assertTrue( - (bool) $bk->add([ + (bool)$bk->add([ 'name' => 'private normal user', 'type' => 1, 'itemtype' => 'Ticket', 'users_id' => $normal_id, 'entities_id' => 0, 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id ]) ); $bk_private_normal_id = $bk->getID(); @@ -212,16 +207,15 @@ public function testGetMine() // test each type of targets so that normal will be able to see them $bks_normal = [ 'private normal user', - 'created public target entity root recursive', + 'created public target entity root recursive' ]; // add normal to a group $group_user = new \Group_User(); $group_user->add([ 'users_id' => 5, - 'groups_id' => $test_group_1_id, + 'groups_id' => $test_group_1_id ]); $this->login('normal', 'normal'); - $mine = $bk->getMine(); $this->assertCount(count($bks_normal), $mine); $this->assertEqualsCanonicalizing( @@ -229,13 +223,12 @@ public function testGetMine() array_column($mine, 'name') ); - //add public saved searches read right for normal profile - $DB->update( - 'glpi_profilerights', - ['rights' => 1], + // add normal as target for another savedsearch + $DB->insert( + 'glpi_savedsearches_usertargets', [ 'users_id' => 5, - 'savedsearches_id' => $bk_target_user_id, + 'savedsearches_id' => $bk_target_user_id ] ); $bks_normal[] = 'target user root recursive'; @@ -246,7 +239,6 @@ public function testGetMine() array_column($mine, 'name') ); - // Check entity restrictions // add the group as target for a bookmark $DB->insert( 'glpi_groups_savedsearches', @@ -254,10 +246,9 @@ public function testGetMine() 'savedsearches_id' => $bk_target_group_id, 'groups_id' => $test_group_1_id, 'entities_id' => 0, - 'is_recursive' => 1, + 'is_recursive' => 1 ] ); - $bks_normal[] = 'target group root recursive'; $mine = $bk->getMine('Ticket'); $this->assertCount(count($bks_normal), $mine); @@ -272,7 +263,7 @@ public function testGetMine() [ 'savedsearches_id' => $bk_private_id, 'entities_id' => $root_entity_id, - 'is_recursive' => 1, + 'is_recursive' => 1 ] ); $bks_normal[] = 'private root recursive'; @@ -282,22 +273,9 @@ public function testGetMine() $bks_normal, array_column($mine, 'name') ); - } - public function testAvailableMassiveActions(): void - { - // Act: get saved searches massive actions - $this->login(); - $actions = MassiveAction::getAllMassiveActions(SavedSearch::class); - - // Assert: validate the available actions - $this->assertEquals([ - 'Delete permanently', - 'Add to transfer list', - 'Unset as default', - 'Change count method', - 'Change visibility', - 'Change entity', - ], array_values($actions)); + $DB->delete($group_user->getTable(), [ + 'id' => $group_user->getID() + ]); } } From a922c3f228c0c1438b8deec0b798f532c9fe4984 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 08:44:39 +0200 Subject: [PATCH 07/33] Fix CS --- phpunit/functional/SavedSearchTest.php | 161 ++++++++++++++----------- 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 1d63d8bd73d..34c7fcc2081 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -36,8 +36,6 @@ use DbTestCase; -use function PHPUnit\Framework\assertContains; - /* Test for inc/savedsearch.class.php */ class SavedSearchTest extends DbTestCase @@ -72,12 +70,15 @@ public function testAddVisibilityRestrict() ); // temporarily add admin profile and switch to it to test can see public - $DB->insert('glpi_profiles_users', [ - 'users_id' => \Session::getLoginUserID(), - 'profiles_id' => 3, - 'entities_id' => 0, - 'is_recursive' => 1 - ]); + $DB->insert( + 'glpi_profiles_users', + [ + 'users_id' => \Session::getLoginUserID(), + 'profiles_id' => 3, + 'entities_id' => 0, + 'is_recursive' => 1, + ] + ); // logout -> login to be able to switch to new profile $this->logOut(); $this->login('normal', 'normal'); @@ -94,12 +95,15 @@ public function testAddVisibilityRestrict() $visibility_restrict3, \SavedSearch::addVisibilityRestrict() ); - $DB->delete('glpi_profiles_users', [ - 'users_id' => \Session::getLoginUserID(), - 'profiles_id' => 3, - 'entities_id' => 0, - 'is_recursive' => 1 - ]); + $DB->delete( + 'glpi_profiles_users', + [ + 'users_id' => \Session::getLoginUserID(), + 'profiles_id' => 3, + 'entities_id' => 0, + 'is_recursive' => 1, + ] + ); } public function testGetMine() @@ -119,53 +123,61 @@ public function testGetMine() // now add a bookmark on Ticket view $bk = new \SavedSearch(); $this->assertTrue( - (bool)$bk->add([ - 'name' => 'private root recursive', - 'type' => 1, - 'itemtype' => 'Ticket', - 'users_id' => $tuuser_id, - 'entities_id' => 0, - 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id - ]) + (bool) $bk->add( + [ + 'name' => 'private root recursive', + 'type' => 1, + 'itemtype' => 'Ticket', + 'users_id' => $tuuser_id, + 'entities_id' => 0, + 'is_recursive' => 1, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + ] + ) ); $bk_private_id = $bk->getID(); $this->assertTrue( - (bool)$bk->add([ - 'name' => 'target user root recursive', - 'type' => 1, - 'itemtype' => 'Ticket', - 'users_id' => $tuuser_id, - 'entities_id' => 0, - 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id - ]) + (bool) $bk->add( + [ + 'name' => 'target user root recursive', + 'type' => 1, + 'itemtype' => 'Ticket', + 'users_id' => $tuuser_id, + 'entities_id' => 0, + 'is_recursive' => 1, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + ] + ) ); $bk_target_user_id = $bk->getID(); $this->assertTrue( - (bool)$bk->add([ - 'name' => 'target group root recursive', - 'type' => 1, - 'itemtype' => 'Ticket', - 'users_id' => $tuuser_id, - 'entities_id' => 0, - 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id - ]) + (bool) $bk->add( + [ + 'name' => 'target group root recursive', + 'type' => 1, + 'itemtype' => 'Ticket', + 'users_id' => $tuuser_id, + 'entities_id' => 0, + 'is_recursive' => 1, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + ] + ) ); $bk_target_group_id = $bk->getID(); // has is_private => 0 in inputs, so a target will be automatically created for the bookmark's entity $this->assertTrue( - (bool)$bk->add([ - 'name' => 'created public target entity root recursive', - 'type' => 1, - 'itemtype' => 'Ticket', - 'users_id' => $tuuser_id, - 'is_private' => 0, - 'entities_id' => 0, - 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id - ]) + (bool) $bk->add( + [ + 'name' => 'created public target entity root recursive', + 'type' => 1, + 'itemtype' => 'Ticket', + 'users_id' => $tuuser_id, + 'is_private' => 0, + 'entities_id' => 0, + 'is_recursive' => 1, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + ] + ) ); $bk_target_entity_id = $bk->getID(); $bk2 = new \SavedSearch(); @@ -173,15 +185,17 @@ public function testGetMine() $this->assertEquals(1, $bk2->countVisibilities()); $this->assertTrue( - (bool)$bk->add([ - 'name' => 'private normal user', - 'type' => 1, - 'itemtype' => 'Ticket', - 'users_id' => $normal_id, - 'entities_id' => 0, - 'is_recursive' => 1, - 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id - ]) + (bool) $bk->add( + [ + 'name' => 'private normal user', + 'type' => 1, + 'itemtype' => 'Ticket', + 'users_id' => $normal_id, + 'entities_id' => 0, + 'is_recursive' => 1, + 'url' => 'front/ticket.php?itemtype=Ticket&sort=2&order=DESC&start=0&criteria[0][field]=5&criteria[0][searchtype]=equals&criteria[0][value]=' . $tuuser_id, + ] + ) ); $bk_private_normal_id = $bk->getID(); // With UPDATE 'config' right, we still shouldn't see other user's searches without targets @@ -207,14 +221,16 @@ public function testGetMine() // test each type of targets so that normal will be able to see them $bks_normal = [ 'private normal user', - 'created public target entity root recursive' + 'created public target entity root recursive', ]; // add normal to a group $group_user = new \Group_User(); - $group_user->add([ - 'users_id' => 5, - 'groups_id' => $test_group_1_id - ]); + $group_user->add( + [ + 'users_id' => 5, + 'groups_id' => $test_group_1_id, + ] + ); $this->login('normal', 'normal'); $mine = $bk->getMine(); $this->assertCount(count($bks_normal), $mine); @@ -228,7 +244,7 @@ public function testGetMine() 'glpi_savedsearches_usertargets', [ 'users_id' => 5, - 'savedsearches_id' => $bk_target_user_id + 'savedsearches_id' => $bk_target_user_id, ] ); $bks_normal[] = 'target user root recursive'; @@ -246,7 +262,7 @@ public function testGetMine() 'savedsearches_id' => $bk_target_group_id, 'groups_id' => $test_group_1_id, 'entities_id' => 0, - 'is_recursive' => 1 + 'is_recursive' => 1, ] ); $bks_normal[] = 'target group root recursive'; @@ -263,7 +279,7 @@ public function testGetMine() [ 'savedsearches_id' => $bk_private_id, 'entities_id' => $root_entity_id, - 'is_recursive' => 1 + 'is_recursive' => 1, ] ); $bks_normal[] = 'private root recursive'; @@ -274,8 +290,11 @@ public function testGetMine() array_column($mine, 'name') ); - $DB->delete($group_user->getTable(), [ - 'id' => $group_user->getID() - ]); + $DB->delete( + $group_user->getTable(), + [ + 'id' => $group_user->getID(), + ] + ); } } From 68c1a00e4389b12ac874ec1fee88ed5fdf4bc921 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 09:07:23 +0200 Subject: [PATCH 08/33] Fix glpi.forbidDynamicInstantiation --- src/CommonDBVisible.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index 7f2dc4b2df2..f1d86da1690 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -473,23 +473,27 @@ public function addVisibility(array $inputs) $item = null; switch ($inputs['_type']) { case 'User': - if (is_a($this->getType() . '_User', CommonDBTM::class, true)) { - $item = new $this->userClass(); + $class = $this->getType() . '_User'; + if (is_a($class, CommonDBRelation::class, true)) { + $item = new $class(); } break; case 'Group': - if (is_a('Group_' . $this->getType(), CommonDBTM::class, true)) { - $item = new $this->groupClass(); + $class = 'Group_' . $this->getType(); + if (is_a($class, CommonDBRelation::class, true)) { + $item = new $class(); } break; case 'Entity': - if (is_a('Entity_' . $this->getType(), CommonDBTM::class, true)) { - $item = new $this->entityClass(); + $class = 'Entity_' . $this->getType(); + if (is_a($class, CommonDBRelation::class, true)) { + $item = new $class(); } break; case 'Profile': - if (is_a('Profile_' . $this->getType(), CommonDBTM::class, true)) { - $item = new $this->profileClass(); + $class = 'Profile_' . $this->getType(); + if (is_a($class, CommonDBRelation::class, true)) { + $item = new $class(); } break; } From 476366b497cd7b7d889b3142d6d0ffcc884462b3 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 09:57:46 +0200 Subject: [PATCH 09/33] Fix phpunit tests --- phpunit/functional/SavedSearchTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 34c7fcc2081..616e9462ffb 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -83,7 +83,7 @@ public function testAddVisibilityRestrict() $this->logOut(); $this->login('normal', 'normal'); \Session::changeProfile(3); - $visibility_restrict2 = "((`glpi_savedsearches`.`users_id` = '5') OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; + $visibility_restrict2 = "(((`glpi_savedsearches`.`users_id` = '5' AND (true)) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; $this->assertSame( $visibility_restrict2, \SavedSearch::addVisibilityRestrict() From e56393876095dab4c97edf4685b039e762ee4b28 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 11:46:15 +0200 Subject: [PATCH 10/33] Fix phpunit tests 2 --- phpunit/functional/SavedSearchTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 616e9462ffb..2b35d44df81 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -83,7 +83,7 @@ public function testAddVisibilityRestrict() $this->logOut(); $this->login('normal', 'normal'); \Session::changeProfile(3); - $visibility_restrict2 = "(((`glpi_savedsearches`.`users_id` = '5' AND (true)) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; + $visibility_restrict2 = "((`glpi_savedsearches`.`users_id` = '5' AND (true)) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR ((`glpi_groups_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))) OR ((`glpi_entities_savedsearches`.`entities_id` IN ('0', '4', '1', '2', '3', '5', '6'))))))"; $this->assertSame( $visibility_restrict2, \SavedSearch::addVisibilityRestrict() From beea6614dff263b7a0a695f3fd6af5844fff8ed8 Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Wed, 9 Jul 2025 16:45:32 +0200 Subject: [PATCH 11/33] Fix SaveSearch_test & migration --- install/migrations/update_10.0.x_to_11.0.0/savedsearch.php | 7 +++---- phpunit/functional/SavedSearchTest.php | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php index c4a39a45492..e366aec16ad 100644 --- a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php +++ b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php @@ -42,10 +42,9 @@ $table = SavedSearch::getTable(); $field = 'is_private'; if ($DB->fieldExists($table, $field)) { - $query = 'INSERT INTO glpi_entities_savedsearches (savedsearches_id, entities_id, is_recursive) -SELECT id, entities_id, is_recursive -FROM glpi_savedsearches WHERE is_private = 0;'; - $DB->doQuery($query); + $DB->doQuery('INSERT INTO `glpi_entities_savedsearches` (`savedsearches_id`, `entities_id`, `is_recursive`) +SELECT `id`, `entities_id`, `is_recursive` +FROM `glpi_savedsearches` WHERE `is_private` = 0;'); $migration->dropField($table, $field); diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index 2b35d44df81..c49640b7d2c 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -57,6 +57,10 @@ public function testGetVisibilityCriteria() public function testAddVisibilityRestrict() { global $DB; + + $test_root = getItemByTypeName('Entity', '_test_root_entity', true); + $test_child_1 = getItemByTypeName('Entity', '_test_child_1', true); + $test_child_2 = getItemByTypeName('Entity', '_test_child_2', true); // super-admin $this->login(); $this->assertSame('', \SavedSearch::addVisibilityRestrict()); @@ -90,7 +94,7 @@ public function testAddVisibilityRestrict() ); // can see public after moving entity $this->setEntity('_test_root_entity', true); - $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; + $visibility_restrict3 = "((`glpi_savedsearches`.`users_id` = '5' AND ((`glpi_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_savedsearches`.`is_recursive` = '1' AND `glpi_savedsearches`.`entities_id` IN ('0'))))) OR ((`glpi_savedsearches_usertargets`.`users_id` = '5' OR (`glpi_groups_savedsearches`.`groups_id` IN ('-1') AND ((`glpi_groups_savedsearches`.`no_entity_restriction` = '1') OR (((`glpi_groups_savedsearches`.`entities_id` IN ('$test_root', '$test_child_1', '$test_child_2') OR (`glpi_groups_savedsearches`.`is_recursive` = '1' AND `glpi_groups_savedsearches`.`entities_id` IN ('0'))))))) OR (((`glpi_entities_savedsearches`.`entities_id` IN ('4', '5', '6') OR (`glpi_entities_savedsearches`.`is_recursive` = '1' AND `glpi_entities_savedsearches`.`entities_id` IN ('0'))))))))"; $this->assertSame( $visibility_restrict3, \SavedSearch::addVisibilityRestrict() From dac696deec77db2d1c4bc1e99001f88e42ea359d Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:56:17 +0200 Subject: [PATCH 12/33] Update ajax/visibility.php Co-authored-by: Johan Cwiklinski --- ajax/visibility.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ajax/visibility.php b/ajax/visibility.php index 554b71169d3..e07894cf456 100644 --- a/ajax/visibility.php +++ b/ajax/visibility.php @@ -60,11 +60,12 @@ echo "
"; switch ($_POST['type']) { case 'User': - $params = ['name' => $prefix . 'users_id' . $suffix]; - if (isset($_POST['right'])) { - $params['right'] = isset($_POST['allusers']) ? 'all' : $_POST['right']; - } else { - $params['right'] = 'all'; + $params = [ + 'right' => 'all, + 'name' => $prefix . 'users_id' . $suffix + ]; + if (isset($_POST['right']) && !isset($_POST['allusers'])) { + $params['right'] = $_POST['right']; } if (isset($_POST['entity']) && $_POST['entity'] >= 0) { $params['entity'] = $_POST['entity']; From 496558be8ad241dfa5502899aff618e9432821ca Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:57:49 +0200 Subject: [PATCH 13/33] Update phpunit/functional/SavedSearchTest.php Co-authored-by: Johan Cwiklinski --- phpunit/functional/SavedSearchTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/phpunit/functional/SavedSearchTest.php b/phpunit/functional/SavedSearchTest.php index c49640b7d2c..48243a2c033 100644 --- a/phpunit/functional/SavedSearchTest.php +++ b/phpunit/functional/SavedSearchTest.php @@ -99,15 +99,6 @@ public function testAddVisibilityRestrict() $visibility_restrict3, \SavedSearch::addVisibilityRestrict() ); - $DB->delete( - 'glpi_profiles_users', - [ - 'users_id' => \Session::getLoginUserID(), - 'profiles_id' => 3, - 'entities_id' => 0, - 'is_recursive' => 1, - ] - ); } public function testGetMine() From 940ed05baf863121f729587fb53a824ab9b0f974 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:04 +0200 Subject: [PATCH 14/33] Update src/CommonDBVisible.php Co-authored-by: Johan Cwiklinski --- src/CommonDBVisible.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index f1d86da1690..f57285a12f1 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -472,7 +472,7 @@ public function addVisibility(array $inputs) $fkField = getForeignKeyFieldForItemType($this->getType()); $item = null; switch ($inputs['_type']) { - case 'User': + case User::class: $class = $this->getType() . '_User'; if (is_a($class, CommonDBRelation::class, true)) { $item = new $class(); From a91e785297bb6540aca89228524dbf5b21f00d21 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:14 +0200 Subject: [PATCH 15/33] Update src/CommonDBVisible.php Co-authored-by: Johan Cwiklinski --- src/CommonDBVisible.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index f57285a12f1..1a750e50aa3 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -484,7 +484,7 @@ public function addVisibility(array $inputs) $item = new $class(); } break; - case 'Entity': + case Entity::class: $class = 'Entity_' . $this->getType(); if (is_a($class, CommonDBRelation::class, true)) { $item = new $class(); From 4ca94058adf7fa30302d465a9aa1478c99c2612c Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:21 +0200 Subject: [PATCH 16/33] Update src/CommonDBVisible.php Co-authored-by: Johan Cwiklinski --- src/CommonDBVisible.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index 1a750e50aa3..60b98dc1233 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -490,7 +490,7 @@ public function addVisibility(array $inputs) $item = new $class(); } break; - case 'Profile': + case Profile::class: $class = 'Profile_' . $this->getType(); if (is_a($class, CommonDBRelation::class, true)) { $item = new $class(); From e50c6278c9658e7705e9e0af276a2e699c1c7e6e Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:29 +0200 Subject: [PATCH 17/33] Update src/Entity_SavedSearch.php Co-authored-by: Johan Cwiklinski --- src/Entity_SavedSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity_SavedSearch.php b/src/Entity_SavedSearch.php index 57b0e2285f7..7301043f19c 100644 --- a/src/Entity_SavedSearch.php +++ b/src/Entity_SavedSearch.php @@ -36,7 +36,7 @@ class Entity_SavedSearch extends CommonDBRelation { // From CommonDBRelation - public static $itemtype_1 = 'SavedSearch'; + public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; public static $itemtype_2 = 'Entity'; public static $items_id_2 = 'entities_id'; From 1953abea8cfbee89c2325ce5550367107e8c76d4 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:47 +0200 Subject: [PATCH 18/33] Update src/SavedSearch.php Co-authored-by: Johan Cwiklinski --- src/SavedSearch.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SavedSearch.php b/src/SavedSearch.php index 8259da10709..1c7b9060ca9 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -358,8 +358,8 @@ public function defineTabs($options = []) { $ong = []; $this->addDefaultFormTab($ong); - $this->addStandardTab('SavedSearch', $ong, $options); - $this->addStandardTab('SavedSearch_Alert', $ong, $options); + $this->addStandardTab(SavedSearch::class, $ong, $options); + $this->addStandardTab(SavedSearch_Alert::class, $ong, $options); return $ong; } From 848072abb90dd9f208695a5add67289bd5ea949d Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:58:56 +0200 Subject: [PATCH 19/33] Update src/SavedSearch_UserTarget.php Co-authored-by: Johan Cwiklinski --- src/SavedSearch_UserTarget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SavedSearch_UserTarget.php b/src/SavedSearch_UserTarget.php index a08abf4f5a2..5e3b0dce8da 100644 --- a/src/SavedSearch_UserTarget.php +++ b/src/SavedSearch_UserTarget.php @@ -37,7 +37,7 @@ class SavedSearch_UserTarget extends CommonDBRelation { public $auto_message_on_action = false; - public static $itemtype_1 = 'SavedSearch'; + public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; public static $itemtype_2 = 'User'; From 84cac52e25beac29ce98de6ad0e97ce4cda60bf0 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 08:59:03 +0200 Subject: [PATCH 20/33] Update src/SavedSearch_UserTarget.php Co-authored-by: Johan Cwiklinski --- src/SavedSearch_UserTarget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SavedSearch_UserTarget.php b/src/SavedSearch_UserTarget.php index 5e3b0dce8da..6055aed49dc 100644 --- a/src/SavedSearch_UserTarget.php +++ b/src/SavedSearch_UserTarget.php @@ -40,7 +40,7 @@ class SavedSearch_UserTarget extends CommonDBRelation public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; - public static $itemtype_2 = 'User'; + public static $itemtype_2 = User::class; public static $items_id_2 = 'users_id'; public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; From 5fd64551f3efb947405d2679e4093bdec99135d6 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:09:33 +0200 Subject: [PATCH 21/33] Update src/CommonDBVisible.php Co-authored-by: Johan Cwiklinski --- src/CommonDBVisible.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index 60b98dc1233..7be5a7ac18d 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -478,7 +478,7 @@ public function addVisibility(array $inputs) $item = new $class(); } break; - case 'Group': + case Group::class: $class = 'Group_' . $this->getType(); if (is_a($class, CommonDBRelation::class, true)) { $item = new $class(); From c6dd332b2b592cfbd3ce1508cecbebc67bd537aa Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 09:18:20 +0200 Subject: [PATCH 22/33] Fix migration & visibility right & displayTabContentForItem --- ajax/visibility.php | 47 ++++++++----------- .../update_10.0.x_to_11.0.0/savedsearch.php | 17 +++++-- src/SavedSearch.php | 6 +-- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/ajax/visibility.php b/ajax/visibility.php index e07894cf456..e0b4c3299c5 100644 --- a/ajax/visibility.php +++ b/ajax/visibility.php @@ -61,7 +61,7 @@ switch ($_POST['type']) { case 'User': $params = [ - 'right' => 'all, + 'right' => 'all', 'name' => $prefix . 'users_id' . $suffix ]; if (isset($_POST['right']) && !isset($_POST['allusers'])) { @@ -88,12 +88,6 @@ 'prefix' => $_POST['prefix'], ], ]; - if (isset($_POST['entity']) && $_POST['entity'] >= 0) { - $params['entity'] = $_POST['entity']; - $params['toupdate']['moreparams']['entity'] = $_POST['entity']; - $params['entity_sons'] = $_POST['is_recursive'] ?? false; - $params['toupdate']['moreparams']['entity_sons'] = $_POST['is_recursive'] ?? false; - } Group::dropdown($params); echo ""; $display = true; @@ -112,27 +106,26 @@ break; case 'Profile': - $checkright = (READ | CREATE | UPDATE | PURGE); - $righttocheck = $_POST['right']; - if ($_POST['right'] == 'faq') { - $righttocheck = 'knowbase'; - $checkright = KnowbaseItem::READFAQ; + $righttocheck = $_POST['right'] ?? null; + if ($righttocheck) { + $checkright = (READ | CREATE | UPDATE | PURGE); + if ($_POST['right'] == 'faq') { + $righttocheck = 'knowbase'; + $checkright = KnowbaseItem::READFAQ; + } + $params['condition'] = [ + 'glpi_profilerights.name' => $righttocheck, + 'glpi_profilerights.rights' => ['&', $checkright], + ]; } - $params = [ - 'rand' => $rand, - 'name' => $prefix . 'profiles_id' . $suffix, - 'condition' => [ - 'glpi_profilerights.name' => $righttocheck, - 'glpi_profilerights.rights' => ['&', $checkright], - ], - ]; - $params['toupdate'] = ['value_fieldname' - => 'value', - 'to_update' => "subvisibility$rand", - 'url' => $CFG_GLPI["root_doc"] . "/ajax/subvisibility.php", - 'moreparams' => ['items_id' => '__VALUE__', - 'type' => $_POST['type'], - 'prefix' => $_POST['prefix'], + $params['toupdate'] = [ + 'value_fieldname' => 'value', + 'to_update' => "subvisibility$rand", + 'url' => $CFG_GLPI["root_doc"] . "/ajax/subvisibility.php", + 'moreparams' => [ + 'items_id' => '__VALUE__', + 'type' => $_POST['type'], + 'prefix' => $_POST['prefix'], ], ]; diff --git a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php index e366aec16ad..7f3953b1d9d 100644 --- a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php +++ b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php @@ -33,6 +33,9 @@ * --------------------------------------------------------------------- */ +use Glpi\DBAL\QuerySubQuery; +use Glpi\DBAL\QueryExpression; + /** * @var \DBmysql $DB * @var \Migration $migration @@ -42,9 +45,17 @@ $table = SavedSearch::getTable(); $field = 'is_private'; if ($DB->fieldExists($table, $field)) { - $DB->doQuery('INSERT INTO `glpi_entities_savedsearches` (`savedsearches_id`, `entities_id`, `is_recursive`) -SELECT `id`, `entities_id`, `is_recursive` -FROM `glpi_savedsearches` WHERE `is_private` = 0;'); + $DB->insert('glpi_entities_savedsearches', new QuerySubQuery([ + 'SELECT' => [ + 'id as savedsearches_id', + 'entities_id', + 'is_recursive', + ], + 'FROM' => 'glpi_savedsearches', + 'WHERE' => [ + 'is_private' => ['<>', 0], + ], + ])); $migration->dropField($table, $field); diff --git a/src/SavedSearch.php b/src/SavedSearch.php index 1c7b9060ca9..af45b52c02b 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -365,10 +365,8 @@ public function defineTabs($options = []) public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { - switch ($item::class) { - case SavedSearch::class: - $item->showVisibility(); - return true; + if ($item::class == SavedSearch::class) { + $item->showVisibility(); } return false; } From cc4b0f6124b07cea584af449719cc0d7f1ae5c5e Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 09:23:04 +0200 Subject: [PATCH 23/33] Min fix --- src/SavedSearch.php | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/SavedSearch.php b/src/SavedSearch.php index af45b52c02b..04d2e7ff429 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -329,26 +329,23 @@ public function post_addItem() public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { - if (self::canView()) { + if (self::canView() + && $item::class == SavedSearch::class) { $nb = 0; - switch ($item::class) { - case SavedSearch::class: - if (self::canCreatePublic()) { - if ($_SESSION['glpishow_count_on_tabs']) { - $nb = $item->countVisibilities(); - } - return [ - 1 => self::createTabEntry( - _n( - 'Target', - 'Targets', - Session::getPluralNumber() - ), - $nb - ), - ]; - } - break; + if (self::canCreatePublic()) { + if ($_SESSION['glpishow_count_on_tabs']) { + $nb = $item->countVisibilities(); + } + return [ + 1 => self::createTabEntry( + _n( + 'Target', + 'Targets', + Session::getPluralNumber() + ), + $nb + ), + ]; } } return ''; From 30c5373b5335a0d57ad9557e0dee8e03fe42dcdc Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 09:24:05 +0200 Subject: [PATCH 24/33] Better fix --- src/SavedSearch.php | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/SavedSearch.php b/src/SavedSearch.php index 04d2e7ff429..8f44b26cadf 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -330,23 +330,22 @@ public function post_addItem() public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { if (self::canView() - && $item::class == SavedSearch::class) { + && $item::class == SavedSearch::class + && self::canCreatePublic()) { $nb = 0; - if (self::canCreatePublic()) { - if ($_SESSION['glpishow_count_on_tabs']) { - $nb = $item->countVisibilities(); - } - return [ - 1 => self::createTabEntry( - _n( - 'Target', - 'Targets', - Session::getPluralNumber() - ), - $nb - ), - ]; + if ($_SESSION['glpishow_count_on_tabs']) { + $nb = $item->countVisibilities(); } + return [ + 1 => self::createTabEntry( + _n( + 'Target', + 'Targets', + Session::getPluralNumber() + ), + $nb + ), + ]; } return ''; } From 7695b1a9aa5b1349d42f0e9f86575b214b39f1cd Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 09:31:13 +0200 Subject: [PATCH 25/33] Fix CS --- ajax/visibility.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax/visibility.php b/ajax/visibility.php index e0b4c3299c5..a0de7171494 100644 --- a/ajax/visibility.php +++ b/ajax/visibility.php @@ -62,7 +62,7 @@ case 'User': $params = [ 'right' => 'all', - 'name' => $prefix . 'users_id' . $suffix + 'name' => $prefix . 'users_id' . $suffix, ]; if (isset($_POST['right']) && !isset($_POST['allusers'])) { $params['right'] = $_POST['right']; From 68d2027ef393eab134cdd9453d8a097bed8d42fe Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:35:45 +0200 Subject: [PATCH 26/33] Apply suggestion from @trasher Co-authored-by: Johan Cwiklinski --- src/Entity_SavedSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity_SavedSearch.php b/src/Entity_SavedSearch.php index 7301043f19c..da9367f93b3 100644 --- a/src/Entity_SavedSearch.php +++ b/src/Entity_SavedSearch.php @@ -38,7 +38,7 @@ class Entity_SavedSearch extends CommonDBRelation // From CommonDBRelation public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; - public static $itemtype_2 = 'Entity'; + public static $itemtype_2 = Entity::class; public static $items_id_2 = 'entities_id'; public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; From 1bb432d3dd3b69ba78fa1ad91e90f8acc113b72d Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:36:20 +0200 Subject: [PATCH 27/33] Apply suggestion from @trasher Co-authored-by: Johan Cwiklinski --- src/Glpi/Search/Provider/SQLProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 65d1a2939e5..48d85f5fd7c 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -2319,7 +2319,7 @@ public static function getDefaultJoinCriteria(string $itemtype, string $ref_tabl } break; - case 'SavedSearch': + case SavedSearch::class: $criterias = \SavedSearch::getVisibilityCriteria(false); if (isset($criterias['LEFT JOIN'])) { $out = ['LEFT JOIN' => $criterias['LEFT JOIN']]; From 60467674345fc09937fb4764dbbc1d46e9f9f47a Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:37:03 +0200 Subject: [PATCH 28/33] Apply suggestion from @trasher Co-authored-by: Johan Cwiklinski --- src/Group_SavedSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Group_SavedSearch.php b/src/Group_SavedSearch.php index 9d5da8f8023..04026bcbfff 100644 --- a/src/Group_SavedSearch.php +++ b/src/Group_SavedSearch.php @@ -36,7 +36,7 @@ class Group_SavedSearch extends CommonDBRelation { // From CommonDBRelation - public static $itemtype_1 = 'SavedSearch'; + public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; public static $itemtype_2 = 'Group'; public static $items_id_2 = 'groups_id'; From 56e840c452b78cf0ca41f0badba8d2aa7a13525d Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:37:16 +0200 Subject: [PATCH 29/33] Apply suggestion from @trasher Co-authored-by: Johan Cwiklinski --- src/Group_SavedSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Group_SavedSearch.php b/src/Group_SavedSearch.php index 04026bcbfff..9dd76e67c1f 100644 --- a/src/Group_SavedSearch.php +++ b/src/Group_SavedSearch.php @@ -38,7 +38,7 @@ class Group_SavedSearch extends CommonDBRelation // From CommonDBRelation public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; - public static $itemtype_2 = 'Group'; + public static $itemtype_2 = Group::class; public static $items_id_2 = 'groups_id'; public static $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS; From 4632987753de10264cfca4944924c1c0ab98e254 Mon Sep 17 00:00:00 2001 From: Xavier CAILLAUD Date: Thu, 10 Jul 2025 09:46:11 +0200 Subject: [PATCH 30/33] Update install/migrations/update_10.0.x_to_11.0.0/savedsearch.php Co-authored-by: Johan Cwiklinski --- install/migrations/update_10.0.x_to_11.0.0/savedsearch.php | 1 + 1 file changed, 1 insertion(+) diff --git a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php index 7f3953b1d9d..fc938849ed8 100644 --- a/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php +++ b/install/migrations/update_10.0.x_to_11.0.0/savedsearch.php @@ -47,6 +47,7 @@ if ($DB->fieldExists($table, $field)) { $DB->insert('glpi_entities_savedsearches', new QuerySubQuery([ 'SELECT' => [ + new QueryExpression('null AS `id`'), 'id as savedsearches_id', 'entities_id', 'is_recursive', From 67d566e10c6fa0efadd381cf5acb4459e03f721e Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 10:21:43 +0200 Subject: [PATCH 31/33] Fix insert User Target & Group without_entity_restriction --- src/CommonDBVisible.php | 9 +++++++-- src/SavedSearch_User.php | 5 ++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/CommonDBVisible.php b/src/CommonDBVisible.php index 7be5a7ac18d..cfce7d3e991 100644 --- a/src/CommonDBVisible.php +++ b/src/CommonDBVisible.php @@ -109,7 +109,7 @@ public function __construct() { // define default values if (!$this->userClass) { - $this->userClass = $this->getType() . '_User'; + $this->userClass = $this->getType() . '_UserTarget'; } if (!$this->groupClass) { $this->groupClass = 'Group_' . $this->getType(); @@ -473,7 +473,7 @@ public function addVisibility(array $inputs) $item = null; switch ($inputs['_type']) { case User::class: - $class = $this->getType() . '_User'; + $class = $this->getType() . '_UserTarget'; if (is_a($class, CommonDBRelation::class, true)) { $item = new $class(); } @@ -497,6 +497,11 @@ public function addVisibility(array $inputs) } break; } + if (array_key_exists('entities_id', $inputs) && $inputs['entities_id'] == -1) { + // "No restriction" value selected + $inputs['entities_id'] = 'NULL'; + $inputs['no_entity_restriction'] = 1; + } if (!is_null($item)) { $item->add($inputs); Event::log( diff --git a/src/SavedSearch_User.php b/src/SavedSearch_User.php index 11ea01a5cc7..a5227c54dd4 100644 --- a/src/SavedSearch_User.php +++ b/src/SavedSearch_User.php @@ -37,10 +37,9 @@ class SavedSearch_User extends CommonDBRelation { public $auto_message_on_action = false; - public static $itemtype_1 = 'SavedSearch'; + public static $itemtype_1 = SavedSearch::class; public static $items_id_1 = 'savedsearches_id'; - - public static $itemtype_2 = 'User'; + public static $itemtype_2 = User::class; public static $items_id_2 = 'users_id'; From d821627bb7c58b98d77d1a1c52c3b9c0c082898d Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Thu, 10 Jul 2025 10:52:30 +0200 Subject: [PATCH 32/33] Apply suggestions from @trasher --- src/SavedSearch.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/SavedSearch.php b/src/SavedSearch.php index 8f44b26cadf..e57b357a717 100644 --- a/src/SavedSearch.php +++ b/src/SavedSearch.php @@ -50,7 +50,7 @@ class SavedSearch extends CommonDBVisible implements ExtraVisibilityCriteria use Clonable; public static $rightname = 'bookmark_public'; - public static $types = ['Group', 'User', 'Entity']; + public static $types = [Group::class, User::class, Entity::class]; public const SEARCH = 1; //SEARCH SYSTEM bookmark public const URI = 2; public const ALERT = 3; //SEARCH SYSTEM search alert @@ -259,7 +259,7 @@ public static function processMassiveActionsForOneItemtype( parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } - public function haveVisibilityAccess() + public function haveVisibilityAccess(): bool { if (!self::canView()) { return false; @@ -276,9 +276,6 @@ public function canCreateItem(): bool return parent::canCreateItem(); } - /** - * @return bool - */ public static function canCreatePublic(): bool { return (Session::haveRight('config', UPDATE) || From b941fbd004d5b71c63d7d6aa15bd2958e3a1f15e Mon Sep 17 00:00:00 2001 From: Thetsmr Date: Fri, 11 Jul 2025 09:50:11 +0200 Subject: [PATCH 33/33] Revert not used --- ajax/subvisibility.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ajax/subvisibility.php b/ajax/subvisibility.php index 4bde9edb933..11eb3163ea8 100644 --- a/ajax/subvisibility.php +++ b/ajax/subvisibility.php @@ -53,10 +53,6 @@ if (Session::canViewAllEntities()) { $params['toadd'] = [-1 => __('No restriction')]; } - if (isset($_POST['entity']) && $_POST['entity'] >= 0) { - $params['entity'] = $_POST['entity']; - $params['entity_sons'] = $_POST['is_recursive'] ?? false; - } echo "
"; echo htmlescape(Entity::getTypeName(1)); echo "";