diff --git a/src/CommonITILObject.php b/src/CommonITILObject.php index 85f0f360a22..2ea2a890a2a 100644 --- a/src/CommonITILObject.php +++ b/src/CommonITILObject.php @@ -10494,10 +10494,20 @@ protected function processRules(int $condition, array &$input, int $entid = -1): } } - public static function getSQLWhereCriteria(string $itemtype, SearchOption $opt, bool $nott, string $searchtype, mixed $val, bool $meta = false): ?array + 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($opt['table'], [Ticket::getTable(), Change::getTable(), Problem::getTable()], true)) { + if (!in_array($itemtype, [Ticket::class, Change::class, Problem::class], true)) { return null; } diff --git a/src/Entity.php b/src/Entity.php index a427c39e41b..500bad76d5a 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; @@ -4409,4 +4412,24 @@ public static function badgeCompletenameLinkById(int $entity_id): ?string } return null; } + + 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 7ff6dc02992..75ac4875021 100644 --- a/src/FieldUnicity.php +++ b/src/FieldUnicity.php @@ -33,10 +33,13 @@ * --------------------------------------------------------------------- */ +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; @@ -642,7 +645,6 @@ public static function showDoubles(FieldUnicity $unicity) echo ""; } - /** * Display debug information for current object **/ @@ -663,9 +665,25 @@ public function showDebug() NotificationEvent::debugEvent($this, $params); } - 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/Group.php b/src/Group.php index 72aa20bb997..ae9ae842f01 100644 --- a/src/Group.php +++ b/src/Group.php @@ -35,11 +35,13 @@ use Glpi\Application\View\TemplateRenderer; use Glpi\DBAL\QuerySubQuery; +use Glpi\Search\AdvancedSearchInterface; +use Glpi\Search\SearchOption; /** * Group class **/ -class Group extends CommonTreeDropdown +class Group extends CommonTreeDropdown implements AdvancedSearchInterface { public $dohistory = true; @@ -1192,4 +1194,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/Search/AdvancedSearchInterface.php b/src/Search/AdvancedSearchInterface.php index db78bb0e331..9708981a1b5 100644 --- a/src/Search/AdvancedSearchInterface.php +++ b/src/Search/AdvancedSearchInterface.php @@ -35,16 +35,37 @@ 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 $nott - * @param string $searchtype - * @param mixed $val * @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 = false): ?array; + 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/Search/Provider/SQLProvider.php b/src/Search/Provider/SQLProvider.php index 96986796bd0..7056ad973db 100644 --- a/src/Search/Provider/SQLProvider.php +++ b/src/Search/Provider/SQLProvider.php @@ -105,24 +105,21 @@ public static function getDefaultSelectCriteria(string $itemtype): array $mayberecursive = $item->maybeRecursive(); } $ret = []; - switch ($itemtype) { - case 'FieldUnicity': - $ret[] = "`glpi_fieldunicities`.`itemtype` AS ITEMTYPE"; - break; + if (method_exists($itemtype, 'getSQLDefaultSelectCriteria')) { + $criteria = $itemtype::getSQLDefaultSelectCriteria($itemtype); + if ($criteria !== null) { + $ret = array_merge($ret, $criteria); + } + } - default: - // Plugin can override core definition for its type - if ($plug = isPluginItemType($itemtype)) { - $default_select = \Plugin::doOneHook($plug['plugin'], 'addDefaultSelect', $itemtype); - if ($default_select !== "") { - $ret[] = new QueryExpression(rtrim($default_select, ' ,')); - } - } + // Plugin can override core definition for its type + if ($plug = isPluginItemType($itemtype)) { + $default_select = \Plugin::doOneHook($plug['plugin'], 'addDefaultSelect', $itemtype); + if ($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"); } @@ -1010,12 +1007,6 @@ public static function getWhereCriteria($nott, $itemtype, $ID, $searchtype, $val return []; } $opt = new SearchOption($searchopt[$ID]); - if (method_exists($itemtype, 'getSQLWhereCriteria')) { - $criteria = $itemtype::getSQLWhereCriteria($itemtype, $opt, $nott, $searchtype, $val, $meta); - if ($criteria !== null) { - return $criteria; - } - } $table = $opt["table"]; $field = $opt["field"]; @@ -1184,186 +1175,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)) { diff --git a/src/Search/SearchOption.php b/src/Search/SearchOption.php index 0e29348305f..0f8caf7bc43 100644 --- a/src/Search/SearchOption.php +++ b/src/Search/SearchOption.php @@ -675,4 +675,48 @@ 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/User.php b/src/User.php index 02f17ee9116..854531b0986 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; @@ -7069,4 +7072,148 @@ final public function isUserNotificationEnable(): bool return $user_pref; } + + 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; + } }