%PDF- %PDF-
Direktori : /var/www/projetos/suporte.iigd.com.br/src/Dashboard/ |
Current File : /var/www/projetos/suporte.iigd.com.br/src/Dashboard/Provider.php |
<?php /** * --------------------------------------------------------------------- * * GLPI - Gestionnaire Libre de Parc Informatique * * http://glpi-project.org * * @copyright 2015-2024 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/>. * * --------------------------------------------------------------------- */ namespace Glpi\Dashboard; use CommonDBTM; use CommonDBVisible; use CommonITILActor; use CommonITILObject; use CommonITILValidation; use CommonTreeDropdown; use CommonDevice; use Config; use DBConnection; use Glpi\Dashboard\Filters\{ DatesFilter, GroupTechFilter, UserTechFilter, }; use Group; use Group_Ticket; use Profile_User; use QueryExpression; use QuerySubQuery; use Session; use Stat; use Ticket; use Ticket_User; use Toolbox; use User; use Search; /** * Provider class **/ class Provider { /** * Retrieve the number of element for a given item * * @param CommonDBTM|null object to count * * @param array $params default values for * - 'apply_filters' values from dashboard filters * * @return array : * - 'number' * - 'url' * - 'label' * - 'icon' */ public static function bigNumberItem(CommonDBTM $item = null, array $params = []): array { $DB = DBConnection::getReadConnection(); $default_params = [ 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $i_table = $item::getTable(); $where = []; if (isset($item->fields['is_deleted'])) { $where['is_deleted'] = 0; } if (isset($item->fields['is_template'])) { $where['is_template'] = 0; } if ($item instanceof User) { $where += getEntitiesRestrictCriteria(Profile_User::getTable()); $request = [ 'SELECT' => ['COUNT DISTINCT' => $item::getTableField($item::getIndexName()) . ' as cpt'], 'FROM' => $i_table, 'INNER JOIN' => [ Profile_User::getTable() => [ 'FKEY' => [ Profile_User::getTable() => 'users_id', User::getTable() => 'id', ] ] ] , 'WHERE' => $where ]; } else { if ($item->isEntityAssign()) { $where += getEntitiesRestrictCriteria($item::getTable()); } $request = [ 'SELECT' => ['COUNT DISTINCT' => $item::getTableField($item::getIndexName()) . ' as cpt'], 'FROM' => $i_table, 'WHERE' => $where ]; } $criteria = array_merge_recursive( $request, self::getFiltersCriteria($i_table, $params['apply_filters']), $item instanceof Ticket ? Ticket::getCriteriaFromProfile() : [] ); $iterator = $DB->request($criteria); $result = $iterator->current(); $nb_items = $result['cpt']; $search_criteria = self::getSearchFiltersCriteria($i_table, $params['apply_filters'], true)['criteria'] ?? []; $search_url = $item::getSearchURL(); $url = $search_url . (str_contains($search_url, '?') ? '&' : '?') . Toolbox::append_params([ 'criteria' => $search_criteria, 'reset' => 'reset', ]); return [ 'number' => $nb_items, 'url' => $url, 'label' => $item::getTypeName($nb_items), 'icon' => $item::getIcon(), ]; } /** * @method self::bigNumberItem * @method self::nbItemByFk */ public static function __callStatic(string $name = "", array $arguments = []) { if (strpos($name, 'bigNumber') !== false) { $itemtype = str_replace('bigNumber', '', $name); if (is_subclass_of($itemtype, 'CommonDBTM')) { $item = new $itemtype(); $item->getEmpty(); return self::bigNumberItem($item, $arguments[0] ?? []); } } if ( strpos($name, 'multipleNumber') !== false && strpos($name, 'By') !== false ) { $tmp = str_replace('multipleNumber', '', $name); $tmp = explode('By', $tmp); if (count($tmp) === 2) { $itemtype = $tmp[0]; $fk_itemtype = $tmp[1]; return self::nbItemByFk( new $itemtype(), new $fk_itemtype(), $arguments[0] ?? [] ); } } if (strpos($name, 'getArticleList') !== false) { $itemtype = str_replace('getArticleList', '', $name); if (is_subclass_of($itemtype, 'CommonDBTM')) { $item = new $itemtype(); $item->getEmpty(); return self::articleListItem($item, $arguments[0] ?? []); } } } /** * Count number of tickets for a given case * * @param string $case: * - 'notold': not closed or solved tickets * - 'late': late tickets * - 'waiting_validation': tickets waiting validation for connected user * - 'incoming': ticket with incoming status * - 'waiting': ticket with waiting status * - 'assigned': ticket with assigned status * - 'planned': ticket with planned status * - 'solved': ticket with solved status * - 'closed': ticket with closed status * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array : * - 'number' * - 'url' * - 'label' * - 'icon' */ public static function nbTicketsGeneric( string $case = "", array $params = [] ): array { $DBread = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => Ticket::getIcon(), 'apply_filters' => [], 'validation_check_user' => false, ]; $params = array_merge($default_params, $params); $nb_tickets = 0; $query_criteria = []; $search_criteria = []; $skip = false; $notold = [ 'field' => 12, 'searchtype' => 'equals', 'value' => 'notold', ]; $table = Ticket::getTable(); $query_criteria = [ 'SELECT' => [ 'COUNT DISTINCT' => "$table.id AS cpt", ], 'FROM' => $table, 'WHERE' => [ "$table.is_deleted" => 0, ] + getEntitiesRestrictCriteria($table), ]; $query_criteria = array_merge_recursive( $query_criteria, Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($table, $params['apply_filters']) ); switch ($case) { case 'notold': $search_criteria = [$notold]; $params['label'] = _x('status', 'Not solved'); $query_criteria['WHERE'] += [ "$table.status" => Ticket::getNotSolvedStatusArray(), ]; break; case 'late': $params['icon'] = "far fa-clock"; $params['label'] = __("Late tickets"); $search_criteria = [ $notold, [ 'link' => 'AND', 'criteria' => [ [ 'field' => 82, 'searchtype' => 'equals', 'value' => 1, ], [ 'link' => 'OR', 'field' => 182, 'searchtype' => 'equals', 'value' => 1, ], [ 'link' => 'OR', 'field' => 159, 'searchtype' => 'equals', 'value' => 1, ], [ 'link' => 'OR', 'field' => 187, 'searchtype' => 'equals', 'value' => 1, ] ] ] ]; $query_criteria['WHERE']["$table.status"] = Ticket::getNotSolvedStatusArray(); $query_criteria['WHERE'][] = [ 'OR' => [ CommonITILObject::generateSLAOLAComputation('time_to_resolve', 'glpi_tickets'), CommonITILObject::generateSLAOLAComputation('internal_time_to_resolve', 'glpi_tickets'), CommonITILObject::generateSLAOLAComputation('time_to_own', 'glpi_tickets'), CommonITILObject::generateSLAOLAComputation('internal_time_to_own', 'glpi_tickets'), ] ]; break; case 'waiting_validation': $params['icon'] = "far fa-eye"; $params['label'] = __("Tickets waiting for validation"); $search_criteria = [ [ 'field' => 55, 'searchtype' => 'equals', 'value' => CommonITILValidation::WAITING, ] ]; if ($params['validation_check_user']) { $search_criteria[] = [ 'link' => 'AND', 'field' => 59, 'searchtype' => 'equals', 'value' => Session::getLoginUserID(), ]; } $where = [ 'glpi_ticketvalidations.status' => CommonITILValidation::WAITING, ]; if ($params['validation_check_user']) { $where['glpi_ticketvalidations.users_id_validate'] = Session::getLoginUserID(); } $query_criteria = array_merge_recursive($query_criteria, [ 'LEFT JOIN' => [ 'glpi_ticketvalidations' => [ 'ON' => [ 'glpi_ticketvalidations' => 'tickets_id', $table => 'id' ] ] ], 'WHERE' => $where, ]); break; // Statuses speciale cases (no break) case 'incoming': $status = Ticket::INCOMING; $params['icon'] = Ticket::getIcon(); $params['label'] = __("Incoming tickets"); $skip = true; //no break case 'waiting': if (!$skip) { $status = Ticket::WAITING; $params['icon'] = "fas fa-pause-circle"; $params['label'] = __("Pending tickets"); $skip = true; } //no break case 'assigned': if (!$skip) { $status = Ticket::ASSIGNED; $params['icon'] = "fas fa-users"; $params['label'] = __("Assigned tickets"); $skip = true; } //no break case 'planned': if (!$skip) { $status = Ticket::PLANNED; $params['icon'] = "fas fa-calendar-check"; $params['label'] = __("Planned tickets"); $skip = true; } //no break case 'solved': if (!$skip) { $status = Ticket::SOLVED; $params['icon'] = "far fa-check-square"; $params['label'] = __("Solved tickets"); $skip = true; } //no break case 'closed': if (!$skip) { $status = Ticket::CLOSED; $params['icon'] = "fas fa-archive"; $params['label'] = __("Closed tickets"); $skip = true; } //no break case 'status': if (!$skip) { $status = Ticket::INCOMING; } $search_criteria = [ [ 'field' => 12, 'searchtype' => 'equals', 'value' => $status, ] ]; $query_criteria = array_merge_recursive($query_criteria, [ 'WHERE' => [ "$table.status" => $status, ] ]); break; } $filter_criteria = self::getSearchFiltersCriteria($table, $params['apply_filters'], count($search_criteria) === 0); $search_criteria = array_merge( $search_criteria, $filter_criteria['criteria'] ?? [], ); $url = Ticket::getSearchURL() . "?" . Toolbox::append_params([ 'criteria' => $search_criteria, 'reset' => 'reset' ]); $iterator = $DBread->request($query_criteria); $result = $iterator->current(); $nb_tickets = $result['cpt']; return [ 'number' => $nb_tickets, 'url' => $url, 'label' => $params['label'], 'icon' => $params['icon'], 's_criteria' => $search_criteria, 'itemtype' => 'Ticket', ]; } public static function nbTicketsByAgreementStatusAndTechnician( array $params = [] ): array { $DBread = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => 'fas fa-stopwatch', 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $query_criteria = []; $table = Ticket::getTable(); $ticketUserTable = Ticket_User::getTable(); $userTable = User::getTable(); $ownExceeded = Ticket::generateSLAOLAComputation('time_to_own', $table); $resolveExceeded = Ticket::generateSLAOLAComputation('time_to_resolve', $table); $slaState = "IF ($ownExceeded AND $resolveExceeded, 3, IF ($resolveExceeded, 2, IF ($ownExceeded, 1, 0)))"; $config = Config::getConfigurationValues('core'); if ($config['names_format'] == User::FIRSTNAME_BEFORE) { $first = "firstname"; $second = "realname"; } else { $first = "realname"; $second = "firstname"; } $friendlyName = "CONCAT(`$userTable`.$first, ' ', `$userTable`.$second)"; $query_criteria = [ 'COUNT' => 'cpt', 'SELECT' => [ new QueryExpression("$friendlyName as `username`"), "$userTable.name", new QueryExpression("$slaState as `sla_state`"), ], 'FROM' => $table, 'INNER JOIN' => [ "$ticketUserTable as ul" => [ 'FKEY' => [ 'ul' => 'tickets_id', $table => 'id', [ 'AND' => [ "ul.type" => Ticket_User::ASSIGN ] ] ], ], $userTable => [ 'FKEY' => [ $userTable => 'id', 'ul' => 'users_id', ], ], ], 'WHERE' => [ "$table.is_deleted" => 0, ] + getEntitiesRestrictCriteria($table), 'GROUPBY' => [ 'sla_state', "$userTable.id", ], 'ORDER' => [ 'sla_state DESC', 'cpt DESC', "$userTable.id", ] ]; unset($params['apply_filters'][GroupTechFilter::getId()]); $query_filter = self::getFiltersCriteria($table, $params['apply_filters']); unset($query_filter['LEFT JOIN']["$ticketUserTable as ul"]); $query_criteria = array_merge_recursive( $query_criteria, Ticket::getCriteriaFromProfile(), $query_filter ); $allLate = []; $resolveLate = []; $ownLate = []; $onTime = []; $names = []; $data = []; // Get data and sort by is_late status $iterator = $DBread->request($query_criteria); foreach ($iterator as $row) { switch ($row['sla_state']) { case 3: // Own and resolve are both late $allLate[$row['name']] = $row['cpt']; break; case 2: // Resolve is late $resolveLate[$row['name']] = $row['cpt']; break; case 1: // own is late $ownLate[$row['name']] = $row['cpt']; break; case 0: $onTime[$row['name']] = $row['cpt']; } $names[$row['name']] = $row['username']; } // set legend for each serie $data['series'][0]['name'] = __('Late own and resolve'); $data['series'][1]['name'] = __('Late resolve'); $data['series'][2]['name'] = __('Late own'); $data['series'][3]['name'] = __('On time'); $data['series'][0]['data'] = []; $data['series'][1]['data'] = []; $data['series'][2]['data'] = []; $data['series'][3]['data'] = []; // ensure thare are 2 values per user (late and in time) foreach ($names as $name => $username) { if (!isset($allLate[$name])) { $allLate[$name] = 0; } if (!isset($resolveLate[$name])) { $resolveLate[$name] = 0; } if (!isset($ownLate[$name])) { $ownLate[$name] = 0; } if (!isset($onTime[$name])) { $onTime[$name] = 0; } $label = $username ?? $name; $data['labels'][] = $label; array_push($data['series'][0]['data'], $allLate[$name]); array_push($data['series'][1]['data'], $resolveLate[$name]); array_push($data['series'][2]['data'], $ownLate[$name]); array_push($data['series'][3]['data'], $onTime[$name]); } if (count($data['series'][0]['data']) < 1) { $data['series'][0]['data'] = []; $data['series'][1]['data'] = []; $data['series'][2]['data'] = []; $data['series'][3]['data'] = []; $data['labels'] = []; $data['nodata'] = true; } return [ 'label' => __('Tickets by SLA status and by technician'), 'data' => $data, 'icon' => $params['icon'], ]; } public static function nbTicketsByAgreementStatusAndTechnicianGroup( array $params = [] ): array { $DBread = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => 'fas fa-stopwatch', 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $query_criteria = []; $table = Ticket::getTable(); $ticketGroupTable = Group_Ticket::getTable(); $groupTable = Group::getTable(); $ownExceeded = Ticket::generateSLAOLAComputation('time_to_own', $table); $resolveExceeded = Ticket::generateSLAOLAComputation('time_to_resolve', $table); $slaState = "IF ($ownExceeded AND $resolveExceeded, 3, IF ($resolveExceeded, 2, IF ($ownExceeded, 1, 0)))"; $query_criteria = [ 'COUNT' => 'cpt', 'SELECT' => [ "$groupTable.name", new QueryExpression("$slaState as `sla_state`"), ], 'FROM' => $table, 'INNER JOIN' => [ "$ticketGroupTable as gl" => [ 'FKEY' => [ 'gl' => 'tickets_id', $table => 'id', [ 'AND' => [ "gl.type" => Ticket_User::ASSIGN ] ] ], ], $groupTable => [ 'FKEY' => [ $groupTable => 'id', 'gl' => 'groups_id', ], ], ], 'WHERE' => [ "$table.is_deleted" => 0, ] + getEntitiesRestrictCriteria($table), 'GROUPBY' => [ 'sla_state', "$groupTable.id", ], 'ORDER' => [ 'sla_state DESC', 'cpt DESC', "$groupTable.id", ] ]; unset($params['apply_filters'][UserTechFilter::getId()]); $query_filter = self::getFiltersCriteria($table, $params['apply_filters']); unset($query_filter['LEFT JOIN']["$ticketGroupTable as gl"]); $query_criteria = array_merge_recursive( $query_criteria, Ticket::getCriteriaFromProfile(), $query_filter ); $allLate = []; $resolveLate = []; $ownLate = []; $onTime = []; $names = []; $data = []; // Get data and sort by is_late status $iterator = $DBread->request($query_criteria); foreach ($iterator as $row) { switch ($row['sla_state']) { case 3: // Own and resolve are both late $allLate[$row['name']] = $row['cpt']; break; case 2: // Resolve is late $resolveLate[$row['name']] = $row['cpt']; break; case 1: // own is late $ownLate[$row['name']] = $row['cpt']; break; case 0: $onTime[$row['name']] = $row['cpt']; } $names[$row['name']] = $row['name']; } // set legend for each serie $data['series'][0]['name'] = __('Late own and resolve'); $data['series'][1]['name'] = __('Late resolve'); $data['series'][2]['name'] = __('Late own'); $data['series'][3]['name'] = __('On time'); $data['series'][0]['data'] = []; $data['series'][1]['data'] = []; $data['series'][2]['data'] = []; $data['series'][3]['data'] = []; // ensure thare are 2 values per user (late and in time) foreach ($names as $name => $username) { if (!isset($allLate[$name])) { $allLate[$name] = 0; } if (!isset($resolveLate[$name])) { $resolveLate[$name] = 0; } if (!isset($ownLate[$name])) { $ownLate[$name] = 0; } if (!isset($onTime[$name])) { $onTime[$name] = 0; } $label = $username ?? $name; $data['labels'][] = $label; array_push($data['series'][0]['data'], $allLate[$name]); array_push($data['series'][1]['data'], $resolveLate[$name]); array_push($data['series'][2]['data'], $ownLate[$name]); array_push($data['series'][3]['data'], $onTime[$name]); } if (count($data['series'][0]['data']) < 1) { $data['series'][0]['data'] = []; $data['series'][1]['data'] = []; $data['series'][2]['data'] = []; $data['series'][3]['data'] = []; $data['labels'] = []; $data['nodata'] = true; } return [ 'label' => __('Tickets by SLA status and by technician group'), 'data' => $data, 'icon' => $params['icon'], ]; } /** * Get multiple counts of computer by a specific foreign key * * @param CommonDBTM $item main item to count * @param CommonDBTM $fk_item groupby by this item (we will find the foreign key in the main item) * @param array $params values for: * - 'title' of the card * - 'icon' of the card * - 'searchoption_id' id corresponding to FK search option * - 'limit' max data to return * - 'join_key' LEFT, INNER, etc JOIN * - 'apply_filters' values from dashboard filters * * @return array : * - 'data': [ * 'url' * 'number' * 'label' * ] * - 'label' * - 'icon' */ public static function nbItemByFk( CommonDBTM $item = null, CommonDBTM $fk_item = null, array $params = [] ): array { $DB = DBConnection::getReadConnection(); $c_table = $item::getTable(); $fk_table = $fk_item::getTable(); $fk_itemtype = $fk_item::getType(); // try to autodetect searchoption id $searchoptions = $item->rawSearchOptions(); $found_so = array_filter($searchoptions, function ($searchoption) use ($fk_table) { return isset($searchoption['table']) && $searchoption['table'] === $fk_table; }); $found_so = array_shift($found_so); $found_so_id = $found_so['id'] ?? 0; $default_params = [ 'label' => "", 'searchoption_id' => $found_so_id, 'icon' => $fk_item::getIcon() ?? $item::getIcon(), 'limit' => 50, 'join_key' => 'LEFT JOIN', 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $where = []; if ($item->maybeDeleted()) { $where["$c_table.is_deleted"] = 0; } if ($item->maybeTemplate()) { $where["$c_table.is_template"] = 0; } $name = 'name'; if ($fk_item instanceof CommonTreeDropdown) { $name = 'completename'; } if ($fk_item instanceof CommonDevice) { $name = 'designation'; } if ($item->isEntityAssign()) { $where += getEntitiesRestrictCriteria($c_table, '', '', $item->maybeRecursive()); } $criteria = array_merge_recursive( [ 'SELECT' => [ "$fk_table.$name AS fk_name", "$fk_table.id AS fk_id", 'COUNT DISTINCT' => "$c_table.id AS cpt", ], 'FROM' => $c_table, $params['join_key'] => [ $fk_table => [ 'ON' => [ $fk_table => 'id', $c_table => getForeignKeyFieldForItemType($fk_itemtype), ] ] ], 'GROUPBY' => "$fk_table.$name", 'ORDERBY' => "cpt DESC", 'LIMIT' => $params['limit'], ], count($where) ? ['WHERE' => $where] : [], self::getFiltersCriteria($c_table, $params['apply_filters']), $item instanceof Ticket ? Ticket::getCriteriaFromProfile() : [] ); $iterator = $DB->request($criteria); $search_criteria = self::getSearchFiltersCriteria($fk_table, $params['apply_filters'])['criteria'] ?? []; $url = $item::getSearchURL(); $data = []; foreach ($iterator as $result) { $result_criteria = $search_criteria; $result_criteria[] = [ 'field' => $params['searchoption_id'], 'searchtype' => 'equals', 'value' => $result['fk_id'] ?? 0, ]; $data[] = [ 'number' => $result['cpt'], 'label' => $result['fk_name'] ?? __("without"), 'url' => $url . (str_contains($url, '?') ? '&' : '?') . Toolbox::append_params([ 'criteria' => $result_criteria, 'reset' => 'reset', ]), ]; } if (count($data) === 0) { $data = [ 'nodata' => true ]; } return [ 'data' => $data, 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * Get a list of article for an compatible item (with date,name,text fields) * * @param CommonDBTM $item the itemtype to list * @param array $params default values for * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function articleListItem(CommonDBTM $item = null, array $params = []): array { $DB = DBConnection::getReadConnection(); $default_params = [ 'icon' => $item::getIcon(), 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $i_table = $item::getTable(); $criteria = array_merge_recursive( [ 'SELECT' => "$i_table.*", 'FROM' => $i_table ], self::getFiltersCriteria($i_table, $params['apply_filters']), $item instanceof CommonDBVisible ? $item::getVisibilityCriteria() : [] ); $iterator = $DB->request($criteria); $data = []; foreach ($iterator as $line) { $data[] = [ 'date' => $line['date'] ?? '', 'label' => $line['name'] ?? '', 'content' => $line['text'] ?? '', 'author' => User::getFriendlyNameById($line['users_id'] ?? 0), 'url' => $item::getFormURLWithID($line['id']), ]; } $nb_items = count($data); if ($nb_items === 0) { $data = [ 'nodata' => true ]; } return [ 'data' => $data, 'number' => $nb_items, 'url' => $item::getSearchURL(), 'label' => $item::getTypeName($nb_items), 'icon' => $item::getIcon(), ]; } /** * get multiple count of ticket by month * * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function ticketsOpened(array $params = []): array { $DB = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => Ticket::getIcon(), 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $t_table = Ticket::getTable(); $criteria = array_merge_recursive( [ 'SELECT' => [ 'COUNT DISTINCT' => "$t_table.id as nb_tickets", new QueryExpression("DATE_FORMAT(" . $DB->quoteName("date") . ", '%Y-%m') AS ticket_month") ], 'FROM' => $t_table, 'WHERE' => [ "$t_table.is_deleted" => 0, ] + getEntitiesRestrictCriteria($t_table), 'GROUPBY' => 'ticket_month', 'ORDER' => 'ticket_month ASC' ], Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($t_table, $params['apply_filters']) ); $iterator = $DB->request($criteria); $s_criteria = [ 'criteria' => [ [ 'link' => 'AND', 'field' => 15, 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 15, 'searchtype' => 'lessthan', 'value' => null ], ], 'reset' => 'reset' ]; $data = []; foreach ($iterator as $result) { list($start_day, $end_day) = self::formatMonthyearDates($result['ticket_month']); $s_criteria['criteria'][0]['value'] = $start_day; $s_criteria['criteria'][1]['value'] = $end_day; $data[] = [ 'number' => $result['nb_tickets'], 'label' => $result['ticket_month'], 'url' => Ticket::getSearchURL() . "?" . Toolbox::append_params($s_criteria), ]; } return [ 'data' => $data, 'distributed' => false, 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * Get ticket evolution by opened, solved, closed, late series and months group * * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function getTicketsEvolution(array $params = []): array { $default_params = [ 'label' => "", 'icon' => Ticket::getIcon(), 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $year = date("Y") - 15; $begin = date("Y-m-d", mktime(1, 0, 0, (int)date("m"), (int)date("d"), $year)); $end = date("Y-m-d"); if ( isset($params['apply_filters'][DatesFilter::getId()]) && count($params['apply_filters'][DatesFilter::getId()]) == 2 ) { $begin = date("Y-m-d", strtotime($params['apply_filters'][DatesFilter::getId()][0])); $end = date("Y-m-d", strtotime($params['apply_filters'][DatesFilter::getId()][1])); unset($params['apply_filters'][DatesFilter::getId()]); } $t_table = Ticket::getTable(); $base_search_criteria = self::getSearchFiltersCriteria($t_table, $params['apply_filters'])['criteria'] ?? []; $series = [ 'inter_total' => [ 'name' => _nx('ticket', 'Opened', 'Opened', Session::getPluralNumber()), 'search' => [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => 15, // creation date 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 15, // creation date 'searchtype' => 'lessthan', 'value' => null ], ], $base_search_criteria ), 'reset' => 'reset' ] ], 'inter_solved' => [ 'name' => _nx('ticket', 'Solved', 'Solved', Session::getPluralNumber()), 'search' => [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => 17, // solve date 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 17, // solve date 'searchtype' => 'lessthan', 'value' => null ], ], $base_search_criteria ), 'reset' => 'reset' ] ], 'inter_solved_late' => [ 'name' => __('Late'), 'search' => [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => 17, // solve date 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 17, // solve date 'searchtype' => 'lessthan', 'value' => null ], [ 'link' => 'AND', 'field' => 82, // time_to_resolve exceed solve date 'searchtype' => 'equals', 'value' => 1 ], ], $base_search_criteria ), 'reset' => 'reset' ] ], 'inter_closed' => [ 'name' => __('Closed'), 'search' => [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => 16, // close date 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 16, // close date 'searchtype' => 'lessthan', 'value' => null ], ], $base_search_criteria ), 'reset' => 'reset' ] ], ]; $filters = array_merge_recursive( Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($t_table, $params['apply_filters']) ); $i = 0; $monthsyears = []; foreach ($series as $stat_type => &$serie) { $values = Stat::constructEntryValues( 'Ticket', $stat_type, $begin, $end, "", "", "", $filters ); if ($i === 0) { $monthsyears = array_keys($values); } $values = array_values($values); foreach ($values as $index => $number) { $current_monthyear = $monthsyears[$index]; list($start_day, $end_day) = self::formatMonthyearDates($current_monthyear); $serie['search']['criteria'][0]['value'] = $start_day; $serie['search']['criteria'][1]['value'] = $end_day; $serie['data'][$index] = [ 'value' => $number, 'url' => Ticket::getSearchURL() . "?" . Toolbox::append_params($serie['search']), ]; } $i++; } return [ 'data' => [ 'labels' => $monthsyears, 'series' => array_values($series), ], 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * get ticket by their curent status and their opening date * * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function getTicketsStatus(array $params = []): array { $DB = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => Ticket::getIcon(), 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $statuses = Ticket::getAllStatusArray(); $t_table = Ticket::getTable(); $sub_query = array_merge_recursive( [ 'DISTINCT' => true, 'SELECT' => ["$t_table.*"], 'FROM' => $t_table, 'WHERE' => [ "$t_table.is_deleted" => 0, ] + getEntitiesRestrictCriteria($t_table), ], // limit count for profiles with limited rights Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($t_table, $params['apply_filters']) ); $criteria = [ 'SELECT' => [ new QueryExpression( "FROM_UNIXTIME(UNIX_TIMESTAMP(" . $DB->quoteName("{$t_table}_distinct.date") . "),'%Y-%m') AS period" ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::INCOMING . ", 1, 0)) as " . $DB->quoteValue(_x('status', 'New')) ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::ASSIGNED . ", 1, 0)) as " . $DB->quoteValue(_x('status', 'Processing (assigned)')) ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::PLANNED . ", 1, 0)) as " . $DB->quoteValue(_x('status', 'Processing (planned)')) ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::WAITING . ", 1, 0)) as " . $DB->quoteValue(__('Pending')) ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::SOLVED . ", 1, 0)) as " . $DB->quoteValue(_x('status', 'Solved')) ), new QueryExpression( "SUM(IF({$t_table}_distinct.status = " . Ticket::CLOSED . ", 1, 0)) as " . $DB->quoteValue(_x('status', 'Closed')) ), ], 'FROM' => new QuerySubQuery($sub_query, "{$t_table}_distinct"), 'ORDER' => 'period ASC', 'GROUP' => ['period'] ]; $iterator = $DB->request($criteria); $s_params = [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => 12, // status 'searchtype' => 'equals', 'value' => null ], [ 'link' => 'AND', 'field' => 15, // creation date 'searchtype' => 'morethan', 'value' => null ], [ 'link' => 'AND', 'field' => 15, // creation date 'searchtype' => 'lessthan', 'value' => null ], ], self::getSearchFiltersCriteria($t_table, $params['apply_filters'])['criteria'] ?? [] ), 'reset' => 'reset' ]; $data = [ 'labels' => [], 'series' => [] ]; foreach ($iterator as $result) { list($start_day, $end_day) = self::formatMonthyearDates($result['period']); $s_params['criteria'][1]['value'] = $start_day; $s_params['criteria'][2]['value'] = $end_day; $data['labels'][] = $result['period']; $tmp = $result; unset($tmp['period'], $tmp['nb_tickets']); $i = 0; foreach ($tmp as $label2 => $value) { $status_key = array_search($label2, $statuses); $s_params['criteria'][0]['value'] = $status_key; $data['series'][$i]['name'] = $label2; $data['series'][$i]['data'][] = [ 'value' => (int) $value, 'url' => Ticket::getSearchURL() . "?" . Toolbox::append_params($s_params), ]; $i++; } } return [ 'data' => $data, 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * Get numbers of tickets grouped by actors * * @param string $case cound be: * - user_requester * - group_requester * - user_observer * - group_observer * - user_assign * - group_assign * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function nbTicketsActor( string $case = "", array $params = [] ): array { $DBread = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => null, 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $t_table = Ticket::getTable(); $li_table = Ticket_User::getTable(); $ug_table = User::getTable(); $n_fields = [ "$ug_table.firstname as first", "$ug_table.realname as second", ]; $where = [ "$t_table.is_deleted" => 0, ]; $case_array = explode('_', $case); if ($case_array[0] == 'user') { $where["$ug_table.is_deleted"] = 0; $params['icon'] = $params['icon'] ?? User::getIcon(); } else if ($case_array[0] == 'group') { $li_table = Group_Ticket::getTable(); $ug_table = Group::getTable(); $n_fields = [ "$ug_table.completename as first" ]; $params['icon'] = $params['icon'] ?? Group::getIcon(); } $type = 0; $soption = 0; switch ($case) { case "user_requester": $type = CommonITILActor::REQUESTER; $soption = 4; break; case "group_requester": $type = CommonITILActor::REQUESTER; $soption = 71; break; case "user_observer": $type = CommonITILActor::OBSERVER; $soption = 66; break; case "group_observer": $type = CommonITILActor::OBSERVER; $soption = 65; break; case "user_assign": $type = CommonITILActor::ASSIGN; $soption = 5; break; case "group_assign": $type = CommonITILActor::ASSIGN; $soption = 8; break; } $criteria = array_merge_recursive( [ 'SELECT' => array_merge([ 'COUNT DISTINCT' => "$t_table.id AS nb_tickets", "$ug_table.id as actor_id", ], $n_fields), 'FROM' => $t_table, 'INNER JOIN' => [ $li_table => [ 'ON' => [ $li_table => getForeignKeyFieldForItemType("Ticket"), $t_table => 'id', [ 'AND' => [ "$li_table.type" => $type ] ] ] ], $ug_table => [ 'ON' => [ $li_table => getForeignKeyFieldForTable($ug_table), $ug_table => 'id' ] ] ], 'GROUPBY' => "$ug_table.id", 'ORDER' => 'nb_tickets DESC', 'WHERE' => $where + getEntitiesRestrictCriteria($t_table), ], Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($t_table, $params['apply_filters']) ); $iterator = $DBread->request($criteria); $s_params = [ 'criteria' => array_merge( [ [ 'link' => 'AND', 'field' => $soption, 'searchtype' => 'equals', 'value' => null ], ], self::getSearchFiltersCriteria($t_table, $params['apply_filters'])['criteria'] ?? [] ), 'reset' => 'reset' ]; $data = []; foreach ($iterator as $result) { $s_params['criteria'][0]['value'] = $result['actor_id']; $data[] = [ 'number' => $result['nb_tickets'], 'label' => $result['first'] . " " . ($result['second'] ?? ""), 'url' => Ticket::getSearchURL() . "?" . Toolbox::append_params($s_params), ]; } return [ 'data' => $data, 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * get average stats (takeintoaccoutn, solve/close delay, waiting) of ticket by month * * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function averageTicketTimes(array $params = []) { $DBread = DBConnection::getReadConnection(); $default_params = [ 'label' => "", 'icon' => "fas fa-stopwatch", 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $t_table = Ticket::getTable(); $criteria = array_merge_recursive( [ 'SELECT' => [ new QueryExpression("DATE_FORMAT(" . $DBread->quoteName("date") . ", '%Y-%m') AS period"), new QueryExpression("AVG(" . $DBread->quoteName("takeintoaccount_delay_stat") . ") AS avg_takeintoaccount_delay_stat"), new QueryExpression("AVG(" . $DBread->quoteName("waiting_duration") . ") AS avg_waiting_duration"), new QueryExpression("AVG(" . $DBread->quoteName("solve_delay_stat") . ") AS avg_solve_delay_stat"), new QueryExpression("AVG(" . $DBread->quoteName("close_delay_stat") . ") AS close_delay_stat"), ], 'FROM' => $t_table, 'WHERE' => [ 'is_deleted' => 0, ] + getEntitiesRestrictCriteria($t_table), 'ORDER' => 'period ASC', 'GROUP' => ['period'] ], Ticket::getCriteriaFromProfile(), self::getFiltersCriteria($t_table, $params['apply_filters']) ); $iterator = $DBread->request($criteria); $data = [ 'labels' => [], 'series' => [ [ 'name' => __("Time to own"), 'data' => [] ], [ 'name' => __("Waiting time"), 'data' => [] ], [ 'name' => __("Time to resolve"), 'data' => [] ], [ 'name' => __("Time to close"), 'data' => [] ] ] ]; foreach ($iterator as $r) { $data['labels'][] = $r['period']; $tmp = $r; unset($tmp['period']); $data['series'][0]['data'][] = round($r['avg_takeintoaccount_delay_stat'] / HOUR_TIMESTAMP, 1); $data['series'][1]['data'][] = round($r['avg_waiting_duration'] / HOUR_TIMESTAMP, 1); $data['series'][2]['data'][] = round($r['avg_solve_delay_stat'] / HOUR_TIMESTAMP, 1); $data['series'][3]['data'][] = round($r['close_delay_stat'] / HOUR_TIMESTAMP, 1); } return [ 'data' => $data, 'label' => $params['label'], 'icon' => $params['icon'], ]; } /** * get multiple count of ticket by status and month * * @param array $params default values for * - 'title' of the card * - 'icon' of the card * - 'apply_filters' values from dashboard filters * * @return array */ public static function getTicketSummary(array $params = []) { $default_params = [ 'label' => "", 'icon' => "", 'apply_filters' => [], ]; $params = array_merge($default_params, $params); $incoming = self::nbTicketsGeneric('incoming', $params); $assigned = self::nbTicketsGeneric('assigned', $params); $waiting = self::nbTicketsGeneric('waiting', $params); $tovalidate = self::nbTicketsGeneric('waiting_validation', $params); $closed = self::nbTicketsGeneric('closed', $params); $solved = self::nbTicketsGeneric('solved', $params); return [ 'data' => [ [ 'number' => $incoming['number'], 'label' => __("New"), 'url' => $incoming['url'], 'color' => '#3bc519', ], [ 'number' => $assigned['number'], 'label' => __("Assigned"), 'url' => $assigned['url'], 'color' => '#f1cd29', ], [ 'number' => $waiting['number'], 'label' => __("Pending"), 'url' => $waiting['url'], 'color' => '#f1a129', ], [ 'number' => $tovalidate['number'], 'label' => __("To validate"), 'url' => $tovalidate['url'], 'color' => '#266ae9', ], [ 'number' => $solved['number'], 'label' => __("Solved"), 'url' => $solved['url'], 'color' => '#edc949', ], [ 'number' => $closed['number'], 'label' => __("Closed"), 'url' => $closed['url'], 'color' => '#555555', ] ], 'label' => $params['label'], 'icon' => $params['icon'], ]; } public static function formatMonthyearDates(string $monthyear): array { $rawdate = explode('-', $monthyear); $year = $rawdate[0]; $month = $rawdate[1]; $monthtime = mktime(0, 0, 0, $month, 1, $year); $start_day = date("Y-m-d H:i:s", strtotime("first day of this month", $monthtime)); $end_day = date("Y-m-d H:i:s", strtotime("first day of next month", $monthtime)); return [$start_day, $end_day]; } /** * Get search criteria based on given filters. * * @param string $table Related itemtype table. * @param array $apply_filters Dashboard filters. * @param bool $default_criteria_on_empty Return default criteria if filters are not producing any criteria. * * @return array An empty array, or an array containing a `criteria` key that contains search criteria. * * @FIXME Remove `criteria` key encapsulation. It cannot be done in 10.0 as some plugins are relying on current signature. */ final public static function getSearchFiltersCriteria(string $table = "", array $apply_filters = [], bool $default_criteria_on_empty = false) { $s_criteria = []; $filters = Filter::getRegisteredFilterClasses(); foreach ($filters as $filter) { if (!$filter::canBeApplied($table) || !array_key_exists($filter::getId(), $apply_filters)) { continue; } $filter_criteria = $filter::getSearchCriteria($table, $apply_filters[$filter::getId()]); array_push($s_criteria, ...$filter_criteria); } $itemtype = getItemTypeForTable($table); if (is_a($itemtype, CommonDBTM::class, true) && $default_criteria_on_empty === true && count($s_criteria) === 0) { $s_criteria = Search::getDefaultCriteria($itemtype); } return ['criteria' => $s_criteria]; } public static function getFiltersCriteria(string $table = "", array $apply_filters = []) { $where = []; $join = []; $filters = Filter::getRegisteredFilterClasses(); foreach ($filters as $filter) { if (!$filter::canBeApplied($table) || !array_key_exists($filter::getId(), $apply_filters)) { continue; } $filter_criteria = $filter::getCriteria($table, $apply_filters[$filter::getId()]); if (isset($filter_criteria['WHERE'])) { $where = array_merge($where, $filter_criteria['WHERE']); } if (isset($filter_criteria['JOIN'])) { $join = array_merge($join, $filter_criteria['JOIN']); } } $criteria = []; if (count($where)) { $criteria['WHERE'] = $where; } if (count($join)) { $criteria['LEFT JOIN'] = $join; } return $criteria; } }