From 486bc9abae7cee95e701881dd5b4eb8ade6533fa Mon Sep 17 00:00:00 2001 From: Curtis Conard Date: Wed, 7 Jun 2023 06:43:25 -0400 Subject: [PATCH] start removing search sql quirks --- src/CommonITILObject.php | 104 +++++- src/Entity.php | 27 +- src/FieldUnicity.php | 21 +- src/Glpi/Search/AdvancedSearchInterface.php | 71 +++++ src/Glpi/Search/Provider/SQLProvider.php | 330 ++------------------ src/Glpi/Search/SearchOption.php | 49 +++ src/Group.php | 74 ++++- src/User.php | 153 ++++++++- 8 files changed, 515 insertions(+), 314 deletions(-) create mode 100644 src/Glpi/Search/AdvancedSearchInterface.php diff --git a/src/CommonITILObject.php b/src/CommonITILObject.php index e4f687ad68e..9c0b9c2db92 100644 --- a/src/CommonITILObject.php +++ b/src/CommonITILObject.php @@ -43,6 +43,8 @@ use Glpi\Form\Destination\AnswersSet_FormDestinationItem; use Glpi\Plugin\Hooks; use Glpi\RichText\RichText; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\SearchOption; use Glpi\Team\Team; /** @@ -52,7 +54,7 @@ * @property-read array $groups * @property-read array $suppliers **/ -abstract class CommonITILObject extends CommonDBTM +abstract class CommonITILObject extends CommonDBTM implements AdvancedSearchInterface { use \Glpi\Features\Clonable; use \Glpi\Features\Timeline; @@ -11390,4 +11392,104 @@ public static function getTeamMemberForm(CommonITILObject $item): string 'main_rand' => mt_rand(), ]); } + + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array + { + return null; + } + + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array + { + return null; + } + + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array + { + // Only handle core ITIL objects here + if (!in_array($itemtype, [Ticket::class, Change::class, Problem::class], true)) { + return null; + } + + /** @var Ticket|Change|Problem|false $item */ + $item = getItemForItemtype($itemtype); + if ($opt['field'] === 'status') { + $to_check = []; + if ($item !== false) { + $to_check = match ($val) { + 'process' => $item->getProcessStatusArray(), + 'notclosed' => array_diff(array_keys($item::getAllStatusArray()), $item::getClosedStatusArray()), + 'old' => array_merge($item::getSolvedStatusArray(), $item::getClosedStatusArray()), + 'notold' => $item::getNotSolvedStatusArray(), + 'all' => array_keys($item::getAllStatusArray()), + default => array_key_exists($val, $item::getAllStatusArray()) ? [$val] : [] + }; + } + if (count($to_check) > 0) { + if ($nott) { + return [ + $opt->getTableField() => ['NOT IN' => $to_check] + ]; + } + return [$opt->getTableField() => $to_check]; + } + } else if ($opt['table'] === Ticket_Ticket::getTable() && $opt['field'] === 'tickets_id_1') { + $tmp_link = $nott ? 'AND' : 'OR'; + $compare = $nott ? '<>' : '='; + $to_add = []; + + if ($nott && ($val !== 'NULL' && $val !== 'null')) { + $to_add = [$opt->getTableField() => null]; + } + + $criteria = [ + $tmp_link => [ + $opt->getTableField() => [$compare, $val], + "{$opt['table']}.tickets_id_2" => [$compare, $val], + ], + Ticket::getTableField('id') => ['<>', $val], + ]; + if (!empty($to_add)) { + $criteria = [ + 'OR' => [ + $criteria, + $to_add + ] + ]; + } + return $criteria; + } else if (in_array($opt['field'], ['impact', 'urgency', 'priority'], true)) { + if (!is_numeric($val)) { + return []; + } + return match (true) { + $val > 0 => [$opt->getTableField() => [$nott ? '<>' : '=', $val]], + $val < 0 => [$opt->getTableField() => [$nott ? '<' : '>=', $val]], + default => [$opt->getTableField() => [$nott ? '<' : '>=', 0]], + }; + } else if ( + in_array( + $opt->getTableField(), + ['glpi_tickets.global_validation', 'glpi_ticketvalidations.status', 'glpi_changes.global_validation', 'glpi_changevalidations.status'], + true + ) + ) { + if ($val == 'all') { + return []; + } + $to_check = match ($val) { + 'can' => CommonITILValidation::getCanValidationStatusArray(), + 'add' => CommonITILValidation::getAllValidationStatusArray(), // Dead case? Handled above + default => [] + }; + if (count($to_check) === 0) { + $to_check = [$val]; + } + return match ($nott) { + true => [$opt->getTableField() => ['NOT IN' => $to_check]], + default => [$opt->getTableField() => $to_check], + }; + } + + return null; + } } diff --git a/src/Entity.php b/src/Entity.php index 3d814b4f582..638dde42cfb 100644 --- a/src/Entity.php +++ b/src/Entity.php @@ -38,11 +38,14 @@ use Glpi\DBAL\QueryFunction; use Glpi\Event; use Glpi\Plugin\Hooks; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\SearchEngine; +use Glpi\Search\SearchOption; /** * Entity class */ -class Entity extends CommonTreeDropdown +class Entity extends CommonTreeDropdown implements AdvancedSearchInterface { use Glpi\Features\Clonable; use MapGeolocation; @@ -3079,7 +3082,7 @@ public static function getEntitySelectorTree(): array $adapt_tree = static function (&$entities) use (&$adapt_tree, $base_path) { foreach ($entities as $entities_id => &$entity) { - $entity['key'] = $entities_id; + $entity['key'] = $entities_id; $title = "" . htmlspecialchars($entity['name']) . ""; $entity['title'] = $title; @@ -3125,4 +3128,24 @@ public static function getEntitySelectorTree(): array return $entitiestree; } + + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array + { + global $DB; + $itemtable = SearchEngine::getOrigTableName($itemtype); + return [ + "{$itemtable}.id AS entities_id", + new QueryExpression($DB::quoteValue('1') . ' AS ' . $DB::quoteName('is_recursive')), + ]; + } + + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array + { + return null; + } + + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array + { + return null; + } } diff --git a/src/FieldUnicity.php b/src/FieldUnicity.php index 81deb5dfbd4..56132ef0ff8 100644 --- a/src/FieldUnicity.php +++ b/src/FieldUnicity.php @@ -34,11 +34,13 @@ */ use Glpi\Application\View\TemplateRenderer; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\SearchOption; /** * FieldUnicity Class **/ -class FieldUnicity extends CommonDropdown +class FieldUnicity extends CommonDropdown implements AdvancedSearchInterface { // From CommonDBTM public $dohistory = true; @@ -589,4 +591,21 @@ public static function getIcon() { return "ti ti-fingerprint"; } + + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array + { + return [ + 'glpi_fieldunicities.itemtype AS ITEMTYPE' + ]; + } + + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array + { + return null; + } + + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array + { + return null; + } } diff --git a/src/Glpi/Search/AdvancedSearchInterface.php b/src/Glpi/Search/AdvancedSearchInterface.php new file mode 100644 index 00000000000..9708981a1b5 --- /dev/null +++ b/src/Glpi/Search/AdvancedSearchInterface.php @@ -0,0 +1,71 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Search; + +use Glpi\DBAL\QueryFunction; + +interface AdvancedSearchInterface +{ + /** + * @param class-string<\CommonDBTM> $itemtype + * @return array|null + */ + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array; + + /** + * @param class-string<\CommonDBTM> $itemtype + * @param SearchOption $opt + * @param bool $meta + * @param string $meta_type + * @return array|null + */ + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array; + + /** + * This method is called on the class that the search option belongs to (based on the table). + * It can return an array of criteria to handle the search option in a non-standard way, or return null to indicate it wasn't handled at all. + * @param class-string<\CommonDBTM> $itemtype The main itemtype being searched on + * @param SearchOption $opt The search option being handled + * @param bool $nott Whether the search option is negated + * @param string $searchtype The search type (e.g. 'contains') + * @param mixed $val The value to search for + * @param bool $meta Whether the search option is for a meta field + * @param callable $fn_append_with_search A helper function to append a criterion to a criteria array in a standardized way + * @phpstan-param callable(array &$criteria, string|QueryFunction $value): void $fn_append_with_search + * @return array|null + */ + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array; +} diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index f0c5cfff4bd..99a2f0eaf2a 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -113,24 +113,22 @@ public static function getDefaultSelectCriteria(string $itemtype): array $mayberecursive = $item->maybeRecursive(); } $ret = []; - switch ($itemtype) { - case 'FieldUnicity': - $ret[] = "`glpi_fieldunicities`.`itemtype` AS ITEMTYPE"; - break; - default: - // Plugin can override core definition for its type - if ($plug = isPluginItemType($itemtype)) { - $default_select = \Plugin::doOneHook($plug['plugin'], 'addDefaultSelect', $itemtype); - if (!empty($default_select)) { - $ret[] = new QueryExpression(rtrim($default_select, ' ,')); - } - } + if (method_exists($itemtype, 'getSQLDefaultSelectCriteria')) { + $criteria = $itemtype::getSQLDefaultSelectCriteria($itemtype); + if ($criteria !== null) { + $ret = array_merge($ret, $criteria); + } + } + + // Plugin can override core definition for its type + if ($plug = isPluginItemType($itemtype)) { + $default_select = \Plugin::doOneHook($plug['plugin'], 'addDefaultSelect', $itemtype); + if (!empty($default_select)) { + $ret[] = new QueryExpression(rtrim($default_select, ' ,')); + } } - if ($itemtable === 'glpi_entities') { - $ret[] = "`$itemtable`.`id` AS entities_id"; - $ret[] = "'1' AS is_recursive"; - } else if ($mayberecursive) { + if ($mayberecursive && $itemtable !== 'glpi_entities') { if ($item->isField('entities_id')) { $ret[] = $DB::quoteName("$itemtable.entities_id"); } @@ -1265,187 +1263,17 @@ public static function getWhereCriteria($nott, $itemtype, $ID, $searchtype, $val $criteria[$value] = $SEARCH; } }; - switch ($inittable . "." . $field) { - // case "glpi_users_validation.name" : - - case "glpi_users.name": - if ($val === 'myself') { - switch ($searchtype) { - case 'equals': - return [ - "$table.id" => $_SESSION['glpiID'] - ]; - - case 'notequals': - return [ - "$table.id" => ['<>', $_SESSION['glpiID']] - ]; - } - } - - if ($itemtype === User::class) { // glpi_users case / not link table - $criteria = [ - 'OR' => [] - ]; - if (in_array($searchtype, ['equals', 'notequals'])) { - $append_criterion_with_search($criteria['OR'], "$table.id"); - - if ($searchtype === 'notequals') { - $nott = !$nott; - } - - // Add NULL if $val = 0 and not negative search - // Or negative search on real value - if ((!$nott && ($val == 0)) || ($nott && ($val != 0))) { - $criteria['OR'][] = ["$table.id" => null]; - } - - return $criteria; - } - return [new QueryExpression(self::makeTextCriteria("`$table`.`$field`", $val, $nott, ''))]; - } - if ($_SESSION["glpinames_format"] == \User::FIRSTNAME_BEFORE) { - $name1 = 'firstname'; - $name2 = 'realname'; - } else { - $name1 = 'realname'; - $name2 = 'firstname'; - } - - if (in_array($searchtype, ['equals', 'notequals'])) { - $criteria = [ - 'OR' => [] - ]; - $append_criterion_with_search($criteria['OR'], "$table.id"); - if ($val == 0) { - if ($searchtype === 'notequals') { - $criteria['OR'][] = [ - 'NOT' => ["$table.id" => null] - ]; - } else { - $criteria['OR'][] = [ - "$table.id" => null - ]; - } - } - return $criteria; - } else if ($searchtype === 'empty') { - $criteria = []; - $append_criterion_with_search($criteria, "$table.id"); - return $criteria; - } - $toadd = ''; - - $tmplink = 'OR'; - if ($nott) { - $tmplink = 'AND'; - } - - if (is_a($itemtype, \CommonITILObject::class, true)) { - $itil_user_tables = ['glpi_tickets_users', 'glpi_changes_users', 'glpi_problems_users']; - $has_join = isset($opt["joinparams"]["beforejoin"]["table"], $opt["joinparams"]["beforejoin"]["joinparams"]); - if ($has_join && in_array($opt["joinparams"]["beforejoin"]["table"], $itil_user_tables, true)) { - $bj = $opt["joinparams"]["beforejoin"]; - $linktable = $bj['table'] . '_' . \Search::computeComplexJoinID($bj['joinparams']) . $addmeta; - //$toadd = "`$linktable`.`alternative_email` $SEARCH $tmplink "; - $toadd = self::makeTextCriteria( - "`$linktable`.`alternative_email`", - $val, - $nott, - $tmplink - ); - // Remove $tmplink (may have spaces around it) from front of $toadd - $toadd = preg_replace('/^\s*' . preg_quote($tmplink, '/') . '\s*/', '', $toadd); - if ($val === '^$') { - return [ - 'OR' => [ - "$linktable.users_id" => null, - "$linktable.alternative_email" => null - ] - ]; - } - } - } - $criteria = [ - $tmplink => [] - ]; - $append_criterion_with_search($criteria[$tmplink], "$table.$name1"); - $append_criterion_with_search($criteria[$tmplink], "$table.$name2"); - $append_criterion_with_search($criteria[$tmplink], "$table.$field"); - $append_criterion_with_search( - $criteria[$tmplink], - QueryFunction::concat([ - "$table.$name1", - new QueryExpression($DB::quoteValue(' ')), - "$table.$name2" - ]) - ); - if ($nott && ($val !== 'NULL') && ($val !== 'null')) { - $criteria = [ - $tmplink => [ - 'OR' => [ - $criteria, - "$table.$field" => null - ], - new QueryExpression($toadd) - ] - ]; - } + $opt_itemtype = getItemTypeForTable($opt['table']); + if ($opt_itemtype === 'UNKNOWN') { + $opt_itemtype = $itemtype; + } + if (method_exists($opt_itemtype, 'getSQLWhereCriteria')) { + $criteria = $opt_itemtype::getSQLWhereCriteria($itemtype, $opt, $nott, $searchtype, $val, $meta, $append_criterion_with_search); + if ($criteria !== null) { return $criteria; - - case "glpi_groups.completename": - if ($val === 'mygroups') { - switch ($searchtype) { - case 'equals': - if (count($_SESSION['glpigroups']) === 0) { - return []; - } - return [ - "$table.id" => $_SESSION['glpigroups'] - ]; - - case 'notequals': - if (count($_SESSION['glpigroups']) === 0) { - return []; - } - return [ - "$table.id" => ['NOT IN', $_SESSION['glpigroups']] - ]; - - case 'under': - if (count($_SESSION['glpigroups']) === 0) { - return []; - } - $groups = $_SESSION['glpigroups']; - foreach ($_SESSION['glpigroups'] as $g) { - $groups += getSonsOf($inittable, $g); - } - $groups = array_unique($groups); - return [ - "$table.id" => $groups - ]; - - case 'notunder': - if (count($_SESSION['glpigroups']) === 0) { - return []; - } - $groups = $_SESSION['glpigroups']; - foreach ($_SESSION['glpigroups'] as $g) { - $groups += getSonsOf($inittable, $g); - } - $groups = array_unique($groups); - return [ - "$table.id" => ['NOT IN', $groups] - ]; - - case 'empty': - $criteria = []; - $append_criterion_with_search($criteria, "$table.id"); - return $criteria; - } - } - break; - + } + } + switch ($inittable . "." . $field) { case "glpi_ipaddresses.name": if (preg_match("/^\s*([<>])([=]*)[[:space:]]*([0-9\.]+)/", $val, $regs)) { if ($nott) { @@ -1459,93 +1287,6 @@ public static function getWhereCriteria($nott, $itemtype, $ID, $searchtype, $val return [new QueryExpression("(INET_ATON(`$table`.`$field`) " . $regs[1] . " INET_ATON('" . $regs[3] . "'))")]; } break; - - case "glpi_tickets.status": - case "glpi_problems.status": - case "glpi_changes.status": - $tocheck = []; - $item = getItemForItemtype($itemtype); - if ($item instanceof CommonITILObject) { - switch ($val) { - case 'process': - $tocheck = $item->getProcessStatusArray(); - break; - - case 'notclosed': - $tocheck = $item::getAllStatusArray(); - foreach ($item::getClosedStatusArray() as $status) { - if (isset($tocheck[$status])) { - unset($tocheck[$status]); - } - } - $tocheck = array_keys($tocheck); - break; - - case 'old': - $tocheck = array_merge( - $item::getSolvedStatusArray(), - $item::getClosedStatusArray() - ); - break; - - case 'notold': - $tocheck = $item::getNotSolvedStatusArray(); - break; - - case 'all': - $tocheck = array_keys($item::getAllStatusArray()); - break; - } - - if (count($tocheck) === 0) { - $statuses = $item::getAllStatusArray(); - if (isset($statuses[$val])) { - $tocheck = [$val]; - } - } - } - - if (count($tocheck)) { - if ($nott) { - return [ - "$table.$field" => ['NOT IN', $tocheck] - ]; - } - return [ - "$table.$field" => $tocheck - ]; - } - break; - - case "glpi_tickets_tickets.tickets_id_1": - $tmplink = 'OR'; - $compare = '='; - if ($nott) { - $tmplink = 'AND'; - $compare = '<>'; - } - $toadd2 = ''; - if ( - $nott - && ($val != 'NULL') && ($val != 'null') - ) { - $toadd2 = " OR `$table`.`$field` IS NULL"; - } - - return new QueryExpression(" (((`$table`.`tickets_id_1` $compare '$val' - $tmplink `$table`.`tickets_id_2` $compare '$val') - AND `glpi_tickets`.`id` <> '$val') - $toadd2)"); - - case "glpi_tickets.priority": - case "glpi_tickets.impact": - case "glpi_tickets.urgency": - case "glpi_problems.priority": - case "glpi_problems.impact": - case "glpi_problems.urgency": - case "glpi_changes.priority": - case "glpi_changes.impact": - case "glpi_changes.urgency": case "glpi_projects.priority": if (is_numeric($val)) { if ($val > 0) { @@ -1567,29 +1308,6 @@ public static function getWhereCriteria($nott, $itemtype, $ID, $searchtype, $val ]; } return []; - - case "glpi_tickets.global_validation": - case "glpi_ticketvalidations.status": - case "glpi_changes.global_validation": - case "glpi_changevalidations.status": - if ($val !== 'can' && !is_numeric($val)) { - return []; - } - $tocheck = []; - if ($val === 'can') { - $tocheck = \CommonITILValidation::getCanValidationStatusArray(); - } else { - $tocheck = [$val]; - } - if ($nott) { - return [ - "$table.$field" => ['NOT IN', $tocheck] - ]; - } - return [ - "$table.$field" => $tocheck - ]; - case "glpi_notifications.event": if (in_array($searchtype, ['equals', 'notequals']) && strpos($val, \Search::SHORTSEP)) { $not = 'notequals' === $searchtype ? 'NOT' : ''; diff --git a/src/Glpi/Search/SearchOption.php b/src/Glpi/Search/SearchOption.php index 62769832187..e2733ae1f4a 100644 --- a/src/Glpi/Search/SearchOption.php +++ b/src/Glpi/Search/SearchOption.php @@ -776,4 +776,53 @@ public static function generateAProbablyUniqueId(string $string_identifier, ?str return $generated_id; } + + public function getTableField(): string + { + return "{$this['table']}.{$this['field']}"; + } + + /** + * @param class-string<\CommonDBTM> $itemtype + * @param bool $meta + * @return string + */ + public function getTableMetaSuffix(string $itemtype, bool $meta = false): string + { + return ($meta && $itemtype::getTable() !== $this['table']) ? ("_" . $itemtype) : ''; + } + + /** + * @param class-string<\CommonDBTM> $itemtype + * @param bool $meta + * @return string + */ + public function getTableReference(string $itemtype, bool $meta = false): string + { + $table = $this['table']; + $is_fkey_composite_on_self = getTableNameForForeignKeyField($this["linkfield"]) === $table + && $this["linkfield"] !== getForeignKeyFieldForTable($table); + $orig_table = SearchEngine::getOrigTableName($itemtype); + if ( + ($table !== 'asset_types') + && ($is_fkey_composite_on_self || $table !== $orig_table) + && ($this["linkfield"] !== getForeignKeyFieldForTable($table)) + ) { + $table .= "_" . $this["linkfield"]; + } + + if (isset($this['joinparams'])) { + $complexjoin = \Search::computeComplexJoinID($this['joinparams']); + + if (!empty($complexjoin)) { + $table .= "_" . $complexjoin; + } + } + + if ($meta && $itemtype::getTable() !== $this['table']) { + $table .= "_" . $itemtype; + } + + return $table; + } } diff --git a/src/Group.php b/src/Group.php index b192ee6fac2..d1292785b5f 100644 --- a/src/Group.php +++ b/src/Group.php @@ -37,11 +37,13 @@ use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; use Glpi\Search\Provider\SQLProvider; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\SearchOption; /** * Group class **/ -class Group extends CommonTreeDropdown +class Group extends CommonTreeDropdown implements AdvancedSearchInterface { use Glpi\Features\Clonable; @@ -986,4 +988,74 @@ public static function updateLastGroupChange() Session::loadGroups(); } } + + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array + { + return null; + } + + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array + { + return null; + } + + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array + { + $table = $opt->getTableReference($itemtype, $meta); + $field = $opt['field']; + if ($field === 'completename') { + if ($val === 'mygroups') { + switch ($searchtype) { + case 'equals': + if (count($_SESSION['glpigroups']) === 0) { + return []; + } + return [ + "$table.id" => $_SESSION['glpigroups'] + ]; + + case 'notequals': + if (count($_SESSION['glpigroups']) === 0) { + return []; + } + return [ + "$table.id" => ['NOT IN', $_SESSION['glpigroups']] + ]; + + case 'under': + if (count($_SESSION['glpigroups']) === 0) { + return []; + } + $groups = $_SESSION['glpigroups']; + foreach ($_SESSION['glpigroups'] as $g) { + $groups += getSonsOf($opt['table'], $g); + } + $groups = array_unique($groups); + return [ + "$table.id" => $groups + ]; + + case 'notunder': + if (count($_SESSION['glpigroups']) === 0) { + return []; + } + $groups = $_SESSION['glpigroups']; + foreach ($_SESSION['glpigroups'] as $g) { + $groups += getSonsOf($opt['table'], $g); + } + $groups = array_unique($groups); + return [ + "$table.id" => ['NOT IN', $groups] + ]; + + case 'empty': + $criteria = []; + $fn_append_with_search($criteria, "$table.id"); + return $criteria; + } + } + } + + return null; + } } diff --git a/src/User.php b/src/User.php index 252ee90ed5c..467320d317d 100644 --- a/src/User.php +++ b/src/User.php @@ -41,9 +41,12 @@ use Glpi\DBAL\QuerySubQuery; use Glpi\Exception\ForgetPasswordException; use Glpi\Plugin\Hooks; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\Provider\SQLProvider; +use Glpi\Search\SearchOption; use Sabre\VObject; -class User extends CommonDBTM +class User extends CommonDBTM implements AdvancedSearchInterface { use Glpi\Features\Clonable { Glpi\Features\Clonable::computeCloneName as baseComputeCloneName; @@ -7324,14 +7327,158 @@ public function toggleSavedSearchPin(string $itemtype): bool return false; } - $all_pinned = importArrayFromDB($this->fields['savedsearches_pinned']); + $all_pinned = importArrayFromDB($this->fields['savedsearches_pinned']); $already_pinned = $all_pinned[$itemtype] ?? 0; $all_pinned[$itemtype] = $already_pinned ? 0 : 1; return $this->update([ - 'id' => $this->fields['id'], + 'id' => $this->fields['id'], 'savedsearches_pinned' => exportArrayToDB($all_pinned), ]); } + + public static function getSQLDefaultSelectCriteria(string $itemtype): ?array + { + return null; + } + + public static function getSQLSelectCriteria(string $itemtype, SearchOption $opt, bool $meta = false, string $meta_type = ''): ?array + { + return null; + } + + public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta, callable $fn_append_with_search): ?array + { + global $DB; + + $table = $opt->getTableReference($itemtype, $meta); + $field = $opt['field']; + + if ($field === 'name') { + if ($val === 'myself') { + switch ($searchtype) { + case 'equals': + return [ + "$table.id" => $_SESSION['glpiID'] + ]; + + case 'notequals': + return [ + "$table.id" => ['<>', $_SESSION['glpiID']] + ]; + } + } + + if ($itemtype === self::class) { + $criteria = ['OR' => []]; + if (in_array($searchtype, ['equals', 'notequals'])) { + $fn_append_with_search($criteria['OR'], "$table.id"); + + if ($searchtype === 'notequals') { + $nott = !$nott; + } + + // Add NULL if $val = 0 and not negative search + // Or negative search on real value + if ((!$nott && ($val == 0)) || ($nott && ($val != 0))) { + $criteria['OR'][] = ["$table.id" => null]; + } + + return $criteria; + } + return [new QueryExpression(SQLProvider::makeTextCriteria("`$table`.`$field`", $val, $nott, ''))]; + } + + if ($_SESSION["glpinames_format"] == \User::FIRSTNAME_BEFORE) { + $name1 = 'firstname'; + $name2 = 'realname'; + } else { + $name1 = 'realname'; + $name2 = 'firstname'; + } + + if (in_array($searchtype, ['equals', 'notequals'])) { + $criteria = [ + 'OR' => [] + ]; + $fn_append_with_search($criteria['OR'], "$table.id"); + if ($val == 0) { + if ($searchtype === 'notequals') { + $criteria['OR'][] = [ + 'NOT' => ["$table.id" => null] + ]; + } else { + $criteria['OR'][] = [ + "$table.id" => null + ]; + } + } + return $criteria; + } else if ($searchtype === 'empty') { + $criteria = []; + $fn_append_with_search($criteria, "$table.id"); + return $criteria; + } + $toadd = ''; + + $tmplink = 'OR'; + if ($nott) { + $tmplink = 'AND'; + } + + if (is_a($itemtype, \CommonITILObject::class, true)) { + $itil_user_tables = ['glpi_tickets_users', 'glpi_changes_users', 'glpi_problems_users']; + $has_join = isset($opt["joinparams"]["beforejoin"]["table"], $opt["joinparams"]["beforejoin"]["joinparams"]); + if ($has_join && in_array($opt["joinparams"]["beforejoin"]["table"], $itil_user_tables, true)) { + $bj = $opt["joinparams"]["beforejoin"]; + $linktable = $bj['table'] . '_' . \Search::computeComplexJoinID($bj['joinparams']) . $opt->getTableMetaSuffix($itemtype, $meta); + //$toadd = "`$linktable`.`alternative_email` $SEARCH $tmplink "; + $toadd = SQLProvider::makeTextCriteria( + "`$linktable`.`alternative_email`", + $val, + $nott, + $tmplink + ); + // Remove $tmplink (may have spaces around it) from front of $toadd + $toadd = preg_replace('/^\s*' . preg_quote($tmplink, '/') . '\s*/', '', $toadd); + if ($val === '^$') { + return [ + 'OR' => [ + "$linktable.users_id" => null, + "$linktable.alternative_email" => null + ] + ]; + } + } + } + $criteria = [ + $tmplink => [] + ]; + $fn_append_with_search($criteria[$tmplink], "$table.$name1"); + $fn_append_with_search($criteria[$tmplink], "$table.$name2"); + $fn_append_with_search($criteria[$tmplink], "$table.$field"); + $fn_append_with_search( + $criteria[$tmplink], + QueryFunction::concat([ + "$table.$name1", + new QueryExpression($DB::quoteValue(' ')), + "$table.$name2" + ]) + ); + if ($nott && ($val !== 'NULL') && ($val !== 'null')) { + $criteria = [ + $tmplink => [ + 'OR' => [ + $criteria, + "$table.$field" => null + ], + new QueryExpression($toadd) + ] + ]; + } + return $criteria; + } + return null; + } }