CI =& get_instance(); } public function record($module, $entityType, $entityId, $action, $before = null, $after = null, $userId = null) { $previous = $this->CI->db->select('record_hash')->order_by('id','DESC')->limit(1)->get('immutable_audit_logs')->row(); $created = date('Y-m-d H:i:s'); $data = array( 'module'=>(string)$module, 'entity_type'=>(string)$entityType, 'entity_id'=>(string)$entityId, 'action'=>(string)$action, 'before_data'=>$this->encode($before), 'after_data'=>$this->encode($after), 'user_id'=>$userId ? (int)$userId : null, 'ip_address'=>isset($this->CI->input) ? $this->CI->input->ip_address() : null, 'user_agent'=>isset($this->CI->input) ? substr((string)$this->CI->input->user_agent(),0,255) : null, 'request_id'=>app_env('HTTP_X_REQUEST_ID', null) ?: null, 'previous_hash'=>$previous ? $previous->record_hash : null, 'created_at'=>$created ); $data['record_hash'] = hash('sha256', ($data['previous_hash'] ?: '') . json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)); if (!$this->CI->db->insert('immutable_audit_logs', $data)) throw new RuntimeException('Gagal menulis audit trail.'); return (int)$this->CI->db->insert_id(); } private function encode($value) { if ($value === null) return null; $value = json_decode(json_encode($value), true); $walk = function (&$item, $key) { if (preg_match('/password|token|secret|authorization/i', (string)$key)) $item = '[REDACTED]'; }; array_walk_recursive($value, $walk); return json_encode($value, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); } }