db = $db; } /** * Log an action * * @param User $user User performing the action * @param string $objectType Type of object (equipment, carrier, panel, etc.) * @param int $objectId ID of the object * @param string $action Action performed (create, update, delete, etc.) * @param string $objectRef Reference/label of the object (optional) * @param string $fieldChanged Specific field changed (optional) * @param mixed $oldValue Previous value (optional) * @param mixed $newValue New value (optional) * @param int $socid Customer ID (optional) * @param int $anlageId Anlage ID (optional) * @param string $note Additional note (optional) * @return int Log entry ID or <0 on error */ public function log($user, $objectType, $objectId, $action, $objectRef = '', $fieldChanged = '', $oldValue = null, $newValue = null, $socid = 0, $anlageId = 0, $note = '') { global $conf; $now = dol_now(); // Serialize complex values if (is_array($oldValue) || is_object($oldValue)) { $oldValue = json_encode($oldValue); } if (is_array($newValue) || is_object($newValue)) { $newValue = json_encode($newValue); } // Get IP address $ipAddress = ''; if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (!empty($_SERVER['REMOTE_ADDR'])) { $ipAddress = $_SERVER['REMOTE_ADDR']; } $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." ("; $sql .= "entity, object_type, object_id, object_ref, fk_societe, fk_anlage,"; $sql .= " action, field_changed, old_value, new_value,"; $sql .= " fk_user, user_login, date_action, note, ip_address"; $sql .= ") VALUES ("; $sql .= ((int) $conf->entity); $sql .= ", '".$this->db->escape($objectType)."'"; $sql .= ", ".((int) $objectId); $sql .= ", ".($objectRef ? "'".$this->db->escape($objectRef)."'" : "NULL"); $sql .= ", ".($socid > 0 ? ((int) $socid) : "NULL"); $sql .= ", ".($anlageId > 0 ? ((int) $anlageId) : "NULL"); $sql .= ", '".$this->db->escape($action)."'"; $sql .= ", ".($fieldChanged ? "'".$this->db->escape($fieldChanged)."'" : "NULL"); $sql .= ", ".($oldValue !== null ? "'".$this->db->escape($oldValue)."'" : "NULL"); $sql .= ", ".($newValue !== null ? "'".$this->db->escape($newValue)."'" : "NULL"); $sql .= ", ".((int) $user->id); $sql .= ", '".$this->db->escape($user->login)."'"; $sql .= ", '".$this->db->idate($now)."'"; $sql .= ", ".($note ? "'".$this->db->escape($note)."'" : "NULL"); $sql .= ", ".($ipAddress ? "'".$this->db->escape($ipAddress)."'" : "NULL"); $sql .= ")"; $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); return -1; } return $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element); } /** * Log object creation */ public function logCreate($user, $objectType, $objectId, $objectRef = '', $socid = 0, $anlageId = 0, $data = null) { return $this->log($user, $objectType, $objectId, self::ACTION_CREATE, $objectRef, '', null, $data, $socid, $anlageId); } /** * Log object update */ public function logUpdate($user, $objectType, $objectId, $objectRef = '', $fieldChanged = '', $oldValue = null, $newValue = null, $socid = 0, $anlageId = 0) { return $this->log($user, $objectType, $objectId, self::ACTION_UPDATE, $objectRef, $fieldChanged, $oldValue, $newValue, $socid, $anlageId); } /** * Log object deletion */ public function logDelete($user, $objectType, $objectId, $objectRef = '', $socid = 0, $anlageId = 0, $data = null) { return $this->log($user, $objectType, $objectId, self::ACTION_DELETE, $objectRef, '', $data, null, $socid, $anlageId); } /** * Log object move (position change) */ public function logMove($user, $objectType, $objectId, $objectRef = '', $oldPosition = null, $newPosition = null, $socid = 0, $anlageId = 0) { return $this->log($user, $objectType, $objectId, self::ACTION_MOVE, $objectRef, 'position', $oldPosition, $newPosition, $socid, $anlageId); } /** * Log object duplication */ public function logDuplicate($user, $objectType, $objectId, $objectRef = '', $sourceId = 0, $socid = 0, $anlageId = 0) { return $this->log($user, $objectType, $objectId, self::ACTION_DUPLICATE, $objectRef, '', $sourceId, $objectId, $socid, $anlageId, 'Kopiert von ID '.$sourceId); } /** * Fetch audit log entries for an object * * @param string $objectType Object type * @param int $objectId Object ID * @param int $limit Max entries (0 = no limit) * @return array Array of AuditLog objects */ public function fetchByObject($objectType, $objectId, $limit = 50) { $results = array(); $sql = "SELECT a.*, u.firstname, u.lastname, s.nom as societe_name"; $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON a.fk_societe = s.rowid"; $sql .= " WHERE a.object_type = '".$this->db->escape($objectType)."'"; $sql .= " AND a.object_id = ".((int) $objectId); $sql .= " ORDER BY a.date_action DESC"; if ($limit > 0) { $sql .= " LIMIT ".((int) $limit); } $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $log = new AuditLog($this->db); $log->id = $obj->rowid; $log->object_type = $obj->object_type; $log->object_id = $obj->object_id; $log->object_ref = $obj->object_ref; $log->fk_societe = $obj->fk_societe; $log->fk_anlage = $obj->fk_anlage; $log->action = $obj->action; $log->field_changed = $obj->field_changed; $log->old_value = $obj->old_value; $log->new_value = $obj->new_value; $log->fk_user = $obj->fk_user; $log->user_login = $obj->user_login; $log->date_action = $this->db->jdate($obj->date_action); $log->note = $obj->note; $log->ip_address = $obj->ip_address; $log->user_name = trim($obj->firstname.' '.$obj->lastname); $log->societe_name = $obj->societe_name; $results[] = $log; } $this->db->free($resql); } return $results; } /** * Fetch audit log entries for an Anlage (installation) * * @param int $anlageId Anlage ID * @param int $limit Max entries * @return array Array of AuditLog objects */ public function fetchByAnlage($anlageId, $limit = 100) { $results = array(); $sql = "SELECT a.*, u.firstname, u.lastname"; $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid"; $sql .= " WHERE a.fk_anlage = ".((int) $anlageId); $sql .= " ORDER BY a.date_action DESC"; if ($limit > 0) { $sql .= " LIMIT ".((int) $limit); } $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $log = new AuditLog($this->db); $log->id = $obj->rowid; $log->object_type = $obj->object_type; $log->object_id = $obj->object_id; $log->object_ref = $obj->object_ref; $log->fk_societe = $obj->fk_societe; $log->fk_anlage = $obj->fk_anlage; $log->action = $obj->action; $log->field_changed = $obj->field_changed; $log->old_value = $obj->old_value; $log->new_value = $obj->new_value; $log->fk_user = $obj->fk_user; $log->user_login = $obj->user_login; $log->date_action = $this->db->jdate($obj->date_action); $log->note = $obj->note; $log->ip_address = $obj->ip_address; $log->user_name = trim($obj->firstname.' '.$obj->lastname); $results[] = $log; } $this->db->free($resql); } return $results; } /** * Fetch audit log entries for a customer * * @param int $socid Societe ID * @param int $limit Max entries * @return array Array of AuditLog objects */ public function fetchBySociete($socid, $limit = 100) { $results = array(); $sql = "SELECT a.*, u.firstname, u.lastname"; $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid"; $sql .= " WHERE a.fk_societe = ".((int) $socid); $sql .= " ORDER BY a.date_action DESC"; if ($limit > 0) { $sql .= " LIMIT ".((int) $limit); } $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $log = new AuditLog($this->db); $log->id = $obj->rowid; $log->object_type = $obj->object_type; $log->object_id = $obj->object_id; $log->object_ref = $obj->object_ref; $log->fk_societe = $obj->fk_societe; $log->fk_anlage = $obj->fk_anlage; $log->action = $obj->action; $log->field_changed = $obj->field_changed; $log->old_value = $obj->old_value; $log->new_value = $obj->new_value; $log->fk_user = $obj->fk_user; $log->user_login = $obj->user_login; $log->date_action = $this->db->jdate($obj->date_action); $log->note = $obj->note; $log->ip_address = $obj->ip_address; $log->user_name = trim($obj->firstname.' '.$obj->lastname); $results[] = $log; } $this->db->free($resql); } return $results; } /** * Get human-readable action label * * @return string Translated action label */ public function getActionLabel() { global $langs; switch ($this->action) { case self::ACTION_CREATE: return $langs->trans('AuditActionCreate'); case self::ACTION_UPDATE: return $langs->trans('AuditActionUpdate'); case self::ACTION_DELETE: return $langs->trans('AuditActionDelete'); case self::ACTION_MOVE: return $langs->trans('AuditActionMove'); case self::ACTION_DUPLICATE: return $langs->trans('AuditActionDuplicate'); case self::ACTION_STATUS_CHANGE: return $langs->trans('AuditActionStatus'); default: return $this->action; } } /** * Get human-readable object type label * * @return string Translated object type label */ public function getObjectTypeLabel() { global $langs; switch ($this->object_type) { case self::TYPE_EQUIPMENT: return $langs->trans('Equipment'); case self::TYPE_CARRIER: return $langs->trans('CarrierLabel'); case self::TYPE_PANEL: return $langs->trans('PanelLabel'); case self::TYPE_ANLAGE: return $langs->trans('Installation'); case self::TYPE_CONNECTION: return $langs->trans('Connection'); case self::TYPE_BUSBAR: return $langs->trans('Busbar'); case self::TYPE_EQUIPMENT_TYPE: return $langs->trans('EquipmentType'); case self::TYPE_BUSBAR_TYPE: return $langs->trans('BusbarType'); default: return $this->object_type; } } /** * Get action icon * * @return string FontAwesome icon class */ public function getActionIcon() { switch ($this->action) { case self::ACTION_CREATE: return 'fa-plus-circle'; case self::ACTION_UPDATE: return 'fa-edit'; case self::ACTION_DELETE: return 'fa-trash'; case self::ACTION_MOVE: return 'fa-arrows'; case self::ACTION_DUPLICATE: return 'fa-copy'; case self::ACTION_STATUS_CHANGE: return 'fa-toggle-on'; default: return 'fa-question'; } } /** * Get action color * * @return string CSS color */ public function getActionColor() { switch ($this->action) { case self::ACTION_CREATE: return '#27ae60'; case self::ACTION_UPDATE: return '#3498db'; case self::ACTION_DELETE: return '#e74c3c'; case self::ACTION_MOVE: return '#9b59b6'; case self::ACTION_DUPLICATE: return '#f39c12'; case self::ACTION_STATUS_CHANGE: return '#1abc9c'; default: return '#95a5a6'; } } /** * Clean old audit log entries * * @param int $daysToKeep Number of days to keep (default: 365) * @return int Number of deleted entries or -1 on error */ public function cleanOldEntries($daysToKeep = 365) { $cutoffDate = dol_now() - ($daysToKeep * 24 * 60 * 60); $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element; $sql .= " WHERE date_action < '".$this->db->idate($cutoffDate)."'"; $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); return -1; } return $this->db->affected_rows($resql); } }