# easy-invoice/2.3.1/includes/Services/QuoteLogService.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.3.1. 494 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.1/code/includes/Services/QuoteLogService.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.1/raw/includes/Services/QuoteLogService.php
- Modified: 2025-08-14T09:51:16+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.3.1/code/includes/Services/QuoteLogService.php#L10-L20`.

```php
<?php
/**
 * Quote Log Service
 *
 * @package     EasyInvoice
 * @author      Your Name
 * @copyright   Copyright (c) 2023, Your Company
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       1.0.0
 */

namespace EasyInvoice\Services;

use EasyInvoice\Models\QuoteLog;

/**
 * Quote Log Service
 *
 * Handles quote activity logging using post meta.
 *
 * @since 1.0.0
 */
class QuoteLogService extends BaseService {
    
    /**
     * Meta key for storing quote logs
     *
     * @var string
     */
    const LOG_META_KEY = '_quote_logs';
    
    /**
     * Constructor
     *
     * @since 1.0.0
     */
    public function __construct() {
        parent::__construct('QuoteLogService');
    }
    
    /**
     * Log a quote activity
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param string $action Action type
     * @param string $description Action description
     * @param array $additional_data Additional data to store
     * @return bool True if logged successfully
     */
    public function logActivity(int $quote_id, string $action, string $description, array $additional_data = []): bool {
        try {
            // Get current user info
            $current_user = wp_get_current_user();
            $user_id = $current_user->ID;
            $user_name = $current_user->display_name ?: $current_user->user_login;
            
            // Get IP address
            $ip_address = $this->getClientIp();
            
            // Get user agent
            $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
            
            // Create log entry
            $log_entry = [
                'action' => $action,
                'description' => $description,
                'user_id' => $user_id,
                'user_name' => $user_name,
                'ip_address' => $ip_address,
                'user_agent' => $user_agent,
                'additional_data' => json_encode($additional_data),
                'created_date' => current_time('mysql'),
            ];
            
            // Validate log entry structure
            if (!isset($log_entry['created_date']) || empty($log_entry['created_date'])) {
                $this->log("Invalid log entry structure for quote {$quote_id}", 'error');
                return false;
            }
            
            // Get existing logs data (raw array, not QuoteLog objects)
            $existing_logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
            
            if (!is_array($existing_logs_data)) {
                $existing_logs_data = [];
            }
            
            // Add new log entry
            $existing_logs_data[] = $log_entry;
            
            // Store logs in post meta
            $result = update_post_meta($quote_id, self::LOG_META_KEY, $existing_logs_data);
            
            if ($result) {
                $this->log("Quote activity logged: {$action} for quote {$quote_id}");
                
                // Allow plugins to perform actions after logging
                do_action('easy_invoice_quote_activity_logged', $quote_id, $action, $description, $additional_data);
                
                return true;
            }
            
            $this->log("Failed to log quote activity: {$action} for quote {$quote_id}", 'error');
            return false;
            
        } catch (\Exception $e) {
            $this->log("Exception while logging quote activity: " . $e->getMessage(), 'error');
            return false;
        }
    }
    
    /**
     * Get logs for a quote
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param int $limit Number of logs to return (0 for all)
     * @return array Array of QuoteLog objects
     */
    public function getLogs(int $quote_id, int $limit = 0): array {
        $logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
        
        if (!is_array($logs_data)) {
            return [];
        }
        
        // Filter out any non-array entries and ensure proper structure
        $valid_logs_data = [];
        $has_corruption = false;
        
        foreach ($logs_data as $log_data) {
            if (is_array($log_data) && isset($log_data['created_date']) && !empty($log_data['created_date'])) {
                $valid_logs_data[] = $log_data;
            } else {
                $has_corruption = true;
            }
        }
        
        // If we found corruption, clean it up
        if ($has_corruption) {
            $this->cleanupCorruptedLogs($quote_id);
        }
        
        if (empty($valid_logs_data)) {
            return [];
        }
        
        // Sort by created date (newest first) - sort the raw data before converting to objects
        usort($valid_logs_data, function($a, $b) {
            return strtotime($b['created_date']) - strtotime($a['created_date']);
        });
        
        // Apply limit if specified
        if ($limit > 0) {
            $valid_logs_data = array_slice($valid_logs_data, 0, $limit);
        }
        
        // Convert to QuoteLog objects
        $logs = [];
        foreach ($valid_logs_data as $log_data) {
            try {
                $logs[] = new QuoteLog($log_data);
            } catch (\Exception $e) {
                // Skip invalid log entries
                continue;
            }
        }
        
        return $logs;
    }
    
    /**
     * Get latest log for a quote
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @return QuoteLog|null
     */
    public function getLatestLog(int $quote_id): ?QuoteLog {
        $logs = $this->getLogs($quote_id, 1);
        return !empty($logs) ? $logs[0] : null;
    }
    
    /**
     * Get logs by action
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param string $action Action type
     * @return array Array of QuoteLog objects
     */
    public function getLogsByAction(int $quote_id, string $action): array {
        $all_logs = $this->getLogs($quote_id);
        $filtered_logs = [];
        
        foreach ($all_logs as $log) {
            if ($log->getAction() === $action) {
                $filtered_logs[] = $log;
            }
        }
        
        return $filtered_logs;
    }
    
    /**
     * Clear logs for a quote
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @return bool True if cleared successfully
     */
    public function clearLogs(int $quote_id): bool {
        $result = delete_post_meta($quote_id, self::LOG_META_KEY);
        
        if ($result) {
            $this->log("Quote logs cleared for quote {$quote_id}");
            do_action('easy_invoice_quote_logs_cleared', $quote_id);
        }
        
        return $result;
    }
    
    /**
     * Clean up corrupted log data
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @return bool True if cleaned successfully
     */
    public function cleanupCorruptedLogs(int $quote_id): bool {
        $logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
        
        if (!is_array($logs_data)) {
            return true; // No data to clean
        }
        
        $valid_logs_data = [];
        foreach ($logs_data as $log_data) {
            if (is_array($log_data) && isset($log_data['created_date']) && !empty($log_data['created_date'])) {
                $valid_logs_data[] = $log_data;
            }
        }
        
        // Update with only valid log entries
        $result = update_post_meta($quote_id, self::LOG_META_KEY, $valid_logs_data);
        
        if ($result) {
            $this->log("Cleaned up corrupted logs for quote {$quote_id}");
        }
        
        return $result;
    }
    
    /**
     * Get client IP address
     *
     * @since 1.0.0
     * @return string
     */
    private function getClientIp(): string {
        $ip_keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
        
        foreach ($ip_keys as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                foreach (explode(',', $_SERVER[$key]) as $ip) {
                    $ip = trim($ip);
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                        return $ip;
                    }
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? '';
    }
    
    /**
     * Log quote creation
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logCreation(int $quote_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'created',
            __('Quote created', 'easy-invoice'),
            $additional_data
        );
    }
    
    /**
     * Log quote update
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $changes Array of changes made
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logUpdate(int $quote_id, array $changes = [], array $additional_data = []): bool {
        $description = __('Quote updated', 'easy-invoice');
        
        if (!empty($changes)) {
            $change_descriptions = [];
            foreach ($changes as $field => $value) {
                $change_descriptions[] = ucfirst(easy_invoice_str_replace('_', ' ', $field)) . ': ' . $value;
            }
            $description .= ' - ' . implode(', ', $change_descriptions);
        }
        
        return $this->logActivity(
            $quote_id,
            'updated',
            $description,
            array_merge(['changes' => $changes], $additional_data)
        );
    }
    
    /**
     * Log quote acceptance
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logAcceptance(int $quote_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'accepted',
            __('Quote accepted by client', 'easy-invoice'),
            $additional_data
        );
    }
    
    /**
     * Log quote decline
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param string $reason Decline reason
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logDecline(int $quote_id, string $reason = '', array $additional_data = []): bool {
        $description = __('Quote declined by client', 'easy-invoice');
        if (!empty($reason)) {
            $description .= ' - ' . $reason;
        }
        
        return $this->logActivity(
            $quote_id,
            'declined',
            $description,
            array_merge(['reason' => $reason], $additional_data)
        );
    }
    
    /**
     * Log quote sent
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param string $email Email address
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logSent(int $quote_id, string $email = '', array $additional_data = []): bool {
        $description = __('Quote sent to client', 'easy-invoice');
        if (!empty($email)) {
            $description .= ' - ' . $email;
        }
        
        return $this->logActivity(
            $quote_id,
            'sent',
            $description,
            array_merge(['email' => $email], $additional_data)
        );
    }
    
    /**
     * Log quote viewed
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logViewed(int $quote_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'viewed',
            __('Quote viewed by client', 'easy-invoice'),
            $additional_data
        );
    }
    
    /**
     * Log status change
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param string $old_status Old status
     * @param string $new_status New status
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logStatusChange(int $quote_id, string $old_status, string $new_status, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'status_changed',
            sprintf(__('Status changed from %s to %s', 'easy-invoice'), $old_status, $new_status),
            array_merge([
                'old_status' => $old_status,
                'new_status' => $new_status
            ], $additional_data)
        );
    }
    
    /**
     * Log quote to invoice conversion
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param int $invoice_id Invoice ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logConversionToInvoice(int $quote_id, int $invoice_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'converted_to_invoice',
            sprintf(__('Quote converted to invoice #%d', 'easy-invoice'), $invoice_id),
            array_merge(['invoice_id' => $invoice_id], $additional_data)
        );
    }
    
    /**
     * Log quote to invoice duplication
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param int $invoice_id Invoice ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logDuplicationToInvoice(int $quote_id, int $invoice_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'duplicated_to_invoice',
            sprintf(__('Quote duplicated to invoice #%d', 'easy-invoice'), $invoice_id),
            array_merge(['invoice_id' => $invoice_id], $additional_data)
        );
    }
    
    /**
     * Log quote deletion
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logDeletion(int $quote_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'deleted',
            __('Quote deleted', 'easy-invoice'),
            $additional_data
        );
    }
    
    /**
     * Log quote restoration
     *
     * @since 1.0.0
     * @param int $quote_id Quote ID
     * @param array $additional_data Additional data
     * @return bool
     */
    public function logRestoration(int $quote_id, array $additional_data = []): bool {
        return $this->logActivity(
            $quote_id,
            'restored',
            __('Quote restored', 'easy-invoice'),
            $additional_data
        );
    }
} 
```
