Sindbad~EG File Manager
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
use Glpi\Application\View\TemplateRenderer;
use Glpi\RichText\RichText;
use Glpi\Socket;
use Glpi\Toolbox\DataExport;
use Glpi\Toolbox\Sanitizer;
/**
* Search Class
*
* Generic class for Search Engine
**/
class Search
{
/**
* Default number of items displayed in global search
* @var int
* @see GLOBAL_SEARCH
*/
const GLOBAL_DISPLAY_COUNT = 10;
// EXPORT TYPE
/**
* The global search view (Search across many item types).
* This is NOT the same as the AllAssets view which is just a special itemtype.
* @var int
*/
const GLOBAL_SEARCH = -1;
/**
* The standard view.
* This includes the following sub-views:
* - Table/List
* - Map
* - Browse
* @var int
*/
const HTML_OUTPUT = 0;
/**
* SYLK export format
* @var int
*/
const SYLK_OUTPUT = 1;
/**
* PDF export format (Landscape mode)
* @var int
*/
const PDF_OUTPUT_LANDSCAPE = 2;
/**
* CSV export format
* @var int
*/
const CSV_OUTPUT = 3;
/**
* PDF export format (Portrait mode)
* @var int
*/
const PDF_OUTPUT_PORTRAIT = 4;
/**
* Names list export format
* @var int
*/
const NAMES_OUTPUT = 5;
/**
* Placeholder for a <br> line break
* @var string
*/
const LBBR = '#LBBR#';
/**
* Placeholder for a <hr> line break
* @var string
*/
const LBHR = '#LBHR#';
/**
* Separator used to separate values of a same element in CONCAT MySQL function.
*
* @var string
* @see LONGSEP
*/
const SHORTSEP = '$#$';
/**
* Separator used to separate each element in GROUP_CONCAT MySQL function.
*
* @var string
* @see SHORTSEP
*/
const LONGSEP = '$$##$$';
/**
* Placeholder for a null value
* @var string
*/
const NULLVALUE = '__NULL__';
/**
* The output format for the search results
* @var int
*/
public static $output_type = self::HTML_OUTPUT;
public static $search = [];
/**
* Display search engine for an type
*
* @param string $itemtype Item type to manage
*
* @return void
**/
public static function show($itemtype)
{
$params = self::manageParams($itemtype, $_GET);
echo "<div class='search_page row'>";
TemplateRenderer::getInstance()->display('layout/parts/saved_searches.html.twig', [
'itemtype' => $itemtype,
]);
echo "<div class='col search-container'>";
if (
$itemtype == "Ticket"
&& $default = Glpi\Dashboard\Grid::getDefaultDashboardForMenu('mini_ticket', true)
) {
$dashboard = new Glpi\Dashboard\Grid($default, 33, 1);
$dashboard->show(true);
}
self::showGenericSearch($itemtype, $params);
if ($params['as_map'] == 1) {
self::showMap($itemtype, $params);
} elseif ($params['browse'] == 1) {
$itemtype::showBrowseView($itemtype, $params);
} else {
self::showList($itemtype, $params);
}
echo "</div>";
echo "</div>";
}
/**
* Display result table for search engine for an type
*
* @param class-string<CommonDBTM> $itemtype Item type to manage
* @param array $params Search params passed to
* prepareDatasForSearch function
* @param array $forcedisplay Array of columns to display (default empty
* = use display pref and search criteria)
*
* @return void
**/
public static function showList(
$itemtype,
$params,
array $forcedisplay = []
) {
$data = self::getDatas($itemtype, $params, $forcedisplay);
switch ($data['display_type']) {
case self::CSV_OUTPUT:
case self::PDF_OUTPUT_LANDSCAPE:
case self::PDF_OUTPUT_PORTRAIT:
case self::SYLK_OUTPUT:
case self::NAMES_OUTPUT:
self::outputData($data);
break;
case self::GLOBAL_SEARCH:
case self::HTML_OUTPUT:
default:
self::displayData($data);
break;
}
}
/**
* Display result table for search engine for an type as a map
*
* @param class-string<CommonDBTM> $itemtype Item type to manage
* @param array $params Search params passed to prepareDatasForSearch function
*
* @return void
**/
public static function showMap($itemtype, $params)
{
global $CFG_GLPI;
if ($itemtype == 'Location') {
$latitude = 21;
$longitude = 20;
} else if ($itemtype == 'Entity') {
$latitude = 67;
$longitude = 68;
} else {
$latitude = 998;
$longitude = 999;
}
$params['criteria'][] = [
'link' => 'AND NOT',
'field' => $latitude,
'searchtype' => 'contains',
'value' => 'NULL'
];
$params['criteria'][] = [
'link' => 'AND NOT',
'field' => $longitude,
'searchtype' => 'contains',
'value' => 'NULL'
];
$data = self::getDatas($itemtype, $params);
self::displayData($data);
if ($data['data']['totalcount'] > 0) {
$target = $data['search']['target'];
$criteria = $data['search']['criteria'];
array_pop($criteria);
array_pop($criteria);
$criteria[] = [
'link' => 'AND',
'field' => ($itemtype == 'Location' || $itemtype == 'Entity') ? 1 : (($itemtype == 'Ticket') ? 83 : 3),
'searchtype' => 'equals',
'value' => 'CURLOCATION'
];
$globallinkto = Toolbox::append_params(
[
'criteria' => Sanitizer::unsanitize($criteria),
'metacriteria' => Sanitizer::unsanitize($data['search']['metacriteria'])
],
'&'
);
$sort_params = Toolbox::append_params([
'sort' => $data['search']['sort'],
'order' => $data['search']['order']
], '&');
$parameters = "as_map=0&" . $sort_params . '&' .
$globallinkto;
if (strpos($target, '?') == false) {
$fulltarget = $target . "?" . $parameters;
} else {
$fulltarget = $target . "&" . $parameters;
}
$typename = class_exists($itemtype) ? $itemtype::getTypeName($data['data']['totalcount']) : $itemtype;
echo "<div class='card border-top-0 rounded-0 search-as-map'>";
echo "<div class='card-body px-0' id='map_container'>";
echo "<small class='text-muted p-1'>" . __('Search results for localized items only') . "</small>";
$js = "$(function() {
var map = initMap($('#map_container'), 'map', 'full');
_loadMap(map, '$itemtype');
});
var _loadMap = function(map_elt, itemtype) {
L.AwesomeMarkers.Icon.prototype.options.prefix = 'far';
var _micon = 'circle';
var stdMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'blue'
});
var aMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'cadetblue'
});
var bMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'purple'
});
var cMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'darkpurple'
});
var dMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'red'
});
var eMarker = L.AwesomeMarkers.icon({
icon: _micon,
markerColor: 'darkred'
});
//retrieve geojson data
map_elt.spin(true);
$.ajax({
dataType: 'json',
method: 'POST',
url: '{$CFG_GLPI['root_doc']}/ajax/map.php',
data: {
itemtype: itemtype,
params: " . json_encode($params) . "
}
}).done(function(data) {
var _points = data.points;
var _markers = L.markerClusterGroup({
iconCreateFunction: function(cluster) {
var childCount = cluster.getChildCount();
var markers = cluster.getAllChildMarkers();
var n = 0;
for (var i = 0; i < markers.length; i++) {
n += markers[i].count;
}
var c = ' marker-cluster-';
if (n < 10) {
c += 'small';
} else if (n < 100) {
c += 'medium';
} else {
c += 'large';
}
return new L.DivIcon({ html: '<div><span>' + n + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
}
});
$.each(_points, function(index, point) {
var _title = '<strong>' + point.title + '</strong><br/><a href=\''+'$fulltarget'.replace(/CURLOCATION/, point.loc_id)+'\'>" . sprintf(__('%1$s %2$s'), 'COUNT', $typename) . "'.replace(/COUNT/, point.count)+'</a>';
if (point.types) {
$.each(point.types, function(tindex, type) {
_title += '<br/>" . sprintf(__('%1$s %2$s'), 'COUNT', 'TYPE') . "'.replace(/COUNT/, type.count).replace(/TYPE/, type.name);
});
}
var _icon = stdMarker;
if (point.count < 10) {
_icon = stdMarker;
} else if (point.count < 100) {
_icon = aMarker;
} else if (point.count < 1000) {
_icon = bMarker;
} else if (point.count < 5000) {
_icon = cMarker;
} else if (point.count < 10000) {
_icon = dMarker;
} else {
_icon = eMarker;
}
var _marker = L.marker([point.lat, point.lng], { icon: _icon, title: point.title });
_marker.count = point.count;
_marker.bindPopup(_title);
_markers.addLayer(_marker);
});
map_elt.addLayer(_markers);
map_elt.fitBounds(
_markers.getBounds(), {
padding: [50, 50],
maxZoom: 12
}
);
}).fail(function (response) {
var _data = response.responseJSON;
var _message = '" . __s('An error occurred loading data :(') . "';
if (_data.message) {
_message = _data.message;
}
var fail_info = L.control();
fail_info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'fail_info');
this._div.innerHTML = _message + '<br/><span id=\'reload_data\'><i class=\'fa fa-sync\'></i> " . __s('Reload') . "</span>';
return this._div;
};
fail_info.addTo(map_elt);
$('#reload_data').on('click', function() {
$('.fail_info').remove();
_loadMap(map_elt);
});
}).always(function() {
//hide spinner
map_elt.spin(false);
});
}
";
echo Html::scriptBlock($js);
echo "</div>"; // .card-body
echo "</div>"; // .card
}
}
/**
* Get data based on search parameters
*
* @since 0.85
*
* @param class-string<CommonDBTM> $itemtype Item type to manage
* @param array $params Search params passed to prepareDatasForSearch function
* @param array $forcedisplay Array of columns to display (default empty = empty use display pref and search criteria)
*
* @return array The data
**/
public static function getDatas($itemtype, $params, array $forcedisplay = [])
{
$data = self::prepareDatasForSearch($itemtype, $params, $forcedisplay);
self::constructSQL($data);
self::constructData($data);
return $data;
}
/**
* Prepare search criteria to be used for a search
*
* @since 0.85
*
* @param class-string<CommonDBTM> $itemtype Item type
* @param array $params Array of parameters
* may include sort, order, start, list_limit, deleted, criteria, metacriteria
* @param array $forcedisplay Array of columns to display (default empty = empty use display pref and search criterias)
*
* @return array prepare to be used for a search (include criteria and others needed information)
**/
public static function prepareDatasForSearch($itemtype, array $params, array $forcedisplay = [])
{
global $CFG_GLPI;
// Default values of parameters
$p['criteria'] = [];
$p['metacriteria'] = [];
$p['sort'] = ['1'];
$p['order'] = ['ASC'];
$p['start'] = 0;//
$p['is_deleted'] = 0;
$p['export_all'] = 0;
if (class_exists($itemtype)) {
$p['target'] = $itemtype::getSearchURL();
} else {
$p['target'] = Toolbox::getItemTypeSearchURL($itemtype);
}
$p['display_type'] = self::HTML_OUTPUT;
$p['showmassiveactions'] = true;
$p['dont_flush'] = false;
$p['show_pager'] = true;
$p['show_footer'] = true;
$p['no_sort'] = false;
$p['list_limit'] = $_SESSION['glpilist_limit'];
$p['massiveactionparams'] = [];
foreach ($params as $key => $val) {
switch ($key) {
case 'order':
if (!is_array($val)) {
// Backward compatibility with GLPI < 10.0 links
if (in_array($val, ['ASC', 'DESC'])) {
$p[$key] = [$val];
}
break;
}
$p[$key] = $val;
break;
case 'sort':
if (!is_array($val)) {
// Backward compatibility with GLPI < 10.0 links
$val = (int) $val;
if ($val >= 0) {
$p[$key] = [$val];
}
break;
}
$p[$key] = $val;
break;
case 'is_deleted':
if ($val == 1) {
$p[$key] = '1';
}
break;
default:
$p[$key] = $val;
break;
}
}
// Set display type for export if define
if (isset($p['display_type'])) {
// Limit to 10 element
if ($p['display_type'] == self::GLOBAL_SEARCH) {
$p['list_limit'] = self::GLOBAL_DISPLAY_COUNT;
}
}
if ($p['export_all']) {
$p['start'] = 0;
}
$data = [];
$data['search'] = $p;
$data['itemtype'] = $itemtype;
// Instanciate an object to access method
$data['item'] = null;
if ($itemtype != AllAssets::getType()) {
$data['item'] = getItemForItemtype($itemtype);
}
$data['display_type'] = $data['search']['display_type'];
if (!$CFG_GLPI['allow_search_all']) {
foreach ($p['criteria'] as $val) {
if (isset($val['field']) && $val['field'] == 'all') {
Html::displayRightError();
}
}
}
if (!$CFG_GLPI['allow_search_view']) {
foreach ($p['criteria'] as $val) {
if (isset($val['field']) && $val['field'] == 'view') {
Html::displayRightError();
}
}
}
/// Get the items to display
// Add searched items
$forcetoview = false;
if (is_array($forcedisplay) && count($forcedisplay)) {
$forcetoview = true;
}
$data['search']['all_search'] = false;
$data['search']['view_search'] = false;
// If no research limit research to display item and compute number of item using simple request
$data['search']['no_search'] = true;
$data['toview'] = self::addDefaultToView($itemtype, $params);
$data['meta_toview'] = [];
if (!$forcetoview) {
// Add items to display depending of personal prefs
$displaypref = DisplayPreference::getForTypeUser($itemtype, Session::getLoginUserID());
if (count($displaypref)) {
foreach ($displaypref as $val) {
array_push($data['toview'], $val);
}
}
} else {
$data['toview'] = array_merge($data['toview'], $forcedisplay);
}
if (count($p['criteria']) > 0) {
// use a recursive closure to push searchoption when using nested criteria
$parse_criteria = function ($criteria) use (&$parse_criteria, &$data) {
foreach ($criteria as $criterion) {
// recursive call
if (isset($criterion['criteria'])) {
$parse_criteria($criterion['criteria']);
} else {
// normal behavior
if (
isset($criterion['field'])
&& !in_array($criterion['field'], $data['toview'])
) {
if (
$criterion['field'] != 'all'
&& $criterion['field'] != 'view'
&& (!isset($criterion['meta'])
|| !$criterion['meta'])
) {
array_push($data['toview'], $criterion['field']);
} else if ($criterion['field'] == 'all') {
$data['search']['all_search'] = true;
} else if ($criterion['field'] == 'view') {
$data['search']['view_search'] = true;
}
}
if (
isset($criterion['value'])
&& (strlen($criterion['value']) > 0)
) {
$data['search']['no_search'] = false;
}
}
}
};
// call the closure
$parse_criteria($p['criteria']);
}
if (count($p['metacriteria'])) {
$data['search']['no_search'] = false;
}
// Add order item
$to_add_view = array_diff($p['sort'], $data['toview']);
array_push($data['toview'], ...$to_add_view);
// Special case for Ticket : put ID in front
if ($itemtype == 'Ticket') {
array_unshift($data['toview'], 2);
}
$limitsearchopt = self::getCleanedOptions($itemtype);
// Clean and reorder toview
$tmpview = [];
foreach ($data['toview'] as $val) {
if (isset($limitsearchopt[$val]) && !in_array($val, $tmpview)) {
$tmpview[] = $val;
}
}
$data['toview'] = $tmpview;
$data['tocompute'] = $data['toview'];
// Force item to display
if ($forcetoview) {
foreach ($data['toview'] as $val) {
if (!in_array($val, $data['tocompute'])) {
array_push($data['tocompute'], $val);
}
}
}
return $data;
}
/**
* Construct SQL request depending of search parameters
*
* Add to data array a field sql containing an array of requests :
* search : request to get items limited to wanted ones
* count : to count all items based on search criterias
* may be an array a request : need to add counts
* maybe empty : use search one to count
*
* @since 0.85
*
* @param array $data Array of search datas prepared to generate SQL
*
* @return void|false May return false if the search request data is invalid
**/
public static function constructSQL(array &$data)
{
global $DB, $CFG_GLPI;
if (!isset($data['itemtype'])) {
return false;
}
$data['sql']['count'] = [];
$data['sql']['search'] = '';
$searchopt = &self::getOptions($data['itemtype']);
$blacklist_tables = [];
$orig_table = self::getOrigTableName($data['itemtype']);
if (isset($CFG_GLPI['union_search_type'][$data['itemtype']])) {
$itemtable = $CFG_GLPI['union_search_type'][$data['itemtype']];
$blacklist_tables[] = $orig_table;
} else {
$itemtable = $orig_table;
}
// hack for AllAssets and ReservationItem
if (isset($CFG_GLPI['union_search_type'][$data['itemtype']])) {
$entity_restrict = true;
} else {
$entity_restrict = $data['item']->isEntityAssign() && $data['item']->isField('entities_id');
}
// Construct the request
//// 1 - SELECT
// request currentuser for SQL supervision, not displayed
$SELECT = "SELECT DISTINCT `$itemtable`.`id` AS id, '" . Toolbox::addslashes_deep($_SESSION['glpiname']) . "' AS currentuser,
" . self::addDefaultSelect($data['itemtype']);
// Add select for all toview item
foreach ($data['toview'] as $val) {
$SELECT .= self::addSelect($data['itemtype'], $val);
}
if (isset($data['search']['as_map']) && $data['search']['as_map'] == 1 && $data['itemtype'] != 'Entity') {
$SELECT .= ' `glpi_locations`.`id` AS loc_id, ';
}
//// 2 - FROM AND LEFT JOIN
// Set reference table
$FROM = " FROM `$itemtable`";
// Init already linked tables array in order not to link a table several times
$already_link_tables = [];
// Put reference table
array_push($already_link_tables, $itemtable);
// Add default join
$COMMONLEFTJOIN = self::addDefaultJoin($data['itemtype'], $itemtable, $already_link_tables);
$FROM .= $COMMONLEFTJOIN;
// Add all table for toview items
foreach ($data['tocompute'] as $val) {
if (!in_array($searchopt[$val]["table"], $blacklist_tables)) {
$FROM .= self::addLeftJoin(
$data['itemtype'],
$itemtable,
$already_link_tables,
$searchopt[$val]["table"],
$searchopt[$val]["linkfield"],
0,
0,
$searchopt[$val]["joinparams"],
$searchopt[$val]["field"]
);
}
}
// Search all case :
if ($data['search']['all_search']) {
foreach ($searchopt as $key => $val) {
// Do not search on Group Name
if (is_array($val) && isset($val['table'])) {
if (!in_array($searchopt[$key]["table"], $blacklist_tables)) {
$FROM .= self::addLeftJoin(
$data['itemtype'],
$itemtable,
$already_link_tables,
$searchopt[$key]["table"],
$searchopt[$key]["linkfield"],
0,
0,
$searchopt[$key]["joinparams"],
$searchopt[$key]["field"]
);
}
}
}
}
//// 3 - WHERE
// default string
$COMMONWHERE = self::addDefaultWhere($data['itemtype']);
$first = empty($COMMONWHERE);
// Add deleted if item have it
if ($data['item'] && $data['item']->maybeDeleted()) {
$LINK = " AND ";
if ($first) {
$LINK = " ";
$first = false;
}
$COMMONWHERE .= $LINK . "`$itemtable`.`is_deleted` = " . (int)$data['search']['is_deleted'] . " ";
}
// Remove template items
if ($data['item'] && $data['item']->maybeTemplate()) {
$LINK = " AND ";
if ($first) {
$LINK = " ";
$first = false;
}
$COMMONWHERE .= $LINK . "`$itemtable`.`is_template` = 0 ";
}
// Add Restrict to current entities
if ($entity_restrict) {
$LINK = " AND ";
if ($first) {
$LINK = " ";
$first = false;
}
if ($data['itemtype'] == 'Entity') {
$COMMONWHERE .= getEntitiesRestrictRequest($LINK, $itemtable);
} else if (isset($CFG_GLPI["union_search_type"][$data['itemtype']])) {
// Will be replace below in Union/Recursivity Hack
$COMMONWHERE .= $LINK . " ENTITYRESTRICT ";
} else {
$COMMONWHERE .= getEntitiesRestrictRequest(
$LINK,
$itemtable,
'',
'',
$data['item']->maybeRecursive() && $data['item']->isField('is_recursive')
);
}
}
$WHERE = "";
$HAVING = "";
// Add search conditions
// If there is search items
if (count($data['search']['criteria'])) {
$WHERE = self::constructCriteriaSQL($data['search']['criteria'], $data, $searchopt);
$HAVING = self::constructCriteriaSQL($data['search']['criteria'], $data, $searchopt, true);
// if criteria (with meta flag) need additional join/from sql
self::constructAdditionalSqlForMetacriteria($data['search']['criteria'], $SELECT, $FROM, $already_link_tables, $data);
}
//// 4 - ORDER
$ORDER = " ORDER BY `id` ";
$sort_fields = [];
$sort_count = count($data['search']['sort']);
for ($i = 0; $i < $sort_count; $i++) {
foreach ($data['tocompute'] as $val) {
if ($data['search']['sort'][$i] == $val) {
$sort_fields[] = [
'searchopt_id' => $data['search']['sort'][$i],
'order' => $data['search']['order'][$i] ?? null
];
}
}
}
if (count($sort_fields)) {
$ORDER = self::addOrderBy($data['itemtype'], $sort_fields);
}
$SELECT = rtrim(trim($SELECT), ',');
//// 7 - Manage GROUP BY
$GROUPBY = "";
// Meta Search / Search All / Count tickets
$criteria_with_meta = array_filter($data['search']['criteria'], function ($criterion) {
return isset($criterion['meta'])
&& $criterion['meta'];
});
if (
(count($data['search']['metacriteria']))
|| count($criteria_with_meta)
|| !empty($HAVING)
|| $data['search']['all_search']
) {
$GROUPBY = " GROUP BY `$itemtable`.`id`";
}
if (empty($GROUPBY)) {
foreach ($data['toview'] as $val2) {
if (!empty($GROUPBY)) {
break;
}
if (isset($searchopt[$val2]["forcegroupby"])) {
$GROUPBY = " GROUP BY `$itemtable`.`id`";
}
}
}
$LIMIT = "";
$numrows = 0;
//No search : count number of items using a simple count(ID) request and LIMIT search
if ($data['search']['no_search']) {
$LIMIT = " LIMIT " . (int)$data['search']['start'] . ", " . (int)$data['search']['list_limit'];
$count = "count(DISTINCT `$itemtable`.`id`)";
// request currentuser for SQL supervision, not displayed
$query_num = "SELECT $count,
'" . Toolbox::addslashes_deep($_SESSION['glpiname']) . "' AS currentuser
FROM `$itemtable`" .
$COMMONLEFTJOIN;
$first = true;
if (!empty($COMMONWHERE)) {
$LINK = " AND ";
if ($first) {
$LINK = " WHERE ";
$first = false;
}
$query_num .= $LINK . $COMMONWHERE;
}
// Union Search :
if (isset($CFG_GLPI["union_search_type"][$data['itemtype']])) {
$tmpquery = $query_num;
foreach ($CFG_GLPI[$CFG_GLPI["union_search_type"][$data['itemtype']]] as $ctype) {
$ctable = $ctype::getTable();
if (
($citem = getItemForItemtype($ctype))
&& $citem->canView()
) {
// State case
if ($data['itemtype'] == AllAssets::getType()) {
$query_num = str_replace(
$CFG_GLPI["union_search_type"][$data['itemtype']],
$ctable,
$tmpquery
);
$query_num = str_replace($data['itemtype'], $ctype, $query_num);
$query_num .= " AND `$ctable`.`id` IS NOT NULL ";
// Add deleted if item have it
if ($citem && $citem->maybeDeleted()) {
$query_num .= " AND `$ctable`.`is_deleted` = 0 ";
}
// Remove template items
if ($citem && $citem->maybeTemplate()) {
$query_num .= " AND `$ctable`.`is_template` = 0 ";
}
} else {// Ref table case
$reftable = $data['itemtype']::getTable();
if ($data['item'] && $data['item']->maybeDeleted()) {
$tmpquery = str_replace(
"`" . $CFG_GLPI["union_search_type"][$data['itemtype']] . "`.
`is_deleted`",
"`$reftable`.`is_deleted`",
$tmpquery
);
}
$replace = "FROM `$reftable`
INNER JOIN `$ctable`
ON (`$reftable`.`items_id` =`$ctable`.`id`
AND `$reftable`.`itemtype` = '$ctype')";
$query_num = str_replace(
"FROM `" .
$CFG_GLPI["union_search_type"][$data['itemtype']] . "`",
$replace,
$tmpquery
);
$query_num = str_replace(
$CFG_GLPI["union_search_type"][$data['itemtype']],
$ctable,
$query_num
);
}
$query_num = str_replace(
"ENTITYRESTRICT",
getEntitiesRestrictRequest(
'',
$ctable,
'',
'',
$citem->maybeRecursive()
),
$query_num
);
$data['sql']['count'][] = $query_num;
}
}
} else {
$data['sql']['count'][] = $query_num;
}
}
// If export_all reset LIMIT condition
if ($data['search']['export_all']) {
$LIMIT = "";
}
if (!empty($WHERE) || !empty($COMMONWHERE)) {
if (!empty($COMMONWHERE)) {
$WHERE = ' WHERE ' . $COMMONWHERE . (!empty($WHERE) ? ' AND ( ' . $WHERE . ' )' : '');
} else {
$WHERE = ' WHERE ' . $WHERE . ' ';
}
$first = false;
}
if (!empty($HAVING)) {
$HAVING = ' HAVING ' . $HAVING;
}
// Create QUERY
if (isset($CFG_GLPI["union_search_type"][$data['itemtype']])) {
$first = true;
$QUERY = "";
foreach ($CFG_GLPI[$CFG_GLPI["union_search_type"][$data['itemtype']]] as $ctype) {
$ctable = $ctype::getTable();
if (
($citem = getItemForItemtype($ctype))
&& $citem->canView()
) {
if ($first) {
$first = false;
} else {
$QUERY .= " UNION ";
}
$tmpquery = "";
// AllAssets case
if ($data['itemtype'] == AllAssets::getType()) {
$tmpquery = $SELECT . ", '$ctype' AS TYPE " .
$FROM .
$WHERE;
$tmpquery .= " AND `$ctable`.`id` IS NOT NULL ";
// Add deleted if item have it
if ($citem && $citem->maybeDeleted()) {
$tmpquery .= " AND `$ctable`.`is_deleted` = 0 ";
}
// Remove template items
if ($citem && $citem->maybeTemplate()) {
$tmpquery .= " AND `$ctable`.`is_template` = 0 ";
}
$tmpquery .= $GROUPBY .
$HAVING;
// Replace 'asset_types' by itemtype table name
$tmpquery = str_replace(
$CFG_GLPI["union_search_type"][$data['itemtype']],
$ctable,
$tmpquery
);
// Replace 'AllAssets' by itemtype
// Use quoted value to prevent replacement of AllAssets in column identifiers
$tmpquery = str_replace(
$DB->quoteValue(AllAssets::getType()),
$DB->quoteValue($ctype),
$tmpquery
);
} else {// Ref table case
$reftable = $data['itemtype']::getTable();
$tmpquery = $SELECT . ", '$ctype' AS TYPE,
`$reftable`.`id` AS refID, " . "
`$ctable`.`entities_id` AS ENTITY " .
$FROM .
$WHERE;
if ($data['item']->maybeDeleted()) {
$tmpquery = str_replace(
"`" . $CFG_GLPI["union_search_type"][$data['itemtype']] . "`.
`is_deleted`",
"`$reftable`.`is_deleted`",
$tmpquery
);
}
$replace = "FROM `$reftable`" . "
INNER JOIN `$ctable`" . "
ON (`$reftable`.`items_id`=`$ctable`.`id`" . "
AND `$reftable`.`itemtype` = '$ctype')";
$tmpquery = str_replace(
"FROM `" .
$CFG_GLPI["union_search_type"][$data['itemtype']] . "`",
$replace,
$tmpquery
);
$tmpquery = str_replace(
$CFG_GLPI["union_search_type"][$data['itemtype']],
$ctable,
$tmpquery
);
$name_field = $ctype::getNameField();
$tmpquery = str_replace("`$ctable`.`name`", "`$ctable`.`$name_field`", $tmpquery);
}
$tmpquery = str_replace(
"ENTITYRESTRICT",
getEntitiesRestrictRequest(
'',
$ctable,
'',
'',
$citem->maybeRecursive()
),
$tmpquery
);
// SOFTWARE HACK
if ($ctype == 'Software') {
$tmpquery = str_replace("`glpi_softwares`.`serial`", "''", $tmpquery);
$tmpquery = str_replace("`glpi_softwares`.`otherserial`", "''", $tmpquery);
}
$QUERY .= $tmpquery;
}
}
if (empty($QUERY)) {
echo self::showError($data['display_type']);
return;
}
$QUERY .= str_replace($CFG_GLPI["union_search_type"][$data['itemtype']] . ".", "", $ORDER) .
$LIMIT;
} else {
$QUERY = $SELECT .
$FROM .
$WHERE .
$GROUPBY .
$HAVING .
$ORDER .
$LIMIT;
}
$data['sql']['search'] = $QUERY;
}
/**
* Construct WHERE (or HAVING) part of the sql based on passed criteria
*
* @since 9.4
*
* @param array $criteria list of search criterion, we should have these keys:
* - link (optionnal): AND, OR, NOT AND, NOT OR
* - field: id of the searchoption
* - searchtype: how to match value (contains, equals, etc)
* - value
* @param array $data common array used by search engine,
* contains all the search part (sql, criteria, params, itemtype etc)
* TODO: should be a property of the class
* @param array $searchopt Search options for the current itemtype
* @param boolean $is_having Do we construct sql WHERE or HAVING part
*
* @return string the sql sub string
*/
public static function constructCriteriaSQL($criteria = [], $data = [], $searchopt = [], $is_having = false)
{
$sql = "";
foreach ($criteria as $criterion) {
if (
!isset($criterion['criteria'])
&& (!isset($criterion['value'])
|| strlen($criterion['value']) <= 0)
) {
continue;
}
$itemtype = $data['itemtype'];
$meta = false;
if (
isset($criterion['meta'])
&& $criterion['meta']
&& isset($criterion['itemtype'])
) {
$itemtype = $criterion['itemtype'];
$meta = true;
$meta_searchopt = &self::getOptions($itemtype);
} else {
// Not a meta, use the same search option everywhere
$meta_searchopt = $searchopt;
}
// common search
if (
!isset($criterion['field'])
|| ($criterion['field'] != "all"
&& $criterion['field'] != "view")
) {
$LINK = " ";
$NOT = 0;
$tmplink = "";
if (
isset($criterion['link'])
&& in_array($criterion['link'], array_keys(self::getLogicalOperators()))
) {
if (strstr($criterion['link'], "NOT")) {
$tmplink = " " . str_replace(" NOT", "", $criterion['link']);
$NOT = 1;
} else {
$tmplink = " " . $criterion['link'];
}
} else {
$tmplink = " AND ";
}
// Manage Link if not first item
if (!empty($sql)) {
$LINK = $tmplink;
}
if (isset($criterion['criteria']) && count($criterion['criteria'])) {
$sub_sql = self::constructCriteriaSQL($criterion['criteria'], $data, $meta_searchopt, $is_having);
if (strlen($sub_sql)) {
if ($NOT) {
$sql .= "$LINK NOT($sub_sql)";
} else {
$sql .= "$LINK ($sub_sql)";
}
}
} else if (
isset($meta_searchopt[$criterion['field']]["usehaving"])
|| ($meta && "AND NOT" === $criterion['link'])
) {
if (!$is_having) {
// the having part will be managed in a second pass
continue;
}
$new_having = self::addHaving(
$LINK,
$NOT,
$itemtype,
$criterion['field'],
$criterion['searchtype'],
$criterion['value']
);
if ($new_having !== false) {
$sql .= $new_having;
}
} else {
if ($is_having) {
// the having part has been already managed in the first pass
continue;
}
$new_where = self::addWhere(
$LINK,
$NOT,
$itemtype,
$criterion['field'],
$criterion['searchtype'],
$criterion['value'],
$meta
);
if ($new_where !== false) {
$sql .= $new_where;
}
}
} else if (
isset($criterion['value'])
&& strlen($criterion['value']) > 0
) { // view and all search
$LINK = " OR ";
$NOT = 0;
$globallink = " AND ";
if (isset($criterion['link'])) {
switch ($criterion['link']) {
case "AND":
$LINK = " OR ";
$globallink = " AND ";
break;
case "AND NOT":
$LINK = " AND ";
$NOT = 1;
$globallink = " AND ";
break;
case "OR":
$LINK = " OR ";
$globallink = " OR ";
break;
case "OR NOT":
$LINK = " AND ";
$NOT = 1;
$globallink = " OR ";
break;
}
} else {
$tmplink = " AND ";
}
// Manage Link if not first item
if (!empty($sql) && !$is_having) {
$sql .= $globallink;
}
$first2 = true;
$items = [];
if (isset($criterion['field']) && $criterion['field'] == "all") {
$items = $searchopt;
} else { // toview case : populate toview
foreach ($data['toview'] as $key2 => $val2) {
$items[$val2] = $searchopt[$val2];
}
}
$view_sql = "";
foreach ($items as $key2 => $val2) {
if (isset($val2['nosearch']) && $val2['nosearch']) {
continue;
}
if (is_array($val2)) {
// Add Where clause if not to be done in HAVING CLAUSE
if (!$is_having && !isset($val2["usehaving"])) {
$tmplink = $LINK;
if ($first2) {
$tmplink = " ";
}
$new_where = self::addWhere(
$tmplink,
$NOT,
$itemtype,
$key2,
$criterion['searchtype'],
$criterion['value'],
$meta
);
if ($new_where !== false) {
$first2 = false;
$view_sql .= $new_where;
}
}
}
}
if (strlen($view_sql)) {
$sql .= " ($view_sql) ";
}
}
}
return $sql;
}
/**
* Construct aditionnal SQL (select, joins, etc) for meta-criteria
*
* @since 9.4
*
* @param array $criteria list of search criterion
* @param string &$SELECT TODO: should be a class property (output parameter)
* @param string &$FROM TODO: should be a class property (output parameter)
* @param array &$already_link_tables TODO: should be a class property (output parameter)
* @param array &$data TODO: should be a class property (output parameter)
*
* @return void
*/
public static function constructAdditionalSqlForMetacriteria(
$criteria = [],
&$SELECT = "",
&$FROM = "",
&$already_link_tables = [],
&$data = []
) {
$data['meta_toview'] = [];
foreach ($criteria as $criterion) {
// manage sub criteria
if (isset($criterion['criteria'])) {
self::constructAdditionalSqlForMetacriteria(
$criterion['criteria'],
$SELECT,
$FROM,
$already_link_tables,
$data
);
continue;
}
// parse only criterion with meta flag
if (
!isset($criterion['itemtype'])
|| empty($criterion['itemtype'])
|| !isset($criterion['meta'])
|| !$criterion['meta']
|| !isset($criterion['value'])
|| strlen($criterion['value']) <= 0
) {
continue;
}
$m_itemtype = $criterion['itemtype'];
$metaopt = &self::getOptions($m_itemtype);
$sopt = $metaopt[$criterion['field']];
//add toview for meta criterion
$data['meta_toview'][$m_itemtype][] = $criterion['field'];
$SELECT .= self::addSelect(
$m_itemtype,
$criterion['field'],
true, // meta-criterion
$m_itemtype
);
$FROM .= self::addMetaLeftJoin(
$data['itemtype'],
$m_itemtype,
$already_link_tables,
$sopt["joinparams"]
);
$FROM .= self::addLeftJoin(
$m_itemtype,
$m_itemtype::getTable(),
$already_link_tables,
$sopt["table"],
$sopt["linkfield"],
1,
$m_itemtype,
$sopt["joinparams"],
$sopt["field"]
);
}
}
/**
* Retrieve datas from DB : construct data array containing columns definitions and rows datas
*
* add to data array a field data containing :
* cols : columns definition
* rows : rows data
*
* @since 0.85
*
* @param array $data array of search data prepared to get data
* @param boolean $onlycount If we just want to count results
*
* @return void|false May return false if the SQL data in $data is not valid
**/
public static function constructData(array &$data, $onlycount = false)
{
if (!isset($data['sql']) || !isset($data['sql']['search'])) {
return false;
}
$data['data'] = [];
// Use a ReadOnly connection if available and configured to be used
$DBread = DBConnection::getReadConnection();
$DBread->query("SET SESSION group_concat_max_len = 8194304;");
$DBread->execution_time = true;
$result = $DBread->query($data['sql']['search']);
if ($result) {
$data['data']['execution_time'] = $DBread->execution_time;
if (isset($data['search']['savedsearches_id'])) {
SavedSearch::updateExecutionTime(
(int)$data['search']['savedsearches_id'],
$DBread->execution_time
);
}
$data['data']['totalcount'] = 0;
// if real search or complete export : get numrows from request
if (
!$data['search']['no_search']
|| $data['search']['export_all']
) {
$data['data']['totalcount'] = $DBread->numrows($result);
} else {
if (
!isset($data['sql']['count'])
|| (count($data['sql']['count']) == 0)
) {
$data['data']['totalcount'] = $DBread->numrows($result);
} else {
foreach ($data['sql']['count'] as $sqlcount) {
$result_num = $DBread->query($sqlcount);
$data['data']['totalcount'] += $DBread->result($result_num, 0, 0);
}
}
}
if ($onlycount) {
//we just want to coutn results; no need to continue process
return;
}
if ($data['search']['start'] > $data['data']['totalcount']) {
$data['search']['start'] = 0;
}
// Search case
$data['data']['begin'] = $data['search']['start'];
$data['data']['end'] = min(
$data['data']['totalcount'],
$data['search']['start'] + $data['search']['list_limit']
) - 1;
//map case
if (isset($data['search']['as_map']) && $data['search']['as_map'] == 1) {
$data['data']['end'] = $data['data']['totalcount'] - 1;
}
// No search Case
if ($data['search']['no_search']) {
$data['data']['begin'] = 0;
$data['data']['end'] = min(
$data['data']['totalcount'] - $data['search']['start'],
$data['search']['list_limit']
) - 1;
}
// Export All case
if ($data['search']['export_all']) {
$data['data']['begin'] = 0;
$data['data']['end'] = $data['data']['totalcount'] - 1;
}
// Get columns
$data['data']['cols'] = [];
$searchopt = &self::getOptions($data['itemtype']);
foreach ($data['toview'] as $opt_id) {
$data['data']['cols'][] = [
'itemtype' => $data['itemtype'],
'id' => $opt_id,
'name' => $searchopt[$opt_id]["name"],
'meta' => 0,
'searchopt' => $searchopt[$opt_id],
];
}
// manage toview column for criteria with meta flag
foreach ($data['meta_toview'] as $m_itemtype => $toview) {
$searchopt = &self::getOptions($m_itemtype);
foreach ($toview as $opt_id) {
$data['data']['cols'][] = [
'itemtype' => $m_itemtype,
'id' => $opt_id,
'name' => $searchopt[$opt_id]["name"],
'meta' => 1,
'searchopt' => $searchopt[$opt_id],
];
}
}
// Display columns Headers for meta items
$already_printed = [];
if (count($data['search']['metacriteria'])) {
foreach ($data['search']['metacriteria'] as $metacriteria) {
if (
isset($metacriteria['itemtype']) && !empty($metacriteria['itemtype'])
&& isset($metacriteria['value']) && (strlen($metacriteria['value']) > 0)
) {
if (!isset($already_printed[$metacriteria['itemtype'] . $metacriteria['field']])) {
$searchopt = &self::getOptions($metacriteria['itemtype']);
$data['data']['cols'][] = [
'itemtype' => $metacriteria['itemtype'],
'id' => $metacriteria['field'],
'name' => $searchopt[$metacriteria['field']]["name"],
'meta' => 1,
'searchopt' => $searchopt[$metacriteria['field']]
];
$already_printed[$metacriteria['itemtype'] . $metacriteria['field']] = 1;
}
}
}
}
// search group (corresponding of dropdown optgroup) of current col
foreach ($data['data']['cols'] as $num => $col) {
// search current col in searchoptions ()
while (
key($searchopt) !== null
&& key($searchopt) != $col['id']
) {
next($searchopt);
}
if (key($searchopt) !== null) {
//search optgroup (non array option)
while (
key($searchopt) !== null
&& is_numeric(key($searchopt))
&& is_array(current($searchopt))
) {
prev($searchopt);
}
if (
key($searchopt) !== null
&& key($searchopt) !== "common"
) {
$data['data']['cols'][$num]['groupname'] = current($searchopt);
}
}
//reset
reset($searchopt);
}
// Get rows
// if real search seek to begin of items to display (because of complete search)
if (!$data['search']['no_search']) {
$DBread->dataSeek($result, $data['search']['start']);
}
$i = $data['data']['begin'];
$data['data']['warning']
= "For compatibility keep raw data (ITEM_X, META_X) at the top for the moment. Will be drop in next version";
$data['data']['rows'] = [];
$data['data']['items'] = [];
self::$output_type = $data['display_type'];
while (($i < $data['data']['totalcount']) && ($i <= $data['data']['end'])) {
$row = $DBread->fetchAssoc($result);
$newrow = [];
$newrow['raw'] = $row;
// Parse datas
foreach ($newrow['raw'] as $key => $val) {
if (preg_match('/ITEM(_(\w[^\d]+))?_(\d+)(_(.+))?/', $key, $matches)) {
$j = $matches[3];
if (isset($matches[2]) && !empty($matches[2])) {
$j = $matches[2] . '_' . $matches[3];
}
$fieldname = 'name';
if (isset($matches[5])) {
$fieldname = $matches[5];
}
// No Group_concat case
if ($fieldname == 'content' || !is_string($val) || strpos($val, self::LONGSEP) === false) {
$newrow[$j]['count'] = 1;
$handled = false;
if ($fieldname != 'content' && is_string($val) && strpos($val, self::SHORTSEP) !== false) {
$split2 = self::explodeWithID(self::SHORTSEP, $val);
if (is_numeric($split2[1])) {
$newrow[$j][0][$fieldname] = $split2[0];
$newrow[$j][0]['id'] = $split2[1];
$handled = true;
}
}
if (!$handled) {
if ($val === self::NULLVALUE) {
$newrow[$j][0][$fieldname] = null;
} else {
$newrow[$j][0][$fieldname] = $val;
}
}
} else {
if (!isset($newrow[$j])) {
$newrow[$j] = [];
}
$split = explode(self::LONGSEP, $val);
$newrow[$j]['count'] = count($split);
foreach ($split as $key2 => $val2) {
$handled = false;
if (strpos($val2, self::SHORTSEP) !== false) {
$split2 = self::explodeWithID(self::SHORTSEP, $val2);
if (is_numeric($split2[1])) {
$newrow[$j][$key2]['id'] = $split2[1];
if ($split2[0] == self::NULLVALUE) {
$newrow[$j][$key2][$fieldname] = null;
} else {
$newrow[$j][$key2][$fieldname] = $split2[0];
}
$handled = true;
}
}
if (!$handled) {
$newrow[$j][$key2][$fieldname] = $val2;
}
}
}
} else {
if ($key == 'currentuser') {
if (!isset($data['data']['currentuser'])) {
$data['data']['currentuser'] = $val;
}
} else {
$newrow[$key] = $val;
// Add id to items list
if ($key == 'id') {
$data['data']['items'][$val] = $i;
}
}
}
}
foreach ($data['data']['cols'] as $val) {
$newrow[$val['itemtype'] . '_' . $val['id']]['displayname'] = self::giveItem(
$val['itemtype'],
$val['id'],
$newrow
);
}
$data['data']['rows'][$i] = $newrow;
$i++;
}
$data['data']['count'] = count($data['data']['rows']);
} else {
$error_no = $DBread->errno();
if ($error_no == 1116) { // Too many tables; MySQL can only use 61 tables in a join
echo self::showError(
$data['search']['display_type'],
__("'All' criterion is not usable with this object list, " .
"sql query fails (too many tables). " .
"Please use 'Items seen' criterion instead")
);
} else {
echo $DBread->error();
}
}
}
/**
* Display datas extracted from DB
*
* @param array $data Array of search datas prepared to get datas
*
* @return void
**/
public static function displayData(array $data)
{
global $CFG_GLPI;
if (!isset($data['data']) || !isset($data['data']['totalcount'])) {
return false;
}
$search = $data['search'];
$itemtype = $data['itemtype'];
$item = $data['item'];
$is_deleted = $search['is_deleted'];
foreach ($search['criteria'] as $key => $criteria) {
if (isset($criteria['virtual']) && $criteria['virtual']) {
unset($search['criteria'][$key]);
}
}
// Contruct parameters
$globallinkto = Toolbox::append_params([
'criteria' => Sanitizer::unsanitize($search['criteria']),
'metacriteria' => Sanitizer::unsanitize($search['metacriteria'])
], '&');
$parameters = http_build_query([
'sort' => $search['sort'],
'order' => $search['order']
]);
$parameters .= "&{$globallinkto}";
if (isset($_GET['_in_modal'])) {
$parameters .= "&_in_modal=1";
}
// For plugin add new parameter if available
if ($plug = isPluginItemType($data['itemtype'])) {
$out = Plugin::doOneHook($plug['plugin'], 'addParamFordynamicReport', $data['itemtype']);
if (is_array($out) && count($out)) {
$parameters .= Toolbox::append_params($out, '&');
}
}
$prehref = $search['target'] . (strpos($search['target'], "?") !== false ? "&" : "?");
$href = $prehref . $parameters;
Session::initNavigateListItems($data['itemtype']);
TemplateRenderer::getInstance()->display('components/search/display_data.html.twig', [
'data' => $data,
'union_search_type' => $CFG_GLPI["union_search_type"],
'rand' => mt_rand(),
'no_sort' => $search['no_sort'] ?? false,
'order' => $search['order'] ?? [],
'sort' => $search['sort'] ?? [],
'start' => $search['start'] ?? 0,
'limit' => $_SESSION['glpilist_limit'],
'count' => $data['data']['totalcount'] ?? 0,
'item' => $item,
'itemtype' => $itemtype,
'href' => $href,
'prehref' => $prehref,
'posthref' => $globallinkto,
'showmassiveactions' => ($search['showmassiveactions'] ?? true)
&& $data['display_type'] != self::GLOBAL_SEARCH
&& ($itemtype == AllAssets::getType()
|| count(MassiveAction::getAllMassiveActions($item, $is_deleted))
),
'massiveactionparams' => $data['search']['massiveactionparams'] + [
'is_deleted' => $is_deleted,
'container' => "massform$itemtype",
],
'can_config' => Session::haveRightsOr('search_config', [
DisplayPreference::PERSONAL,
DisplayPreference::GENERAL
]),
'may_be_deleted' => $item instanceof CommonDBTM && $item->maybeDeleted(),
'may_be_located' => $item instanceof CommonDBTM && $item->maybeLocated(),
'may_be_browsed' => $item !== null && Toolbox::hasTrait($item, \Glpi\Features\TreeBrowse::class),
]);
// Add items in item list
foreach ($data['data']['rows'] as $row) {
if ($itemtype !== AllAssets::class) {
Session::addToNavigateListItems($itemtype, $row["id"]);
} else {
// In case of a global search, reset and empty navigation list to ensure navigation in
// item header context is not shown. Indeed, this list does not support navigation through
// multiple itemtypes, so it should not be displayed in global search context.
Session::initNavigateListItems($row['TYPE'] ?? $data['itemtype']);
}
}
// Clean previous selection
$_SESSION['glpimassiveactionselected'] = [];
}
/**
* Output data (for export in CSV, PDF, ...).
*
* @param array $data Array of search datas prepared to get datas
*
* @return void
**/
public static function outputData(array $data)
{
global $CFG_GLPI;
if (
!isset($data['data'])
|| !isset($data['data']['totalcount'])
|| $data['data']['count'] <= 0
|| $data['search']['as_map'] != 0
) {
return false;
}
// Define begin and end var for loop
// Search case
$begin_display = $data['data']['begin'];
$end_display = $data['data']['end'];
// Compute number of columns to display
// Add toview elements
$nbcols = count($data['data']['cols']);
// Display List Header
echo self::showHeader($data['display_type'], $end_display - $begin_display + 1, $nbcols);
// New Line for Header Items Line
$headers_line = '';
$headers_line_top = '';
$headers_line_top .= self::showBeginHeader($data['display_type']);
$headers_line_top .= self::showNewLine($data['display_type']);
$header_num = 1;
// Display column Headers for toview items
$metanames = [];
foreach ($data['data']['cols'] as $val) {
$name = $val["name"];
// prefix by group name (corresponding to optgroup in dropdown) if exists
if (isset($val['groupname'])) {
$groupname = $val['groupname'];
if (is_array($groupname)) {
//since 9.2, getSearchOptions has been changed
$groupname = $groupname['name'];
}
$name = "$groupname - $name";
}
// Not main itemtype add itemtype to display
if ($data['itemtype'] != $val['itemtype']) {
if (!isset($metanames[$val['itemtype']])) {
if ($metaitem = getItemForItemtype($val['itemtype'])) {
$metanames[$val['itemtype']] = $metaitem->getTypeName();
}
}
$name = sprintf(
__('%1$s - %2$s'),
$metanames[$val['itemtype']],
$val["name"]
);
}
$headers_line .= self::showHeaderItem(
$data['display_type'],
$name,
$header_num,
'',
(!$val['meta']
&& ($data['search']['sort'] == $val['id'])),
$data['search']['order']
);
}
// Add specific column Header
if (isset($CFG_GLPI["union_search_type"][$data['itemtype']])) {
$headers_line .= self::showHeaderItem(
$data['display_type'],
__('Item type'),
$header_num
);
}
// End Line for column headers
$headers_line .= self::showEndLine($data['display_type'], true);
$headers_line_top .= $headers_line;
$headers_line_top .= self::showEndHeader($data['display_type']);
echo $headers_line_top;
// Num of the row (1=header_line)
$row_num = 1;
$typenames = [];
// Display Loop
foreach ($data['data']['rows'] as $row) {
// Column num
$item_num = 1;
$row_num++;
// New line
echo self::showNewLine(
$data['display_type'],
($row_num % 2),
$data['search']['is_deleted']
);
// Print other toview items
foreach ($data['data']['cols'] as $col) {
$colkey = "{$col['itemtype']}_{$col['id']}";
if (!$col['meta']) {
echo self::showItem(
$data['display_type'],
$row[$colkey]['displayname'],
$item_num,
$row_num,
self::displayConfigItem(
$data['itemtype'],
$col['id'],
$row,
$colkey
)
);
} else { // META case
echo self::showItem(
$data['display_type'],
$row[$colkey]['displayname'],
$item_num,
$row_num
);
}
}
if (isset($CFG_GLPI["union_search_type"][$data['itemtype']])) {
if (!isset($typenames[$row["TYPE"]])) {
if ($itemtmp = getItemForItemtype($row["TYPE"])) {
$typenames[$row["TYPE"]] = $itemtmp->getTypeName();
}
}
echo self::showItem(
$data['display_type'],
$typenames[$row["TYPE"]],
$item_num,
$row_num
);
}
// End Line
echo self::showEndLine($data['display_type']);
}
// Create title
$title = '';
if (
($data['display_type'] == self::PDF_OUTPUT_LANDSCAPE)
|| ($data['display_type'] == self::PDF_OUTPUT_PORTRAIT)
) {
$title = self::computeTitle($data);
}
// Display footer (close table)
echo self::showFooter($data['display_type'], $title, $data['data']['count']);
}
/**
* Compute title (use case of PDF OUTPUT)
*
* @param array $data Array data of search
*
* @return string Title
**/
public static function computeTitle($data)
{
$title = "";
if (count($data['search']['criteria'])) {
//Drop the first link as it is not needed, or convert to clean link (AND NOT -> NOT)
if (isset($data['search']['criteria']['0']['link'])) {
$notpos = strpos($data['search']['criteria']['0']['link'], 'NOT');
//If link was like '%NOT%' just use NOT. Otherwise remove the link
if ($notpos > 0) {
$data['search']['criteria']['0']['link'] = 'NOT';
} else if (!$notpos) {
unset($data['search']['criteria']['0']['link']);
}
}
foreach ($data['search']['criteria'] as $criteria) {
if (isset($criteria['itemtype'])) {
$searchopt = &self::getOptions($criteria['itemtype']);
} else {
$searchopt = &self::getOptions($data['itemtype']);
}
$titlecontain = '';
if (isset($criteria['criteria'])) {
//This is a group criteria, call computeTitle again and concat
$newdata = $data;
$oldlink = $criteria['link'];
$newdata['search'] = $criteria;
$titlecontain = sprintf(
__('%1$s %2$s (%3$s)'),
$titlecontain,
$oldlink,
Search::computeTitle($newdata)
);
} else {
if (strlen($criteria['value']) > 0) {
if (isset($criteria['link'])) {
$titlecontain = " " . $criteria['link'] . " ";
}
$gdname = '';
$valuename = '';
switch ($criteria['field']) {
case "all":
$titlecontain = sprintf(__('%1$s %2$s'), $titlecontain, __('All'));
break;
case "view":
$titlecontain = sprintf(__('%1$s %2$s'), $titlecontain, __('Items seen'));
break;
default:
if (isset($criteria['meta']) && $criteria['meta']) {
$searchoptname = sprintf(
__('%1$s / %2$s'),
$criteria['itemtype'],
$searchopt[$criteria['field']]["name"]
);
} else {
$searchoptname = $searchopt[$criteria['field']]["name"];
}
$titlecontain = sprintf(__('%1$s %2$s'), $titlecontain, $searchoptname);
$itemtype = getItemTypeForTable($searchopt[$criteria['field']]["table"]);
$valuename = '';
if ($item = getItemForItemtype($itemtype)) {
$valuename = $item->getValueToDisplay(
$searchopt[$criteria['field']],
$criteria['value']
);
}
$gdname = Dropdown::getDropdownName(
$searchopt[$criteria['field']]["table"],
$criteria['value']
);
}
if (empty($valuename)) {
$valuename = $criteria['value'];
}
switch ($criteria['searchtype']) {
case "equals":
if (
in_array(
$searchopt[$criteria['field']]["field"],
['name', 'completename']
)
) {
$titlecontain = sprintf(__('%1$s = %2$s'), $titlecontain, $gdname);
} else {
$titlecontain = sprintf(__('%1$s = %2$s'), $titlecontain, $valuename);
}
break;
case "notequals":
if (
in_array(
$searchopt[$criteria['field']]["field"],
['name', 'completename']
)
) {
$titlecontain = sprintf(__('%1$s <> %2$s'), $titlecontain, $gdname);
} else {
$titlecontain = sprintf(__('%1$s <> %2$s'), $titlecontain, $valuename);
}
break;
case "lessthan":
$titlecontain = sprintf(__('%1$s < %2$s'), $titlecontain, $valuename);
break;
case "morethan":
$titlecontain = sprintf(__('%1$s > %2$s'), $titlecontain, $valuename);
break;
case "contains":
$titlecontain = sprintf(
__('%1$s = %2$s'),
$titlecontain,
'%' . $valuename . '%'
);
break;
case "notcontains":
$titlecontain = sprintf(
__('%1$s <> %2$s'),
$titlecontain,
'%' . $valuename . '%'
);
break;
case "under":
$titlecontain = sprintf(
__('%1$s %2$s'),
$titlecontain,
sprintf(__('%1$s %2$s'), __('under'), $gdname)
);
break;
case "notunder":
$titlecontain = sprintf(
__('%1$s %2$s'),
$titlecontain,
sprintf(__('%1$s %2$s'), __('not under'), $gdname)
);
break;
default:
$titlecontain = sprintf(__('%1$s = %2$s'), $titlecontain, $valuename);
break;
}
}
}
$title .= $titlecontain;
}
}
if (
isset($data['search']['metacriteria']) &&
count($data['search']['metacriteria'])
) {
$metanames = [];
foreach ($data['search']['metacriteria'] as $metacriteria) {
$searchopt = &self::getOptions($metacriteria['itemtype']);
if (!isset($metanames[$metacriteria['itemtype']])) {
if ($metaitem = getItemForItemtype($metacriteria['itemtype'])) {
$metanames[$metacriteria['itemtype']] = $metaitem->getTypeName();
}
}
$titlecontain2 = '';
if (strlen($metacriteria['value']) > 0) {
if (isset($metacriteria['link'])) {
$titlecontain2 = sprintf(
__('%1$s %2$s'),
$titlecontain2,
$metacriteria['link']
);
}
$titlecontain2
= sprintf(
__('%1$s %2$s'),
$titlecontain2,
sprintf(
__('%1$s / %2$s'),
$metanames[$metacriteria['itemtype']],
$searchopt[$metacriteria['field']]["name"]
)
);
$gdname2 = Dropdown::getDropdownName(
$searchopt[$metacriteria['field']]["table"],
$metacriteria['value']
);
switch ($metacriteria['searchtype']) {
case "equals":
if (
in_array(
$searchopt[$metacriteria['link']]
["field"],
['name', 'completename']
)
) {
$titlecontain2 = sprintf(
__('%1$s = %2$s'),
$titlecontain2,
$gdname2
);
} else {
$titlecontain2 = sprintf(
__('%1$s = %2$s'),
$titlecontain2,
$metacriteria['value']
);
}
break;
case "notequals":
if (
in_array(
$searchopt[$metacriteria['link']]["field"],
['name', 'completename']
)
) {
$titlecontain2 = sprintf(
__('%1$s <> %2$s'),
$titlecontain2,
$gdname2
);
} else {
$titlecontain2 = sprintf(
__('%1$s <> %2$s'),
$titlecontain2,
$metacriteria['value']
);
}
break;
case "lessthan":
$titlecontain2 = sprintf(
__('%1$s < %2$s'),
$titlecontain2,
$metacriteria['value']
);
break;
case "morethan":
$titlecontain2 = sprintf(
__('%1$s > %2$s'),
$titlecontain2,
$metacriteria['value']
);
break;
case "contains":
$titlecontain2 = sprintf(
__('%1$s = %2$s'),
$titlecontain2,
'%' . $metacriteria['value'] . '%'
);
break;
case "notcontains":
$titlecontain2 = sprintf(
__('%1$s <> %2$s'),
$titlecontain2,
'%' . $metacriteria['value'] . '%'
);
break;
case "under":
$titlecontain2 = sprintf(
__('%1$s %2$s'),
$titlecontain2,
sprintf(
__('%1$s %2$s'),
__('under'),
$gdname2
)
);
break;
case "notunder":
$titlecontain2 = sprintf(
__('%1$s %2$s'),
$titlecontain2,
sprintf(
__('%1$s %2$s'),
__('not under'),
$gdname2
)
);
break;
default:
$titlecontain2 = sprintf(
__('%1$s = %2$s'),
$titlecontain2,
$metacriteria['value']
);
break;
}
}
$title .= $titlecontain2;
}
}
return $title;
}
/**
* Get meta types available for search engine
*
* @param class-string<CommonDBTM> $itemtype Type to display the form
*
* @return array Array of available itemtype
**/
public static function getMetaItemtypeAvailable($itemtype)
{
global $CFG_GLPI;
$itemtype = self::getMetaReferenceItemtype($itemtype);
if (!(($item = getItemForItemtype($itemtype)) instanceof CommonDBTM)) {
return [];
}
$linked = [];
foreach ($CFG_GLPI as $key => $values) {
if ($key === 'link_types') {
// Links are associated to all items of a type, it does not make any sense to use them in meta search
continue;
}
if ($key === 'ticket_types' && $item instanceof CommonITILObject) {
// Linked are filtered by CommonITILObject::getAllTypesForHelpdesk()
$linked = array_merge($linked, array_keys($item::getAllTypesForHelpdesk()));
continue;
}
foreach (self::getMetaParentItemtypesForTypesConfig($key) as $config_itemtype) {
if ($itemtype === $config_itemtype::getType()) {
// List is related to source itemtype, all types of list are so linked
$linked = array_merge($linked, $values);
} else if (in_array($itemtype, $values)) {
// Source itemtype is inside list, type corresponding to list is so linked
$linked[] = $config_itemtype::getType();
}
}
}
return array_unique($linked);
}
/**
* Returns parents itemtypes having subitems defined in given config key.
* This list is filtered and is only valid in a "meta" search context.
*
* @param string $config_key
*
* @return string[]
*/
private static function getMetaParentItemtypesForTypesConfig(string $config_key): array
{
$matches = [];
if (preg_match('/^(.+)_types$/', $config_key, $matches) === 0) {
return [];
}
$key_to_itemtypes = [
'directconnect_types' => ['Computer'],
'infocom_types' => ['Budget', 'Infocom'],
'linkgroup_types' => ['Group'],
// 'linkgroup_tech_types' => ['Group'], // Cannot handle ambiguity with 'Group' from 'linkgroup_types'
'linkuser_types' => ['User'],
// 'linkuser_tech_types' => ['User'], // Cannot handle ambiguity with 'User' from 'linkuser_types'
'project_asset_types' => ['Project'],
'rackable_types' => ['Enclosure', 'Rack'],
'socket_types' => [Socket::class],
'ticket_types' => ['Change', 'Problem', 'Ticket'],
];
if (array_key_exists($config_key, $key_to_itemtypes)) {
return $key_to_itemtypes[$config_key];
}
$itemclass = $matches[1];
if (is_a($itemclass, CommonDBTM::class, true)) {
return [$itemclass::getType()];
}
return [];
}
/**
* Check if an itemtype is a possible subitem of another itemtype in a "meta" search context.
*
* @param string $parent_itemtype
* @param string $child_itemtype
*
* @return boolean
*/
private static function isPossibleMetaSubitemOf(string $parent_itemtype, string $child_itemtype)
{
global $CFG_GLPI;
if (
is_a($parent_itemtype, CommonITILObject::class, true)
&& in_array($child_itemtype, array_keys($parent_itemtype::getAllTypesForHelpdesk()))
) {
return true;
}
foreach ($CFG_GLPI as $key => $values) {
if (
in_array($parent_itemtype, self::getMetaParentItemtypesForTypesConfig($key))
&& in_array($child_itemtype, $values)
) {
return true;
}
}
return false;
}
/**
* Gets the class to use if the specified itemtype extends one of the known reference types.
*
* @param class-string<CommonDBTM> $itemtype
*
* @return string|false The reference class name. If the provided itemtype is from a plugin, the provided itemtype is returned.
* If the itemtype is not from a plugin and not exactly or extended from a reference itemtype, false will be returned.
* @since 0.85
*/
public static function getMetaReferenceItemtype($itemtype)
{
if (!isPluginItemType($itemtype)) {
return $itemtype;
}
// Use reference type if given itemtype extends a reference type.
$types = [
'Computer',
'Problem',
'Change',
'Ticket',
'Printer',
'Monitor',
'Peripheral',
'Software',
'Phone'
];
foreach ($types as $type) {
if (is_a($itemtype, $type, true)) {
return $type;
}
}
return false;
}
/**
* Get dropdown options of logical operators.
* @return string[]|array<string, string>
* @since 0.85
**/
public static function getLogicalOperators($only_not = false)
{
if ($only_not) {
return [
'AND' => Dropdown::EMPTY_VALUE,
'AND NOT' => __("NOT")
];
}
return [
'AND' => __('AND'),
'OR' => __('OR'),
'AND NOT' => __('AND NOT'),
'OR NOT' => __('OR NOT')
];
}
/**
* Print generic search form
*
* Params need to parsed before using Search::manageParams function
*
* @param class-string<CommonDBTM> $itemtype Type to display the form
* @param array $params Array of parameters may include sort, is_deleted, criteria, metacriteria
*
* @return void
**/
public static function showGenericSearch($itemtype, array $params)
{
global $CFG_GLPI;
// Default values of parameters
$p['sort'] = '';
$p['is_deleted'] = 0;
$p['as_map'] = 0;
$p['browse'] = 0;
$p['criteria'] = [];
$p['metacriteria'] = [];
if (class_exists($itemtype)) {
$p['target'] = $itemtype::getSearchURL();
} else {
$p['target'] = Toolbox::getItemTypeSearchURL($itemtype);
}
$p['showreset'] = true;
$p['showbookmark'] = true;
$p['showfolding'] = true;
$p['mainform'] = true;
$p['prefix_crit'] = '';
$p['addhidden'] = [];
$p['actionname'] = 'search';
$p['actionvalue'] = _sx('button', 'Search');
foreach ($params as $key => $val) {
$p[$key] = $val;
}
// Itemtype name used in JS function names, etc
$normalized_itemtype = strtolower(str_replace('\\', '', $itemtype));
$rand_criteria = mt_rand();
$main_block_class = '';
$card_class = 'search-form card card-sm mb-4';
if ($p['mainform']) {
echo "<form name='searchform$normalized_itemtype' class='search-form-container' method='get' action='" . $p['target'] . "'>";
} else {
$main_block_class = "sub_criteria";
$card_class = 'border d-inline-block ms-1';
}
$display = $_SESSION['glpifold_search'] ? 'style="display: none;"' : '';
echo "<div class='$card_class' $display>";
echo "<div id='searchcriteria$rand_criteria' class='$main_block_class' >";
$nbsearchcountvar = 'nbcriteria' . $normalized_itemtype . mt_rand();
$searchcriteriatableid = 'criteriatable' . $normalized_itemtype . mt_rand();
// init criteria count
echo Html::scriptBlock("
var $nbsearchcountvar = " . count($p['criteria']) . ";
");
echo "<div class='list-group list-group-flush list-group-hoverable criteria-list pt-2' id='$searchcriteriatableid'>";
// Display normal search parameters
$i = 0;
foreach (array_keys($p['criteria']) as $i) {
self::displayCriteria([
'itemtype' => $itemtype,
'num' => $i,
'p' => $p
]);
}
echo "<a id='more-criteria$rand_criteria' role='button'
class='normalcriteria fold-search list-group-item p-2 border-0'
style='display: none;'>...</a>";
echo "</div>"; // .list
// Keep track of the current savedsearches on reload
if (isset($_GET['savedsearches_id'])) {
echo Html::input("savedsearches_id", [
'type' => "hidden",
'value' => $_GET['savedsearches_id'],
]);
}
echo "<div class='card-footer d-flex search_actions'>";
$linked = self::getMetaItemtypeAvailable($itemtype);
echo "<button id='addsearchcriteria$rand_criteria' class='btn btn-sm btn-outline-secondary me-1' type='button'>
<i class='ti ti-square-plus'></i>
<span class='d-none d-sm-block'>" . __s('rule') . "</span>
</button>";
if (count($linked)) {
echo "<button id='addmetasearchcriteria$rand_criteria' class='btn btn-sm btn-outline-secondary me-1' type='button'>
<i class='ti ti-circle-plus'></i>
<span class='d-none d-sm-block'>" . __s('global rule') . "</span>
</button>";
}
echo "<button id='addcriteriagroup$rand_criteria' class='btn btn-sm btn-outline-secondary me-1' type='button'>
<i class='ti ti-code-plus'></i>
<span class='d-none d-sm-block'>" . __s('group') . "</span>
</button>";
$json_p = json_encode($p);
if ($p['mainform']) {
// Display submit button
echo "<button class='btn btn-sm btn-primary me-1' type='submit' name='" . $p['actionname'] . "'>
<i class='ti ti-list-search'></i>
<span class='d-none d-sm-block'>" . $p['actionvalue'] . "</span>
</button>";
if ($p['showbookmark'] || $p['showreset']) {
if ($p['showbookmark']) {
SavedSearch::showSaveButton(
SavedSearch::SEARCH,
$itemtype,
isset($_GET['savedsearches_id'])
);
}
if ($p['showreset']) {
echo "<a class='btn btn-ghost-secondary btn-icon btn-sm me-1 search-reset'
data-bs-toggle='tooltip' data-bs-placement='bottom'
href='"
. $p['target']
. (strpos($p['target'], '?') ? '&' : '?')
. "reset=reset' title=\"" . __s('Blank') . "\"
><i class='ti ti-circle-x'></i></a>";
}
}
}
echo "</div>"; //.search_actions
// idor checks
$idor_display_criteria = Session::getNewIDORToken($itemtype);
$idor_display_meta_criteria = Session::getNewIDORToken($itemtype);
$idor_display_criteria_group = Session::getNewIDORToken($itemtype);
$itemtype_escaped = addslashes($itemtype);
$JS = <<<JAVASCRIPT
$('#addsearchcriteria$rand_criteria').on('click', function(event) {
event.preventDefault();
$.post('{$CFG_GLPI['root_doc']}/ajax/search.php', {
'action': 'display_criteria',
'itemtype': '$itemtype_escaped',
'num': $nbsearchcountvar,
'p': $json_p,
'_idor_token': '$idor_display_criteria'
})
.done(function(data) {
$(data).insertBefore('#more-criteria$rand_criteria');
$nbsearchcountvar++;
});
});
$('#addmetasearchcriteria$rand_criteria').on('click', function(event) {
event.preventDefault();
$.post('{$CFG_GLPI['root_doc']}/ajax/search.php', {
'action': 'display_meta_criteria',
'itemtype': '$itemtype_escaped',
'meta': true,
'num': $nbsearchcountvar,
'p': $json_p,
'_idor_token': '$idor_display_meta_criteria'
})
.done(function(data) {
$(data).insertBefore('#more-criteria$rand_criteria');
$nbsearchcountvar++;
});
});
$('#addcriteriagroup$rand_criteria').on('click', function(event) {
event.preventDefault();
$.post('{$CFG_GLPI['root_doc']}/ajax/search.php', {
'action': 'display_criteria_group',
'itemtype': '$itemtype_escaped',
'meta': true,
'num': $nbsearchcountvar,
'p': $json_p,
'_idor_token': '$idor_display_criteria_group'
})
.done(function(data) {
$(data).insertBefore('#more-criteria$rand_criteria');
$nbsearchcountvar++;
});
});
JAVASCRIPT;
if ($p['mainform']) {
$JS .= <<<JAVASCRIPT
var toggle_fold_search = function(show_search) {
$('#searchcriteria{$rand_criteria}').closest('.search-form').toggle(show_search);
};
// Init search_criteria state
var search_criteria_visibility = window.localStorage.getItem('show_full_searchcriteria');
if (search_criteria_visibility !== undefined && search_criteria_visibility == 'false') {
$('.fold-search').click();
}
$(document).on("click", ".remove-search-criteria", function() {
// force removal of tooltip
var tooltip = bootstrap.Tooltip.getInstance($(this)[0]);
if (tooltip !== null) {
tooltip.dispose();
}
var rowID = $(this).data('rowid');
$('#' + rowID).remove();
$('#searchcriteria{$rand_criteria} .criteria-list .list-group-item:first-child').addClass('headerRow').show();
});
JAVASCRIPT;
}
echo Html::scriptBlock($JS);
if (count($p['addhidden'])) {
foreach ($p['addhidden'] as $key => $val) {
echo Html::hidden($key, ['value' => $val]);
}
}
if ($p['mainform']) {
// For dropdown
echo Html::hidden('itemtype', ['value' => $itemtype]);
// Reset to start when submit new search
echo Html::hidden('start', ['value' => 0]);
}
echo "</div>"; // #searchcriteria
echo "</div>"; // .card
if ($p['mainform']) {
Html::closeForm();
}
}
/**
* Display a criteria field set, this function should be called by ajax/search.php
*
* @since 9.4
*
* @param array $request we should have these keys of parameters:
* - itemtype: main itemtype for criteria, sub one for metacriteria
* - num: index of the criteria
* - p: params of showGenericSearch method
*
* @return void
*/
public static function displayCriteria($request = [])
{
global $CFG_GLPI;
if (
!isset($request["itemtype"])
|| !isset($request["num"])
) {
return;
}
$num = (int) $request['num'];
$p = $request['p'];
$options = self::getCleanedOptions($request["itemtype"]);
$randrow = mt_rand();
$normalized_itemtype = strtolower(str_replace('\\', '', $request["itemtype"]));
$rowid = 'searchrow' . $normalized_itemtype . $randrow;
$addclass = $num == 0 ? ' headerRow' : '';
$prefix = isset($p['prefix_crit']) ? $p['prefix_crit'] : '';
$parents_num = isset($p['parents_num']) ? $p['parents_num'] : [];
$criteria = [];
$from_meta = isset($request['from_meta']) && $request['from_meta'];
$sess_itemtype = $request["itemtype"];
if ($from_meta) {
$sess_itemtype = $request["parent_itemtype"];
}
if (!$criteria = self::findCriteriaInSession($sess_itemtype, $num, $parents_num)) {
$criteria = self::getDefaultCriteria($request["itemtype"]);
}
if (
isset($criteria['meta'])
&& $criteria['meta']
&& !$from_meta
) {
self::displayMetaCriteria($request);
return;
}
if (
isset($criteria['criteria'])
&& is_array($criteria['criteria'])
) {
self::displayCriteriaGroup($request);
return;
}
$add_padding = "p-2";
if (isset($request["from_meta"])) {
$add_padding = "p-0";
}
echo "<div class='list-group-item $add_padding border-0 normalcriteria$addclass' id='$rowid'>";
echo "<div class='row g-1'>";
if (!$from_meta) {
// First line display add / delete images for normal and meta search items
if (
$num == 0
&& isset($p['mainform'])
&& $p['mainform']
) {
// Instanciate an object to access method
$item = null;
if ($request["itemtype"] != AllAssets::getType()) {
$item = getItemForItemtype($request["itemtype"]);
}
if ($item && $item->maybeDeleted()) {
echo Html::hidden('is_deleted', [
'value' => $p['is_deleted'],
'id' => 'is_deleted'
]);
}
echo Html::hidden('as_map', [
'value' => $p['as_map'],
'id' => 'as_map'
]);
echo Html::hidden('browse', [
'value' => $p['browse'],
'id' => 'browse'
]);
}
echo "<div class='col-auto'>";
echo "<button class='btn btn-sm btn-icon btn-ghost-secondary remove-search-criteria' type='button' data-rowid='$rowid'
data-bs-toggle='tooltip' data-bs-placement='left'
title=\"" . __s('Delete a rule') . "\">
<i class='ti ti-square-minus' alt='-'></i>
</button>";
echo "</div>";
}
// Display link item
$value = '';
if (!$from_meta) {
echo "<div class='col-auto'>";
if (isset($criteria["link"])) {
$value = $criteria["link"];
}
$operators = Search::getLogicalOperators(($num == 0));
Dropdown::showFromArray("criteria{$prefix}[$num][link]", $operators, [
'value' => $value,
]);
echo "</div>";
}
$values = [];
// display select box to define search item
if ($CFG_GLPI['allow_search_view'] == 2 && !isset($request['from_meta'])) {
$values['view'] = __('Items seen');
}
reset($options);
$group = '';
foreach ($options as $key => $val) {
// print groups
if (!is_array($val)) {
$group = $val;
} else if (count($val) == 1) {
$group = $val['name'];
} else {
if (
(!isset($val['nosearch']) || ($val['nosearch'] == false))
&& (!$from_meta || !array_key_exists('nometa', $val) || $val['nometa'] !== true)
) {
$values[$group][$key] = $val["name"];
}
}
}
if ($CFG_GLPI['allow_search_view'] == 1 && !isset($request['from_meta'])) {
$values['view'] = __('Items seen');
}
if ($CFG_GLPI['allow_search_all'] && !isset($request['from_meta'])) {
$values['all'] = __('All');
}
$value = '';
if (isset($criteria['field'])) {
$value = $criteria['field'];
}
echo "<div class='col-auto'>";
$rand = Dropdown::showFromArray("criteria{$prefix}[$num][field]", $values, [
'value' => $value,
]);
echo "</div>";
$field_id = Html::cleanId("dropdown_criteria{$prefix}[$num][field]$rand");
$spanid = Html::cleanId('SearchSpan' . $normalized_itemtype . $prefix . $num);
echo "<div class='col-auto'>";
echo "<div class='row g-1' id='$spanid'>";
$used_itemtype = $request["itemtype"];
// Force Computer itemtype for AllAssets to permit to show specific items
if ($request["itemtype"] == AllAssets::getType()) {
$used_itemtype = 'Computer';
}
$searchtype = isset($criteria['searchtype'])
? $criteria['searchtype']
: "";
$p_value = isset($criteria['value'])
? Sanitizer::dbUnescape($criteria['value'])
: "";
$params = [
'itemtype' => $used_itemtype,
'_idor_token' => Session::getNewIDORToken($used_itemtype),
'field' => $value,
'searchtype' => $searchtype,
'value' => $p_value,
'num' => $num,
'p' => $p,
];
Search::displaySearchoption($params);
echo "</div>";
Ajax::updateItemOnSelectEvent(
$field_id,
$spanid,
$CFG_GLPI["root_doc"] . "/ajax/search.php",
[
'action' => 'display_searchoption',
'field' => '__VALUE__',
] + $params
);
echo "</div>"; //.row
echo "</div>"; //#$spanid
echo "</div>";
}
/**
* Display a meta-criteria field set, this function should be called by ajax/search.php
* Call displayCriteria method after displaying its itemtype field
*
* @since 9.4
*
* @param array $request @see displayCriteria method
*
* @return void
*/
public static function displayMetaCriteria($request = [])
{
global $CFG_GLPI;
if (
!isset($request["itemtype"])
|| !isset($request["num"])
) {
return "";
}
$p = $request['p'];
$num = (int) $request['num'];
$prefix = isset($p['prefix_crit']) ? $p['prefix_crit'] : '';
$parents_num = isset($p['parents_num']) ? $p['parents_num'] : [];
$itemtype = $request["itemtype"];
$metacriteria = [];
if (!$metacriteria = self::findCriteriaInSession($itemtype, $num, $parents_num)) {
$metacriteria = [];
// Set default field
$options = Search::getCleanedOptions($itemtype);
foreach ($options as $key => $val) {
if (is_array($val) && isset($val['table'])) {
$metacriteria['field'] = $key;
break;
}
}
}
$linked = Search::getMetaItemtypeAvailable($itemtype);
$rand = mt_rand();
$rowid = 'metasearchrow' . $request['itemtype'] . $rand;
echo "<div class='list-group-item border-0 metacriteria p-2' id='$rowid'>";
echo "<div class='row g-1'>";
echo "<div class='col-auto'>";
echo "<button class='btn btn-sm btn-icon btn-ghost-secondary remove-search-criteria' type='button' data-rowid='$rowid'>
<i class='ti ti-square-minus' alt='-' title=\"" .
__s('Delete a global rule') . "\"></i>
</button>";
echo "</div>";
// Display link item (not for the first item)
echo "<div class='col-auto'>";
Dropdown::showFromArray(
"criteria{$prefix}[$num][link]",
Search::getLogicalOperators(),
[
'value' => isset($metacriteria["link"])
? $metacriteria["link"]
: "",
]
);
echo "</div>";
// Display select of the linked item type available
echo "<div class='col-auto'>";
$rand = Dropdown::showItemTypes("criteria{$prefix}[$num][itemtype]", $linked, [
'value' => isset($metacriteria['itemtype'])
&& !empty($metacriteria['itemtype'])
? $metacriteria['itemtype']
: "",
]);
echo "</div>";
echo Html::hidden("criteria{$prefix}[$num][meta]", [
'value' => true
]);
$field_id = Html::cleanId("dropdown_criteria{$prefix}[$num][itemtype]$rand");
$spanid = Html::cleanId("show_" . $request["itemtype"] . "_" . $prefix . $num . "_$rand");
// Ajax script for display search met& item
$params = [
'action' => 'display_criteria',
'itemtype' => '__VALUE__',
'parent_itemtype' => $request['itemtype'],
'from_meta' => true,
'num' => $num,
'p' => $request["p"],
'_idor_token' => Session::getNewIDORToken("", [
'parent_itemtype' => $request['itemtype']
])
];
Ajax::updateItemOnSelectEvent(
$field_id,
$spanid,
$CFG_GLPI["root_doc"] . "/ajax/search.php",
$params
);
echo "<div class='col-auto' id='$spanid'>";
echo "<div class=row'>";
if (
isset($metacriteria['itemtype'])
&& !empty($metacriteria['itemtype'])
) {
$params['itemtype'] = $metacriteria['itemtype'];
self::displayCriteria($params);
}
echo "</div>";
echo "</div>";
}
/**
* Display a group of nested criteria.
* A group (parent) criteria can contains children criteria (who also cantains children, etc)
*
* @since 9.4
*
* @param array $request @see displayCriteria method
*
* @return void
*/
public static function displayCriteriaGroup($request = [])
{
$num = (int) $request['num'];
$p = $request['p'];
$randrow = mt_rand();
$rowid = 'searchrow' . $request['itemtype'] . $randrow;
$addclass = $num == 0 ? ' headerRow' : '';
$prefix = isset($p['prefix_crit']) ? $p['prefix_crit'] : '';
$parents_num = isset($p['parents_num']) ? $p['parents_num'] : [];
if (!$criteria = self::findCriteriaInSession($request['itemtype'], $num, $parents_num)) {
$criteria = [
'criteria' => self::getDefaultCriteria($request['itemtype']),
];
}
echo "<div class='list-group-item p-2 border-0 normalcriteria$addclass' id='$rowid'>";
echo "<div class='row g-1'>";
echo "<div class='col-auto'>";
echo "<button class='btn btn-sm btn-icon btn-ghost-secondary remove-search-criteria' type='button' data-rowid='$rowid'
data-bs-toggle='tooltip' data-bs-placement='left'
title=\"" . __s('Delete a rule') . "\"
>
<i class='ti ti-square-minus' alt='-'></i>
</button>";
echo "</div>";
echo "<div class='col-auto'>";
Dropdown::showFromArray("criteria{$prefix}[$num][link]", Search::getLogicalOperators(), [
'value' => isset($criteria["link"]) ? $criteria["link"] : '',
]);
echo "</div>";
$parents_num = isset($p['parents_num']) ? $p['parents_num'] : [];
array_push($parents_num, $num);
$params = [
'mainform' => false,
'prefix_crit' => "{$prefix}[$num][criteria]",
'parents_num' => $parents_num,
'criteria' => $criteria['criteria'],
];
echo "<div class='col-auto'>";
self::showGenericSearch($request['itemtype'], $params);
echo "</div>";
echo "</div>";//.row
echo "</div>";//.list-group-item
}
/**
* Retrieve a single criteria in Session by its index
*
* @since 9.4
*
* @param string $itemtype which glpi type we must search in session
* @param integer $num index of the criteria
* @param array $parents_num node indexes of the parents (@see displayCriteriaGroup)
*
* @return array|false the found criteria array, or false if nothing found
*/
public static function findCriteriaInSession($itemtype = '', $num = 0, $parents_num = [])
{
if (!isset($_SESSION['glpisearch'][$itemtype]['criteria'])) {
return false;
}
$criteria = &$_SESSION['glpisearch'][$itemtype]['criteria'];
if (count($parents_num)) {
foreach ($parents_num as $parent) {
if (!isset($criteria[$parent]['criteria'])) {
return false;
}
$criteria = &$criteria[$parent]['criteria'];
}
}
if (
isset($criteria[$num])
&& is_array($criteria[$num])
) {
return $criteria[$num];
}
return false;
}
/**
* construct the default criteria for an itemtype
*
* @since 9.4
*
* @param class-string<CommonDBTM> $itemtype
*
* @return array criteria
*/
public static function getDefaultCriteria($itemtype = '')
{
global $CFG_GLPI;
$field = '';
if ($CFG_GLPI['allow_search_view'] == 2) {
$field = 'view';
} else {
$options = self::getCleanedOptions($itemtype);
foreach ($options as $key => $val) {
if (
is_array($val)
&& isset($val['table'])
) {
$field = $key;
break;
}
}
}
return [
[
'field' => $field,
'link' => 'contains',
'value' => ''
]
];
}
/**
* Display first part of criteria (field + searchtype, just after link)
* will call displaySearchoptionValue for the next part (value)
*
* @since 9.4
*
* @param array $request we should have these keys of parameters:
* - itemtype: main itemtype for criteria, sub one for metacriteria
* - num: index of the criteria
* - field: field key of the criteria
* - p: params of showGenericSearch method
*
* @return void
*/
public static function displaySearchoption($request = [])
{
global $CFG_GLPI;
if (
!isset($request["itemtype"])
|| !isset($request["field"])
|| !isset($request["num"])
) {
return "";
}
$p = $request['p'];
$num = (int) $request['num'];
$prefix = isset($p['prefix_crit']) ? $p['prefix_crit'] : '';
if (!is_subclass_of($request['itemtype'], 'CommonDBTM')) {
throw new \RuntimeException('Invalid itemtype provided!');
}
if (isset($request['meta']) && $request['meta']) {
$fieldname = 'metacriteria';
} else {
$fieldname = 'criteria';
$request['meta'] = 0;
}
$actions = Search::getActionsFor($request["itemtype"], $request["field"]);
// is it a valid action for type ?
if (
count($actions)
&& (empty($request['searchtype']) || !isset($actions[$request['searchtype']]))
) {
$tmp = $actions;
unset($tmp['searchopt']);
$request['searchtype'] = key($tmp);
unset($tmp);
}
$rands = -1;
$normalized_itemtype = strtolower(str_replace('\\', '', $request["itemtype"]));
$dropdownname = Html::cleanId("spansearchtype$fieldname" .
$normalized_itemtype .
$prefix .
$num);
$searchopt = [];
if (count($actions) > 0) {
// get already get search options
if (isset($actions['searchopt'])) {
$searchopt = $actions['searchopt'];
// No name for clean array with quotes
unset($searchopt['name']);
unset($actions['searchopt']);
}
$searchtype_name = "{$fieldname}{$prefix}[$num][searchtype]";
echo "<div class='col-auto'>";
$rands = Dropdown::showFromArray($searchtype_name, $actions, [
'value' => $request["searchtype"],
]);
echo "</div>";
$fieldsearch_id = Html::cleanId("dropdown_$searchtype_name$rands");
}
echo "<div class='col-auto' id='$dropdownname' data-itemtype='{$request["itemtype"]}' data-fieldname='$fieldname' data-prefix='$prefix' data-num='$num'>";
$params = [
'value' => rawurlencode(Sanitizer::dbUnescape($request['value'])),
'searchopt' => $searchopt,
'searchtype' => $request["searchtype"],
'num' => $num,
'itemtype' => $request["itemtype"],
'_idor_token' => Session::getNewIDORToken($request["itemtype"]),
'from_meta' => isset($request['from_meta'])
? $request['from_meta']
: false,
'field' => $request["field"],
'p' => $p,
];
self::displaySearchoptionValue($params);
echo "</div>";
Ajax::updateItemOnSelectEvent(
$fieldsearch_id,
$dropdownname,
$CFG_GLPI["root_doc"] . "/ajax/search.php",
[
'action' => 'display_searchoption_value',
'searchtype' => '__VALUE__',
] + $params
);
}
/**
* Display last part of criteria (value, just after searchtype)
* called by displaySearchoptionValue
*
* @since 9.4
*
* @param array $request we should have these keys of parameters:
* - searchtype: (contains, equals) passed by displaySearchoption
*
* @return void
*/
public static function displaySearchoptionValue($request = [])
{
if (!isset($request['searchtype'])) {
return "";
}
$p = $request['p'];
$prefix = isset($p['prefix_crit']) ? $p['prefix_crit'] : '';
$searchopt = isset($request['searchopt']) ? $request['searchopt'] : [];
$request['value'] = rawurldecode($request['value']);
$fieldname = isset($request['meta']) && $request['meta']
? 'metacriteria'
: 'criteria';
$inputname = $fieldname . $prefix . '[' . $request['num'] . '][value]';
$display = false;
$item = getItemForItemtype($request['itemtype']);
$options2 = [];
$options2['value'] = $request['value'];
$options2['width'] = '100%';
// For tree dropdpowns
$options2['permit_select_parent'] = true;
switch ($request['searchtype']) {
case "equals":
case "notequals":
case "morethan":
case "lessthan":
case "under":
case "notunder":
if (!$display && isset($searchopt['field'])) {
// Specific cases
switch ($searchopt['table'] . "." . $searchopt['field']) {
// Add mygroups choice to searchopt
case "glpi_groups.completename":
$searchopt['toadd'] = ['mygroups' => __('My groups')];
break;
case "glpi_changes.status":
case "glpi_changes.impact":
case "glpi_changes.urgency":
case "glpi_problems.status":
case "glpi_problems.impact":
case "glpi_problems.urgency":
case "glpi_tickets.status":
case "glpi_tickets.impact":
case "glpi_tickets.urgency":
$options2['showtype'] = 'search';
break;
case "glpi_changes.priority":
case "glpi_problems.priority":
case "glpi_tickets.priority":
$options2['showtype'] = 'search';
$options2['withmajor'] = true;
break;
case "glpi_tickets.global_validation":
$options2['all'] = true;
break;
case "glpi_ticketvalidations.status":
$options2['all'] = true;
break;
case "glpi_users.name":
$options2['right'] = (isset($searchopt['right']) ? $searchopt['right'] : 'all');
$options2['inactive_deleted'] = 1;
$searchopt['toadd'] = [
[
'id' => 'myself',
'text' => __('Myself'),
]
];
break;
}
// Standard datatype usage
if (!$display && isset($searchopt['datatype'])) {
switch ($searchopt['datatype']) {
case "date":
case "date_delay":
case "datetime":
$options2['relative_dates'] = true;
break;
}
}
$out = $item->getValueToSelect($searchopt, $inputname, $request['value'], $options2);
if (strlen($out)) {
echo $out;
$display = true;
}
//Could display be handled by a plugin ?
if (
!$display
&& $plug = isPluginItemType(getItemTypeForTable($searchopt['table']))
) {
$display = Plugin::doOneHook(
$plug['plugin'],
'searchOptionsValues',
[
'name' => $inputname,
'searchtype' => $request['searchtype'],
'searchoption' => $searchopt,
'value' => $request['value']
]
);
}
}
break;
}
// Default case : text field
if (!$display) {
echo "<input type='text' class='form-control' size='13' name='$inputname' value=\"" .
Html::cleanInputText($request['value']) . "\">";
}
}
/**
* Generic Function to add to a HAVING clause
*
* @since 9.4: $num param has been dropped
*
* @param string $LINK link to use
* @param string $NOT is is a negative search ?
* @param string $itemtype item type
* @param integer $ID ID of the item to search
* @param string $searchtype search type ('contains' or 'equals')
* @param string $val value search
*
* @return string|false HAVING clause sub-string (Does not include the "HAVING" keyword).
* May return false if the related search option is not valid for SQL searching.
**/
public static function addHaving($LINK, $NOT, $itemtype, $ID, $searchtype, $val)
{
global $DB;
$searchopt = &self::getOptions($itemtype);
if (!isset($searchopt[$ID]['table'])) {
return false;
}
$table = $searchopt[$ID]["table"];
$NAME = "ITEM_{$itemtype}_{$ID}";
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'addHaving',
$LINK,
$NOT,
$itemtype,
$ID,
$val,
"{$itemtype}_{$ID}"
);
if (!empty($out)) {
return $out;
}
}
//// Default cases
// Link with plugin tables
if (preg_match("/^glpi_plugin_([a-z0-9]+)/", $table, $matches)) {
if (count($matches) == 2) {
$plug = $matches[1];
$out = Plugin::doOneHook(
$plug,
'addHaving',
$LINK,
$NOT,
$itemtype,
$ID,
$val,
"{$itemtype}_{$ID}"
);
if (!empty($out)) {
return $out;
}
}
}
if (in_array($searchtype, ["notequals", "notcontains"])) {
$NOT = !$NOT;
}
// Preformat items
if (isset($searchopt[$ID]["datatype"])) {
if ($searchopt[$ID]["datatype"] == "mio") {
// Parse value as it may contain a few different formats
$val = Toolbox::getMioSizeFromString($val);
}
switch ($searchopt[$ID]["datatype"]) {
case "datetime":
if (in_array($searchtype, ['contains', 'notcontains'])) {
break;
}
$force_day = false;
if (strstr($val, 'BEGIN') || strstr($val, 'LAST')) {
$force_day = true;
}
$val = Html::computeGenericDateTimeSearch($val, $force_day);
$operator = '';
switch ($searchtype) {
case 'equals':
$operator = !$NOT ? '=' : '!=';
break;
case 'notequals':
$operator = !$NOT ? '!=' : '=';
break;
case 'lessthan':
$operator = !$NOT ? '<' : '>';
break;
case 'morethan':
$operator = !$NOT ? '>' : '<';
break;
}
return " {$LINK} ({$DB->quoteName($NAME)} $operator {$DB->quoteValue($val)}) ";
break;
case "count":
case "mio":
case "number":
case "decimal":
case "timestamp":
$search = ["/\</","/\>/"];
$replace = ["<",">"];
$val = preg_replace($search, $replace, $val);
if (preg_match("/([<>])([=]*)[[:space:]]*([0-9]+)/", $val, $regs)) {
if ($NOT) {
if ($regs[1] == '<') {
$regs[1] = '>';
} else {
$regs[1] = '<';
}
}
$regs[1] .= $regs[2];
return " $LINK (`$NAME` " . $regs[1] . " " . $regs[3] . " ) ";
}
if (is_numeric($val)) {
if (isset($searchopt[$ID]["width"])) {
if (!$NOT) {
return " $LINK (`$NAME` < " . (intval($val) + $searchopt[$ID]["width"]) . "
AND `$NAME` > " .
(intval($val) - $searchopt[$ID]["width"]) . ") ";
}
return " $LINK (`$NAME` > " . (intval($val) + $searchopt[$ID]["width"]) . "
OR `$NAME` < " .
(intval($val) - $searchopt[$ID]["width"]) . " ) ";
}
// Exact search
if (!$NOT) {
return " $LINK (`$NAME` = " . (intval($val)) . ") ";
}
return " $LINK (`$NAME` <> " . (intval($val)) . ") ";
}
break;
}
}
return self::makeTextCriteria("`$NAME`", $val, $NOT, $LINK);
}
/**
* Generic Function to add ORDER BY to a request
*
* @since 9.4: $key param has been dropped
* @since 10.0.0: Parameters changed to allow multiple sort fields.
* Old functionality maintained by checking the type of the first parameter.
* This backwards compatibility will be removed in a later version.
*
* @param class-string<CommonDBTM> $itemtype The itemtype
* @param array $sort_fields The search options to order on. This array should contain one or more associative arrays containing:
* - id: The search option ID
* - order: The sort direction (Default: ASC). Invalid sort directions will be replaced with the default option
* @param ?integer $_id field to add (Deprecated)
*
* @return string ORDER BY query string
*
**/
public static function addOrderBy($itemtype, $sort_fields, $_id = 'ASC')
{
global $CFG_GLPI;
// BC parameter conversion
if (!is_array($sort_fields)) {
// < 10.0.0 parameters
Toolbox::deprecated('The parameters for Search::addOrderBy have changed to allow sorting by multiple fields. Please update your calling code.');
$sort_fields = [
[
'searchopt_id' => $sort_fields,
'order' => $_id
]
];
}
$orderby_criteria = [];
$searchopt = &self::getOptions($itemtype);
foreach ($sort_fields as $sort_field) {
$ID = $sort_field['searchopt_id'];
if (isset($searchopt[$ID]['nosort']) && $searchopt[$ID]['nosort']) {
continue;
}
$order = $sort_field['order'] ?? 'ASC';
// Order security check
if ($order != 'ASC') {
$order = 'DESC';
}
$criterion = null;
$table = $searchopt[$ID]["table"];
$field = $searchopt[$ID]["field"];
$addtable = '';
$is_fkey_composite_on_self = getTableNameForForeignKeyField($searchopt[$ID]["linkfield"]) == $table
&& $searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table);
$orig_table = self::getOrigTableName($itemtype);
if (
($is_fkey_composite_on_self || $table != $orig_table)
&& ($searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table))
) {
$addtable .= "_" . $searchopt[$ID]["linkfield"];
}
if (isset($searchopt[$ID]['joinparams'])) {
$complexjoin = self::computeComplexJoinID($searchopt[$ID]['joinparams']);
if (!empty($complexjoin)) {
$addtable .= "_" . $complexjoin;
}
}
if (isset($CFG_GLPI["union_search_type"][$itemtype])) {
$criterion = "`ITEM_{$itemtype}_{$ID}` $order";
}
// Plugin can override core definition for its type
if ($criterion === null && $plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'addOrderBy',
$itemtype,
$ID,
$order,
"{$itemtype}_{$ID}"
);
$out = $out !== null ? trim($out) : null;
if (!empty($out)) {
$out = preg_replace('/^ORDER BY /', '', $out);
$criterion = $out;
}
}
if ($criterion === null) {
switch ($table . "." . $field) {
// FIXME Dead case? Can't see any itemtype referencing this table in their search options to be able to get here.
case "glpi_auth_tables.name":
$user_searchopt = self::getOptions('User');
$criterion = "`glpi_users`.`authtype` $order,
`glpi_authldaps" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[30]['joinparams']) . "`.
`name` $order,
`glpi_authmails" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[31]['joinparams']) . "`.
`name` $order";
break;
case "glpi_users.name":
if ($itemtype != 'User') {
if ($_SESSION["glpinames_format"] == User::FIRSTNAME_BEFORE) {
$name1 = 'firstname';
$name2 = 'realname';
} else {
$name1 = 'realname';
$name2 = 'firstname';
}
$criterion = "`" . $table . $addtable . "`.`$name1` $order,
`" . $table . $addtable . "`.`$name2` $order,
`" . $table . $addtable . "`.`name` $order";
} else {
$criterion = "`" . $table . $addtable . "`.`name` $order";
}
break;
//FIXME glpi_networkequipments.ip seems like a dead case
case "glpi_networkequipments.ip":
case "glpi_ipaddresses.name":
$criterion = "INET_ATON(`$table$addtable`.`$field`) $order";
break;
}
}
//// Default cases
// Link with plugin tables
if ($criterion === null && preg_match("/^glpi_plugin_([a-z0-9]+)/", $table, $matches)) {
if (count($matches) == 2) {
$plug = $matches[1];
$out = Plugin::doOneHook(
$plug,
'addOrderBy',
$itemtype,
$ID,
$order,
"{$itemtype}_{$ID}"
);
$out = $out !== null ? trim($out) : null;
if (!empty($out)) {
$out = preg_replace('/^ORDER BY /', '', $out);
$criterion = $out;
}
}
}
// Preformat items
if ($criterion === null && isset($searchopt[$ID]["datatype"])) {
switch ($searchopt[$ID]["datatype"]) {
case "date_delay":
$interval = "MONTH";
if (isset($searchopt[$ID]['delayunit'])) {
$interval = $searchopt[$ID]['delayunit'];
}
$add_minus = '';
if (isset($searchopt[$ID]["datafields"][3])) {
$add_minus = "- `$table$addtable`.`" . $searchopt[$ID]["datafields"][3] . "`";
}
$criterion = "ADDDATE(`$table$addtable`.`" . $searchopt[$ID]["datafields"][1] . "`,
INTERVAL (`$table$addtable`.`" .
$searchopt[$ID]["datafields"][2] . "` $add_minus)
$interval) $order";
}
}
$orderby_criteria[] = $criterion ?? "`ITEM_{$itemtype}_{$ID}` $order";
}
if (count($orderby_criteria) === 0) {
return '';
}
return ' ORDER BY ' . implode(', ', $orderby_criteria) . ' ';
}
/**
* Generic Function to add default columns to view
*
* @param class-string<CommonDBTM> $itemtype Item type
* @param array $params array of parameters
*
* @return array Array of search option IDs to be shown in the results
**/
public static function addDefaultToView($itemtype, $params)
{
global $CFG_GLPI;
$toview = [];
$item = null;
$entity_check = true;
if ($itemtype != AllAssets::getType()) {
$item = getItemForItemtype($itemtype);
$entity_check = $item->isEntityAssign();
}
// Add first element (name)
array_push($toview, 1);
if (isset($params['as_map']) && $params['as_map'] == 1) {
// Add location name when map mode
array_push($toview, ($itemtype == 'Location' ? 1 : ($itemtype == 'Ticket' ? 83 : 3)));
}
// Add entity view :
if (
Session::isMultiEntitiesMode()
&& $entity_check
&& (isset($CFG_GLPI["union_search_type"][$itemtype])
|| ($item && $item->maybeRecursive())
|| isset($_SESSION['glpiactiveentities']) && (count($_SESSION["glpiactiveentities"]) > 1))
) {
array_push($toview, 80);
}
return $toview;
}
/**
* Generic Function to add default select to a request
*
* @param class-string<CommonDBTM> $itemtype device type
*
* @return string Select string
**/
public static function addDefaultSelect($itemtype)
{
global $DB;
$itemtable = self::getOrigTableName($itemtype);
$item = null;
$mayberecursive = false;
if ($itemtype != AllAssets::getType()) {
$item = getItemForItemtype($itemtype);
$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)) {
$ret = Plugin::doOneHook(
$plug['plugin'],
'addDefaultSelect',
$itemtype
);
}
}
if ($itemtable == 'glpi_entities') {
$ret .= "`$itemtable`.`id` AS entities_id, '1' AS is_recursive, ";
} else if ($mayberecursive) {
if ($item->isField('entities_id')) {
$ret .= $DB->quoteName("$itemtable.entities_id") . ", ";
}
if ($item->isField('is_recursive')) {
$ret .= $DB->quoteName("$itemtable.is_recursive") . ", ";
}
}
return $ret;
}
/**
* Generic Function to add select to a request
*
* @since 9.4: $num param has been dropped
*
* @param string $itemtype item type
* @param integer $ID ID of the item to add
* @param boolean $meta boolean is a meta
* @param integer $meta_type meta type table ID (default 0)
*
* @return string Select string
**/
public static function addSelect($itemtype, $ID, $meta = 0, $meta_type = 0)
{
global $DB, $CFG_GLPI;
$searchopt = &self::getOptions($itemtype);
$table = $searchopt[$ID]["table"];
$field = $searchopt[$ID]["field"];
$addtable = "";
$addtable2 = "";
$NAME = "ITEM_{$itemtype}_{$ID}";
$complexjoin = '';
if (isset($searchopt[$ID]['joinparams'])) {
$complexjoin = self::computeComplexJoinID($searchopt[$ID]['joinparams']);
}
$is_fkey_composite_on_self = getTableNameForForeignKeyField($searchopt[$ID]["linkfield"]) == $table
&& $searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table);
$orig_table = self::getOrigTableName($itemtype);
if (
((($is_fkey_composite_on_self || $table != $orig_table)
&& (!isset($CFG_GLPI["union_search_type"][$itemtype])
|| ($CFG_GLPI["union_search_type"][$itemtype] != $table)))
|| !empty($complexjoin))
&& ($searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table))
) {
$addtable .= "_" . $searchopt[$ID]["linkfield"];
}
if (!empty($complexjoin)) {
$addtable .= "_" . $complexjoin;
$addtable2 .= "_" . $complexjoin;
}
$addmeta = "";
if ($meta) {
// $NAME = "META";
if ($meta_type::getTable() != $table) {
$addmeta = "_" . $meta_type;
$addtable .= $addmeta;
$addtable2 .= $addmeta;
}
}
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'addSelect',
$itemtype,
$ID,
"{$itemtype}_{$ID}"
);
if (!empty($out)) {
return $out;
}
}
$tocompute = "`$table$addtable`.`$field`";
$tocomputeid = "`$table$addtable`.`id`";
$tocomputetrans = "IFNULL(`$table" . $addtable . "_trans_" . $field . "`.`value`,'" . self::NULLVALUE . "') ";
$ADDITONALFIELDS = '';
if (
isset($searchopt[$ID]["additionalfields"])
&& count($searchopt[$ID]["additionalfields"])
) {
foreach ($searchopt[$ID]["additionalfields"] as $key) {
if (
$meta
|| (isset($searchopt[$ID]["forcegroupby"]) && $searchopt[$ID]["forcegroupby"])
) {
$ADDITONALFIELDS .= " IFNULL(GROUP_CONCAT(DISTINCT CONCAT(IFNULL(`$table$addtable`.`$key`,
'" . self::NULLVALUE . "'),
'" . self::SHORTSEP . "', $tocomputeid)ORDER BY $tocomputeid SEPARATOR '" . self::LONGSEP . "'), '" . self::NULLVALUE . self::SHORTSEP . "')
AS `" . $NAME . "_$key`, ";
} else {
$ADDITONALFIELDS .= "`$table$addtable`.`$key` AS `" . $NAME . "_$key`, ";
}
}
}
// Virtual display no select : only get additional fields
if (strpos($field, '_virtual') === 0) {
return $ADDITONALFIELDS;
}
switch ($table . "." . $field) {
case "glpi_users.name":
if ($itemtype != 'User') {
if ((isset($searchopt[$ID]["forcegroupby"]) && $searchopt[$ID]["forcegroupby"])) {
$addaltemail = "";
if (
(($itemtype == 'Ticket') || ($itemtype == 'Problem'))
&& isset($searchopt[$ID]['joinparams']['beforejoin']['table'])
&& (($searchopt[$ID]['joinparams']['beforejoin']['table']
== 'glpi_tickets_users')
|| ($searchopt[$ID]['joinparams']['beforejoin']['table']
== 'glpi_problems_users')
|| ($searchopt[$ID]['joinparams']['beforejoin']['table']
== 'glpi_changes_users'))
) { // For tickets_users
$ticket_user_table
= $searchopt[$ID]['joinparams']['beforejoin']['table'] .
"_" . self::computeComplexJoinID($searchopt[$ID]['joinparams']['beforejoin']
['joinparams']) . $addmeta;
$addaltemail
= "GROUP_CONCAT(DISTINCT CONCAT(`$ticket_user_table`.`users_id`, ' ',
`$ticket_user_table`.`alternative_email`)
SEPARATOR '" . self::LONGSEP . "') AS `" . $NAME . "_2`, ";
}
return " GROUP_CONCAT(DISTINCT `$table$addtable`.`id` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$addaltemail
$ADDITONALFIELDS";
}
return " `$table$addtable`.`$field` AS `" . $NAME . "`,
`$table$addtable`.`realname` AS `" . $NAME . "_realname`,
`$table$addtable`.`id` AS `" . $NAME . "_id`,
`$table$addtable`.`firstname` AS `" . $NAME . "_firstname`,
$ADDITONALFIELDS";
}
break;
case "glpi_softwarelicenses.number":
if ($meta) {
return " FLOOR(SUM(`$table$addtable2`.`$field`)
* COUNT(DISTINCT `$table$addtable2`.`id`)
/ COUNT(`$table$addtable2`.`id`)) AS `" . $NAME . "`,
MIN(`$table$addtable2`.`$field`) AS `" . $NAME . "_min`,
$ADDITONALFIELDS";
} else {
return " FLOOR(SUM(`$table$addtable`.`$field`)
* COUNT(DISTINCT `$table$addtable`.`id`)
/ COUNT(`$table$addtable`.`id`)) AS `" . $NAME . "`,
MIN(`$table$addtable`.`$field`) AS `" . $NAME . "_min`,
$ADDITONALFIELDS";
}
case "glpi_profiles.name":
if (
($itemtype == 'User')
&& ($ID == 20)
) {
$addtable2 = '';
if ($meta) {
$addtable2 = "_" . $meta_type;
}
return " GROUP_CONCAT(`$table$addtable`.`$field` SEPARATOR '" . self::LONGSEP . "') AS `" . $NAME . "`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`entities_id` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_entities_id`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`is_recursive` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_is_recursive`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`is_dynamic` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_is_dynamic`,
$ADDITONALFIELDS";
}
break;
case "glpi_entities.completename":
if (
($itemtype == 'User')
&& ($ID == 80)
) {
$addtable2 = '';
if ($meta) {
$addtable2 = "_" . $meta_type;
}
return " GROUP_CONCAT(`$table$addtable`.`completename` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`profiles_id` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_profiles_id`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`is_recursive` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_is_recursive`,
GROUP_CONCAT(`glpi_profiles_users$addtable2`.`is_dynamic` SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_is_dynamic`,
$ADDITONALFIELDS";
}
break;
case "glpi_auth_tables.name":
$user_searchopt = self::getOptions('User');
return " `glpi_users`.`authtype` AS `" . $NAME . "`,
`glpi_users`.`auths_id` AS `" . $NAME . "_auths_id`,
`glpi_authldaps" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[30]['joinparams']) . $addmeta . "`.`$field`
AS `" . $NAME . "_" . $ID . "_ldapname`,
`glpi_authmails" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[31]['joinparams']) . $addmeta . "`.`$field`
AS `" . $NAME . "_mailname`,
$ADDITONALFIELDS";
case "glpi_softwareversions.name":
if ($meta && ($meta_type == 'Software')) {
return " GROUP_CONCAT(DISTINCT CONCAT(`glpi_softwares`.`name`, ' - ',
`$table$addtable2`.`$field`, '" . self::SHORTSEP . "',
`$table$addtable2`.`id`) SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$ADDITONALFIELDS";
}
break;
case "glpi_softwareversions.comment":
if ($meta && ($meta_type == 'Software')) {
return " GROUP_CONCAT(DISTINCT CONCAT(`glpi_softwares`.`name`, ' - ',
`$table$addtable2`.`$field`,'" . self::SHORTSEP . "',
`$table$addtable2`.`id`) SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$ADDITONALFIELDS";
}
return " GROUP_CONCAT(DISTINCT CONCAT(`$table$addtable`.`name`, ' - ',
`$table$addtable`.`$field`, '" . self::SHORTSEP . "',
`$table$addtable`.`id`) SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$ADDITONALFIELDS";
case "glpi_states.name":
if ($meta && ($meta_type == 'Software')) {
return " GROUP_CONCAT(DISTINCT CONCAT(`glpi_softwares`.`name`, ' - ',
`glpi_softwareversions$addtable`.`name`, ' - ',
`$table$addtable2`.`$field`, '" . self::SHORTSEP . "',
`$table$addtable2`.`id`) SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$ADDITONALFIELDS";
} else if ($itemtype == 'Software') {
return " GROUP_CONCAT(DISTINCT CONCAT(`glpi_softwareversions`.`name`, ' - ',
`$table$addtable`.`$field`,'" . self::SHORTSEP . "',
`$table$addtable`.`id`) SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$ADDITONALFIELDS";
}
break;
case "glpi_itilfollowups.content":
case "glpi_tickettasks.content":
case "glpi_changetasks.content":
if (is_subclass_of($itemtype, "CommonITILObject")) {
// force ordering by date desc
return " GROUP_CONCAT(
DISTINCT CONCAT(
IFNULL($tocompute, '" . self::NULLVALUE . "'),
'" . self::SHORTSEP . "',
$tocomputeid
)
ORDER BY `$table$addtable`.`date` DESC
SEPARATOR '" . self::LONGSEP . "'
) AS `" . $NAME . "`, $ADDITONALFIELDS";
}
break;
default:
break;
}
//// Default cases
// Link with plugin tables
if (preg_match("/^glpi_plugin_([a-z0-9]+)/", $table, $matches)) {
if (count($matches) == 2) {
$plug = $matches[1];
$out = Plugin::doOneHook(
$plug,
'addSelect',
$itemtype,
$ID,
"{$itemtype}_{$ID}"
);
if (!empty($out)) {
return $out;
}
}
}
if (isset($searchopt[$ID]["computation"])) {
$tocompute = $searchopt[$ID]["computation"];
$tocompute = str_replace($DB->quoteName('TABLE'), 'TABLE', $tocompute);
$tocompute = str_replace("TABLE", $DB->quoteName("$table$addtable"), $tocompute);
}
// Preformat items
if (isset($searchopt[$ID]["datatype"])) {
switch ($searchopt[$ID]["datatype"]) {
case "count":
return " COUNT(DISTINCT `$table$addtable`.`$field`) AS `" . $NAME . "`,
$ADDITONALFIELDS";
case "date_delay":
$interval = "MONTH";
if (isset($searchopt[$ID]['delayunit'])) {
$interval = $searchopt[$ID]['delayunit'];
}
$add_minus = '';
if (isset($searchopt[$ID]["datafields"][3])) {
$add_minus = "-`$table$addtable`.`" . $searchopt[$ID]["datafields"][3] . "`";
}
if (
$meta
|| (isset($searchopt[$ID]["forcegroupby"]) && $searchopt[$ID]["forcegroupby"])
) {
return " GROUP_CONCAT(DISTINCT ADDDATE(`$table$addtable`.`" .
$searchopt[$ID]["datafields"][1] . "`,
INTERVAL (`$table$addtable`.`" .
$searchopt[$ID]["datafields"][2] .
"` $add_minus) $interval)
SEPARATOR '" . self::LONGSEP . "') AS `" . $NAME . "`,
$ADDITONALFIELDS";
}
return "ADDDATE(`$table$addtable`.`" . $searchopt[$ID]["datafields"][1] . "`,
INTERVAL (`$table$addtable`.`" . $searchopt[$ID]["datafields"][2] .
"` $add_minus) $interval) AS `" . $NAME . "`,
$ADDITONALFIELDS";
case "itemlink":
if (
$meta
|| (isset($searchopt[$ID]["forcegroupby"]) && $searchopt[$ID]["forcegroupby"])
) {
$TRANS = '';
if (Session::haveTranslations(getItemTypeForTable($table), $field)) {
$TRANS = "GROUP_CONCAT(DISTINCT CONCAT(IFNULL($tocomputetrans, '" . self::NULLVALUE . "'),
'" . self::SHORTSEP . "',$tocomputeid) ORDER BY $tocomputeid
SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_trans_" . $field . "`, ";
}
return " GROUP_CONCAT(DISTINCT CONCAT($tocompute, '" . self::SHORTSEP . "' ,
`$table$addtable`.`id`) ORDER BY `$table$addtable`.`id`
SEPARATOR '" . self::LONGSEP . "') AS `" . $NAME . "`,
$TRANS
$ADDITONALFIELDS";
}
return " $tocompute AS `" . $NAME . "`,
`$table$addtable`.`id` AS `" . $NAME . "_id`,
$ADDITONALFIELDS";
}
}
// Default case
if (
$meta
|| (isset($searchopt[$ID]["forcegroupby"]) && $searchopt[$ID]["forcegroupby"]
&& (!isset($searchopt[$ID]["computation"])
|| isset($searchopt[$ID]["computationgroupby"])
&& $searchopt[$ID]["computationgroupby"]))
) { // Not specific computation
$TRANS = '';
if (Session::haveTranslations(getItemTypeForTable($table), $field)) {
$TRANS = "GROUP_CONCAT(DISTINCT CONCAT(IFNULL($tocomputetrans, '" . self::NULLVALUE . "'),
'" . self::SHORTSEP . "',$tocomputeid) ORDER BY $tocomputeid SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "_trans_" . $field . "`, ";
}
return " GROUP_CONCAT(DISTINCT CONCAT(IFNULL($tocompute, '" . self::NULLVALUE . "'),
'" . self::SHORTSEP . "',$tocomputeid) ORDER BY $tocomputeid SEPARATOR '" . self::LONGSEP . "')
AS `" . $NAME . "`,
$TRANS
$ADDITONALFIELDS";
}
$TRANS = '';
if (Session::haveTranslations(getItemTypeForTable($table), $field)) {
$TRANS = $tocomputetrans . " AS `" . $NAME . "_trans_" . $field . "`, ";
}
return "$tocompute AS `" . $NAME . "`, $TRANS $ADDITONALFIELDS";
}
/**
* Generic Function to add default where to a request
*
* @param class-string<CommonDBTM> $itemtype device type
*
* @return string Where string
**/
public static function addDefaultWhere($itemtype)
{
$condition = '';
switch ($itemtype) {
case 'Reservation':
$condition = getEntitiesRestrictRequest("", ReservationItem::getTable(), '', '', true);
break;
case 'Reminder':
$condition = Reminder::addVisibilityRestrict();
break;
case 'RSSFeed':
$condition = RSSFeed::addVisibilityRestrict();
break;
case 'Notification':
if (!Config::canView()) {
$condition = " `glpi_notifications`.`itemtype` NOT IN ('CronTask', 'DBConnection') ";
}
break;
// No link
case 'User':
// View all entities
if (!Session::canViewAllEntities()) {
$condition = getEntitiesRestrictRequest("", "glpi_profiles_users", '', '', true);
}
break;
case 'ProjectTask':
$condition = '';
$teamtable = 'glpi_projecttaskteams';
$condition .= "`glpi_projects`.`is_template` = 0";
$condition .= " AND ((`$teamtable`.`itemtype` = 'User'
AND `$teamtable`.`items_id` = '" . Session::getLoginUserID() . "')";
if (count($_SESSION['glpigroups'])) {
$condition .= " OR (`$teamtable`.`itemtype` = 'Group'
AND `$teamtable`.`items_id`
IN (" . implode(",", $_SESSION['glpigroups']) . "))";
}
$condition .= ") ";
break;
case 'Project':
$condition = '';
if (!Session::haveRight("project", Project::READALL)) {
$teamtable = 'glpi_projectteams';
$condition .= "(`glpi_projects`.users_id = '" . Session::getLoginUserID() . "'
OR (`$teamtable`.`itemtype` = 'User'
AND `$teamtable`.`items_id` = '" . Session::getLoginUserID() . "')";
if (count($_SESSION['glpigroups'])) {
$condition .= " OR (`glpi_projects`.`groups_id`
IN (" . implode(",", $_SESSION['glpigroups']) . "))";
$condition .= " OR (`$teamtable`.`itemtype` = 'Group'
AND `$teamtable`.`items_id`
IN (" . implode(",", $_SESSION['glpigroups']) . "))";
}
$condition .= ") ";
}
break;
case 'Ticket':
// Same structure in addDefaultJoin
$condition = '';
if (!Session::haveRight("ticket", Ticket::READALL)) {
$searchopt
= &self::getOptions($itemtype);
$requester_table
= '`glpi_tickets_users_' .
self::computeComplexJoinID($searchopt[4]['joinparams']['beforejoin']
['joinparams']) . '`';
$requestergroup_table
= '`glpi_groups_tickets_' .
self::computeComplexJoinID($searchopt[71]['joinparams']['beforejoin']
['joinparams']) . '`';
$assign_table
= '`glpi_tickets_users_' .
self::computeComplexJoinID($searchopt[5]['joinparams']['beforejoin']
['joinparams']) . '`';
$assigngroup_table
= '`glpi_groups_tickets_' .
self::computeComplexJoinID($searchopt[8]['joinparams']['beforejoin']
['joinparams']) . '`';
$observer_table
= '`glpi_tickets_users_' .
self::computeComplexJoinID($searchopt[66]['joinparams']['beforejoin']
['joinparams']) . '`';
$observergroup_table
= '`glpi_groups_tickets_' .
self::computeComplexJoinID($searchopt[65]['joinparams']['beforejoin']
['joinparams']) . '`';
$condition = "(";
if (Session::haveRight("ticket", Ticket::READMY)) {
$condition .= " $requester_table.users_id = '" . Session::getLoginUserID() . "'
OR $observer_table.users_id = '" . Session::getLoginUserID() . "'
OR `glpi_tickets`.`users_id_recipient` = '" . Session::getLoginUserID() . "'";
} else {
$condition .= "0=1";
}
if (Session::haveRight("ticket", Ticket::READGROUP)) {
if (count($_SESSION['glpigroups'])) {
$condition .= " OR $requestergroup_table.`groups_id`
IN (" . implode(",", $_SESSION['glpigroups']) . ")";
$condition .= " OR $observergroup_table.`groups_id`
IN (" . implode(",", $_SESSION['glpigroups']) . ")";
}
}
if (Session::haveRight("ticket", Ticket::OWN)) {// Can own ticket : show assign to me
$condition .= " OR $assign_table.users_id = '" . Session::getLoginUserID() . "' ";
}
if (Session::haveRight("ticket", Ticket::READASSIGN)) { // assign to me
$condition .= " OR $assign_table.`users_id` = '" . Session::getLoginUserID() . "'";
if (count($_SESSION['glpigroups'])) {
$condition .= " OR $assigngroup_table.`groups_id`
IN (" . implode(",", $_SESSION['glpigroups']) . ")";
}
if (Session::haveRight('ticket', Ticket::ASSIGN)) {
$condition .= " OR `glpi_tickets`.`status`='" . CommonITILObject::INCOMING . "'";
}
}
if (
Session::haveRightsOr(
'ticketvalidation',
[TicketValidation::VALIDATEINCIDENT,
TicketValidation::VALIDATEREQUEST
]
)
) {
$condition .= " OR `glpi_ticketvalidations`.`users_id_validate`
= '" . Session::getLoginUserID() . "'";
}
$condition .= ") ";
}
break;
case 'Change':
case 'Problem':
if ($itemtype == 'Change') {
$right = 'change';
$table = 'changes';
$groupetable = "`glpi_changes_groups_";
} else if ($itemtype == 'Problem') {
$right = 'problem';
$table = 'problems';
$groupetable = "`glpi_groups_problems_";
}
// Same structure in addDefaultJoin
$condition = '';
if (!Session::haveRight("$right", $itemtype::READALL)) {
$searchopt = &self::getOptions($itemtype);
if (Session::haveRight("$right", $itemtype::READMY)) {
$requester_table = '`glpi_' . $table . '_users_' .
self::computeComplexJoinID($searchopt[4]['joinparams']
['beforejoin']['joinparams']) . '`';
$requestergroup_table = $groupetable .
self::computeComplexJoinID($searchopt[71]['joinparams']
['beforejoin']['joinparams']) . '`';
$observer_table = '`glpi_' . $table . '_users_' .
self::computeComplexJoinID($searchopt[66]['joinparams']
['beforejoin']['joinparams']) . '`';
$observergroup_table = $groupetable .
self::computeComplexJoinID($searchopt[65]['joinparams']
['beforejoin']['joinparams']) . '`';
$assign_table = '`glpi_' . $table . '_users_' .
self::computeComplexJoinID($searchopt[5]['joinparams']
['beforejoin']['joinparams']) . '`';
$assigngroup_table = $groupetable .
self::computeComplexJoinID($searchopt[8]['joinparams']
['beforejoin']['joinparams']) . '`';
}
$condition = "(";
if (Session::haveRight("$right", $itemtype::READMY)) {
$condition .= " $requester_table.users_id = '" . Session::getLoginUserID() . "'
OR $observer_table.users_id = '" . Session::getLoginUserID() . "'
OR $assign_table.users_id = '" . Session::getLoginUserID() . "'
OR `glpi_" . $table . "`.`users_id_recipient` = '" . Session::getLoginUserID() . "'";
if (count($_SESSION['glpigroups'])) {
$my_groups_keys = "'" . implode("','", $_SESSION['glpigroups']) . "'";
$condition .= " OR $requestergroup_table.groups_id IN ($my_groups_keys)
OR $observergroup_table.groups_id IN ($my_groups_keys)
OR $assigngroup_table.groups_id IN ($my_groups_keys)";
}
} else {
$condition .= "0=1";
}
$condition .= ") ";
}
break;
case 'Config':
$availableContexts = ['core'] + Plugin::getPlugins();
$availableContexts = implode("', '", $availableContexts);
$condition = "`context` IN ('$availableContexts')";
break;
case 'SavedSearch':
$condition = SavedSearch::addVisibilityRestrict();
break;
case 'TicketTask':
// Filter on is_private
$allowed_is_private = [];
if (Session::haveRight(TicketTask::$rightname, CommonITILTask::SEEPRIVATE)) {
$allowed_is_private[] = 1;
}
if (Session::haveRight(TicketTask::$rightname, CommonITILTask::SEEPUBLIC)) {
$allowed_is_private[] = 0;
}
// If the user can't see public and private
if (!count($allowed_is_private)) {
$condition = "0 = 1";
break;
}
$in = "IN ('" . implode("','", $allowed_is_private) . "')";
$condition = "(`glpi_tickettasks`.`is_private` $in ";
// Check for assigned or created tasks
$condition .= "OR `glpi_tickettasks`.`users_id` = " . Session::getLoginUserID() . " ";
$condition .= "OR `glpi_tickettasks`.`users_id_tech` = " . Session::getLoginUserID() . " ";
// Check for parent item visibility unless the user can see all the
// possible parents
if (!Session::haveRight('ticket', Ticket::READALL)) {
$condition .= "AND " . TicketTask::buildParentCondition();
}
$condition .= ")";
break;
case 'ITILFollowup':
// Filter on is_private
$allowed_is_private = [];
if (Session::haveRight(ITILFollowup::$rightname, ITILFollowup::SEEPRIVATE)) {
$allowed_is_private[] = 1;
}
if (Session::haveRight(ITILFollowup::$rightname, ITILFollowup::SEEPUBLIC)) {
$allowed_is_private[] = 0;
}
// If the user can't see public and private
if (!count($allowed_is_private)) {
$condition = "0 = 1";
break;
}
$in = "IN ('" . implode("','", $allowed_is_private) . "')";
$condition = "(`glpi_itilfollowups`.`is_private` $in ";
// Now filter on parent item visiblity
$condition .= "AND (";
// Filter for "ticket" parents
$condition .= ITILFollowup::buildParentCondition(\Ticket::getType());
$condition .= "OR ";
// Filter for "change" parents
$condition .= ITILFollowup::buildParentCondition(
\Change::getType(),
'changes_id',
"glpi_changes_users",
"glpi_changes_groups"
);
$condition .= "OR ";
// Fitler for "problem" parents
$condition .= ITILFollowup::buildParentCondition(
\Problem::getType(),
'problems_id',
"glpi_problems_users",
"glpi_groups_problems"
);
$condition .= "))";
break;
default:
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$condition = Plugin::doOneHook($plug['plugin'], 'addDefaultWhere', $itemtype);
}
break;
}
/* Hook to restrict user right on current itemtype */
list($itemtype, $condition) = Plugin::doHookFunction('add_default_where', [$itemtype, $condition]);
return $condition;
}
/**
* Generic Function to add where to a request
*
* @param string $link Link string
* @param boolean $nott Is it a negative search ?
* @param string $itemtype Item type
* @param integer $ID ID of the item to search
* @param string $searchtype Searchtype used (equals or contains)
* @param string $val Item num in the request
* @param integer $meta Is a meta search (meta=2 in search.class.php) (default 0)
*
* @return string Where string
**/
public static function addWhere($link, $nott, $itemtype, $ID, $searchtype, $val, $meta = 0)
{
global $DB;
$searchopt = &self::getOptions($itemtype);
if (!isset($searchopt[$ID]['table'])) {
return false;
}
$table = $searchopt[$ID]["table"];
$field = $searchopt[$ID]["field"];
$inittable = $table;
$addtable = '';
$is_fkey_composite_on_self = getTableNameForForeignKeyField($searchopt[$ID]["linkfield"]) == $table
&& $searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table);
$orig_table = self::getOrigTableName($itemtype);
if (
($table != 'asset_types')
&& ($is_fkey_composite_on_self || $table != $orig_table)
&& ($searchopt[$ID]["linkfield"] != getForeignKeyFieldForTable($table))
) {
$addtable = "_" . $searchopt[$ID]["linkfield"];
$table .= $addtable;
}
if (isset($searchopt[$ID]['joinparams'])) {
$complexjoin = self::computeComplexJoinID($searchopt[$ID]['joinparams']);
if (!empty($complexjoin)) {
$table .= "_" . $complexjoin;
}
}
$addmeta = "";
if (
$meta
&& ($itemtype::getTable() != $inittable)
) {
$addmeta = "_" . $itemtype;
$table .= $addmeta;
}
// Hack to allow search by ID on every sub-table
if (preg_match('/^\$\$\$\$([0-9]+)$/', $val, $regs)) {
return $link . " (`$table`.`id` " . ($nott ? "<>" : "=") . $regs[1] . " " .
(($regs[1] == 0) ? " OR `$table`.`id` IS NULL" : '') . ") ";
}
// Preparse value
if (isset($searchopt[$ID]["datatype"])) {
switch ($searchopt[$ID]["datatype"]) {
case "datetime":
case "date":
case "date_delay":
$force_day = true;
if (
$searchopt[$ID]["datatype"] == 'datetime'
&& !(strstr($val, 'BEGIN') || strstr($val, 'LAST') || strstr($val, 'DAY'))
) {
$force_day = false;
}
$val = Html::computeGenericDateTimeSearch($val, $force_day);
break;
}
}
switch ($searchtype) {
case "notcontains":
$nott = !$nott;
//negated, use contains case
case "contains":
if (isset($searchopt[$ID]["datatype"]) && ($searchopt[$ID]["datatype"] === 'decimal')) {
$matches = [];
if (preg_match('/^(\d+.?\d?)/', $val, $matches)) {
$val = $matches[1];
if (!str_contains($val, '.')) {
$val .= '.';
}
}
}
$SEARCH = self::makeTextSearch($val, $nott);
break;
case "equals":
if ($nott) {
$SEARCH = " <> " . DBmysql::quoteValue($val);
} else {
$SEARCH = " = " . DBmysql::quoteValue($val);
}
break;
case "notequals":
if ($nott) {
$SEARCH = " = " . DBmysql::quoteValue($val);
} else {
$SEARCH = " <> " . DBmysql::quoteValue($val);
}
break;
case "under":
if ($nott) {
$SEARCH = " NOT IN ('" . implode("','", getSonsOf($inittable, $val)) . "')";
} else {
$SEARCH = " IN ('" . implode("','", getSonsOf($inittable, $val)) . "')";
}
break;
case "notunder":
if ($nott) {
$SEARCH = " IN ('" . implode("','", getSonsOf($inittable, $val)) . "')";
} else {
$SEARCH = " NOT IN ('" . implode("','", getSonsOf($inittable, $val)) . "')";
}
break;
}
//Check in current item if a specific where is defined
if (method_exists($itemtype, 'addWhere')) {
$out = $itemtype::addWhere($link, $nott, $itemtype, $ID, $searchtype, $val);
if (!empty($out)) {
return $out;
}
}
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'addWhere',
$link,
$nott,
$itemtype,
$ID,
$val,
$searchtype
);
if (!empty($out)) {
return $out;
}
}
switch ($inittable . "." . $field) {
// case "glpi_users_validation.name" :
case "glpi_users.name":
if ($val == 'myself') {
switch ($searchtype) {
case 'equals':
return " $link (`$table`.`id` = " . $DB->quoteValue($_SESSION['glpiID']) . ") ";
case 'notequals':
return " $link (`$table`.`id` <> " . $DB->quoteValue($_SESSION['glpiID']) . ") ";
}
}
if ($itemtype == 'User') { // glpi_users case / not link table
if (in_array($searchtype, ['equals', 'notequals'])) {
$search_str = "`$table`.`id`" . $SEARCH;
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))) {
$search_str .= " OR `$table`.`id` IS NULL";
}
return " $link ($search_str)";
}
return self::makeTextCriteria("`$table`.`$field`", $val, $nott, $link);
}
if ($_SESSION["glpinames_format"] == User::FIRSTNAME_BEFORE) {
$name1 = 'firstname';
$name2 = 'realname';
} else {
$name1 = 'realname';
$name2 = 'firstname';
}
if (in_array($searchtype, ['equals', 'notequals'])) {
return " $link (`$table`.`id`" . $SEARCH .
(($val == 0) ? " OR `$table`.`id` IS" .
(($searchtype == "notequals") ? " NOT" : "") . " NULL" : '') . ') ';
}
$toadd = '';
$tmplink = 'OR';
if ($nott) {
$tmplink = 'AND';
}
if (is_a($itemtype, CommonITILObject::class, true)) {
if (
isset($searchopt[$ID]["joinparams"]["beforejoin"]["table"])
&& isset($searchopt[$ID]["joinparams"]["beforejoin"]["joinparams"])
&& (($searchopt[$ID]["joinparams"]["beforejoin"]["table"]
== 'glpi_tickets_users')
|| ($searchopt[$ID]["joinparams"]["beforejoin"]["table"]
== 'glpi_problems_users')
|| ($searchopt[$ID]["joinparams"]["beforejoin"]["table"]
== 'glpi_changes_users'))
) {
$bj = $searchopt[$ID]["joinparams"]["beforejoin"];
$linktable = $bj['table'] . '_' . self::computeComplexJoinID($bj['joinparams']) . $addmeta;
//$toadd = "`$linktable`.`alternative_email` $SEARCH $tmplink ";
$toadd = self::makeTextCriteria(
"`$linktable`.`alternative_email`",
$val,
$nott,
$tmplink
);
if ($val == '^$') {
return $link . " ((`$linktable`.`users_id` IS NULL)
OR `$linktable`.`alternative_email` IS NULL)";
}
}
}
$toadd2 = '';
if (
$nott
&& ($val != 'NULL') && ($val != 'null')
) {
$toadd2 = " OR `$table`.`$field` IS NULL";
}
return $link . " (((`$table`.`$name1` $SEARCH
$tmplink `$table`.`$name2` $SEARCH
$tmplink `$table`.`$field` $SEARCH
$tmplink CONCAT(`$table`.`$name1`, ' ', `$table`.`$name2`) $SEARCH )
$toadd2) $toadd)";
case "glpi_groups.completename":
if ($val == 'mygroups') {
switch ($searchtype) {
case 'equals':
return " $link (`$table`.`id` IN ('" . implode(
"','",
$_SESSION['glpigroups']
) . "')) ";
case 'notequals':
return " $link (`$table`.`id` NOT IN ('" . implode(
"','",
$_SESSION['glpigroups']
) . "')) ";
case 'under':
$groups = $_SESSION['glpigroups'];
foreach ($_SESSION['glpigroups'] as $g) {
$groups += getSonsOf($inittable, $g);
}
$groups = array_unique($groups);
return " $link (`$table`.`id` IN ('" . implode("','", $groups) . "')) ";
case 'notunder':
$groups = $_SESSION['glpigroups'];
foreach ($_SESSION['glpigroups'] as $g) {
$groups += getSonsOf($inittable, $g);
}
$groups = array_unique($groups);
return " $link (`$table`.`id` NOT IN ('" . implode("','", $groups) . "')) ";
}
}
break;
case "glpi_auth_tables.name":
$user_searchopt = self::getOptions('User');
$tmplink = 'OR';
if ($nott) {
$tmplink = 'AND';
}
return $link . " (`glpi_authmails" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[31]['joinparams']) . $addmeta . "`.`name`
$SEARCH
$tmplink `glpi_authldaps" . $addtable . "_" .
self::computeComplexJoinID($user_searchopt[30]['joinparams']) . $addmeta . "`.`name`
$SEARCH ) ";
case "glpi_ipaddresses.name":
$search = ["/\</","/\>/"];
$replace = ["<",">"];
$val = preg_replace($search, $replace, $val);
if (preg_match("/^\s*([<>])([=]*)[[:space:]]*([0-9\.]+)/", $val, $regs)) {
if ($nott) {
if ($regs[1] == '<') {
$regs[1] = '>';
} else {
$regs[1] = '<';
}
}
$regs[1] .= $regs[2];
return $link . " (INET_ATON(`$table`.`$field`) " . $regs[1] . " INET_ATON('" . $regs[3] . "')) ";
}
break;
case "glpi_tickets.status":
case "glpi_problems.status":
case "glpi_changes.status":
$tocheck = [];
if ($item = getItemForItemtype($itemtype)) {
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 $link . " `$table`.`$field` NOT IN ('" . implode("','", $tocheck) . "')";
}
return $link . " `$table`.`$field` IN ('" . implode("','", $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 $link . " (((`$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) {
$compare = ($nott ? '<>' : '=');
return $link . " `$table`.`$field` $compare '$val'";
}
if ($val < 0) {
$compare = ($nott ? '<' : '>=');
return $link . " `$table`.`$field` $compare '" . abs($val) . "'";
}
// Show all
$compare = ($nott ? '<' : '>=');
return $link . " `$table`.`$field` $compare '0' ";
}
return "";
case "glpi_tickets.global_validation":
case "glpi_ticketvalidations.status":
case "glpi_changes.global_validation":
case "glpi_changevalidations.status":
if ($val == 'all') {
return "";
}
$tocheck = [];
switch ($val) {
case 'can':
$tocheck = CommonITILValidation::getCanValidationStatusArray();
break;
case 'all':
$tocheck = CommonITILValidation::getAllValidationStatusArray();
break;
}
if (count($tocheck) == 0) {
$tocheck = [$val];
}
if (count($tocheck)) {
if ($nott) {
return $link . " `$table`.`$field` NOT IN ('" . implode("','", $tocheck) . "')";
}
return $link . " `$table`.`$field` IN ('" . implode("','", $tocheck) . "')";
}
break;
case "glpi_notifications.event":
if (in_array($searchtype, ['equals', 'notequals']) && strpos($val, self::SHORTSEP)) {
$not = 'notequals' === $searchtype ? 'NOT' : '';
list($itemtype_val, $event_val) = explode(self::SHORTSEP, $val);
return " $link $not(`$table`.`event` = '$event_val'
AND `$table`.`itemtype` = '$itemtype_val')";
}
break;
}
//// Default cases
// Link with plugin tables
if (preg_match("/^glpi_plugin_([a-z0-9]+)/", $inittable, $matches)) {
if (count($matches) == 2) {
$plug = $matches[1];
$out = Plugin::doOneHook(
$plug,
'addWhere',
$link,
$nott,
$itemtype,
$ID,
$val,
$searchtype
);
if (!empty($out)) {
return $out;
}
}
}
$tocompute = "`$table`.`$field`";
$tocomputetrans = "`" . $table . "_trans_" . $field . "`.`value`";
if (isset($searchopt[$ID]["computation"])) {
$tocompute = $searchopt[$ID]["computation"];
$tocompute = str_replace($DB->quoteName('TABLE'), 'TABLE', $tocompute);
$tocompute = str_replace("TABLE", $DB->quoteName("$table"), $tocompute);
}
// Preformat items
if (isset($searchopt[$ID]["datatype"])) {
if ($searchopt[$ID]["datatype"] == "mio") {
// Parse value as it may contain a few different formats
$val = Toolbox::getMioSizeFromString($val);
}
switch ($searchopt[$ID]["datatype"]) {
case "itemtypename":
if (in_array($searchtype, ['equals', 'notequals'])) {
return " $link (`$table`.`$field`" . $SEARCH . ') ';
}
break;
case "itemlink":
if (in_array($searchtype, ['equals', 'notequals', 'under', 'notunder'])) {
return " $link (`$table`.`id`" . $SEARCH . ') ';
}
break;
case "datetime":
case "date":
case "date_delay":
if ($searchopt[$ID]["datatype"] == 'datetime') {
// Specific search for datetime
if (in_array($searchtype, ['equals', 'notequals'])) {
$val = preg_replace("/:00$/", '', $val);
$val = '^' . $val;
if ($searchtype == 'notequals') {
$nott = !$nott;
}
return self::makeTextCriteria("`$table`.`$field`", $val, $nott, $link);
}
}
if ($searchtype == 'lessthan') {
$val = '<' . $val;
}
if ($searchtype == 'morethan') {
$val = '>' . $val;
}
if ($searchtype) {
$date_computation = $tocompute;
}
if (in_array($searchtype, ["contains", "notcontains"])) {
$default_charset = DBConnection::getDefaultCharset();
$date_computation = "CONVERT($date_computation USING {$default_charset})";
}
$search_unit = ' MONTH ';
if (isset($searchopt[$ID]['searchunit'])) {
$search_unit = $searchopt[$ID]['searchunit'];
}
if ($searchopt[$ID]["datatype"] == "date_delay") {
$delay_unit = ' MONTH ';
if (isset($searchopt[$ID]['delayunit'])) {
$delay_unit = $searchopt[$ID]['delayunit'];
}
$add_minus = '';
if (isset($searchopt[$ID]["datafields"][3])) {
$add_minus = "-`$table`.`" . $searchopt[$ID]["datafields"][3] . "`";
}
$date_computation = "ADDDATE(`$table`." . $searchopt[$ID]["datafields"][1] . ",
INTERVAL (`$table`." . $searchopt[$ID]["datafields"][2] . "
$add_minus)
$delay_unit)";
}
if (in_array($searchtype, ['equals', 'notequals'])) {
return " $link ($date_computation " . $SEARCH . ') ';
}
$search = ["/\</","/\>/"];
$replace = ["<",">"];
$val = preg_replace($search, $replace, $val);
if (preg_match("/^\s*([<>=]+)(.*)/", $val, $regs)) {
if (is_numeric($regs[2])) {
return $link . " $date_computation " . $regs[1] . "
ADDDATE(NOW(), INTERVAL " . $regs[2] . " $search_unit) ";
}
// ELSE Reformat date if needed
$regs[2] = preg_replace(
'@(\d{1,2})(-|/)(\d{1,2})(-|/)(\d{4})@',
'\5-\3-\1',
$regs[2]
);
if (preg_match('/[0-9]{2,4}-[0-9]{1,2}-[0-9]{1,2}/', $regs[2])) {
$ret = $link;
if ($nott) {
$ret .= " NOT(";
}
$ret .= " $date_computation {$regs[1]} '{$regs[2]}'";
if ($nott) {
$ret .= ")";
}
return $ret;
}
return "";
}
// ELSE standard search
// Date format modification if needed
$val = preg_replace('@(\d{1,2})(-|/)(\d{1,2})(-|/)(\d{4})@', '\5-\3-\1', $val);
if ($date_computation) {
return self::makeTextCriteria($date_computation, $val, $nott, $link);
}
return '';
case "right":
if ($searchtype == 'notequals') {
$nott = !$nott;
}
return $link . ($nott ? ' NOT' : '') . " ($tocompute & '$val') ";
case "bool":
if (!is_numeric($val)) {
if (strcasecmp($val, __('No')) == 0) {
$val = 0;
} else if (strcasecmp($val, __('Yes')) == 0) {
$val = 1;
}
}
// No break here : use number comparaison case
case "count":
case "mio":
case "number":
case "decimal":
case "timestamp":
case "progressbar":
$decimal_contains = $searchopt[$ID]["datatype"] === 'decimal' && $searchtype === 'contains';
$search = ["/\</", "/\>/"];
$replace = ["<", ">"];
$val = preg_replace($search, $replace, $val);
if (preg_match("/([<>])([=]*)[[:space:]]*([0-9]+)/", $val, $regs)) {
if (in_array($searchtype, ["notequals", "notcontains"])) {
$nott = !$nott;
}
if ($nott) {
if ($regs[1] == '<') {
$regs[1] = '>';
} else {
$regs[1] = '<';
}
}
$regs[1] .= $regs[2];
return $link . " ($tocompute " . $regs[1] . " " . $regs[3] . ") ";
}
if (is_numeric($val) && !$decimal_contains) {
$numeric_val = floatval($val);
if (in_array($searchtype, ["notequals", "notcontains"])) {
$nott = !$nott;
}
if (isset($searchopt[$ID]["width"])) {
$ADD = "";
if (
$nott
&& ($val != 'NULL') && ($val != 'null')
) {
$ADD = " OR $tocompute IS NULL";
}
if ($nott) {
return $link . " ($tocompute < " . ($numeric_val - $searchopt[$ID]["width"]) . "
OR $tocompute > " . ($numeric_val + $searchopt[$ID]["width"]) . "
$ADD) ";
}
return $link . " (($tocompute >= " . ($numeric_val - $searchopt[$ID]["width"]) . "
AND $tocompute <= " . ($numeric_val + $searchopt[$ID]["width"]) . ")
$ADD) ";
}
if (!$nott) {
return " $link ($tocompute = $numeric_val) ";
}
return " $link ($tocompute <> $numeric_val) ";
}
break;
}
}
// Default case
if (in_array($searchtype, ['equals', 'notequals','under', 'notunder'])) {
if (
(!isset($searchopt[$ID]['searchequalsonfield'])
|| !$searchopt[$ID]['searchequalsonfield'])
&& ($itemtype == AllAssets::getType()
|| $table != $itemtype::getTable())
) {
$out = " $link (`$table`.`id`" . $SEARCH;
} else {
$out = " $link (`$table`.`$field`" . $SEARCH;
}
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))
) {
$out .= " OR `$table`.`id` IS NULL";
}
$out .= ')';
return $out;
}
$transitemtype = getItemTypeForTable($inittable);
if (Session::haveTranslations($transitemtype, $field)) {
return " $link (" . self::makeTextCriteria($tocompute, $val, $nott, '') . "
OR " . self::makeTextCriteria($tocomputetrans, $val, $nott, '') . ")";
}
return self::makeTextCriteria($tocompute, $val, $nott, $link);
}
/**
* Generic Function to add Default left join to a request
*
* @param class-string<CommonDBTM> $itemtype Reference item type
* @param string $ref_table Reference table
* @param array &$already_link_tables Array of tables already joined
*
* @return string Left join string
**/
public static function addDefaultJoin($itemtype, $ref_table, array &$already_link_tables)
{
$out = '';
switch ($itemtype) {
// No link
case 'User':
$out = self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_profiles_users",
"profiles_users_id",
0,
0,
['jointype' => 'child']
);
break;
case 'Reservation':
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
ReservationItem::getTable(),
ReservationItem::getForeignKeyField(),
);
break;
case 'Reminder':
$out = Reminder::addVisibilityJoins();
break;
case 'RSSFeed':
$out = RSSFeed::addVisibilityJoins();
break;
case 'ProjectTask':
// Same structure in addDefaultWhere
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_projects",
"projects_id"
);
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_projecttaskteams",
"projecttaskteams_id",
0,
0,
['jointype' => 'child']
);
break;
case 'Project':
// Same structure in addDefaultWhere
if (!Session::haveRight("project", Project::READALL)) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_projectteams",
"projectteams_id",
0,
0,
['jointype' => 'child']
);
}
break;
case 'Ticket':
// Same structure in addDefaultWhere
if (!Session::haveRight("ticket", Ticket::READALL)) {
$searchopt = &self::getOptions($itemtype);
// show mine : requester
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_tickets_users",
"tickets_users_id",
0,
0,
$searchopt[4]['joinparams']['beforejoin']['joinparams']
);
if (Session::haveRight("ticket", Ticket::READGROUP)) {
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_groups_tickets",
"groups_tickets_id",
0,
0,
$searchopt[71]['joinparams']['beforejoin']
['joinparams']
);
}
}
// show mine : observer
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_tickets_users",
"tickets_users_id",
0,
0,
$searchopt[66]['joinparams']['beforejoin']['joinparams']
);
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_groups_tickets",
"groups_tickets_id",
0,
0,
$searchopt[65]['joinparams']['beforejoin']['joinparams']
);
}
if (Session::haveRight("ticket", Ticket::OWN)) { // Can own ticket : show assign to me
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_tickets_users",
"tickets_users_id",
0,
0,
$searchopt[5]['joinparams']['beforejoin']['joinparams']
);
}
if (Session::haveRightsOr("ticket", [Ticket::READMY, Ticket::READASSIGN])) { // show mine + assign to me
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_tickets_users",
"tickets_users_id",
0,
0,
$searchopt[5]['joinparams']['beforejoin']['joinparams']
);
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_groups_tickets",
"groups_tickets_id",
0,
0,
$searchopt[8]['joinparams']['beforejoin']
['joinparams']
);
}
}
if (
Session::haveRightsOr(
'ticketvalidation',
[TicketValidation::VALIDATEINCIDENT,
TicketValidation::VALIDATEREQUEST
]
)
) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_ticketvalidations",
"ticketvalidations_id",
0,
0,
$searchopt[58]['joinparams']['beforejoin']['joinparams']
);
}
}
break;
case 'Change':
case 'Problem':
if ($itemtype == 'Change') {
$right = 'change';
$table = 'changes';
$groupetable = "glpi_changes_groups";
$linkfield = "changes_groups_id";
} else if ($itemtype == 'Problem') {
$right = 'problem';
$table = 'problems';
$groupetable = "glpi_groups_problems";
$linkfield = "groups_problems_id";
}
// Same structure in addDefaultWhere
$out = '';
if (!Session::haveRight("$right", $itemtype::READALL)) {
$searchopt = &self::getOptions($itemtype);
if (Session::haveRight("$right", $itemtype::READMY)) {
// show mine : requester
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_" . $table . "_users",
$table . "_users_id",
0,
0,
$searchopt[4]['joinparams']['beforejoin']['joinparams']
);
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
$groupetable,
$linkfield,
0,
0,
$searchopt[71]['joinparams']['beforejoin']['joinparams']
);
}
// show mine : observer
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_" . $table . "_users",
$table . "_users_id",
0,
0,
$searchopt[66]['joinparams']['beforejoin']['joinparams']
);
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
$groupetable,
$linkfield,
0,
0,
$searchopt[65]['joinparams']['beforejoin']['joinparams']
);
}
// show mine : assign
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
"glpi_" . $table . "_users",
$table . "_users_id",
0,
0,
$searchopt[5]['joinparams']['beforejoin']['joinparams']
);
if (count($_SESSION['glpigroups'])) {
$out .= self::addLeftJoin(
$itemtype,
$ref_table,
$already_link_tables,
$groupetable,
$linkfield,
0,
0,
$searchopt[8]['joinparams']['beforejoin']['joinparams']
);
}
}
}
break;
default:
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$plugin_name = $plug['plugin'];
$hook_function = 'plugin_' . strtolower($plugin_name) . '_addDefaultJoin';
$hook_closure = function () use ($hook_function, $itemtype, $ref_table, &$already_link_tables) {
if (is_callable($hook_function)) {
return $hook_function($itemtype, $ref_table, $already_link_tables);
}
};
$out = Plugin::doOneHook($plugin_name, $hook_closure);
}
break;
}
list($itemtype, $out) = Plugin::doHookFunction('add_default_join', [$itemtype, $out]);
return $out;
}
/**
* Generic Function to add left join to a request
*
* @param string $itemtype Item type
* @param string $ref_table Reference table
* @param array $already_link_tables Array of tables already joined
* @param string $new_table New table to join
* @param string $linkfield Linkfield for LeftJoin
* @param boolean $meta Is it a meta item ? (default 0)
* @param integer $meta_type Meta type table (default 0)
* @param array $joinparams Array join parameters (condition / joinbefore...)
* @param string $field Field to display (needed for translation join) (default '')
*
* @return string Left join string
**/
public static function addLeftJoin(
$itemtype,
$ref_table,
array &$already_link_tables,
$new_table,
$linkfield,
$meta = 0,
$meta_type = 0,
$joinparams = [],
$field = ''
) {
// Rename table for meta left join
$AS = "";
$nt = $new_table;
$cleannt = $nt;
// Virtual field no link
if (strpos($linkfield, '_virtual') === 0) {
return '';
}
$complexjoin = self::computeComplexJoinID($joinparams);
$is_fkey_composite_on_self = getTableNameForForeignKeyField($linkfield) == $ref_table
&& $linkfield != getForeignKeyFieldForTable($ref_table);
// Auto link
if (
($ref_table == $new_table)
&& empty($complexjoin)
&& !$is_fkey_composite_on_self
) {
$transitemtype = getItemTypeForTable($new_table);
if (Session::haveTranslations($transitemtype, $field)) {
$transAS = $nt . '_trans_' . $field;
return self::joinDropdownTranslations(
$transAS,
$nt,
$transitemtype,
$field
);
}
return "";
}
// Multiple link possibilies case
if (!empty($linkfield) && ($linkfield != getForeignKeyFieldForTable($new_table))) {
$nt .= "_" . $linkfield;
$AS = " AS `$nt`";
}
if (!empty($complexjoin)) {
$nt .= "_" . $complexjoin;
$AS = " AS `$nt`";
}
$addmetanum = "";
$rt = $ref_table;
$cleanrt = $rt;
if ($meta && $meta_type::getTable() != $new_table) {
$addmetanum = "_" . $meta_type;
$AS = " AS `$nt$addmetanum`";
$nt = $nt . $addmetanum;
}
// Do not take into account standard linkfield
$tocheck = $nt . "." . $linkfield;
if ($linkfield == getForeignKeyFieldForTable($new_table)) {
$tocheck = $nt;
}
if (in_array($tocheck, $already_link_tables)) {
return "";
}
array_push($already_link_tables, $tocheck);
$specific_leftjoin = '';
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$plugin_name = $plug['plugin'];
$hook_function = 'plugin_' . strtolower($plugin_name) . '_addLeftJoin';
$hook_closure = function () use ($hook_function, $itemtype, $ref_table, $new_table, $linkfield, &$already_link_tables) {
if (is_callable($hook_function)) {
return $hook_function($itemtype, $ref_table, $new_table, $linkfield, $already_link_tables);
}
};
$specific_leftjoin = Plugin::doOneHook($plugin_name, $hook_closure);
}
// Link with plugin tables : need to know left join structure
if (
empty($specific_leftjoin)
&& preg_match("/^glpi_plugin_([a-z0-9]+)/", $new_table, $matches)
) {
if (count($matches) == 2) {
$plugin_name = $matches[1];
$hook_function = 'plugin_' . strtolower($plugin_name) . '_addLeftJoin';
$hook_closure = function () use ($hook_function, $itemtype, $ref_table, $new_table, $linkfield, &$already_link_tables) {
if (is_callable($hook_function)) {
return $hook_function($itemtype, $ref_table, $new_table, $linkfield, $already_link_tables);
}
};
$specific_leftjoin = Plugin::doOneHook($plugin_name, $hook_closure);
}
}
if (!empty($linkfield)) {
$before = '';
if (isset($joinparams['beforejoin']) && is_array($joinparams['beforejoin'])) {
if (isset($joinparams['beforejoin']['table'])) {
$joinparams['beforejoin'] = [$joinparams['beforejoin']];
}
foreach ($joinparams['beforejoin'] as $tab) {
if (isset($tab['table'])) {
$intertable = $tab['table'];
if (isset($tab['linkfield'])) {
$interlinkfield = $tab['linkfield'];
} else {
$interlinkfield = getForeignKeyFieldForTable($intertable);
}
$interjoinparams = [];
if (isset($tab['joinparams'])) {
$interjoinparams = $tab['joinparams'];
}
$before .= self::addLeftJoin(
$itemtype,
$rt,
$already_link_tables,
$intertable,
$interlinkfield,
$meta,
$meta_type,
$interjoinparams
);
}
// No direct link with the previous joins
if (!isset($tab['joinparams']['nolink']) || !$tab['joinparams']['nolink']) {
$cleanrt = $intertable;
$complexjoin = self::computeComplexJoinID($interjoinparams);
if (!empty($complexjoin)) {
$intertable .= "_" . $complexjoin;
}
if ($meta && $meta_type::getTable() != $cleanrt) {
$intertable .= "_" . $meta_type;
}
$rt = $intertable;
}
}
}
$addcondition = '';
if (isset($joinparams['condition'])) {
$condition = $joinparams['condition'];
if (is_array($condition)) {
$it = new DBmysqlIterator(null);
$condition = ' AND ' . $it->analyseCrit($condition);
}
$from = ["`REFTABLE`", "REFTABLE", "`NEWTABLE`", "NEWTABLE"];
$to = ["`$rt`", "`$rt`", "`$nt`", "`$nt`"];
$addcondition = str_replace($from, $to, $condition);
$addcondition = $addcondition . " ";
}
if (!isset($joinparams['jointype'])) {
$joinparams['jointype'] = 'standard';
}
if (empty($specific_leftjoin)) {
switch ($new_table) {
// No link
case "glpi_auth_tables":
$user_searchopt = self::getOptions('User');
$specific_leftjoin = self::addLeftJoin(
$itemtype,
$rt,
$already_link_tables,
"glpi_authldaps",
'auths_id',
0,
0,
$user_searchopt[30]['joinparams']
);
$specific_leftjoin .= self::addLeftJoin(
$itemtype,
$rt,
$already_link_tables,
"glpi_authmails",
'auths_id',
0,
0,
$user_searchopt[31]['joinparams']
);
break;
}
}
if (empty($specific_leftjoin)) {
switch ($joinparams['jointype']) {
case 'child':
$linkfield = getForeignKeyFieldForTable($cleanrt);
if (isset($joinparams['linkfield'])) {
$linkfield = $joinparams['linkfield'];
}
// Child join
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON (`$rt`.`id` = `$nt`.`$linkfield`
$addcondition)";
break;
case 'item_item':
// Item_Item join
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON ((`$rt`.`id`
= `$nt`.`" . getForeignKeyFieldForTable($cleanrt) . "_1`
OR `$rt`.`id`
= `$nt`.`" . getForeignKeyFieldForTable($cleanrt) . "_2`)
$addcondition)";
break;
case 'item_item_revert':
// Item_Item join reverting previous item_item
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON ((`$nt`.`id`
= `$rt`.`" . getForeignKeyFieldForTable($cleannt) . "_1`
OR `$nt`.`id`
= `$rt`.`" . getForeignKeyFieldForTable($cleannt) . "_2`)
$addcondition)";
break;
case "mainitemtype_mainitem":
$addmain = 'main';
//addmain defined to be used in itemtype_item case
case "itemtype_item":
if (!isset($addmain)) {
$addmain = '';
}
$used_itemtype = $itemtype;
if (
isset($joinparams['specific_itemtype'])
&& !empty($joinparams['specific_itemtype'])
) {
$used_itemtype = $joinparams['specific_itemtype'];
}
// Itemtype join
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON (`$rt`.`id` = `$nt`.`" . $addmain . "items_id`
AND `$nt`.`" . $addmain . "itemtype` = '$used_itemtype'
$addcondition) ";
break;
case "itemtype_item_revert":
if (!isset($addmain)) {
$addmain = '';
}
$used_itemtype = $itemtype;
if (
isset($joinparams['specific_itemtype'])
&& !empty($joinparams['specific_itemtype'])
) {
$used_itemtype = $joinparams['specific_itemtype'];
}
// Itemtype join
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON (`$nt`.`id` = `$rt`.`" . $addmain . "items_id`
AND `$rt`.`" . $addmain . "itemtype` = '$used_itemtype'
$addcondition) ";
break;
case "itemtypeonly":
$used_itemtype = $itemtype;
if (
isset($joinparams['specific_itemtype'])
&& !empty($joinparams['specific_itemtype'])
) {
$used_itemtype = $joinparams['specific_itemtype'];
}
// Itemtype join
$specific_leftjoin = " LEFT JOIN `$new_table` $AS
ON (`$nt`.`itemtype` = '$used_itemtype'
$addcondition) ";
break;
default:
// Standard join
$specific_leftjoin = "LEFT JOIN `$new_table` $AS
ON (`$rt`.`$linkfield` = `$nt`.`id`
$addcondition)";
$transitemtype = getItemTypeForTable($new_table);
if (Session::haveTranslations($transitemtype, $field)) {
$transAS = $nt . '_trans_' . $field;
$specific_leftjoin .= self::joinDropdownTranslations(
$transAS,
$nt,
$transitemtype,
$field
);
}
break;
}
}
return $before . $specific_leftjoin;
}
return '';
}
/**
* Generic Function to add left join for meta items
*
* @param string $from_type Reference item type ID
* @param string $to_type Item type to add
* @param array $already_link_tables2 Array of tables already joined
*showGenericSearch
* @return string Meta Left join string
**/
public static function addMetaLeftJoin(
$from_type,
$to_type,
array &$already_link_tables2,
$joinparams = []
) {
global $CFG_GLPI;
$from_referencetype = self::getMetaReferenceItemtype($from_type);
$LINK = " LEFT JOIN ";
$from_table = $from_type::getTable();
$from_fk = getForeignKeyFieldForTable($from_table);
$to_table = $to_type::getTable();
$to_fk = getForeignKeyFieldForTable($to_table);
$to_obj = getItemForItemtype($to_type);
$to_entity_restrict = $to_obj->isField('entities_id') ? getEntitiesRestrictRequest('AND', $to_table) : '';
$complexjoin = self::computeComplexJoinID($joinparams);
$alias_suffix = ($complexjoin != '' ? '_' . $complexjoin : '') . '_' . $to_type;
$JOIN = "";
// Specific JOIN
if ($from_referencetype === 'Software' && in_array($to_type, $CFG_GLPI['software_types'])) {
// From Software to software_types
$softwareversions_table = "glpi_softwareversions{$alias_suffix}";
if (!in_array($softwareversions_table, $already_link_tables2)) {
array_push($already_link_tables2, $softwareversions_table);
$JOIN .= "$LINK `glpi_softwareversions` AS `$softwareversions_table`
ON (`$softwareversions_table`.`softwares_id` = `$from_table`.`id`) ";
}
$items_softwareversions_table = "glpi_items_softwareversions_{$alias_suffix}";
if (!in_array($items_softwareversions_table, $already_link_tables2)) {
array_push($already_link_tables2, $items_softwareversions_table);
$JOIN .= "$LINK `glpi_items_softwareversions` AS `$items_softwareversions_table`
ON (`$items_softwareversions_table`.`softwareversions_id` = `$softwareversions_table`.`id`
AND `$items_softwareversions_table`.`itemtype` = '$to_type'
AND `$items_softwareversions_table`.`is_deleted` = 0) ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$items_softwareversions_table`.`items_id` = `$to_table`.`id`
AND `$items_softwareversions_table`.`itemtype` = '$to_type'
$to_entity_restrict) ";
}
return $JOIN;
}
if ($to_type === 'Software' && in_array($from_referencetype, $CFG_GLPI['software_types'])) {
// From software_types to Software
$items_softwareversions_table = "glpi_items_softwareversions{$alias_suffix}";
if (!in_array($items_softwareversions_table, $already_link_tables2)) {
array_push($already_link_tables2, $items_softwareversions_table);
$JOIN .= "$LINK `glpi_items_softwareversions` AS `$items_softwareversions_table`
ON (`$items_softwareversions_table`.`items_id` = `$from_table`.`id`
AND `$items_softwareversions_table`.`itemtype` = '$from_type'
AND `$items_softwareversions_table`.`is_deleted` = 0) ";
}
$softwareversions_table = "glpi_softwareversions{$alias_suffix}";
if (!in_array($softwareversions_table, $already_link_tables2)) {
array_push($already_link_tables2, $softwareversions_table);
$JOIN .= "$LINK `glpi_softwareversions` AS `$softwareversions_table`
ON (`$items_softwareversions_table`.`softwareversions_id` = `$softwareversions_table`.`id`) ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$softwareversions_table`.`softwares_id` = `$to_table`.`id`) ";
}
$softwarelicenses_table = "glpi_softwarelicenses{$alias_suffix}";
if (!in_array($softwarelicenses_table, $already_link_tables2)) {
array_push($already_link_tables2, $softwarelicenses_table);
$JOIN .= "$LINK `glpi_softwarelicenses` AS `$softwarelicenses_table`
ON ($to_table.`id` = `$softwarelicenses_table`.`softwares_id`"
. getEntitiesRestrictRequest(' AND', $softwarelicenses_table, '', '', true) . ") ";
}
return $JOIN;
}
if ($from_referencetype === 'Budget' && in_array($to_type, $CFG_GLPI['infocom_types'])) {
// From Budget to infocom_types
$infocom_alias = "glpi_infocoms{$alias_suffix}";
if (!in_array($infocom_alias, $already_link_tables2)) {
array_push($already_link_tables2, $infocom_alias);
$JOIN .= "$LINK `glpi_infocoms` AS `$infocom_alias`
ON (`$from_table`.`id` = `$infocom_alias`.`budgets_id`) ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$to_table`.`id` = `$infocom_alias`.`items_id`
AND `$infocom_alias`.`itemtype` = '$to_type'
$to_entity_restrict) ";
}
return $JOIN;
}
if ($to_type === 'Budget' && in_array($from_referencetype, $CFG_GLPI['infocom_types'])) {
// From infocom_types to Budget
$infocom_alias = "glpi_infocoms{$alias_suffix}";
if (!in_array($infocom_alias, $already_link_tables2)) {
array_push($already_link_tables2, $infocom_alias);
$JOIN .= "$LINK `glpi_infocoms` AS `$infocom_alias`
ON (`$from_table`.`id` = `$infocom_alias`.`items_id`
AND `$infocom_alias`.`itemtype` = '$from_type') ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$infocom_alias`.`$to_fk` = `$to_table`.`id`
$to_entity_restrict) ";
}
return $JOIN;
}
if ($from_referencetype === 'Reservation' && in_array($to_type, $CFG_GLPI['reservation_types'])) {
// From Reservation to reservation_types
$reservationitems_alias = "glpi_reservationitems{$alias_suffix}";
if (!in_array($reservationitems_alias, $already_link_tables2)) {
array_push($already_link_tables2, $reservationitems_alias);
$JOIN .= "$LINK `glpi_reservationitems` AS `$reservationitems_alias`
ON (`$from_table`.`reservationitems_id` = `$reservationitems_alias`.`id`) ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$to_table`.`id` = `$reservationitems_alias`.`items_id`
AND `$reservationitems_alias`.`itemtype` = '$to_type'
$to_entity_restrict) ";
}
return $JOIN;
}
if ($to_type === 'Reservation' && in_array($from_referencetype, $CFG_GLPI['reservation_types'])) {
// From reservation_types to Reservation
$reservationitems_alias = "glpi_reservationitems{$alias_suffix}";
if (!in_array($reservationitems_alias, $already_link_tables2)) {
array_push($already_link_tables2, $reservationitems_alias);
$JOIN .= "$LINK `glpi_reservationitems` AS `$reservationitems_alias`
ON (`$from_table`.`id` = `$reservationitems_alias`.`items_id`
AND `$reservationitems_alias`.`itemtype` = '$from_type') ";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$reservationitems_alias`.`id` = `$to_table`.`reservationitems_id`
$to_entity_restrict) ";
}
return $JOIN;
}
// Generic JOIN
$from_obj = getItemForItemtype($from_referencetype);
$from_item_obj = null;
$to_obj = getItemForItemtype($to_type);
$to_item_obj = null;
if (self::isPossibleMetaSubitemOf($from_referencetype, $to_type)) {
$from_item_obj = getItemForItemtype($from_referencetype . '_Item');
if (!$from_item_obj) {
$from_item_obj = getItemForItemtype('Item_' . $from_referencetype);
}
}
if (self::isPossibleMetaSubitemOf($to_type, $from_referencetype)) {
$to_item_obj = getItemForItemtype($to_type . '_Item');
if (!$to_item_obj) {
$to_item_obj = getItemForItemtype('Item_' . $to_type);
}
}
if ($from_obj && $from_obj->isField($to_fk)) {
// $from_table has a foreign key corresponding to $to_table
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$from_table`.`$to_fk` = `$to_table`.`id`
$to_entity_restrict) ";
}
} else if ($to_obj && $to_obj->isField($from_fk)) {
// $to_table has a foreign key corresponding to $from_table
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$from_table`.`id` = `$to_table`.`$from_fk`
$to_entity_restrict) ";
}
} else if ($from_obj && $from_obj->isField('itemtype') && $from_obj->isField('items_id')) {
// $from_table has items_id/itemtype fields
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$from_table`.`items_id` = `$to_table`.`id`
AND `$from_table`.`itemtype` = '$to_type'
$to_entity_restrict) ";
}
} else if ($to_obj && $to_obj->isField('itemtype') && $to_obj->isField('items_id')) {
// $to_table has items_id/itemtype fields
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$from_table`.`id` = `$to_table`.`items_id`
AND `$to_table`.`itemtype` = '$from_type'
$to_entity_restrict) ";
}
} else if ($from_item_obj && $from_item_obj->isField($from_fk)) {
// glpi_$from_items table exists and has a foreign key corresponding to $to_table
$items_table = $from_item_obj::getTable();
$items_table_alias = $items_table . $alias_suffix;
if (!in_array($items_table_alias, $already_link_tables2)) {
array_push($already_link_tables2, $items_table_alias);
$deleted = $from_item_obj->isField('is_deleted') ? "AND `$items_table_alias`.`is_deleted` = 0" : "";
$JOIN .= "$LINK `$items_table` AS `$items_table_alias`
ON (`$items_table_alias`.`$from_fk` = `$from_table`.`id`
AND `$items_table_alias`.`itemtype` = '$to_type'
$deleted)";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$items_table_alias`.`items_id` = `$to_table`.`id`
$to_entity_restrict) ";
}
} else if ($to_item_obj && $to_item_obj->isField($to_fk)) {
// glpi_$to_items table exists and has a foreign key corresponding to $from_table
$items_table = $to_item_obj::getTable();
$items_table_alias = $items_table . $alias_suffix;
if (!in_array($items_table_alias, $already_link_tables2)) {
array_push($already_link_tables2, $items_table_alias);
$deleted = $to_item_obj->isField('is_deleted') ? "AND `$items_table_alias`.`is_deleted` = 0" : "";
$JOIN .= "$LINK `$items_table` AS `$items_table_alias`
ON (`$items_table_alias`.`items_id` = `$from_table`.`id`
AND `$items_table_alias`.`itemtype` = '$from_type'
$deleted)";
}
if (!in_array($to_table, $already_link_tables2)) {
array_push($already_link_tables2, $to_table);
$JOIN .= "$LINK `$to_table`
ON (`$items_table_alias`.`$to_fk` = `$to_table`.`id`
$to_entity_restrict) ";
}
}
return $JOIN;
}
/**
* Generic Function to display Items
*
* @since 9.4: $num param has been dropped
*
* @param string $itemtype item type
* @param integer $ID ID of the SEARCH_OPTION item
* @param array $data array retrieved data array
*
* @return string String to print
**/
public static function displayConfigItem($itemtype, $ID, $data = [])
{
$searchopt = &self::getOptions($itemtype);
$table = $searchopt[$ID]["table"];
$field = $searchopt[$ID]["field"];
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'displayConfigItem',
$itemtype,
$ID,
$data,
"{$itemtype}_{$ID}"
);
if (!empty($out)) {
return $out;
}
}
$out = "";
$NAME = "{$itemtype}_{$ID}";
switch ($table . "." . $field) {
case "glpi_tickets.time_to_resolve":
case "glpi_tickets.internal_time_to_resolve":
case "glpi_problems.time_to_resolve":
case "glpi_changes.time_to_resolve":
if (in_array($ID, [151, 181])) {
break; // Skip "TTR + progress" search options
}
$value = $data[$NAME][0]['name'];
$status = $data[$NAME][0]['status'];
$solve_date = $data[$NAME][0]['solvedate'];
$is_late = !empty($value)
&& $status != CommonITILObject::WAITING
&& (
$solve_date > $value
|| ($solve_date == null && $value < $_SESSION['glpi_currenttime'])
);
if ($is_late) {
$out = " class=\"shadow-none\" style=\"background-color: #cf9b9b\" ";
}
break;
case "glpi_tickets.time_to_own":
case "glpi_tickets.internal_time_to_own":
if (in_array($ID, [158, 186])) {
break; // Skip "TTO + progress" search options
}
$value = $data[$NAME][0]['name'];
$status = $data[$NAME][0]['status'];
$opening_date = $data[$NAME][0]['date'];
$tia_time = $data[$NAME][0]['takeintoaccount_delay_stat'];
$is_late = !empty($value)
&& $status != CommonITILObject::WAITING
&& (
$tia_time > strtotime($opening_date) - strtotime($value)
|| ($tia_time == 0 && $value < $_SESSION['glpi_currenttime'])
);
if ($is_late) {
$out = " class=\"shadow-none\" style=\"background-color: #cf9b9b\" ";
}
break;
case "glpi_projectstates.color":
case "glpi_cables.color":
$bg_color = $data[$NAME][0]['name'];
if (!empty($bg_color)) {
$out = " class=\"shadow-none\" style=\"background-color: $bg_color;\" ";
}
break;
case "glpi_projectstates.name":
if (array_key_exists('color', $data[$NAME][0])) {
$bg_color = $data[$NAME][0]['color'];
if (!empty($bg_color)) {
$out = " class=\"shadow-none\" style=\"background-color: $bg_color;\" ";
}
}
break;
case "glpi_domains.date_expiration":
case "glpi_certificates.date_expiration":
if (
!empty($data[$NAME][0]['name'])
&& ($data[$NAME][0]['name'] < $_SESSION['glpi_currenttime'])
) {
$out = " class=\"shadow-none\" style=\"background-color: #cf9b9b\" ";
}
break;
}
return $out;
}
/**
* Generic Function to display Items
*
* @since 9.4: $num param has been dropped
*
* @param string $itemtype item type
* @param integer $ID ID of the SEARCH_OPTION item
* @param array $data array containing data results
* @param boolean $meta is a meta item ? (default 0)
* @param array $addobjectparams array added parameters for union search
* @param string $orig_itemtype Original itemtype, used for union_search_type
*
* @return string String to print
**/
public static function giveItem(
$itemtype,
$ID,
array $data,
$meta = 0,
array $addobjectparams = [],
$orig_itemtype = null
) {
global $CFG_GLPI;
$searchopt = &self::getOptions($itemtype);
if (
isset($CFG_GLPI["union_search_type"][$itemtype])
&& ($CFG_GLPI["union_search_type"][$itemtype] == $searchopt[$ID]["table"])
) {
$oparams = [];
if (
isset($searchopt[$ID]['addobjectparams'])
&& $searchopt[$ID]['addobjectparams']
) {
$oparams = $searchopt[$ID]['addobjectparams'];
}
// Search option may not exists in subtype
// This is the case for "Inventory number" for a Software listed from ReservationItem search
$subtype_so = &self::getOptions($data["TYPE"]);
if (!array_key_exists($ID, $subtype_so)) {
return '';
}
return self::giveItem($data["TYPE"], $ID, $data, $meta, $oparams, $itemtype);
}
$so = $searchopt[$ID];
$orig_id = $ID;
$ID = ($orig_itemtype !== null ? $orig_itemtype : $itemtype) . '_' . $ID;
if (count($addobjectparams)) {
$so = array_merge($so, $addobjectparams);
}
// Plugin can override core definition for its type
if ($plug = isPluginItemType($itemtype)) {
$out = Plugin::doOneHook(
$plug['plugin'],
'giveItem',
$itemtype,
$orig_id,
$data,
$ID
);
if (!empty($out)) {
return $out;
}
}
if (isset($so["table"])) {
$table = $so["table"];
$field = $so["field"];
$linkfield = $so["linkfield"];
/// TODO try to clean all specific cases using SpecificToDisplay
switch ($table . '.' . $field) {
case "glpi_users.name":
// USER search case
if (
($itemtype != 'User')
&& isset($so["forcegroupby"]) && $so["forcegroupby"]
) {
$out = "";
$count_display = 0;
$added = [];
$showuserlink = 0;
if (Session::haveRight('user', READ)) {
$showuserlink = 1;
}
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (
(isset($data[$ID][$k]['name']) && ($data[$ID][$k]['name'] > 0))
|| (isset($data[$ID][$k][2]) && ($data[$ID][$k][2] != ''))
) {
if ($count_display) {
$out .= self::LBBR;
}
if ($itemtype == 'Ticket') {
if (
isset($data[$ID][$k]['name'])
&& $data[$ID][$k]['name'] > 0
) {
if (
Session::getCurrentInterface() == 'helpdesk'
&& $orig_id == 5 // -> Assigned user
&& !empty($anon_name = User::getAnonymizedNameForUser(
$data[$ID][$k]['name'],
$itemtype::getById($data['id'])->getEntityId()
))
) {
$out .= $anon_name;
} else {
$userdata = getUserName($data[$ID][$k]['name'], 2);
$tooltip = "";
if (Session::haveRight('user', READ)) {
$tooltip = Html::showToolTip(
$userdata["comment"],
['link' => $userdata["link"],
'display' => false
]
);
}
$out .= sprintf(__('%1$s %2$s'), $userdata['name'], $tooltip);
}
$count_display++;
}
} else {
$out .= getUserName($data[$ID][$k]['name'], $showuserlink);
$count_display++;
}
// Manage alternative_email for tickets_users
if (
($itemtype == 'Ticket')
&& isset($data[$ID][$k][2])
) {
$split = explode(self::LONGSEP, $data[$ID][$k][2]);
for ($l = 0; $l < count($split); $l++) {
$split2 = explode(" ", $split[$l]);
if ((count($split2) == 2) && ($split2[0] == 0) && !empty($split2[1])) {
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
$out .= "<a href='mailto:" . $split2[1] . "'>" . $split2[1] . "</a>";
}
}
}
}
}
return $out;
}
if ($itemtype != 'User') {
$toadd = '';
if (
($itemtype == 'Ticket')
&& ($data[$ID][0]['id'] > 0)
) {
$userdata = getUserName($data[$ID][0]['id'], 2);
$toadd = Html::showToolTip(
$userdata["comment"],
['link' => $userdata["link"],
'display' => false
]
);
}
$usernameformat = formatUserName(
$data[$ID][0]['id'],
$data[$ID][0]['name'],
$data[$ID][0]['realname'],
$data[$ID][0]['firstname'],
1
);
return sprintf(__('%1$s %2$s'), $usernameformat, $toadd);
}
$current_users_id = $data[$ID][0]['id'] ?? 0;
if ($current_users_id > 0) {
return TemplateRenderer::getInstance()->render('components/user/picture.html.twig', [
'users_id' => $current_users_id,
'display_login' => true,
'force_login' => true,
'avatar_size' => "avatar-sm",
]);
}
break;
case "glpi_profiles.name":
if (
($itemtype == 'User')
&& ($orig_id == 20)
) {
$out = "";
$count_display = 0;
$added = [];
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (
strlen(trim($data[$ID][$k]['name'])) > 0
&& !in_array(
$data[$ID][$k]['name'] . "-" . $data[$ID][$k]['entities_id'],
$added
)
) {
$text = sprintf(
__('%1$s - %2$s'),
$data[$ID][$k]['name'],
Dropdown::getDropdownName(
'glpi_entities',
$data[$ID][$k]['entities_id']
)
);
$comp = '';
if ($data[$ID][$k]['is_recursive']) {
$comp = __('R');
if ($data[$ID][$k]['is_dynamic']) {
$comp = sprintf(__('%1$s%2$s'), $comp, ", ");
}
}
if ($data[$ID][$k]['is_dynamic']) {
$comp = sprintf(__('%1$s%2$s'), $comp, __('D'));
}
if (!empty($comp)) {
$text = sprintf(__('%1$s %2$s'), $text, "(" . $comp . ")");
}
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
$out .= $text;
$added[] = $data[$ID][$k]['name'] . "-" . $data[$ID][$k]['entities_id'];
}
}
return $out;
}
break;
case "glpi_entities.completename":
if ($itemtype == 'User') {
$out = "";
$added = [];
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (
isset($data[$ID][$k]['name'])
&& (strlen(trim($data[$ID][$k]['name'])) > 0)
&& !in_array(
$data[$ID][$k]['name'] . "-" . $data[$ID][$k]['profiles_id'],
$added
)
) {
$text = sprintf(
__('%1$s - %2$s'),
Entity::badgeCompletename($data[$ID][$k]['name']),
Dropdown::getDropdownName(
'glpi_profiles',
$data[$ID][$k]['profiles_id']
)
);
$comp = '';
if ($data[$ID][$k]['is_recursive']) {
$comp = __('R');
if ($data[$ID][$k]['is_dynamic']) {
$comp = sprintf(__('%1$s%2$s'), $comp, ", ");
}
}
if ($data[$ID][$k]['is_dynamic']) {
$comp = sprintf(__('%1$s%2$s'), $comp, __('D'));
}
if (!empty($comp)) {
$text = sprintf(__('%1$s %2$s'), $text, "(" . $comp . ")");
}
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
$out .= $text;
$added[] = $data[$ID][$k]['name'] . "-" . $data[$ID][$k]['profiles_id'];
}
}
return $out;
} else if (($so["datatype"] ?? "") != "itemlink" && !empty($data[$ID][0]['name'])) {
return Entity::badgeCompletename($data[$ID][0]['name']);
}
break;
case "glpi_documenttypes.icon":
if (!empty($data[$ID][0]['name'])) {
return "<img class='middle' alt='' src='" . $CFG_GLPI["typedoc_icon_dir"] . "/" .
$data[$ID][0]['name'] . "'>";
}
return " ";
case "glpi_documents.filename":
$doc = new Document();
if ($doc->getFromDB($data['id'])) {
return $doc->getDownloadLink();
}
return NOT_AVAILABLE;
case "glpi_tickets_tickets.tickets_id_1":
$out = "";
$displayed = [];
for ($k = 0; $k < $data[$ID]['count']; $k++) {
$linkid = ($data[$ID][$k]['tickets_id_2'] == $data['id'])
? $data[$ID][$k]['name']
: $data[$ID][$k]['tickets_id_2'];
// If link ID is int or integer string, force conversion to int. Coversion to int and then string to compare is needed to ensure it isn't a decimal
if (is_numeric($linkid) && ((string)(int)$linkid === (string)$linkid)) {
$linkid = (int) $linkid;
}
if ((is_int($linkid) && $linkid > 0) && !isset($displayed[$linkid])) {
$text = "<a ";
$text .= "href=\"" . Ticket::getFormURLWithID($linkid) . "\">";
$text .= Dropdown::getDropdownName('glpi_tickets', $linkid) . "</a>";
if (count($displayed)) {
$out .= self::LBBR;
}
$displayed[$linkid] = $linkid;
$out .= $text;
}
}
return $out;
case "glpi_problems.id":
if ($so["datatype"] == 'count') {
if (
($data[$ID][0]['name'] > 0)
&& Session::haveRight("problem", Problem::READALL)
) {
if ($itemtype == 'ITILCategory') {
$options['criteria'][0]['field'] = 7;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = $data['id'];
$options['criteria'][0]['link'] = 'AND';
} else {
$options['criteria'][0]['field'] = 12;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = 'all';
$options['criteria'][0]['link'] = 'AND';
$options['metacriteria'][0]['itemtype'] = $itemtype;
$options['metacriteria'][0]['field'] = self::getOptionNumber(
$itemtype,
'name'
);
$options['metacriteria'][0]['searchtype'] = 'equals';
$options['metacriteria'][0]['value'] = $data['id'];
$options['metacriteria'][0]['link'] = 'AND';
}
$options['reset'] = 'reset';
$out = "<a id='problem$itemtype" . $data['id'] . "' ";
$out .= "href=\"" . $CFG_GLPI["root_doc"] . "/front/problem.php?" .
Toolbox::append_params($options, '&') . "\">";
$out .= $data[$ID][0]['name'] . "</a>";
return $out;
}
}
break;
case "glpi_tickets.id":
if ($so["datatype"] == 'count') {
if (
($data[$ID][0]['name'] > 0)
&& Session::haveRight("ticket", Ticket::READALL)
) {
if ($itemtype == 'User') {
// Requester
if ($ID == 'User_60') {
$options['criteria'][0]['field'] = 4;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = $data['id'];
$options['criteria'][0]['link'] = 'AND';
}
// Writer
if ($ID == 'User_61') {
$options['criteria'][0]['field'] = 22;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = $data['id'];
$options['criteria'][0]['link'] = 'AND';
}
// Assign
if ($ID == 'User_64') {
$options['criteria'][0]['field'] = 5;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = $data['id'];
$options['criteria'][0]['link'] = 'AND';
}
} else if ($itemtype == 'ITILCategory') {
$options['criteria'][0]['field'] = 7;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = $data['id'];
$options['criteria'][0]['link'] = 'AND';
} else {
$options['criteria'][0]['field'] = 12;
$options['criteria'][0]['searchtype'] = 'equals';
$options['criteria'][0]['value'] = 'all';
$options['criteria'][0]['link'] = 'AND';
$options['metacriteria'][0]['itemtype'] = $itemtype;
$options['metacriteria'][0]['field'] = self::getOptionNumber(
$itemtype,
'name'
);
$options['metacriteria'][0]['searchtype'] = 'equals';
$options['metacriteria'][0]['value'] = $data['id'];
$options['metacriteria'][0]['link'] = 'AND';
}
$options['reset'] = 'reset';
$out = "<a id='ticket$itemtype" . $data['id'] . "' ";
$out .= "href=\"" . $CFG_GLPI["root_doc"] . "/front/ticket.php?" .
Toolbox::append_params($options, '&') . "\">";
$out .= $data[$ID][0]['name'] . "</a>";
return $out;
}
}
break;
case "glpi_tickets.time_to_resolve":
case "glpi_problems.time_to_resolve":
case "glpi_changes.time_to_resolve":
case "glpi_tickets.time_to_own":
case "glpi_tickets.internal_time_to_own":
case "glpi_tickets.internal_time_to_resolve":
// Due date + progress
if (in_array($orig_id, [151, 158, 181, 186])) {
$out = Html::convDateTime($data[$ID][0]['name']);
// No due date in waiting status
if ($data[$ID][0]['status'] == CommonITILObject::WAITING) {
return '';
}
if (empty($data[$ID][0]['name'])) {
return '';
}
if (
($data[$ID][0]['status'] == Ticket::SOLVED)
|| ($data[$ID][0]['status'] == Ticket::CLOSED)
) {
return $out;
}
$itemtype = getItemTypeForTable($table);
$item = new $itemtype();
$item->getFromDB($data['id']);
$percentage = 0;
$totaltime = 0;
$currenttime = 0;
$slaField = 'slas_id';
// define correct sla field
switch ($table . '.' . $field) {
case "glpi_tickets.time_to_resolve":
$slaField = 'slas_id_ttr';
$sla_class = 'SLA';
break;
case "glpi_tickets.time_to_own":
$slaField = 'slas_id_tto';
$sla_class = 'SLA';
break;
case "glpi_tickets.internal_time_to_own":
$slaField = 'olas_id_tto';
$sla_class = 'OLA';
break;
case "glpi_tickets.internal_time_to_resolve":
$slaField = 'olas_id_ttr';
$sla_class = 'OLA';
break;
}
switch ($table . '.' . $field) {
// If ticket has been taken into account : no progression display
case "glpi_tickets.time_to_own":
case "glpi_tickets.internal_time_to_own":
if (($item->fields['takeintoaccount_delay_stat'] > 0)) {
return $out;
}
break;
}
if ($item->isField($slaField) && $item->fields[$slaField] != 0) { // Have SLA
$sla = new $sla_class();
$sla->getFromDB($item->fields[$slaField]);
$currenttime = $sla->getActiveTimeBetween(
$item->fields['date'],
date('Y-m-d H:i:s')
);
$totaltime = $sla->getActiveTimeBetween(
$item->fields['date'],
$data[$ID][0]['name']
);
} else {
$calendars_id = Entity::getUsedConfig(
'calendars_strategy',
$item->fields['entities_id'],
'calendars_id',
0
);
$calendar = new Calendar();
if ($calendars_id > 0 && $calendar->getFromDB($calendars_id)) { // Ticket entity have calendar
$currenttime = $calendar->getActiveTimeBetween(
$item->fields['date'],
date('Y-m-d H:i:s')
);
$totaltime = $calendar->getActiveTimeBetween(
$item->fields['date'],
$data[$ID][0]['name']
);
} else { // No calendar
$currenttime = strtotime(date('Y-m-d H:i:s'))
- strtotime($item->fields['date']);
$totaltime = strtotime($data[$ID][0]['name'])
- strtotime($item->fields['date']);
}
}
if ($totaltime != 0) {
$percentage = round((100 * $currenttime) / $totaltime);
} else {
// Total time is null : no active time
$percentage = 100;
}
if ($percentage > 100) {
$percentage = 100;
}
$percentage_text = $percentage;
if ($_SESSION['glpiduedatewarning_unit'] == '%') {
$less_warn_limit = $_SESSION['glpiduedatewarning_less'];
$less_warn = (100 - $percentage);
} else if ($_SESSION['glpiduedatewarning_unit'] == 'hour') {
$less_warn_limit = $_SESSION['glpiduedatewarning_less'] * HOUR_TIMESTAMP;
$less_warn = ($totaltime - $currenttime);
} else if ($_SESSION['glpiduedatewarning_unit'] == 'day') {
$less_warn_limit = $_SESSION['glpiduedatewarning_less'] * DAY_TIMESTAMP;
$less_warn = ($totaltime - $currenttime);
}
if ($_SESSION['glpiduedatecritical_unit'] == '%') {
$less_crit_limit = $_SESSION['glpiduedatecritical_less'];
$less_crit = (100 - $percentage);
} else if ($_SESSION['glpiduedatecritical_unit'] == 'hour') {
$less_crit_limit = $_SESSION['glpiduedatecritical_less'] * HOUR_TIMESTAMP;
$less_crit = ($totaltime - $currenttime);
} else if ($_SESSION['glpiduedatecritical_unit'] == 'day') {
$less_crit_limit = $_SESSION['glpiduedatecritical_less'] * DAY_TIMESTAMP;
$less_crit = ($totaltime - $currenttime);
}
$color = $_SESSION['glpiduedateok_color'];
if ($less_crit < $less_crit_limit) {
$color = $_SESSION['glpiduedatecritical_color'];
} else if ($less_warn < $less_warn_limit) {
$color = $_SESSION['glpiduedatewarning_color'];
}
if (!isset($so['datatype'])) {
$so['datatype'] = 'progressbar';
}
$progressbar_data = [
'text' => Html::convDateTime($data[$ID][0]['name']),
'percent' => $percentage,
'percent_text' => $percentage_text,
'color' => $color
];
}
break;
case "glpi_softwarelicenses.number":
if ($data[$ID][0]['min'] == -1) {
return __('Unlimited');
}
if (empty($data[$ID][0]['name'])) {
return 0;
}
return $data[$ID][0]['name'];
case "glpi_auth_tables.name":
return Auth::getMethodName(
$data[$ID][0]['name'],
$data[$ID][0]['auths_id'],
1,
$data[$ID][0]['ldapname'] . $data[$ID][0]['mailname']
);
case "glpi_reservationitems.comment":
if (empty($data[$ID][0]['name'])) {
$text = __('None');
} else {
$text = Html::resume_text($data[$ID][0]['name']);
}
if (Session::haveRight('reservation', UPDATE)) {
return "<a title=\"" . __s('Modify the comment') . "\"
href='" . ReservationItem::getFormURLWithID($data['refID']) . "' >" . $text . "</a>";
}
return $text;
case 'glpi_crontasks.description':
$tmp = new CronTask();
return $tmp->getDescription($data[$ID][0]['name']);
case 'glpi_changes.status':
$status = Change::getStatus($data[$ID][0]['name']);
return "<span class='text-nowrap'>" .
Change::getStatusIcon($data[$ID][0]['name']) . " $status" .
"</span>";
case 'glpi_problems.status':
$status = Problem::getStatus($data[$ID][0]['name']);
return "<span class='text-nowrap'>" .
Problem::getStatusIcon($data[$ID][0]['name']) . " $status" .
"</span>";
case 'glpi_tickets.status':
$status = Ticket::getStatus($data[$ID][0]['name']);
return "<span class='text-nowrap'>" .
Ticket::getStatusIcon($data[$ID][0]['name']) . " $status" .
"</span>";
case 'glpi_projectstates.name':
$out = '';
$name = $data[$ID][0]['name'];
if (isset($data[$ID][0]['trans'])) {
$name = $data[$ID][0]['trans'];
}
if ($itemtype == 'ProjectState') {
$out = "<a href='" . ProjectState::getFormURLWithID($data[$ID][0]["id"]) . "'>" . $name . "</a></div>";
} else {
$out = $name;
}
return $out;
case 'glpi_items_tickets.items_id':
case 'glpi_items_problems.items_id':
case 'glpi_changes_items.items_id':
case 'glpi_certificates_items.items_id':
case 'glpi_appliances_items.items_id':
if (!empty($data[$ID])) {
$items = [];
foreach ($data[$ID] as $key => $val) {
if (is_numeric($key)) {
if (
!empty($val['itemtype'])
&& ($item = getItemForItemtype($val['itemtype']))
) {
if ($item->getFromDB($val['name'])) {
$items[] = $item->getLink(['comments' => true]);
}
}
}
}
if (!empty($items)) {
return implode("<br>", $items);
}
}
return ' ';
case 'glpi_items_tickets.itemtype':
case 'glpi_items_problems.itemtype':
if (!empty($data[$ID])) {
$itemtypes = [];
foreach ($data[$ID] as $key => $val) {
if (is_numeric($key)) {
if (
!empty($val['name'])
&& ($item = getItemForItemtype($val['name']))
) {
$item = new $val['name']();
$name = $item->getTypeName();
$itemtypes[] = __($name);
}
}
}
if (!empty($itemtypes)) {
return implode("<br>", $itemtypes);
}
}
return ' ';
case 'glpi_tickets.name':
case 'glpi_problems.name':
case 'glpi_changes.name':
if (
isset($data[$ID][0]['content'])
&& isset($data[$ID][0]['id'])
&& isset($data[$ID][0]['status'])
) {
$link = $itemtype::getFormURLWithID($data[$ID][0]['id']);
$out = "<a id='$itemtype" . $data[$ID][0]['id'] . "' href=\"" . $link;
// Force solution tab if solved
if ($item = getItemForItemtype($itemtype)) {
if (in_array($data[$ID][0]['status'], $item->getSolvedStatusArray())) {
$out .= "&forcetab=$itemtype$2";
}
}
$out .= "\">";
$name = $data[$ID][0]['name'];
if (
$_SESSION["glpiis_ids_visible"]
|| empty($data[$ID][0]['name'])
) {
$name = sprintf(__('%1$s (%2$s)'), $name, $data[$ID][0]['id']);
}
$out .= $name . "</a>";
$out = sprintf(
__('%1$s %2$s'),
$out,
Html::showToolTip(
RichText::getEnhancedHtml($data[$ID][0]['content']),
['applyto' => $itemtype .
$data[$ID][0]['id'],
'display' => false
]
)
);
return $out;
}
break;
case 'glpi_ticketvalidations.status':
$out = '';
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if ($data[$ID][$k]['name']) {
$status = TicketValidation::getStatus($data[$ID][$k]['name']);
$bgcolor = TicketValidation::getStatusColor($data[$ID][$k]['name']);
$out .= (empty($out) ? '' : self::LBBR) .
"<div style=\"background-color:" . $bgcolor . ";\">" . $status . '</div>';
}
}
return $out;
case 'glpi_cables.color':
//do not display 'real' value (#.....)
return "";
case 'glpi_ticketsatisfactions.satisfaction':
if (self::$output_type == self::HTML_OUTPUT) {
return TicketSatisfaction::displaySatisfaction($data[$ID][0]['name']);
}
break;
case 'glpi_projects._virtual_planned_duration':
return Html::timestampToString(
ProjectTask::getTotalPlannedDurationForProject($data["id"]),
false
);
case 'glpi_projects._virtual_effective_duration':
return Html::timestampToString(
ProjectTask::getTotalEffectiveDurationForProject($data["id"]),
false
);
case 'glpi_cartridgeitems._virtual':
return Cartridge::getCount(
$data["id"],
$data[$ID][0]['alarm_threshold'],
self::$output_type != self::HTML_OUTPUT
);
case 'glpi_printers._virtual':
return Cartridge::getCountForPrinter(
$data["id"],
self::$output_type != self::HTML_OUTPUT
);
case 'glpi_consumableitems._virtual':
return Consumable::getCount(
$data["id"],
$data[$ID][0]['alarm_threshold'],
self::$output_type != self::HTML_OUTPUT
);
case 'glpi_links._virtual':
$out = '';
$link = new Link();
if (
($item = getItemForItemtype($itemtype))
&& $item->getFromDB($data['id'])
) {
$data = Link::getLinksDataForItem($item);
$count_display = 0;
foreach ($data as $val) {
$links = Link::getAllLinksFor($item, $val);
foreach ($links as $link) {
if ($count_display) {
$out .= self::LBBR;
}
$out .= $link;
$count_display++;
}
}
}
return $out;
case 'glpi_reservationitems._virtual':
if ($data[$ID][0]['is_active']) {
return "<a href='reservation.php?reservationitems_id=" .
$data["refID"] . "' title=\"" . __s('See planning') . "\">" .
"<i class='far fa-calendar-alt'></i><span class='sr-only'>" . __('See planning') . "</span></a>";
} else {
return " ";
}
case "glpi_tickets.priority":
case "glpi_problems.priority":
case "glpi_changes.priority":
case "glpi_projects.priority":
$index = $data[$ID][0]['name'];
$color = $_SESSION["glpipriority_$index"];
$name = CommonITILObject::getPriorityName($index);
return "<div class='priority_block' style='border-color: $color'>
<span style='background: $color'></span> $name
</div>";
}
}
//// Default case
if (
$itemtype == 'Ticket'
&& Session::getCurrentInterface() == 'helpdesk'
&& $orig_id == 8
&& !empty($anon_name = Group::getAnonymizedName(
$itemtype::getById($data['id'])->getEntityId()
))
) {
// Assigned groups
return $anon_name;
}
// Link with plugin tables : need to know left join structure
if (isset($table)) {
if (preg_match("/^glpi_plugin_([a-z0-9]+)/", $table . '.' . $field, $matches)) {
if (count($matches) == 2) {
$plug = $matches[1];
$out = Plugin::doOneHook(
$plug,
'giveItem',
$itemtype,
$orig_id,
$data,
$ID
);
if (!empty($out)) {
return $out;
}
}
}
}
$unit = '';
if (isset($so['unit'])) {
$unit = $so['unit'];
}
// Preformat items
if (isset($so["datatype"])) {
switch ($so["datatype"]) {
case "itemlink":
$linkitemtype = getItemTypeForTable($so["table"]);
$out = "";
$count_display = 0;
$separate = self::LBBR;
if (isset($so['splititems']) && $so['splititems']) {
$separate = self::LBHR;
}
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (isset($data[$ID][$k]['id'])) {
if ($count_display) {
$out .= $separate;
}
$count_display++;
$page = $linkitemtype::getFormURLWithID($data[$ID][$k]['id']);
$name = $data[$ID][$k]['name'];
if ($_SESSION["glpiis_ids_visible"] || empty($data[$ID][$k]['name'])) {
$name = sprintf(__('%1$s (%2$s)'), $name, $data[$ID][$k]['id']);
}
if ($field === 'completename') {
$chunks = preg_split('/ > /', $name);
$completename = '';
foreach ($chunks as $key => $element_name) {
$class = $key === array_key_last($chunks) ? '' : 'class="text-muted"';
$separator = $key === array_key_last($chunks) ? '' : ' > ';
$completename .= sprintf('<span %s>%s</span>%s', $class, $element_name, $separator);
}
$name = $completename;
}
$out .= "<a id='" . $linkitemtype . "_" . $data['id'] . "_" .
$data[$ID][$k]['id'] . "' href='$page'>" .
$name . "</a>";
}
}
return $out;
case "text":
$separate = self::LBBR;
if (isset($so['splititems']) && $so['splititems']) {
$separate = self::LBHR;
}
$out = '';
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (strlen(trim((string)$data[$ID][$k]['name'])) > 0) {
if ($count_display) {
$out .= $separate;
}
$count_display++;
$plaintext = RichText::getTextFromHtml($data[$ID][$k]['name'], false, true, self::$output_type == self::HTML_OUTPUT);
if (self::$output_type == self::HTML_OUTPUT && (Toolbox::strlen($plaintext) > $CFG_GLPI['cut'])) {
$rand = mt_rand();
$popup_params = [
'display' => false,
'awesome-class' => 'fa-comments',
'autoclose' => false,
'onclick' => true,
];
$out .= sprintf(
__('%1$s %2$s'),
"<span id='text$rand'>" . Html::resume_text($plaintext, $CFG_GLPI['cut']) . '</span>',
Html::showToolTip(
'<div class="fup-popup">' . RichText::getEnhancedHtml($data[$ID][$k]['name']) . '</div>',
$popup_params
)
);
} else {
$out .= $plaintext;
}
}
}
return $out;
case "date":
case "date_delay":
$out = '';
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (
is_null($data[$ID][$k]['name'])
&& isset($so['emptylabel']) && $so['emptylabel']
) {
$out .= (empty($out) ? '' : self::LBBR) . $so['emptylabel'];
} else {
$out .= (empty($out) ? '' : self::LBBR) . Html::convDate($data[$ID][$k]['name']);
}
}
$out = "<span class='text-nowrap'>$out</span>";
return $out;
case "datetime":
$out = '';
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (
is_null($data[$ID][$k]['name'])
&& isset($so['emptylabel']) && $so['emptylabel']
) {
$out .= (empty($out) ? '' : self::LBBR) . $so['emptylabel'];
} else {
$out .= (empty($out) ? '' : self::LBBR) . Html::convDateTime($data[$ID][$k]['name']);
}
}
$out = "<span class='text-nowrap'>$out</span>";
return $out;
case "timestamp":
$withseconds = false;
if (isset($so['withseconds'])) {
$withseconds = $so['withseconds'];
}
$withdays = true;
if (isset($so['withdays'])) {
$withdays = $so['withdays'];
}
$out = '';
for ($k = 0; $k < $data[$ID]['count']; $k++) {
$out .= (empty($out) ? '' : '<br>') . Html::timestampToString(
$data[$ID][$k]['name'],
$withseconds,
$withdays
);
}
$out = "<span class='text-nowrap'>$out</span>";
return $out;
case "email":
$out = '';
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
if (!empty($data[$ID][$k]['name'])) {
$out .= (empty($out) ? '' : self::LBBR);
$out .= "<a href='mailto:" . Html::entities_deep($data[$ID][$k]['name']) . "'>" . $data[$ID][$k]['name'];
$out .= "</a>";
}
}
return (empty($out) ? " " : $out);
case "weblink":
$orig_link = trim((string)$data[$ID][0]['name']);
if (!empty($orig_link) && Toolbox::isValidWebUrl($orig_link)) {
// strip begin of link
$link = preg_replace('/https?:\/\/(www[^\.]*\.)?/', '', $orig_link);
$link = preg_replace('/\/$/', '', $link);
if (Toolbox::strlen($link) > $CFG_GLPI["url_maxlength"]) {
$link = Toolbox::substr($link, 0, $CFG_GLPI["url_maxlength"]) . "...";
}
return "<a href=\"" . Toolbox::formatOutputWebLink($orig_link) . "\" target='_blank'>$link</a>";
}
return " ";
case "count":
case "number":
case "mio":
$out = "";
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (strlen(trim((string)$data[$ID][$k]['name'])) > 0) {
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
if (
isset($so['toadd'])
&& isset($so['toadd'][$data[$ID][$k]['name']])
) {
$out .= $so['toadd'][$data[$ID][$k]['name']];
} else {
$out .= Dropdown::getValueWithUnit($data[$ID][$k]['name'], $unit);
}
}
}
$out = "<span class='text-nowrap'>$out</span>";
return $out;
case "decimal":
$out = "";
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (strlen(trim((string)$data[$ID][$k]['name'])) > 0) {
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
if (
isset($so['toadd'])
&& isset($so['toadd'][$data[$ID][$k]['name']])
) {
$out .= $so['toadd'][$data[$ID][$k]['name']];
} else {
$out .= Dropdown::getValueWithUnit($data[$ID][$k]['name'], $unit, $CFG_GLPI["decimal_number"]);
}
}
}
$out = "<span class='text-nowrap'>$out</span>";
return $out;
case "bool":
$out = "";
$count_display = 0;
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if (strlen(trim((string)$data[$ID][$k]['name'])) > 0) {
if ($count_display) {
$out .= self::LBBR;
}
$count_display++;
$out .= Dropdown::getYesNo($data[$ID][$k]['name']);
}
}
return $out;
case "itemtypename":
if ($obj = getItemForItemtype($data[$ID][0]['name'])) {
return $obj->getTypeName();
}
return "";
case "language":
if (isset($CFG_GLPI['languages'][$data[$ID][0]['name']])) {
return $CFG_GLPI['languages'][$data[$ID][0]['name']][0];
}
return __('Default value');
case 'progressbar':
if (!isset($progressbar_data)) {
$bar_color = 'green';
$percent = ltrim(($data[$ID][0]['name'] ?? ""), 0);
$progressbar_data = [
'percent' => $percent,
'percent_text' => $percent,
'color' => $bar_color,
'text' => ''
];
}
$out = "";
if ($progressbar_data['percent'] !== null) {
$out = <<<HTML
<span class='text-nowrap'>
{$progressbar_data['text']}
</span>
<div class="progress" style="height: 16px">
<div class="progress-bar progress-bar-striped" role="progressbar"
style="width: {$progressbar_data['percent']}%; background-color: {$progressbar_data['color']};"
aria-valuenow="{$progressbar_data['percent']}"
aria-valuemin="0" aria-valuemax="100">
{$progressbar_data['percent_text']}%
</div>
</div>
HTML;
}
return $out;
break;
}
}
// Manage items with need group by / group_concat
$out = "";
$count_display = 0;
$separate = self::LBBR;
if (isset($so['splititems']) && $so['splititems']) {
$separate = self::LBHR;
}
for ($k = 0; $k < $data[$ID]['count']; $k++) {
if ($count_display) {
$out .= $separate;
}
$count_display++;
// Get specific display if available
if (isset($table)) {
$itemtype = getItemTypeForTable($table);
if ($item = getItemForItemtype($itemtype)) {
$tmpdata = $data[$ID][$k];
// Copy name to real field
$tmpdata[$field] = $data[$ID][$k]['name'] ?? '';
$specific = $item->getSpecificValueToDisplay(
$field,
$tmpdata,
[
'html' => true,
'searchopt' => $so,
'raw_data' => $data
]
);
}
}
if (!empty($specific)) {
$out .= $specific;
} else {
if (
isset($so['toadd'])
&& isset($so['toadd'][$data[$ID][$k]['name']])
) {
$out .= $so['toadd'][$data[$ID][$k]['name']];
} else {
// Empty is 0 or empty
if (empty($split[0]) && isset($so['emptylabel'])) {
$out .= $so['emptylabel'];
} else {
// Trans field exists
if (isset($data[$ID][$k]['trans']) && !empty($data[$ID][$k]['trans'])) {
$out .= $data[$ID][$k]['trans'];
} else {
$value = $data[$ID][$k]['name'];
$out .= $value !== null && $so['field'] === 'completename'
? CommonTreeDropdown::sanitizeSeparatorInCompletename($value)
: $value;
}
}
}
}
}
return $out;
}
/**
* Reset save searches
*
* @return void
**/
public static function resetSaveSearch()
{
unset($_SESSION['glpisearch']);
$_SESSION['glpisearch'] = [];
}
/**
* Completion of the URL $_GET values with the $_SESSION values or define default values
*
* @param string $itemtype Item type to manage
* @param array $params Params to parse
* @param boolean $usesession Use datas save in session (true by default)
* @param boolean $forcebookmark Force trying to load parameters from default bookmark:
* used for global search (false by default)
*
* @return array parsed params
**/
public static function manageParams(
$itemtype,
$params = [],
$usesession = true,
$forcebookmark = false
) {
$default_values = [];
$default_values["start"] = 0;
$default_values["order"] = "ASC";
$default_values["sort"] = 1;
$default_values["is_deleted"] = 0;
$default_values["as_map"] = 0;
$default_values["browse"] = 0;
if (isset($params['start'])) {
$params['start'] = (int)$params['start'];
}
$default_values["criteria"] = self::getDefaultCriteria($itemtype);
$default_values["metacriteria"] = [];
// Reorg search array
// start
// order
// sort
// is_deleted
// itemtype
// criteria : array (0 => array (link =>
// field =>
// searchtype =>
// value => (contains)
// metacriteria : array (0 => array (itemtype =>
// link =>
// field =>
// searchtype =>
// value => (contains)
if ($itemtype != AllAssets::getType() && class_exists($itemtype)) {
// retrieve default values for current itemtype
$itemtype_default_values = [];
if (method_exists($itemtype, 'getDefaultSearchRequest')) {
$itemtype_default_values = call_user_func([$itemtype, 'getDefaultSearchRequest']);
}
// retrieve default values for the current user
$user_default_values = SavedSearch_User::getDefault(Session::getLoginUserID(), $itemtype);
if ($user_default_values === false) {
$user_default_values = [];
}
// we construct default values in this order:
// - general default
// - itemtype default
// - user default
//
// The last ones erase values or previous
// So, we can combine each part (order from itemtype, criteria from user, etc)
$default_values = array_merge(
$default_values,
$itemtype_default_values,
$user_default_values
);
}
// First view of the page or force bookmark : try to load a bookmark
if (
$forcebookmark
|| ($usesession
&& !isset($params["reset"])
&& !isset($_SESSION['glpisearch'][$itemtype]))
) {
$user_default_values = SavedSearch_User::getDefault(Session::getLoginUserID(), $itemtype);
if ($user_default_values) {
$_SESSION['glpisearch'][$itemtype] = [];
// Only get datas for bookmarks
if ($forcebookmark) {
$params = $user_default_values;
} else {
$bookmark = new SavedSearch();
$bookmark->load($user_default_values['savedsearches_id'], false);
}
}
}
// Force reorder criterias
if (
isset($params["criteria"])
&& is_array($params["criteria"])
&& count($params["criteria"])
) {
$tmp = $params["criteria"];
$params["criteria"] = [];
foreach ($tmp as $val) {
$params["criteria"][] = $val;
}
}
// transform legacy meta-criteria in criteria (with flag meta=true)
// at the end of the array, as before there was only at the end of the query
if (
isset($params["metacriteria"])
&& is_array($params["metacriteria"])
) {
// as we will append meta to criteria, check the key exists
if (!isset($params["criteria"])) {
$params["criteria"] = [];
}
foreach ($params["metacriteria"] as $val) {
$params["criteria"][] = $val + ['meta' => 1];
}
$params["metacriteria"] = [];
}
if (
$usesession
&& isset($params["reset"])
) {
if (isset($_SESSION['glpisearch'][$itemtype])) {
unset($_SESSION['glpisearch'][$itemtype]);
}
}
if (
isset($params) && is_array($params)
&& $usesession
) {
foreach ($params as $key => $val) {
$_SESSION['glpisearch'][$itemtype][$key] = $val;
}
}
$saved_params = $params;
foreach ($default_values as $key => $val) {
if (!isset($params[$key])) {
if (
$usesession
&& ($key == 'is_deleted' || $key == 'as_map' || $key == 'browse' || !isset($saved_params['criteria'])) // retrieve session only if not a new request
&& isset($_SESSION['glpisearch'][$itemtype][$key])
) {
$params[$key] = $_SESSION['glpisearch'][$itemtype][$key];
} else {
$params[$key] = $val;
$_SESSION['glpisearch'][$itemtype][$key] = $val;
}
}
}
return $params;
}
/**
* Clean search options depending of user active profile
*
* @param string $itemtype Item type to manage
* @param integer $action Action which is used to manupulate searchoption
* (default READ)
* @param boolean $withplugins Get plugins options (true by default)
*
* @return array Clean $SEARCH_OPTION array
**/
public static function getCleanedOptions($itemtype, $action = READ, $withplugins = true)
{
global $CFG_GLPI;
$options = &self::getOptions($itemtype, $withplugins);
$todel = [];
if (
!Session::haveRight('infocom', $action)
&& Infocom::canApplyOn($itemtype)
) {
$itemstodel = Infocom::getSearchOptionsToAdd($itemtype);
$todel = array_merge($todel, array_keys($itemstodel));
}
if (
!Session::haveRight('contract', $action)
&& in_array($itemtype, $CFG_GLPI["contract_types"])
) {
$itemstodel = Contract::getSearchOptionsToAdd();
$todel = array_merge($todel, array_keys($itemstodel));
}
if (
!Session::haveRight('document', $action)
&& Document::canApplyOn($itemtype)
) {
$itemstodel = Document::getSearchOptionsToAdd();
$todel = array_merge($todel, array_keys($itemstodel));
}
// do not show priority if you don't have right in profile
if (
($itemtype == 'Ticket')
&& ($action == UPDATE)
&& !Session::haveRight('ticket', Ticket::CHANGEPRIORITY)
) {
$todel[] = 3;
}
if ($itemtype == 'Computer') {
if (!Session::haveRight('networking', $action)) {
$itemstodel = NetworkPort::getSearchOptionsToAdd($itemtype);
$todel = array_merge($todel, array_keys($itemstodel));
}
}
if (!Session::haveRight(strtolower($itemtype), READNOTE)) {
$todel[] = 90;
}
if (count($todel)) {
foreach ($todel as $ID) {
if (isset($options[$ID])) {
unset($options[$ID]);
}
}
}
return $options;
}
/**
*
* Get an option number in the SEARCH_OPTION array
*
* @param class-string<CommonDBTM> $itemtype Item type
* @param string $field Name
*
* @return integer
**/
public static function getOptionNumber($itemtype, $field)
{
$table = $itemtype::getTable();
$opts = &self::getOptions($itemtype);
foreach ($opts as $num => $opt) {
if (
is_array($opt) && isset($opt['table'])
&& ($opt['table'] == $table)
&& ($opt['field'] == $field)
) {
return $num;
}
}
return 0;
}
/**
* Get the SEARCH_OPTION array
*
* @param string $itemtype Item type
* @param boolean $withplugins Get search options from plugins (true by default)
*
* @return array The reference to the array of search options for the given item type
**/
public static function &getOptions($itemtype, $withplugins = true)
{
global $CFG_GLPI;
$item = null;
if (!isset(self::$search[$itemtype])) {
// standard type first
switch ($itemtype) {
case 'Internet':
self::$search[$itemtype]['common'] = __('Characteristics');
self::$search[$itemtype][1]['table'] = 'networkport_types';
self::$search[$itemtype][1]['field'] = 'name';
self::$search[$itemtype][1]['name'] = __('Name');
self::$search[$itemtype][1]['datatype'] = 'itemlink';
self::$search[$itemtype][1]['searchtype'] = 'contains';
self::$search[$itemtype][2]['table'] = 'networkport_types';
self::$search[$itemtype][2]['field'] = 'id';
self::$search[$itemtype][2]['name'] = __('ID');
self::$search[$itemtype][2]['searchtype'] = 'contains';
self::$search[$itemtype][31]['table'] = 'glpi_states';
self::$search[$itemtype][31]['field'] = 'completename';
self::$search[$itemtype][31]['name'] = __('Status');
self::$search[$itemtype] += NetworkPort::getSearchOptionsToAdd('networkport_types');
break;
case AllAssets::getType():
self::$search[$itemtype]['common'] = __('Characteristics');
self::$search[$itemtype][1]['table'] = 'asset_types';
self::$search[$itemtype][1]['field'] = 'name';
self::$search[$itemtype][1]['name'] = __('Name');
self::$search[$itemtype][1]['datatype'] = 'itemlink';
self::$search[$itemtype][1]['searchtype'] = 'contains';
self::$search[$itemtype][2]['table'] = 'asset_types';
self::$search[$itemtype][2]['field'] = 'id';
self::$search[$itemtype][2]['name'] = __('ID');
self::$search[$itemtype][2]['searchtype'] = 'contains';
self::$search[$itemtype][31]['table'] = 'glpi_states';
self::$search[$itemtype][31]['field'] = 'completename';
self::$search[$itemtype][31]['name'] = __('Status');
self::$search[$itemtype] += Location::getSearchOptionsToAdd();
self::$search[$itemtype][5]['table'] = 'asset_types';
self::$search[$itemtype][5]['field'] = 'serial';
self::$search[$itemtype][5]['name'] = __('Serial number');
self::$search[$itemtype][6]['table'] = 'asset_types';
self::$search[$itemtype][6]['field'] = 'otherserial';
self::$search[$itemtype][6]['name'] = __('Inventory number');
self::$search[$itemtype][16]['table'] = 'asset_types';
self::$search[$itemtype][16]['field'] = 'comment';
self::$search[$itemtype][16]['name'] = __('Comments');
self::$search[$itemtype][16]['datatype'] = 'text';
self::$search[$itemtype][70]['table'] = 'glpi_users';
self::$search[$itemtype][70]['field'] = 'name';
self::$search[$itemtype][70]['name'] = User::getTypeName(1);
self::$search[$itemtype][7]['table'] = 'asset_types';
self::$search[$itemtype][7]['field'] = 'contact';
self::$search[$itemtype][7]['name'] = __('Alternate username');
self::$search[$itemtype][7]['datatype'] = 'string';
self::$search[$itemtype][8]['table'] = 'asset_types';
self::$search[$itemtype][8]['field'] = 'contact_num';
self::$search[$itemtype][8]['name'] = __('Alternate username number');
self::$search[$itemtype][8]['datatype'] = 'string';
self::$search[$itemtype][71]['table'] = 'glpi_groups';
self::$search[$itemtype][71]['field'] = 'completename';
self::$search[$itemtype][71]['name'] = Group::getTypeName(1);
self::$search[$itemtype][19]['table'] = 'asset_types';
self::$search[$itemtype][19]['field'] = 'date_mod';
self::$search[$itemtype][19]['name'] = __('Last update');
self::$search[$itemtype][19]['datatype'] = 'datetime';
self::$search[$itemtype][19]['massiveaction'] = false;
self::$search[$itemtype][23]['table'] = 'glpi_manufacturers';
self::$search[$itemtype][23]['field'] = 'name';
self::$search[$itemtype][23]['name'] = Manufacturer::getTypeName(1);
self::$search[$itemtype][24]['table'] = 'glpi_users';
self::$search[$itemtype][24]['field'] = 'name';
self::$search[$itemtype][24]['linkfield'] = 'users_id_tech';
self::$search[$itemtype][24]['name'] = __('Technician in charge of the hardware');
self::$search[$itemtype][24]['condition'] = ['is_assign' => 1];
self::$search[$itemtype][49]['table'] = 'glpi_groups';
self::$search[$itemtype][49]['field'] = 'completename';
self::$search[$itemtype][49]['linkfield'] = 'groups_id_tech';
self::$search[$itemtype][49]['name'] = __('Group in charge of the hardware');
self::$search[$itemtype][49]['condition'] = ['is_assign' => 1];
self::$search[$itemtype][49]['datatype'] = 'dropdown';
self::$search[$itemtype][80]['table'] = 'glpi_entities';
self::$search[$itemtype][80]['field'] = 'completename';
self::$search[$itemtype][80]['name'] = Entity::getTypeName(1);
break;
default:
if ($item = getItemForItemtype($itemtype)) {
self::$search[$itemtype] = $item->searchOptions();
}
break;
}
if (
Session::getLoginUserID()
&& in_array($itemtype, $CFG_GLPI["ticket_types"])
) {
self::$search[$itemtype]['tracking'] = __('Assistance');
self::$search[$itemtype][60]['table'] = 'glpi_tickets';
self::$search[$itemtype][60]['field'] = 'id';
self::$search[$itemtype][60]['datatype'] = 'count';
self::$search[$itemtype][60]['name'] = _x('quantity', 'Number of tickets');
self::$search[$itemtype][60]['forcegroupby'] = true;
self::$search[$itemtype][60]['usehaving'] = true;
self::$search[$itemtype][60]['massiveaction'] = false;
self::$search[$itemtype][60]['joinparams'] = ['beforejoin'
=> ['table'
=> 'glpi_items_tickets',
'joinparams'
=> ['jointype'
=> 'itemtype_item'
]
],
'condition'
=> getEntitiesRestrictRequest(
'AND',
'NEWTABLE'
)
];
self::$search[$itemtype][140]['table'] = 'glpi_problems';
self::$search[$itemtype][140]['field'] = 'id';
self::$search[$itemtype][140]['datatype'] = 'count';
self::$search[$itemtype][140]['name'] = _x('quantity', 'Number of problems');
self::$search[$itemtype][140]['forcegroupby'] = true;
self::$search[$itemtype][140]['usehaving'] = true;
self::$search[$itemtype][140]['massiveaction'] = false;
self::$search[$itemtype][140]['joinparams'] = ['beforejoin'
=> ['table'
=> 'glpi_items_problems',
'joinparams'
=> ['jointype'
=> 'itemtype_item'
]
],
'condition'
=> getEntitiesRestrictRequest(
'AND',
'NEWTABLE'
)
];
}
if (
in_array($itemtype, $CFG_GLPI["networkport_types"])
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += NetworkPort::getSearchOptionsToAdd($itemtype);
}
if (
in_array($itemtype, $CFG_GLPI["contract_types"])
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += Contract::getSearchOptionsToAdd();
}
if (
Document::canApplyOn($itemtype)
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += Document::getSearchOptionsToAdd();
}
if (
Infocom::canApplyOn($itemtype)
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += Infocom::getSearchOptionsToAdd($itemtype);
}
if (
in_array($itemtype, $CFG_GLPI["domain_types"])
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += Domain::getSearchOptionsToAdd($itemtype);
}
if (
in_array($itemtype, $CFG_GLPI["appliance_types"])
|| ($itemtype == AllAssets::getType())
) {
self::$search[$itemtype] += Appliance::getSearchOptionsToAdd($itemtype);
}
if (in_array($itemtype, $CFG_GLPI["link_types"])) {
self::$search[$itemtype]['link'] = Link::getTypeName(Session::getPluralNumber());
self::$search[$itemtype] += Link::getSearchOptionsToAdd($itemtype);
self::$search[$itemtype]['manuallink'] = ManualLink::getTypeName(Session::getPluralNumber());
self::$search[$itemtype] += ManualLink::getSearchOptionsToAdd($itemtype);
}
if ($withplugins) {
// Search options added by plugins
$plugsearch = Plugin::getAddSearchOptions($itemtype);
$plugsearch = $plugsearch + Plugin::getAddSearchOptionsNew($itemtype);
if (count($plugsearch)) {
self::$search[$itemtype] += ['plugins' => _n('Plugin', 'Plugins', Session::getPluralNumber())];
self::$search[$itemtype] += $plugsearch;
}
}
// Complete linkfield if not define
if (is_null($item)) { // Special union type
$itemtable = $CFG_GLPI['union_search_type'][$itemtype];
} else {
if ($item = getItemForItemtype($itemtype)) {
$itemtable = $item->getTable();
}
}
foreach (self::$search[$itemtype] as $key => $val) {
if (!is_array($val) || count($val) == 1) {
// skip sub-menu
continue;
}
// Compatibility before 0.80 : Force massive action to false if linkfield is empty :
if (isset($val['linkfield']) && empty($val['linkfield'])) {
self::$search[$itemtype][$key]['massiveaction'] = false;
}
// Set default linkfield
if (!isset($val['linkfield']) || empty($val['linkfield'])) {
if (
(strcmp($itemtable, $val['table']) == 0)
&& (!isset($val['joinparams']) || (count($val['joinparams']) == 0))
) {
self::$search[$itemtype][$key]['linkfield'] = $val['field'];
} else {
self::$search[$itemtype][$key]['linkfield'] = getForeignKeyFieldForTable($val['table']);
}
}
// Add default joinparams
if (!isset($val['joinparams'])) {
self::$search[$itemtype][$key]['joinparams'] = [];
}
}
}
return self::$search[$itemtype];
}
/**
* Is the search item related to infocoms
*
* @param string $itemtype Item type
* @param integer $searchID ID of the element in $SEARCHOPTION
*
* @return boolean
**/
public static function isInfocomOption($itemtype, $searchID)
{
if (!Infocom::canApplyOn($itemtype)) {
return false;
}
$infocom_options = Infocom::rawSearchOptionsToAdd($itemtype);
$found_infocoms = array_filter($infocom_options, function ($option) use ($searchID) {
return isset($option['id']) && $searchID == $option['id'];
});
return (count($found_infocoms) > 0);
}
/**
* @param string $itemtype
* @param integer $field_num
**/
public static function getActionsFor($itemtype, $field_num)
{
$searchopt = &self::getOptions($itemtype);
$actions = [
'contains' => __('contains'),
'notcontains' => __('not contains'),
'searchopt' => []
];
if (isset($searchopt[$field_num]) && isset($searchopt[$field_num]['table'])) {
$actions['searchopt'] = $searchopt[$field_num];
// Force search type
if (isset($actions['searchopt']['searchtype'])) {
// Reset search option
$actions = [];
$actions['searchopt'] = $searchopt[$field_num];
if (!is_array($actions['searchopt']['searchtype'])) {
$actions['searchopt']['searchtype'] = [$actions['searchopt']['searchtype']];
}
foreach ($actions['searchopt']['searchtype'] as $searchtype) {
switch ($searchtype) {
case "equals":
$actions['equals'] = __('is');
break;
case "notequals":
$actions['notequals'] = __('is not');
break;
case "contains":
$actions['contains'] = __('contains');
$actions['notcontains'] = __('not contains');
break;
case "notcontains":
$actions['notcontains'] = __('not contains');
break;
case "under":
$actions['under'] = __('under');
break;
case "notunder":
$actions['notunder'] = __('not under');
break;
case "lessthan":
$actions['lessthan'] = __('before');
break;
case "morethan":
$actions['morethan'] = __('after');
break;
}
}
return $actions;
}
if (isset($searchopt[$field_num]['datatype'])) {
switch ($searchopt[$field_num]['datatype']) {
case 'mio':
case 'count':
case 'number':
$opt = [
'contains' => __('contains'),
'notcontains' => __('not contains'),
'equals' => __('is'),
'notequals' => __('is not'),
'searchopt' => $searchopt[$field_num]
];
// No is / isnot if no limits defined
if (
!isset($searchopt[$field_num]['min'])
&& !isset($searchopt[$field_num]['max'])
) {
unset($opt['equals']);
unset($opt['notequals']);
// https://github.com/glpi-project/glpi/issues/6917
// change filter wording for numeric values to be more
// obvious if the number dropdown will not be used
$opt['contains'] = __('is');
$opt['notcontains'] = __('is not');
}
return $opt;
case 'bool':
return [
'equals' => __('is'),
'notequals' => __('is not'),
'contains' => __('contains'),
'notcontains' => __('not contains'),
'searchopt' => $searchopt[$field_num]
];
case 'right':
return ['equals' => __('is'),
'notequals' => __('is not'),
'searchopt' => $searchopt[$field_num]
];
case 'itemtypename':
return ['equals' => __('is'),
'notequals' => __('is not'),
'searchopt' => $searchopt[$field_num]
];
case 'date':
case 'datetime':
case 'date_delay':
return [
'equals' => __('is'),
'notequals' => __('is not'),
'lessthan' => __('before'),
'morethan' => __('after'),
'contains' => __('contains'),
'notcontains' => __('not contains'),
'searchopt' => $searchopt[$field_num]
];
}
}
// switch ($searchopt[$field_num]['table']) {
// case 'glpi_users_validation' :
// return array('equals' => __('is'),
// 'notequals' => __('is not'),
// 'searchopt' => $searchopt[$field_num]);
// }
switch ($searchopt[$field_num]['field']) {
case 'id':
return ['equals' => __('is'),
'notequals' => __('is not'),
'searchopt' => $searchopt[$field_num]
];
case 'name':
case 'completename':
$actions = [
'contains' => __('contains'),
'notcontains' => __('not contains'),
'equals' => __('is'),
'notequals' => __('is not'),
'searchopt' => $searchopt[$field_num]
];
// Specific case of TreeDropdown : add under
$itemtype_linked = getItemTypeForTable($searchopt[$field_num]['table']);
if ($itemlinked = getItemForItemtype($itemtype_linked)) {
if ($itemlinked instanceof CommonTreeDropdown) {
$actions['under'] = __('under');
$actions['notunder'] = __('not under');
}
return $actions;
}
}
}
return $actions;
}
/**
* Print generic Header Column
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param string $value Value to display
* @param integer &$num Column number
* @param string $linkto Link display element (HTML specific) (default '')
* @param boolean|integer $issort Is the sort column ? (default 0)
* @param string $order Order type ASC or DESC (defaut '')
* @param string $options Options to add (default '')
*
* @return string HTML to display
**/
public static function showHeaderItem(
$type,
$value,
&$num,
$linkto = "",
$issort = 0,
$order = "",
$options = ""
) {
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE:
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$PDF_TABLE .= "<th $options>";
$PDF_TABLE .= htmlspecialchars($value);
$PDF_TABLE .= "</th>";
break;
case self::SYLK_OUTPUT: //sylk
global $SYLK_HEADER,$SYLK_SIZE;
$SYLK_HEADER[$num] = self::sylk_clean($value);
$SYLK_SIZE[$num] = Toolbox::strlen($SYLK_HEADER[$num]);
break;
case self::CSV_OUTPUT: //CSV
$out = "\"" . self::csv_clean($value) . "\"" . $_SESSION["glpicsv_delimiter"];
break;
case self::NAMES_OUTPUT:
$out = "";
break;
default:
$class = "";
if ($issort) {
$class = "order_$order";
}
$out = "<th $options class='$class'>";
if (!empty($linkto)) {
$out .= "<a href=\"$linkto\">";
}
$out .= $value;
if (!empty($linkto)) {
$out .= "</a>";
}
$out .= "</th>\n";
}
$num++;
return $out;
}
/**
* Print generic normal Item Cell
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param string $value Value to display
* @param integer &$num Column number
* @param integer $row Row number
* @param string $extraparam Extra parameters for display (default '')
*
* @return string HTML to display
**/
public static function showItem($type, $value, &$num, $row, $extraparam = '')
{
$out = "";
// Handle null values
if ($value === null) {
$value = '';
}
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$value = DataExport::normalizeValueForTextExport($value ?? '');
$value = htmlspecialchars($value);
$value = preg_replace('/' . self::LBBR . '/', '<br>', $value);
$value = preg_replace('/' . self::LBHR . '/', '<hr>', $value);
$PDF_TABLE .= "<td $extraparam valign='top'>";
$PDF_TABLE .= $value;
$PDF_TABLE .= "</td>";
break;
case self::SYLK_OUTPUT: //sylk
global $SYLK_ARRAY,$SYLK_SIZE;
$value = DataExport::normalizeValueForTextExport($value);
$value = preg_replace('/' . self::LBBR . '/', '<br>', $value);
$value = preg_replace('/' . self::LBHR . '/', '<hr>', $value);
$SYLK_ARRAY[$row][$num] = self::sylk_clean($value);
$SYLK_SIZE[$num] = max(
$SYLK_SIZE[$num],
Toolbox::strlen($SYLK_ARRAY[$row][$num])
);
break;
case self::CSV_OUTPUT: //csv
$value = DataExport::normalizeValueForTextExport($value);
$value = preg_replace('/' . self::LBBR . '/', '<br>', $value);
$value = preg_replace('/' . self::LBHR . '/', '<hr>', $value);
$out = "\"" . self::csv_clean($value) . "\"" . $_SESSION["glpicsv_delimiter"];
break;
case self::NAMES_OUTPUT:
// We only want to display one column (the name of the item).
// The name field is always the first column expect for tickets
// which have their ids as the first column instead, thus moving the
// name to the second column.
// We don't have access to the itemtype so we must rely on data
// types to figure which column to use :
// - Ticket will have a numeric first column (id) and an HTML
// link containing the name as the second column.
// - Other items will have an HTML link containing the name as
// the first column and a simple string containing the entity
// name as the second column.
// -> We can check that the column is the first or second AND is html
if (
strip_tags($value) !== $value
&& ($num == 1 || $num == 2)
) {
// Use a regex to keep only the link, there may be other content
// after that we don't need (script, tooltips, ...)
if (preg_match('/<a.*<\/a>/', $value, $matches)) {
$out = html_entity_decode(strip_tags($matches[0]));
}
}
break;
default:
global $CFG_GLPI;
$out = "<td $extraparam valign='top'>";
if (!preg_match('/' . self::LBHR . '/', $value)) {
$values = preg_split('/' . self::LBBR . '/i', $value);
$line_delimiter = '<br>';
} else {
$values = preg_split('/' . self::LBHR . '/i', $value);
$line_delimiter = '<hr>';
}
if (
count($values) > 1
&& Toolbox::strlen($value) > $CFG_GLPI['cut']
) {
$value = '';
foreach ($values as $v) {
$value .= $v . $line_delimiter;
}
$value = preg_replace('/' . self::LBBR . '/', '<br>', $value);
$value = preg_replace('/' . self::LBHR . '/', '<hr>', $value);
$value = '<div class="fup-popup">' . $value . '</div>';
$valTip = " " . Html::showToolTip(
$value,
[
'awesome-class' => 'fa-comments',
'display' => false,
'autoclose' => false,
'onclick' => true
]
);
$out .= $values[0] . $valTip;
} else {
$value = preg_replace('/' . self::LBBR . '/', '<br>', $value);
$value = preg_replace('/' . self::LBHR . '/', '<hr>', $value);
$out .= $value;
}
$out .= "</td>\n";
}
$num++;
return $out;
}
/**
* Print generic error
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param string $message Message to display, if empty "no item found" will be displayed
*
* @return string HTML to display
**/
public static function showError($type, $message = "")
{
if (strlen($message) == 0) {
$message = __('No item found');
}
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
case self::SYLK_OUTPUT: //sylk
case self::CSV_OUTPUT: //csv
break;
default:
$out = "<div class='center b'>$message</div>\n";
}
return $out;
}
/**
* Print generic footer
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param string $title title of file : used for PDF (default '')
* @param integer $count Total number of results
*
* @return string HTML to display
**/
public static function showFooter($type, $title = "", $count = null)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$font = 'helvetica';
$fontsize = 8;
if (isset($_SESSION['glpipdffont']) && $_SESSION['glpipdffont']) {
$font = $_SESSION['glpipdffont'];
}
$pdf = new GLPIPDF(
[
'font_size' => $fontsize,
'font' => $font,
'orientation' => $type == self::PDF_OUTPUT_LANDSCAPE ? 'L' : 'P',
],
$count,
$title,
);
$PDF_TABLE .= '</table>';
$pdf->writeHTML($PDF_TABLE, true, false, true);
$pdf->Output('glpi.pdf', 'I');
break;
case self::SYLK_OUTPUT: //sylk
global $SYLK_HEADER,$SYLK_ARRAY,$SYLK_SIZE;
// largeurs des colonnes
foreach ($SYLK_SIZE as $num => $val) {
$out .= "F;W" . $num . " " . $num . " " . min(50, $val) . "\n";
}
$out .= "\n";
// Header
foreach ($SYLK_HEADER as $num => $val) {
$out .= "F;SDM4;FG0C;" . ($num == 1 ? "Y1;" : "") . "X$num\n";
$out .= "C;N;K\"$val\"\n";
$out .= "\n";
}
// Datas
foreach ($SYLK_ARRAY as $row => $tab) {
foreach ($tab as $num => $val) {
$out .= "F;P3;FG0L;" . ($num == 1 ? "Y" . $row . ";" : "") . "X$num\n";
$out .= "C;N;K\"$val\"\n";
}
}
$out .= "E\n";
break;
case self::CSV_OUTPUT: //csv
case self::NAMES_OUTPUT:
break;
default:
$out = "</table></div>\n";
}
return $out;
}
/**
* Print generic footer
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param integer $rows Number of rows
* @param integer $cols Number of columns
* @param boolean|integer $fixed Used tab_cadre_fixe table for HTML export ? (default 0)
*
* @return string HTML to display
**/
public static function showHeader($type, $rows, $cols, $fixed = 0)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$PDF_TABLE = "<table cellspacing=\"0\" cellpadding=\"1\" border=\"1\" >";
break;
case self::SYLK_OUTPUT: // Sylk
global $SYLK_ARRAY, $SYLK_HEADER, $SYLK_SIZE;
$SYLK_ARRAY = [];
$SYLK_HEADER = [];
$SYLK_SIZE = [];
// entetes HTTP
header("Expires: Mon, 26 Nov 1962 00:00:00 GMT");
header('Pragma: private'); /// IE BUG + SSL
header('Cache-control: private, must-revalidate'); /// IE BUG + SSL
header("Content-disposition: filename=glpi.slk");
header('Content-type: application/octetstream');
// entete du fichier
echo "ID;PGLPI_EXPORT\n"; // ID;Pappli
echo "\n";
// formats
echo "P;PGeneral\n";
echo "P;P#,##0.00\n"; // P;Pformat_1 (reels)
echo "P;P#,##0\n"; // P;Pformat_2 (entiers)
echo "P;P@\n"; // P;Pformat_3 (textes)
echo "\n";
// polices
echo "P;EArial;M200\n";
echo "P;EArial;M200\n";
echo "P;EArial;M200\n";
echo "P;FArial;M200;SB\n";
echo "\n";
// nb lignes * nb colonnes
echo "B;Y" . $rows;
echo ";X" . $cols . "\n"; // B;Yligmax;Xcolmax
echo "\n";
break;
case self::CSV_OUTPUT: // csv
header("Expires: Mon, 26 Nov 1962 00:00:00 GMT");
header('Pragma: private'); /// IE BUG + SSL
header('Cache-control: private, must-revalidate'); /// IE BUG + SSL
header("Content-disposition: filename=glpi.csv");
header('Content-type: text/csv');
// zero width no break space (for excel)
echo"\xEF\xBB\xBF";
break;
case self::NAMES_OUTPUT:
header("Content-disposition: filename=glpi.txt");
header('Content-type: file/txt');
break;
default:
if ($fixed) {
$out = "<div class='center'><table border='0' class='table'>";
} else {
$out = "<div class='center'><table border='0' class='table card-table table-hover'>";
}
}
return $out;
}
/**
* Print begin of header part
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
*
* @since 0.85
*
* @return string HTML to display
**/
public static function showBeginHeader($type)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$PDF_TABLE .= "<thead>";
break;
case self::SYLK_OUTPUT: //sylk
case self::CSV_OUTPUT: //csv
case self::NAMES_OUTPUT:
break;
default:
$out = "<thead>";
}
return $out;
}
/**
* Print end of header part
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
*
* @since 0.85
*
* @return string to display
**/
public static function showEndHeader($type)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$PDF_TABLE .= "</thead>";
break;
case self::SYLK_OUTPUT: //sylk
case self::CSV_OUTPUT: //csv
case self::NAMES_OUTPUT:
break;
default:
$out = "</thead>";
}
return $out;
}
/**
* Print generic new line
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
* @param boolean $odd Is it a new odd line ? (false by default)
* @param boolean $is_deleted Is it a deleted search ? (false by default)
*
* @return string HTML to display
**/
public static function showNewLine($type, $odd = false, $is_deleted = false)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$style = "";
if ($odd) {
$style = " style=\"background-color:#DDDDDD;\" ";
}
$PDF_TABLE .= "<tr $style nobr=\"true\">";
break;
case self::SYLK_OUTPUT: //sylk
case self::CSV_OUTPUT: //csv
case self::NAMES_OUTPUT:
break;
default:
$class = " class='tab_bg_2" . ($is_deleted ? '_2' : '') . "' ";
if ($odd) {
$class = " class='tab_bg_1" . ($is_deleted ? '_2' : '') . "' ";
}
$out = "<tr $class>";
}
return $out;
}
/**
* Print generic end line
*
* @param integer $type Display type (0=HTML, 1=Sylk, 2=PDF, 3=CSV)
*
* @return string HTML to display
**/
public static function showEndLine($type, bool $is_header_line = false)
{
$out = "";
switch ($type) {
case self::PDF_OUTPUT_LANDSCAPE: //pdf
case self::PDF_OUTPUT_PORTRAIT:
global $PDF_TABLE;
$PDF_TABLE .= '</tr>';
break;
case self::SYLK_OUTPUT: //sylk
break;
case self::CSV_OUTPUT: //csv
case self::NAMES_OUTPUT:
// NAMES_OUTPUT has no output on header lines
$newline = $type != self::NAMES_OUTPUT || !$is_header_line;
if ($newline) {
$out = "\n";
}
break;
default:
$out = "</tr>";
}
return $out;
}
/**
* @param array $joinparams
*/
public static function computeComplexJoinID(array $joinparams)
{
$complexjoin = '';
if (isset($joinparams['condition'])) {
if (!is_array($joinparams['condition'])) {
$complexjoin .= $joinparams['condition'];
} else {
global $DB;
$dbi = new DBmysqlIterator($DB);
$sql_clause = $dbi->analyseCrit($joinparams['condition']);
$complexjoin .= ' AND ' . $sql_clause; //TODO: and should came from conf
}
}
// For jointype == child
if (
isset($joinparams['jointype']) && ($joinparams['jointype'] == 'child')
&& isset($joinparams['linkfield'])
) {
$complexjoin .= $joinparams['linkfield'];
}
if (isset($joinparams['beforejoin'])) {
if (isset($joinparams['beforejoin']['table'])) {
$joinparams['beforejoin'] = [$joinparams['beforejoin']];
}
foreach ($joinparams['beforejoin'] as $tab) {
if (isset($tab['table'])) {
$complexjoin .= $tab['table'];
}
if (isset($tab['joinparams']) && isset($tab['joinparams']['condition'])) {
if (!is_array($tab['joinparams']['condition'])) {
$complexjoin .= $tab['joinparams']['condition'];
} else {
global $DB;
$dbi = new DBmysqlIterator($DB);
$sql_clause = $dbi->analyseCrit($tab['joinparams']['condition']);
$complexjoin .= ' AND ' . $sql_clause; //TODO: and should came from conf
}
}
}
}
if (!empty($complexjoin)) {
$complexjoin = md5($complexjoin);
}
return $complexjoin;
}
/**
* Clean display value for csv export
*
* @param string $value value
*
* @return string Clean value
**/
public static function csv_clean($value)
{
$value = str_replace("\"", "''", $value);
return $value;
}
/**
* Clean display value for sylk export
*
* @param string $value value
*
* @return string Clean value
**/
public static function sylk_clean($value)
{
$value = preg_replace('/\x0A/', ' ', $value);
$value = preg_replace('/\x0D/', '', $value);
$value = str_replace("\"", "''", $value);
$value = str_replace("\n", " | ", $value);
return $value;
}
/**
* Create SQL search condition
*
* @param string $field Nname (should be ` protected)
* @param string $val Value to search
* @param boolean $not Is a negative search ? (false by default)
* @param string $link With previous criteria (default 'AND')
*
* @return search SQL string
**/
public static function makeTextCriteria($field, $val, $not = false, $link = 'AND')
{
$sql = $field . self::makeTextSearch($val, $not);
// mange empty field (string with length = 0)
$sql_or = "";
if (strtolower($val) == "null") {
$sql_or = "OR $field = ''";
}
if (
($not && ($val != 'NULL') && ($val != 'null') && ($val != '^$')) // Not something
|| (!$not && ($val == '^$'))
) { // Empty
$sql = "($sql OR $field IS NULL)";
}
return " $link ($sql $sql_or)";
}
/**
* Create SQL search value
*
* @since 9.4
*
* @param string $val value to search
*
* @return string|null
**/
public static function makeTextSearchValue($val)
{
// `$val` will mostly comes from sanitized input, but may also be raw value.
// 1. Unsanitize value to be sure to use raw value.
// 2. Escape raw value to protect SQL special chars.
$val = Sanitizer::dbEscape(Sanitizer::unsanitize($val));
// escape _ char used as wildcard in mysql likes
$val = str_replace('_', '\\_', $val);
if ($val === 'NULL' || $val === 'null') {
return null;
}
$val = trim($val);
if ($val === '^') {
// Special case, searching "^" means we are searching for a non empty/null field
return '%';
}
if ($val === '' || $val === '^$' || $val === '$') {
return '';
}
if (preg_match('/^\^/', $val)) {
// Remove leading `^`
$val = ltrim(preg_replace('/^\^/', '', $val));
} else {
// Add % wildcard before searched string if not begining by a `^`
$val = '%' . $val;
}
if (preg_match('/\$$/', $val)) {
// Remove trailing `$`
$val = rtrim(preg_replace('/\$$/', '', $val));
} else {
// Add % wildcard after searched string if not ending by a `$`
$val = $val . '%';
}
return $val;
}
/**
* Create SQL search condition
*
* @param string $val Value to search
* @param boolean $not Is a negative search ? (false by default)
*
* @return string Search string
**/
public static function makeTextSearch($val, $not = false)
{
$NOT = "";
if ($not) {
$NOT = "NOT";
}
$val = self::makeTextSearchValue($val);
if ($val == null) {
$SEARCH = " IS $NOT NULL ";
} else {
$SEARCH = " $NOT LIKE " . DBmysql::quoteValue($val) . " ";
}
return $SEARCH;
}
/**
* @since 0.84
*
* @param string $pattern
* @param string $subject
**/
public static function explodeWithID($pattern, $subject)
{
$tab = explode($pattern, $subject);
if (isset($tab[1]) && !is_numeric($tab[1])) {
// Report $ to tab[0]
if (preg_match('/^(\\$*)(.*)/', $tab[1], $matchs)) {
if (isset($matchs[2]) && is_numeric($matchs[2])) {
$tab[1] = $matchs[2];
$tab[0] .= $matchs[1];
}
}
}
// Manage NULL value
if ($tab[0] == self::NULLVALUE) {
$tab[0] = null;
}
return $tab;
}
/**
* Add join for dropdown translations
*
* @param string $alias Alias for translation table
* @param string $table Table to join on
* @param class-string<CommonDBTM> $itemtype Item type
* @param string $field Field name
*
* @return string
*/
public static function joinDropdownTranslations($alias, $table, $itemtype, $field)
{
return "LEFT JOIN `glpi_dropdowntranslations` AS `$alias`
ON (`$alias`.`itemtype` = '$itemtype'
AND `$alias`.`items_id` = `$table`.`id`
AND `$alias`.`language` = '" .
$_SESSION['glpilanguage'] . "'
AND `$alias`.`field` = '$field')";
}
/**
* Get table name for item type
*
* @param class-string<CommonDBTM> $itemtype
*
* @return string
*/
public static function getOrigTableName(string $itemtype): string
{
return (is_a($itemtype, CommonDBTM::class, true)) ? $itemtype::getTable() : getTableForItemType($itemtype);
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists