. * --------------------------------------------------------------------- */ if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } /** * Log Class **/ class Log extends CommonDBTM { const HISTORY_ADD_DEVICE = 1; const HISTORY_UPDATE_DEVICE = 2; const HISTORY_DELETE_DEVICE = 3; const HISTORY_INSTALL_SOFTWARE = 4; const HISTORY_UNINSTALL_SOFTWARE = 5; const HISTORY_DISCONNECT_DEVICE = 6; const HISTORY_CONNECT_DEVICE = 7; const HISTORY_LOCK_DEVICE = 8; const HISTORY_UNLOCK_DEVICE = 9; const HISTORY_LOG_SIMPLE_MESSAGE = 12; const HISTORY_DELETE_ITEM = 13; const HISTORY_RESTORE_ITEM = 14; const HISTORY_ADD_RELATION = 15; const HISTORY_DEL_RELATION = 16; const HISTORY_ADD_SUBITEM = 17; const HISTORY_UPDATE_SUBITEM = 18; const HISTORY_DELETE_SUBITEM = 19; const HISTORY_CREATE_ITEM = 20; const HISTORY_UPDATE_RELATION = 21; const HISTORY_LOCK_RELATION = 22; const HISTORY_LOCK_SUBITEM = 23; const HISTORY_UNLOCK_RELATION = 24; const HISTORY_UNLOCK_SUBITEM = 25; const HISTORY_LOCK_ITEM = 26; const HISTORY_UNLOCK_ITEM = 27; // Plugin must use value starting from const HISTORY_PLUGIN = 1000; static $rightname = 'logs'; static function getTypeName($nb = 0) { return __('Historical'); } function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { $nb = 0; if ($_SESSION['glpishow_count_on_tabs']) { $nb = countElementsInTable('glpi_logs', ['itemtype' => $item->getType(), 'items_id' => $item->getID()]); } return self::createTabEntry(self::getTypeName(1), $nb); } static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { self::showForItem($item); return true; } /** * Construct history for an item * * @param $item CommonDBTM object * @param $oldvalues array of old values updated * @param $values array of all values of the item * * @return boolean for success (at least 1 log entry added) **/ static function constructHistory(CommonDBTM $item, &$oldvalues, &$values) { if (!count($oldvalues)) { return false; } // needed to have $SEARCHOPTION list($real_type, $real_id) = $item->getLogTypeID(); $searchopt = Search::getOptions($real_type); if (!is_array($searchopt)) { return false; } $result = 0; foreach ($oldvalues as $key => $oldval) { $changes = []; // Parsing $SEARCHOPTION to find changed field foreach ($searchopt as $key2 => $val2) { if (!isset($val2['table'])) { // skip sub-title continue; } // specific for profile if (($item->getType() == 'ProfileRight') && ($key == 'rights')) { if (isset($val2['rightname']) && ($val2['rightname'] == $item->fields['name'])) { $id_search_option = $key2; $changes = [$id_search_option, addslashes($oldval), $values[$key]]; } } else if (($val2['linkfield'] == $key && $real_type === $item->getType()) || ($key == $val2['field'] && $val2['table'] == $item->getTable())) { // Linkfield or standard field not massive action enable $id_search_option = $key2; // Give ID of the $SEARCHOPTION if ($val2['table'] == $item->getTable()) { $changes = [$id_search_option, addslashes($oldval), $values[$key]]; } else { // other cases; link field -> get data from dropdown if ($val2["table"] != 'glpi_auth_tables') { $changes = [$id_search_option, addslashes(sprintf(__('%1$s (%2$s)'), Dropdown::getDropdownName($val2["table"], $oldval), $oldval)), addslashes(sprintf(__('%1$s (%2$s)'), Dropdown::getDropdownName($val2["table"], $values[$key]), $values[$key]))]; } } break; } } if (count($changes)) { $result = self::history($real_id, $real_type, $changes); } } return $result; } // function construct_history /** * Log history * * @param $items_id * @param $itemtype * @param $changes * @param $itemtype_link (default '') * @param $linked_action (default '0') * * @return boolean success **/ static function history ($items_id, $itemtype, $changes, $itemtype_link = '', $linked_action = '0') { global $DB; $date_mod = $_SESSION["glpi_currenttime"]; if (empty($changes)) { return false; } // create a query to insert history $id_search_option = $changes[0]; $old_value = $changes[1]; $new_value = $changes[2]; if ($uid = Session::getLoginUserID(false)) { if (is_numeric($uid)) { $username = sprintf(__('%1$s (%2$s)'), getUserName($uid), $uid); } else { // For cron management $username = $uid; } } else { $username = ""; } if (Session::isImpersonateActive()) { $impersonator_id = Session::getImpersonatorId(); $username = sprintf( __('%1$s impersonated by %2$s'), $username, sprintf(__('%1$s (%2$s)'), getUserName($impersonator_id), $impersonator_id) ); } $old_value = $DB->escape(Toolbox::substr(stripslashes($old_value), 0, 180)); $new_value = $DB->escape(Toolbox::substr(stripslashes($new_value), 0, 180)); // Security to be sure that values do not pass over the max length if (Toolbox::strlen($old_value) > 255) { $old_value = Toolbox::substr($old_value, 0, 250); } if (Toolbox::strlen($new_value) > 255) { $new_value = Toolbox::substr($new_value, 0, 250); } $params = [ 'items_id' => $items_id, 'itemtype' => $itemtype, 'itemtype_link' => $itemtype_link, 'linked_action' => $linked_action, 'user_name' => addslashes($username), 'date_mod' => $date_mod, 'id_search_option' => $id_search_option, 'old_value' => $old_value, 'new_value' => $new_value ]; $result = $DB->insert(self::getTable(), $params); if ($result && $DB->affectedRows($result) > 0) { return $_SESSION['glpi_maxhistory'] = $DB->insertId(); } return false; } /** * Show History of an item * * @param $item CommonDBTM object * @param $withtemplate integer withtemplate param (default 0) **/ static function showForItem(CommonDBTM $item, $withtemplate = 0) { $itemtype = $item->getType(); $items_id = $item->getField('id'); $SEARCHOPTION = Search::getOptions($itemtype); if (isset($_GET["start"])) { $start = intval($_GET["start"]); } else { $start = 0; } $sql_filters = self::convertFiltersValuesToSqlCriteria(isset($_GET['filters']) ? $_GET['filters'] : []); // Total Number of events $total_number = countElementsInTable("glpi_logs", ['items_id' => $items_id, 'itemtype' => $itemtype ]); $filtered_number = countElementsInTable("glpi_logs", ['items_id' => $items_id, 'itemtype' => $itemtype ] + $sql_filters); // No Events in database if ($total_number < 1) { echo "
"; echo ""; echo ""; echo "
".__('No historical')."
"; echo "

"; return; } // Display the pager $additional_params = isset($_GET['filters']) ? http_build_query(['filters' => $_GET['filters']]) : ''; Html::printAjaxPager(self::getTypeName(1), $start, $filtered_number, '', true, $additional_params); // Output events echo "
"; echo ""; $header = ""; $header .= ""; $header .= ""; $header .= ""; $header .= ""; //TRANS: a noun, modification, change $header .= ""; $header .= ""; echo ""; echo $header; if (isset($_GET['filters'])) { echo ""; echo ""; $dateValue = isset($_GET['filters']['date']) ? Html::cleanInputText($_GET['filters']['date']) : null; echo ""; echo ""; echo ""; echo ""; echo ""; } else { echo ""; echo ""; echo ""; } echo ""; echo "$header"; echo ""; if ($filtered_number > 0) { foreach (self::getHistoryData($item, $start, $_SESSION['glpilist_limit'], $sql_filters) as $data) { if ($data['display_history']) { // show line echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } } else { echo ""; echo ""; echo ""; } echo ""; echo "
".__('ID').""._n('Date', 'Dates', 1)."".User::getTypeName(1).""._n('Field', 'Fields', 1).""._x('name', 'Update')."
"; echo ""; echo ""; echo ""; Dropdown::showFromArray( "filters[users_names]", Log::getDistinctUserNamesValuesInItemLog($item), [ 'multiple' => true, 'display_emptychoice' => true, 'values' => isset($_GET['filters']['users_names']) ? $_GET['filters']['users_names'] : [], 'width' => "100%", ] ); echo ""; Dropdown::showFromArray( "filters[affected_fields]", Log::getDistinctAffectedFieldValuesInItemLog($item), [ 'multiple' => true, 'display_emptychoice' => true, 'values' => isset($_GET['filters']['affected_fields']) ? $_GET['filters']['affected_fields'] : [], 'width' => "100%", ] ); echo ""; Dropdown::showFromArray( "filters[linked_actions]", Log::getDistinctLinkedActionValuesInItemLog($item), [ 'multiple' => true, 'display_emptychoice' => true, 'values' => isset($_GET['filters']['linked_actions']) ? $_GET['filters']['linked_actions'] : [], 'width' => "100%", ] ); echo "
"; echo "" . __('Show filters') . " "; echo "
".$data['id']."".$data['date_mod']."".$data['user_name']."".$data['field']."".$data['change']."
" . __('No historical matching your filters') . "
"; echo "
"; Html::printAjaxPager(self::getTypeName(1), $start, $filtered_number, '', true, $additional_params); } /** * Retrieve last history Data for an item * * @param CommonDBTM $item Object instance * @param integer $start First line to retrieve (default 0) * @param integer $limit Max number of line to retrieve (0 for all) (default 0) * @param array $sqlfilters SQL filters applied to history (default []) * * @return array of localized log entry (TEXT only, no HTML) **/ static function getHistoryData(CommonDBTM $item, $start = 0, $limit = 0, array $sqlfilters = []) { $DBread = DBConnection::getReadConnection(); $itemtype = $item->getType(); $items_id = $item->getField('id'); $itemtable = $item->getTable(); $SEARCHOPTION = Search::getOptions($itemtype); $query = [ 'FROM' => self::getTable(), 'WHERE' => [ 'items_id' => $items_id, 'itemtype' => $itemtype ] + $sqlfilters, 'ORDER' => 'id DESC' ]; if ($limit) { $query['START'] = (int)$start; $query['LIMIT'] = (int)$limit; } $iterator = $DBread->request($query); $changes = []; while ($data = $iterator->next()) { $tmp = []; $tmp['display_history'] = true; $tmp['id'] = $data["id"]; $tmp['date_mod'] = Html::convDateTime($data["date_mod"]); $tmp['user_name'] = $data["user_name"]; $tmp['field'] = ""; $tmp['change'] = ""; $tmp['datatype'] = ""; // This is an internal device ? if ($data["linked_action"]) { $action_label = self::getLinkedActionLabel($data["linked_action"]); // Yes it is an internal device switch ($data["linked_action"]) { case self::HISTORY_CREATE_ITEM : case self::HISTORY_DELETE_ITEM : case self::HISTORY_LOCK_ITEM : case self::HISTORY_UNLOCK_ITEM : case self::HISTORY_RESTORE_ITEM : $tmp['change'] = $action_label; break; case self::HISTORY_ADD_DEVICE : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the component name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); break; case self::HISTORY_UPDATE_DEVICE : $tmp['field'] = NOT_AVAILABLE; $change = ''; $linktype_field = explode('#', $data["itemtype_link"]); $linktype = $linktype_field[0]; $field = $linktype_field[1]; $devicetype = $linktype::getDeviceType(); $tmp['field'] = $devicetype; $specif_fields = $linktype::getSpecificities(); if (isset($specif_fields[$field]['short name'])) { $tmp['field'] = $devicetype; $tmp['field'] .= " (".$specif_fields[$field]['short name'].")"; } //TRANS: %1$s is the old_value, %2$s is the new_value $tmp['change'] = sprintf(__('%1$s: %2$s'), sprintf(__('%1$s (%2$s)'), $action_label, $tmp['field']), sprintf(__('%1$s by %2$s'), $data["old_value"], $data[ "new_value"])); break; case self::HISTORY_DELETE_DEVICE : $tmp['field']=NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the component name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_LOCK_DEVICE : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the component name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_UNLOCK_DEVICE : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the component name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); break; case self::HISTORY_INSTALL_SOFTWARE : $tmp['field'] = _n('Software', 'Software', 1); //TRANS: %s is the software name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); break; case self::HISTORY_UNINSTALL_SOFTWARE : $tmp['field'] = _n('Software', 'Software', 1); //TRANS: %s is the software name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_DISCONNECT_DEVICE : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the item name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_CONNECT_DEVICE : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { if ($item2 instanceof Item_Devices) { $tmp['field'] = $item2->getDeviceTypeName(1); } else { $tmp['field'] = $item2->getTypeName(1); } } //TRANS: %s is the item name $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); break; case self::HISTORY_LOG_SIMPLE_MESSAGE : $tmp['field'] = ""; $tmp['change'] = $data["new_value"]; break; case self::HISTORY_ADD_RELATION : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); if ($data['itemtype'] == 'Ticket') { if ($data['id_search_option']) { // Recent record - see CommonITILObject::getSearchOptionsActors() $as = $SEARCHOPTION[$data['id_search_option']]['name']; } else { // Old record switch ($data['itemtype_link']) { case 'Group': $is = 'isGroup'; break; case 'User': $is = 'isUser'; break; case 'Supplier': $is = 'isSupplier'; break; default: $is = $isr = $isa = $iso = false; break; } if ($is) { $iditem = intval(substr($data['new_value'], strrpos($data['new_value'], '(')+1)); // This is terrible idea $isr = $item->$is(CommonITILActor::REQUESTER, $iditem); $isa = $item->$is(CommonITILActor::ASSIGN, $iditem); $iso = $item->$is(CommonITILActor::OBSERVER, $iditem); } // Simple Heuristic, of course not enough if ($isr && !$isa && !$iso) { $as = _n('Requester', 'Requesters', 1); } else if (!$isr && $isa && !$iso) { $as = __('Assigned to'); } else if (!$isr && !$isa && $iso) { $as = _n('Watcher', 'Watchers', 1); } else { // Deleted or Ambiguous $as = false; } } if ($as) { $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $data["new_value"], $as)); } else { $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); } } break; case self::HISTORY_UPDATE_RELATION : $tmp['field'] = NOT_AVAILABLE; if ($linktype_field = explode('#', $data["itemtype_link"])) { $linktype = $linktype_field[0]; $tmp['field'] = $linktype::getTypeName(); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $data["old_value"], $data["new_value"])); break; case self::HISTORY_DEL_RELATION : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_LOCK_RELATION : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["old_value"]); break; case self::HISTORY_UNLOCK_RELATION : $tmp['field'] = NOT_AVAILABLE; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, $data["new_value"]); break; case self::HISTORY_ADD_SUBITEM : $tmp['field'] = ''; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $tmp['field'], $data["new_value"])); break; case self::HISTORY_UPDATE_SUBITEM : $tmp['field'] = ''; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $tmp['field'], $data["new_value"])); break; case self::HISTORY_DELETE_SUBITEM : $tmp['field'] = ''; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $tmp['field'], $data["old_value"])); break; case self::HISTORY_LOCK_SUBITEM : $tmp['field'] = ''; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $tmp['field'], $data["old_value"])); break; case self::HISTORY_UNLOCK_SUBITEM : $tmp['field'] = ''; if ($item2 = getItemForItemtype($data["itemtype_link"])) { $tmp['field'] = $item2->getTypeName(1); } $tmp['change'] = sprintf(__('%1$s: %2$s'), $action_label, sprintf(__('%1$s (%2$s)'), $tmp['field'], $data["new_value"])); break; default : $fct = [$data['itemtype_link'], 'getHistoryEntry']; if (($data['linked_action'] >= self::HISTORY_PLUGIN) && $data['itemtype_link'] && is_callable($fct)) { $tmp['field'] = $data['itemtype_link']::getTypeName(1); $tmp['change'] = call_user_func($fct, $data); } $tmp['display_history'] = !empty($tmp['change']); } } else { $fieldname = ""; $searchopt = []; $tablename = ''; // It's not an internal device foreach ($SEARCHOPTION as $key2 => $val2) { if ($key2 == $data["id_search_option"]) { $tmp['field'] = $val2["name"]; $tablename = $val2["table"]; $fieldname = $val2["field"]; $searchopt = $val2; if (isset($val2['datatype'])) { $tmp['datatype'] = $val2["datatype"]; } break; } } if (($itemtable == $tablename) || ($tmp['datatype'] == 'right')) { switch ($tmp['datatype']) { // specific case for text field case 'text' : $tmp['change'] = __('Update of the field'); break; default : $data["old_value"] = $item->getValueToDisplay($searchopt, $data["old_value"]); $data["new_value"] = $item->getValueToDisplay($searchopt, $data["new_value"]); break; } } if (empty($tmp['change'])) { $newval = $data["new_value"]; $oldval = $data["old_value"]; if ($data['id_search_option'] == '70') { $newval_expl = explode(' ', $newval); $oldval_expl = explode(' ', $oldval); if ($oldval_expl[0] == ' ') { $oldval = $data["old_value"]; } else { $old_iterator = $DBread->request('glpi_users', ['name' => $oldval_expl[0]]); while ($val = $old_iterator->next()) { $oldval = sprintf(__('%1$s %2$s'), formatUserName($val['id'], $oldval_expl[0], $val['realname'], $val['firstname']), $oldval_expl[1]); } } if ($newval_expl[0] == ' ') { $newval = $data["new_value"]; } else { $new_iterator = $DBread->request('glpi_users', ['name' => $newval_expl[0]]); while ($val = $new_iterator->next()) { $newval = sprintf(__('%1$s %2$s'), formatUserName($val['id'], $newval_expl[0], $val['realname'], $val['firstname']), $newval_expl[1]); } } } $tmp['change'] = sprintf(__('Change %1$s to %2$s'), $oldval, $newval); } } $changes[] = $tmp; } return $changes; } /** * Retrieve distinct values for user_name field in item log. * Return is made to be used as select tag options. * * @param CommonDBTM $item Object instance * * @return array * * @since 9.3 **/ static function getDistinctUserNamesValuesInItemLog(CommonDBTM $item) { global $DB; $itemtype = $item->getType(); $items_id = $item->getField('id'); $iterator = $DB->request([ 'SELECT' => 'user_name', 'DISTINCT' => true, 'FROM' => self::getTable(), 'WHERE' => [ 'items_id' => $items_id, 'itemtype' => $itemtype ], 'ORDER' => 'id DESC' ]); $values = []; while ($data = $iterator->next()) { if (empty($data['user_name'])) { continue; } $values[$data['user_name']] = $data['user_name']; } asort($values, SORT_NATURAL | SORT_FLAG_CASE); return $values; } /** * Retrieve distinct values for affected field in item log. * Return is made to be used as select tag options. * * @param CommonDBTM $item Object instance * * @return array * * @since 9.3 **/ static function getDistinctAffectedFieldValuesInItemLog(CommonDBTM $item) { global $DB; $itemtype = $item->getType(); $items_id = $item->getField('id'); $affected_fields = ['linked_action', 'itemtype_link', 'id_search_option']; $iterator = $DB->request([ 'SELECT' => $affected_fields, 'FROM' => self::getTable(), 'WHERE' => [ 'items_id' => $items_id, 'itemtype' => $itemtype ], 'GROUPBY' => $affected_fields, 'ORDER' => 'id DESC' ]); $values = []; while ($data = $iterator->next()) { $key = null; $value = null; // This is an internal device ? if ($data["linked_action"]) { // Yes it is an internal device switch ($data["linked_action"]) { case self::HISTORY_ADD_DEVICE : case self::HISTORY_DELETE_DEVICE : case self::HISTORY_LOCK_DEVICE : case self::HISTORY_UNLOCK_DEVICE : case self::HISTORY_DISCONNECT_DEVICE : case self::HISTORY_CONNECT_DEVICE : case self::HISTORY_ADD_RELATION : case self::HISTORY_DEL_RELATION : case self::HISTORY_LOCK_RELATION : case self::HISTORY_UNLOCK_RELATION : case self::HISTORY_ADD_SUBITEM : case self::HISTORY_UPDATE_SUBITEM : case self::HISTORY_DELETE_SUBITEM : case self::HISTORY_UPDATE_RELATION : case self::HISTORY_LOCK_SUBITEM : case self::HISTORY_UNLOCK_SUBITEM : $linked_action_values = [ self::HISTORY_ADD_DEVICE, self::HISTORY_DELETE_DEVICE, self::HISTORY_LOCK_DEVICE, self::HISTORY_UNLOCK_DEVICE, self::HISTORY_DISCONNECT_DEVICE, self::HISTORY_CONNECT_DEVICE, self::HISTORY_ADD_RELATION, self::HISTORY_UPDATE_RELATION, self::HISTORY_DEL_RELATION, self::HISTORY_LOCK_RELATION, self::HISTORY_UNLOCK_RELATION, self::HISTORY_ADD_SUBITEM, self::HISTORY_UPDATE_SUBITEM, self::HISTORY_DELETE_SUBITEM, self::HISTORY_LOCK_SUBITEM, self::HISTORY_UNLOCK_SUBITEM, ]; $key = 'linked_action::' . implode(',', $linked_action_values) . ';' . 'itemtype_link::' . $data['itemtype_link'] . ';'; if ($linked_item = getItemForItemtype($data["itemtype_link"])) { if ($linked_item instanceof Item_Devices) { $value = $linked_item->getDeviceTypeName(1); } else { $value = $linked_item->getTypeName(1); } } break; case self::HISTORY_UPDATE_DEVICE : $key = 'linked_action::' . self::HISTORY_UPDATE_DEVICE . ';' . 'itemtype_link::' . $data['itemtype_link'] . ';'; $linktype_field = explode('#', $data["itemtype_link"]); $linktype = $linktype_field[0]; $field = $linktype_field[1]; $devicetype = $linktype::getDeviceType(); $specif_fields = $linktype::getSpecificities(); $value = $devicetype; if (isset($specif_fields[$field]['short name'])) { $value .= " (".$specif_fields[$field]['short name'].")"; } break; case self::HISTORY_INSTALL_SOFTWARE : case self::HISTORY_UNINSTALL_SOFTWARE : $linked_action_values = [ self::HISTORY_INSTALL_SOFTWARE, self::HISTORY_UNINSTALL_SOFTWARE, ]; $key = 'linked_action::' . implode(',', $linked_action_values) . ';'; $value = _n('Software', 'Software', 1); break; default : $linked_action_values_to_exclude = [ 0, //Exclude lines corresponding to no action. self::HISTORY_ADD_DEVICE, self::HISTORY_DELETE_DEVICE, self::HISTORY_LOCK_DEVICE, self::HISTORY_UNLOCK_DEVICE, self::HISTORY_DISCONNECT_DEVICE, self::HISTORY_CONNECT_DEVICE, self::HISTORY_ADD_RELATION, self::HISTORY_UPDATE_RELATION, self::HISTORY_DEL_RELATION, self::HISTORY_LOCK_RELATION, self::HISTORY_UNLOCK_RELATION, self::HISTORY_ADD_SUBITEM, self::HISTORY_UPDATE_SUBITEM, self::HISTORY_DELETE_SUBITEM, self::HISTORY_LOCK_SUBITEM, self::HISTORY_UNLOCK_SUBITEM, self::HISTORY_UPDATE_DEVICE, self::HISTORY_INSTALL_SOFTWARE, self::HISTORY_UNINSTALL_SOFTWARE, ]; $key = 'linked_action:NOT:' . implode(',', $linked_action_values_to_exclude) . ';'; $value = __('Others'); break; } } else { // It's not an internal device foreach (Search::getOptions($itemtype) as $search_opt_key => $search_opt_val) { if ($search_opt_key == $data["id_search_option"]) { $key = 'id_search_option::' . $data['id_search_option'] . ';'; $value = $search_opt_val["name"]; break; } } } if (null !== $key && null !== $value) { $values[$key] = $value; } } uasort( $values, function ($a, $b) { $other = __('Others'); if ($a === $other) { return 1; } else if ($b === $other) { return -1; } return strcmp($a, $b); } ); return $values; } /** * Retrieve distinct values for action in item log. * Return is made to be used as select tag options. * * @param CommonDBTM $item Object instance * * @return array * * @since 9.3 **/ static function getDistinctLinkedActionValuesInItemLog(CommonDBTM $item) { global $DB; $itemtype = $item->getType(); $items_id = $item->getField('id'); $iterator = $DB->request([ 'SELECT' => 'linked_action', 'DISTINCT' => true, 'FROM' => self::getTable(), 'WHERE' => [ 'items_id' => $items_id, 'itemtype' => $itemtype ], 'ORDER' => 'id DESC' ]); $values = []; while ($data = $iterator->next()) { $key = $data["linked_action"]; $value = null; // This is an internal device ? if ($data["linked_action"]) { $value = self::getLinkedActionLabel($data["linked_action"]); if (null === $value) { $key = 'other'; $value = __('Others'); } } else { $value = __('Update a field'); } if (null !== $value) { $values[$key] = $value; } } uasort( $values, function ($a, $b) { $other = __('Others'); if ($a === $other) { return 1; } else if ($b === $other) { return -1; } return strcmp($a, $b); } ); return $values; } /** * Returns label corresponding to the linked action of a log entry. * * @param integer $linked_action Linked action value of a log entry. * * @return string * * @since 9.3 **/ static function getLinkedActionLabel($linked_action) { $label = null; switch ($linked_action) { case self::HISTORY_CREATE_ITEM : $label = __('Add the item'); break; case self::HISTORY_DELETE_ITEM : $label = __('Delete the item'); break; case self::HISTORY_LOCK_ITEM : $label = __('Lock the item'); break; case self::HISTORY_UNLOCK_ITEM : $label = __('Unlock the item'); break; case self::HISTORY_RESTORE_ITEM : $label = __('Restore the item'); break; case self::HISTORY_ADD_DEVICE : $label = __('Add a component'); break; case self::HISTORY_UPDATE_DEVICE : $label = __('Change a component'); break; case self::HISTORY_DELETE_DEVICE : $label = __('Delete a component'); break; case self::HISTORY_LOCK_DEVICE : $label = __('Lock a component'); break; case self::HISTORY_UNLOCK_DEVICE : $label = __('Unlock a component'); break; case self::HISTORY_INSTALL_SOFTWARE : $label = __('Install a software'); break; case self::HISTORY_UNINSTALL_SOFTWARE : $label = __('Uninstall a software'); break; case self::HISTORY_DISCONNECT_DEVICE : $label = __('Disconnect an item'); break; case self::HISTORY_CONNECT_DEVICE : $label = __('Connect an item'); break; case self::HISTORY_ADD_RELATION : $label = __('Add a link with an item'); break; case self::HISTORY_UPDATE_RELATION : $label = __('Update a link with an item'); break; case self::HISTORY_DEL_RELATION : $label = __('Delete a link with an item'); break; case self::HISTORY_LOCK_RELATION : $label = __('Lock a link with an item'); break; case self::HISTORY_UNLOCK_RELATION : $label = __('Unlock a link with an item'); break; case self::HISTORY_ADD_SUBITEM : $label = __('Add an item'); break; case self::HISTORY_UPDATE_SUBITEM : $label = __('Update an item'); break; case self::HISTORY_DELETE_SUBITEM : $label = __('Delete an item'); break; case self::HISTORY_LOCK_SUBITEM : $label = __('Lock an item'); break; case self::HISTORY_UNLOCK_SUBITEM : $label = __('Unlock an item'); break; case self::HISTORY_LOG_SIMPLE_MESSAGE : default : break; } return $label; } /** * Convert filters values into SQL filters usable in 'WHERE' condition of request build with 'DBmysqlIterator'. * * @param array $filters Filters values. * Filters values must be passed as indexed array using following rules : * - 'affected_fields' key for values corresponding to values built in 'self::getDistinctAffectedFieldValuesInItemLog()', * - 'date' key for a date value in 'Y-m-d H:i:s' format, * - 'linked_actions' key for values corresponding to values built in 'self::getDistinctLinkedActionValuesInItemLog()', * - 'users_names' key for values corresponding to values built in 'self::getDistinctUserNamesValuesInItemLog()'. * * @return array * * @since 9.3 **/ static function convertFiltersValuesToSqlCriteria(array $filters) { $sql_filters = []; if (isset($filters['affected_fields']) && !empty($filters['affected_fields'])) { $affected_field_crit = []; foreach ($filters['affected_fields'] as $index => $affected_field) { $affected_field_crit[$index] = []; foreach (explode(";", $affected_field) as $var) { if (1 === preg_match('/^(?P.+):(?P.*):(?P.+)$/', $var, $matches)) { $key = $matches['key']; $operator = $matches['operator']; // Each field can have multiple values for a given filter $values = explode(',', $matches['values']); // linked_action and id_search_option are stored as integers if (in_array($key, ['linked_action', 'id_search_option'])) { $values = array_map('intval', $values); } if (!empty($operator)) { $affected_field_crit[$index][$operator][$key] = $values; } else { $affected_field_crit[$index][$key] = $values; } } } } $sql_filters[] = [ 'OR' => $affected_field_crit ]; } if (isset($filters['date']) && !empty($filters['date'])) { $sql_filters['date_mod'] = ['LIKE', "%{$filters['date']}%"]; } if (isset($filters['linked_actions']) && !empty($filters['linked_actions'])) { $linked_action_crit = []; foreach ($filters['linked_actions'] as $linked_action) { if ($linked_action === 'other') { $linked_action_crit[] = ['linked_action' => self::HISTORY_LOG_SIMPLE_MESSAGE]; $linked_action_crit[] = ['linked_action' => ['>=', self::HISTORY_PLUGIN]]; } else { $linked_action_crit[] = ['linked_action' => $linked_action]; } } $sql_filters[] = ['OR' => $linked_action_crit]; } if (isset($filters['users_names']) && !empty($filters['users_names'])) { $sql_filters['user_name'] = $filters['users_names']; } return $sql_filters; } /** * Actions done after the ADD of the item in the database * * @since 0.83 * * @see CommonDBTM::post_addItem() **/ function post_addItem() { $_SESSION['glpi_maxhistory'] = $this->fields['id']; } function getRights($interface = 'central') { $values = [ READ => __('Read')]; return $values; } }