/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ CI =& get_instance(); // Load required models $this->CI->load->model('clients_model'); $this->CI->load->model('invoices_model'); $this->CI->load->model('estimates_model'); // Initialize cache $this->CI->load->driver('cache'); } /** * Check if client can access a specific document * * @param int $clientId * @param int $documentId * @param string $documentType Optional document type for optimization * @return bool */ public function canAccessDocument($clientId, $documentId, $documentType = null) { // Input validation if (!is_numeric($clientId) || !is_numeric($documentId) || $clientId <= 0 || $documentId <= 0) { return false; } // Check cache first $cacheKey = $this->cachePrefix . "doc_{$clientId}_{$documentId}"; $cachedResult = $this->CI->cache->get($cacheKey); if ($cachedResult !== false) { return $cachedResult === 'allowed'; } $hasAccess = false; try { // Verify client exists and is active if (!$this->_isClientActiveAndValid($clientId)) { $this->_cacheAccessResult($cacheKey, false); return false; } // If document type is specified, check only that type if ($documentType) { $hasAccess = $this->_checkDocumentTypeAccess($clientId, $documentId, $documentType); } else { // Check all document types $hasAccess = $this->_checkInvoiceAccess($clientId, $documentId) || $this->_checkEstimateAccess($clientId, $documentId) || $this->_checkCreditNoteAccess($clientId, $documentId) || $this->_checkReceiptAccess($clientId, $documentId); } // Cache the result $this->_cacheAccessResult($cacheKey, $hasAccess); } catch (Exception $e) { log_message('error', 'Document access control error: ' . $e->getMessage()); $hasAccess = false; } return $hasAccess; } /** * Check if client can access multiple documents * * @param int $clientId * @param array $documentIds * @return array Associative array [documentId => bool] */ public function canAccessMultipleDocuments($clientId, array $documentIds) { $results = []; foreach ($documentIds as $documentId) { $results[$documentId] = $this->canAccessDocument($clientId, $documentId); } return $results; } /** * Get list of document IDs accessible by client * * @param int $clientId * @param string $documentType Optional filter by document type * @param array $filters Optional additional filters * @return array */ public function getAccessibleDocuments($clientId, $documentType = null, array $filters = []) { // Input validation if (!is_numeric($clientId) || $clientId <= 0) { return []; } // Check if client is valid if (!$this->_isClientActiveAndValid($clientId)) { return []; } $documentIds = []; try { if (!$documentType || $documentType === 'invoice') { $invoiceIds = $this->_getClientInvoiceIds($clientId, $filters); $documentIds = array_merge($documentIds, $invoiceIds); } if (!$documentType || $documentType === 'estimate') { $estimateIds = $this->_getClientEstimateIds($clientId, $filters); $documentIds = array_merge($documentIds, $estimateIds); } if (!$documentType || $documentType === 'credit_note') { $creditNoteIds = $this->_getClientCreditNoteIds($clientId, $filters); $documentIds = array_merge($documentIds, $creditNoteIds); } if (!$documentType || $documentType === 'receipt') { $receiptIds = $this->_getClientReceiptIds($clientId, $filters); $documentIds = array_merge($documentIds, $receiptIds); } } catch (Exception $e) { log_message('error', 'Get accessible documents error: ' . $e->getMessage()); return []; } return array_unique($documentIds); } /** * Validate document access with detailed security checks * * @param int $clientId * @param int $documentId * @param string $action Action being performed (view, download, etc.) * @return array Validation result with details */ public function validateDocumentAccess($clientId, $documentId, $action = 'view') { $result = [ 'allowed' => false, 'reason' => 'Access denied', 'document_type' => null, 'security_level' => 'standard' ]; try { // Basic validation if (!is_numeric($clientId) || !is_numeric($documentId) || $clientId <= 0 || $documentId <= 0) { $result['reason'] = 'Invalid parameters'; return $result; } // Check client validity if (!$this->_isClientActiveAndValid($clientId)) { $result['reason'] = 'Client not active or invalid'; return $result; } // Check document existence and ownership $documentInfo = $this->_getDocumentInfo($documentId); if (!$documentInfo) { $result['reason'] = 'Document not found'; return $result; } if ($documentInfo['client_id'] != $clientId) { $result['reason'] = 'Document does not belong to client'; $this->_logSecurityViolation($clientId, $documentId, $action, 'ownership_violation'); return $result; } // Check action permissions if (!$this->_isActionAllowed($documentInfo['type'], $action)) { $result['reason'] = 'Action not allowed for document type'; return $result; } // Check document-specific security rules if (!$this->_checkDocumentSecurityRules($documentInfo, $action)) { $result['reason'] = 'Document security rules violation'; return $result; } // All checks passed $result['allowed'] = true; $result['reason'] = 'Access granted'; $result['document_type'] = $documentInfo['type']; $result['security_level'] = $this->_getDocumentSecurityLevel($documentInfo); } catch (Exception $e) { log_message('error', 'Document access validation error: ' . $e->getMessage()); $result['reason'] = 'System error during validation'; } return $result; } /** * Log security violation attempt * * @param int $clientId * @param int $documentId * @param string $action * @param string $violationType */ public function logSecurityViolation($clientId, $documentId, $action, $violationType) { $this->_logSecurityViolation($clientId, $documentId, $action, $violationType); } /** * Clear access cache for client * * @param int $clientId */ public function clearClientAccessCache($clientId) { // This would clear all cached access results for the client // Implementation depends on cache driver capabilities $pattern = $this->cachePrefix . "doc_{$clientId}_*"; // For file cache, we'd need to scan and delete // For Redis, we could use pattern deletion // For now, we'll just document the intent log_message('info', "Access cache cleared for client {$clientId}"); } // Private Methods /** * Check if client is active and valid */ private function _isClientActiveAndValid($clientId) { $client = $this->CI->clients_model->get($clientId); return $client && $client['active'] == 1; } /** * Check access for specific document type */ private function _checkDocumentTypeAccess($clientId, $documentId, $documentType) { switch ($documentType) { case 'invoice': return $this->_checkInvoiceAccess($clientId, $documentId); case 'estimate': return $this->_checkEstimateAccess($clientId, $documentId); case 'credit_note': return $this->_checkCreditNoteAccess($clientId, $documentId); case 'receipt': return $this->_checkReceiptAccess($clientId, $documentId); default: return false; } } /** * Check invoice access */ private function _checkInvoiceAccess($clientId, $documentId) { $invoice = $this->CI->invoices_model->get($documentId); return $invoice && $invoice['clientid'] == $clientId; } /** * Check estimate access */ private function _checkEstimateAccess($clientId, $documentId) { $estimate = $this->CI->estimates_model->get($documentId); return $estimate && $estimate['clientid'] == $clientId; } /** * Check credit note access */ private function _checkCreditNoteAccess($clientId, $documentId) { // Credit notes in Perfex CRM are typically linked to invoices $creditNote = $this->CI->db->get_where('tblcreditnotes', ['id' => $documentId])->row_array(); return $creditNote && $creditNote['clientid'] == $clientId; } /** * Check receipt access */ private function _checkReceiptAccess($clientId, $documentId) { // Receipts are typically payment records in Perfex CRM $receipt = $this->CI->db->get_where('tblinvoicepaymentrecords', ['id' => $documentId])->row_array(); if (!$receipt) { return false; } // Check if the payment belongs to an invoice owned by the client $invoice = $this->CI->invoices_model->get($receipt['invoiceid']); return $invoice && $invoice['clientid'] == $clientId; } /** * Cache access result */ private function _cacheAccessResult($cacheKey, $hasAccess) { $value = $hasAccess ? 'allowed' : 'denied'; $this->CI->cache->save($cacheKey, $value, $this->cacheTimeout); } /** * Get client invoice IDs */ private function _getClientInvoiceIds($clientId, array $filters = []) { $this->CI->db->select('id'); $this->CI->db->where('clientid', $clientId); // Apply filters if (isset($filters['status'])) { $this->CI->db->where('status', $filters['status']); } if (isset($filters['from_date'])) { $this->CI->db->where('date >=', $filters['from_date']); } if (isset($filters['to_date'])) { $this->CI->db->where('date <=', $filters['to_date']); } $query = $this->CI->db->get('tblinvoices'); return array_column($query->result_array(), 'id'); } /** * Get client estimate IDs */ private function _getClientEstimateIds($clientId, array $filters = []) { $this->CI->db->select('id'); $this->CI->db->where('clientid', $clientId); // Apply filters if (isset($filters['status'])) { $this->CI->db->where('status', $filters['status']); } if (isset($filters['from_date'])) { $this->CI->db->where('date >=', $filters['from_date']); } if (isset($filters['to_date'])) { $this->CI->db->where('date <=', $filters['to_date']); } $query = $this->CI->db->get('tblestimates'); return array_column($query->result_array(), 'id'); } /** * Get client credit note IDs */ private function _getClientCreditNoteIds($clientId, array $filters = []) { $this->CI->db->select('id'); $this->CI->db->where('clientid', $clientId); // Apply filters if table exists if ($this->CI->db->table_exists('tblcreditnotes')) { if (isset($filters['from_date'])) { $this->CI->db->where('date >=', $filters['from_date']); } if (isset($filters['to_date'])) { $this->CI->db->where('date <=', $filters['to_date']); } $query = $this->CI->db->get('tblcreditnotes'); return array_column($query->result_array(), 'id'); } return []; } /** * Get client receipt IDs */ private function _getClientReceiptIds($clientId, array $filters = []) { // Get receipts through invoice payments $this->CI->db->select('tblinvoicepaymentrecords.id'); $this->CI->db->join('tblinvoices', 'tblinvoices.id = tblinvoicepaymentrecords.invoiceid'); $this->CI->db->where('tblinvoices.clientid', $clientId); // Apply filters if (isset($filters['from_date'])) { $this->CI->db->where('tblinvoicepaymentrecords.date >=', $filters['from_date']); } if (isset($filters['to_date'])) { $this->CI->db->where('tblinvoicepaymentrecords.date <=', $filters['to_date']); } $query = $this->CI->db->get('tblinvoicepaymentrecords'); return array_column($query->result_array(), 'id'); } /** * Get document information */ private function _getDocumentInfo($documentId) { // Try to find document in different tables // Check invoices $invoice = $this->CI->db->get_where('tblinvoices', ['id' => $documentId])->row_array(); if ($invoice) { return [ 'id' => $documentId, 'type' => 'invoice', 'client_id' => $invoice['clientid'], 'status' => $invoice['status'], 'data' => $invoice ]; } // Check estimates $estimate = $this->CI->db->get_where('tblestimates', ['id' => $documentId])->row_array(); if ($estimate) { return [ 'id' => $documentId, 'type' => 'estimate', 'client_id' => $estimate['clientid'], 'status' => $estimate['status'], 'data' => $estimate ]; } // Check credit notes if ($this->CI->db->table_exists('tblcreditnotes')) { $creditNote = $this->CI->db->get_where('tblcreditnotes', ['id' => $documentId])->row_array(); if ($creditNote) { return [ 'id' => $documentId, 'type' => 'credit_note', 'client_id' => $creditNote['clientid'], 'status' => $creditNote['status'] ?? 'active', 'data' => $creditNote ]; } } // Check receipts (payment records) $receipt = $this->CI->db->get_where('tblinvoicepaymentrecords', ['id' => $documentId])->row_array(); if ($receipt) { // Get client ID from associated invoice $invoice = $this->CI->db->get_where('tblinvoices', ['id' => $receipt['invoiceid']])->row_array(); if ($invoice) { return [ 'id' => $documentId, 'type' => 'receipt', 'client_id' => $invoice['clientid'], 'status' => 'paid', 'data' => $receipt ]; } } return null; } /** * Check if action is allowed for document type */ private function _isActionAllowed($documentType, $action) { $allowedActions = [ 'invoice' => ['view', 'download', 'print'], 'estimate' => ['view', 'download', 'print'], 'credit_note' => ['view', 'download', 'print'], 'receipt' => ['view', 'download', 'print'] ]; return isset($allowedActions[$documentType]) && in_array($action, $allowedActions[$documentType]); } /** * Check document-specific security rules */ private function _checkDocumentSecurityRules($documentInfo, $action) { // Example security rules: // Draft documents may have restricted access if ($documentInfo['type'] === 'estimate' && $documentInfo['status'] == 1) { // Draft estimate - only allow view return $action === 'view'; } // Cancelled documents may be read-only if (isset($documentInfo['data']['status']) && $documentInfo['data']['status'] == 5) { // Cancelled - only allow view return $action === 'view'; } // All other cases are allowed by default return true; } /** * Get document security level */ private function _getDocumentSecurityLevel($documentInfo) { // Determine security level based on document properties if ($documentInfo['type'] === 'invoice' && isset($documentInfo['data']['total']) && $documentInfo['data']['total'] > 10000) { return 'high'; // High-value invoices } return 'standard'; } /** * Log security violation */ private function _logSecurityViolation($clientId, $documentId, $action, $violationType) { $logData = [ 'client_id' => $clientId, 'document_id' => $documentId, 'action' => $action, 'violation_type' => $violationType, 'ip_address' => $this->CI->input->ip_address(), 'user_agent' => $this->CI->input->user_agent(), 'timestamp' => date('Y-m-d H:i:s') ]; // Log to system log log_message('warning', 'Security violation: ' . json_encode($logData)); // Could also save to database security log table if it exists if ($this->CI->db->table_exists('tblsecurity_violations')) { $this->CI->db->insert('tblsecurity_violations', $logData); } } }