/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ load->library('encryption'); // Initialize encryption key (should be from secure config) $this->encryptionKey = $this->getEncryptionKey(); // Load database $this->load->database(); } /** * Get secure encryption key * * @return string */ private function getEncryptionKey(): string { // In production, this should come from secure configuration // For now, using app key with salt $appKey = get_option('encryption_key') ?: 'desk_moloni_default_key'; return hash('sha256', $appKey . 'desk_moloni_salt_v3', true); } /** * Encrypt sensitive data using AES-256-GCM * * @param string $data Data to encrypt * @return string Encrypted data with nonce */ protected function encryptData(string $data): string { if (empty($data)) { return $data; } try { // Generate random nonce $nonce = random_bytes(12); // 96-bit nonce for GCM // Encrypt data $encrypted = openssl_encrypt( $data, 'aes-256-gcm', $this->encryptionKey, OPENSSL_RAW_DATA, $nonce, $tag ); if ($encrypted === false) { throw new Exception('Encryption failed'); } // Combine nonce + tag + encrypted data and base64 encode return base64_encode($nonce . $tag . $encrypted); } catch (Exception $e) { log_message('error', 'Desk-Moloni encryption error: ' . $e->getMessage()); throw new Exception('Failed to encrypt sensitive data'); } } /** * Decrypt sensitive data using AES-256-GCM * * @param string $encryptedData Encrypted data with nonce * @return string Decrypted data */ protected function decryptData(string $encryptedData): string { if (empty($encryptedData)) { return $encryptedData; } try { // Decode base64 $data = base64_decode($encryptedData); if ($data === false || strlen($data) < 28) { // 12 + 16 + at least some data throw new Exception('Invalid encrypted data format'); } // Extract components $nonce = substr($data, 0, 12); $tag = substr($data, 12, 16); $encrypted = substr($data, 28); // Decrypt data $decrypted = openssl_decrypt( $encrypted, 'aes-256-gcm', $this->encryptionKey, OPENSSL_RAW_DATA, $nonce, $tag ); if ($decrypted === false) { throw new Exception('Decryption failed - data may be corrupted'); } return $decrypted; } catch (Exception $e) { log_message('error', 'Desk-Moloni decryption error: ' . $e->getMessage()); throw new Exception('Failed to decrypt sensitive data'); } } /** * Validate JSON data * * @param string $jsonString JSON string to validate * @return bool True if valid JSON */ protected function validateJSON(string $jsonString): bool { if ($jsonString === null || $jsonString === '') { return true; // NULL and empty strings are valid } json_decode($jsonString); return json_last_error() === JSON_ERROR_NONE; } /** * Validate ENUM values * * @param string $value Value to validate * @param array $allowedValues Array of allowed ENUM values * @return bool True if value is valid */ protected function validateEnum(string $value, array $allowedValues): bool { return in_array($value, $allowedValues, true); } /** * Get table name with prefix * * @param string $tableSuffix Table suffix (e.g., 'config', 'mapping') * @return string Full table name */ protected function getTableName(string $tableSuffix): string { return $this->tablePrefix . $tableSuffix; } /** * Log database operations for audit trail * * @param string $operation Operation type (create, update, delete) * @param string $table Table name * @param array $data Operation data * @param int|null $recordId Record ID if applicable */ protected function logDatabaseOperation(string $operation, string $table, array $data, ?int $recordId = null): void { try { $logData = [ 'operation' => $operation, 'table_name' => $table, 'record_id' => $recordId, 'data_snapshot' => json_encode($data), 'user_id' => get_staff_user_id(), 'ip_address' => $this->input->ip_address(), 'user_agent' => $this->input->user_agent(), 'created_at' => date('Y-m-d H:i:s') ]; // Insert into audit log (if table exists) if ($this->db->table_exists($this->getTableName('audit_log'))) { $this->db->insert($this->getTableName('audit_log'), $logData); } } catch (Exception $e) { // Don't fail the main operation if logging fails log_message('error', 'Desk-Moloni audit log error: ' . $e->getMessage()); } } /** * Validate required fields * * @param array $data Data to validate * @param array $requiredFields Required field names * @return array Validation errors (empty if valid) */ protected function validateRequiredFields(array $data, array $requiredFields): array { $errors = []; foreach ($requiredFields as $field) { if (!isset($data[$field]) || $data[$field] === '' || $data[$field] === null) { $errors[] = "Field '{$field}' is required"; } } return $errors; } /** * Validate field lengths * * @param array $data Data to validate * @param array $fieldLimits Field length limits ['field' => max_length] * @return array Validation errors */ protected function validateFieldLengths(array $data, array $fieldLimits): array { $errors = []; foreach ($fieldLimits as $field => $maxLength) { if (isset($data[$field]) && strlen($data[$field]) > $maxLength) { $errors[] = "Field '{$field}' exceeds maximum length of {$maxLength} characters"; } } return $errors; } /** * Sanitize data for database insertion * * @param array $data Data to sanitize * @return array Sanitized data */ protected function sanitizeData(array $data): array { $sanitized = []; foreach ($data as $key => $value) { if (is_string($value)) { // Trim whitespace and sanitize $sanitized[$key] = trim($value); } else { $sanitized[$key] = $value; } } return $sanitized; } /** * Check if table exists * * @param string $tableName Table name to check * @return bool True if table exists */ protected function tableExists(string $tableName): bool { return $this->db->table_exists($tableName); } /** * Execute transaction with rollback on failure * * @param callable $callback Function to execute in transaction * @return mixed Result of callback or false on failure */ protected function executeTransaction(callable $callback): mixed { $this->db->trans_begin(); try { $result = $callback(); if ($this->db->trans_status() === false) { throw new Exception('Transaction failed'); } $this->db->trans_commit(); return $result; } catch (Exception $e) { $this->db->trans_rollback(); log_message('error', 'Desk-Moloni transaction error: ' . $e->getMessage()); return false; } } /** * Get human-readable timestamp * * @param string $timestamp Database timestamp * @return string Formatted timestamp */ protected function formatTimestamp($timestamp): ?string { if (empty($timestamp) || $timestamp === '0000-00-00 00:00:00') { return null; } return date('Y-m-d H:i:s', strtotime($timestamp)); } /** * Check if current user has permission for operation * * @param string $permission Permission to check * @return bool True if user has permission */ protected function hasPermission(string $permission): bool { // Check if user is admin or has specific permission if (is_admin()) { return true; } // Check module-specific permissions return has_permission($permission, '', 'view') || has_permission($permission, '', 'create'); } /** * Get current user ID * * @return int|null User ID or null if not logged in */ protected function getCurrentUserId() { return get_staff_user_id(); } }