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

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

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.1/code/includes/Services/QuoteService.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.1/raw/includes/Services/QuoteService.php
- Modified: 2026-02-15T10:35:10+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/QuoteService.php#L10-L20`.

```php
<?php
/**
 * Quote Service Class
 *
 * @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\Quote;
use EasyInvoice\Repositories\QuoteRepository;
use EasyInvoice\Interfaces\QuoteRepositoryInterface;

/**
 * Quote Service Class
 *
 * Handles business logic for quotes and provides extension points for plugins.
 *
 * @since 1.0.0
 */
class QuoteService extends BaseService {
    
    /**
     * Quote repository
     *
     * @var QuoteRepositoryInterface
     */
    private $repository;
    
    /**
     * Constructor
     *
     * @since 1.0.0
     * @param QuoteRepositoryInterface $repository The quote repository
     */
    public function __construct(QuoteRepositoryInterface $repository) {
        parent::__construct('QuoteService');
        $this->repository = $repository;
    }
    
    /**
     * Create a new quote
     *
     * @since 1.0.0
     * @param array $data The quote data
     * @return Quote|false The created quote or false on failure
     */
    public function createQuote(array $data) {
        // Allow plugins to modify data before creation
        $data = apply_filters('easy_invoice_service_quote_create_data', $data);
        
        // Validate required fields
        $required_fields = ['title'];
        $errors = $this->validateRequiredFields($data, $required_fields);
        
        if (!empty($errors)) {
            $this->log('Quote creation failed: ' . implode(', ', $errors), 'error');
            return false;
        }
        
        // Sanitize data
        $sanitized_data = $this->sanitizeQuoteData($data);
        
        // Allow plugins to perform actions before creation
        do_action('easy_invoice_service_quote_before_create', $sanitized_data);
        
        // Create the quote
        $quote = $this->repository->create($sanitized_data);
        
        if ($quote) {
            $this->log('Quote created successfully: ' . $quote->getId());
            
            // Allow plugins to perform actions after creation
            do_action('easy_invoice_service_quote_created', $quote, $sanitized_data);
            
            return $quote;
        }
        
        $this->log('Quote creation failed', 'error');
        return false;
    }
    
    /**
     * Update an existing quote
     *
     * @since 1.0.0
     * @param int $id The quote ID
     * @param array $data The quote data
     * @return Quote|null The updated quote or null on failure
     */
    public function updateQuote(int $id, array $data) {
        // Allow plugins to modify data before update
        $data = apply_filters('easy_invoice_service_quote_update_data', $data, $id);
        
        // Get the existing quote
        $quote = $this->repository->find($id);
        if (!$quote) {
            $this->log('Quote not found for update: ' . $id, 'error');
            return null;
        }
        
        // Sanitize data
        $sanitized_data = $this->sanitizeQuoteData($data);
        
        // Allow plugins to perform actions before update
        do_action('easy_invoice_service_quote_before_update', $quote, $sanitized_data);
        
        // Update the quote
        $updated_quote = $this->repository->update($id, $sanitized_data);
        
        if ($updated_quote) {
            $this->log('Quote updated successfully: ' . $id);
            
            // Allow plugins to perform actions after update
            do_action('easy_invoice_service_quote_updated', $updated_quote, $sanitized_data);
            
            return $updated_quote;
        }
        
        $this->log('Quote update failed: ' . $id, 'error');
        return null;
    }
    
    /**
     * Delete a quote
     *
     * @since 1.0.0
     * @param int $id The quote ID
     * @return bool True if deleted successfully
     */
    public function deleteQuote(int $id): bool {
        // Get the quote before deletion
        $quote = $this->repository->find($id);
        if (!$quote) {
            $this->log('Quote not found for deletion: ' . $id, 'error');
            return false;
        }
        
        // Allow plugins to perform actions before deletion
        do_action('easy_invoice_service_quote_before_delete', $quote);
        
        // Delete the quote
        $deleted = $this->repository->delete($id);
        
        if ($deleted) {
            $this->log('Quote deleted successfully: ' . $id);
            
            // Allow plugins to perform actions after deletion
            do_action('easy_invoice_service_quote_deleted', $id);
            
            return true;
        }
        
        $this->log('Quote deletion failed: ' . $id, 'error');
        return false;
    }
    
    /**
     * Get quote by ID
     *
     * @since 1.0.0
     * @param int $id The quote ID
     * @return Quote|null The quote or null if not found
     */
    public function getQuote(int $id) {
        $quote = $this->repository->find($id);
        
        // Allow plugins to modify the found quote
        return apply_filters('easy_invoice_service_quote_found', $quote, $id);
    }
    
    /**
     * Get all quotes
     *
     * @since 1.0.0
     * @param array $args Optional arguments to filter the results
     * @return array Array of Quote models
     */
    public function getAllQuotes(array $args = []): array {
        $quotes = $this->repository->all($args);
        
        // Allow plugins to modify the quotes list
        return apply_filters('easy_invoice_service_quotes_found', $quotes, $args);
    }
    
    /**
     * Get quotes by customer
     *
     * @since 1.0.0
     * @param int $customer_id The customer ID
     * @return array Array of Quote models
     */
    public function getQuotesByCustomer(int $customer_id): array {
        $quotes = $this->repository->findByCustomer($customer_id);
        
        // Allow plugins to modify the filtered results
        return apply_filters('easy_invoice_service_quotes_by_customer', $quotes, $customer_id);
    }
    
    /**
     * Get quotes by status
     *
     * @since 1.0.0
     * @param string $status The quote status
     * @return array Array of Quote models
     */
    public function getQuotesByStatus(string $status): array {
        $quotes = $this->repository->findByStatus($status);
        
        // Allow plugins to modify the filtered results
        return apply_filters('easy_invoice_service_quotes_by_status', $quotes, $status);
    }
    
    /**
     * Get quotes by expiry date
     *
     * @since 1.0.0
     * @param string $start_date The start date in 'Y-m-d' format
     * @param string $end_date The end date in 'Y-m-d' format
     * @return array Array of Quote models
     */
    public function getQuotesByExpiryDate(string $start_date, ?string $end_date = null): array {
        $quotes = $this->repository->findByExpiryDate($start_date, $end_date);
        
        // Allow plugins to modify the filtered results
        return apply_filters('easy_invoice_service_quotes_by_expiry_date', $quotes, $start_date, $end_date);
    }
    
    /**
     * Count quotes
     *
     * @since 1.0.0
     * @param array $args Optional arguments to filter the results
     * @return int Number of quotes
     */
    public function countQuotes(array $args = []): int {
        $count = $this->repository->count($args);
        
        // Allow plugins to modify the count
        return apply_filters('easy_invoice_service_quote_count', $count, $args);
    }
    
    /**
     * Calculate quote total
     *
     * @since 1.0.0
     * @param Quote $quote The quote
     * @return float The calculated total
     */
    public function calculateQuoteTotal(Quote $quote): float {
        $items = $quote->getItems();
        $subtotal = 0;
        
        // Calculate subtotal from items
        foreach ($items as $item) {
            $quantity = floatval($item['quantity'] ?? 0);
            $price = floatval($item['price'] ?? 0);
            $subtotal += $quantity * $price;
        }
        
        // Apply discount
        $discount_type = $quote->getDiscountType();
        $discount_value = floatval($quote->getDiscountValue() ?? 0);
        
        if ($discount_type === 'percentage' && $discount_value > 0) {
            $discount_amount = $subtotal * ($discount_value / 100);
            $subtotal -= $discount_amount;
        } elseif ($discount_type === 'fixed' && $discount_value > 0) {
            $subtotal -= $discount_value;
        }
        
        // Apply tax
        $tax_rate = floatval($quote->getTaxRate() ?? 0);
        $prices_include_tax = $quote->getPricesIncludeTax();
        
        if ($tax_rate > 0) {
            if ($prices_include_tax) {
                // Tax is already included in prices
                $total = $subtotal;
            } else {
                // Add tax to subtotal
                $tax_amount = $subtotal * ($tax_rate / 100);
                $total = $subtotal + $tax_amount;
            }
        } else {
            $total = $subtotal;
        }
        
        // Allow plugins to modify the calculated total
        return apply_filters('easy_invoice_service_quote_total', $total, $quote, $subtotal);
    }
    
    /**
     * Accept a quote
     *
     * @since 1.0.0
     * @param int $quote_id The quote ID
     * @return bool True if quote was accepted successfully
     */
    public function acceptQuote(int $quote_id): bool {
        $quote = $this->repository->find($quote_id);
        if (!$quote) {
            $this->log('Quote not found for acceptance: ' . $quote_id, 'error');
            return false;
        }
        
        // Allow plugins to perform actions before acceptance
        do_action('easy_invoice_service_quote_before_accept', $quote);
        
        // Update quote status to accepted
        $updated = $this->repository->update($quote_id, ['status' => 'accepted']);
        
        if ($updated) {
            $this->log('Quote accepted successfully: ' . $quote_id);
            
            // Send acceptance notification
            $this->sendQuoteAcceptanceNotification($updated);
            
            // Allow plugins to perform actions after acceptance
            do_action('easy_invoice_service_quote_accepted', $updated);
            
            return true;
        }
        
        $this->log('Quote acceptance failed: ' . $quote_id, 'error');
        return false;
    }
    
    /**
     * Decline a quote
     *
     * @since 1.0.0
     * @param int $quote_id The quote ID
     * @param string $reason The decline reason
     * @return bool True if quote was declined successfully
     */
    public function declineQuote(int $quote_id, string $reason = ''): bool {
        $quote = $this->repository->find($quote_id);
        if (!$quote) {
            $this->log('Quote not found for decline: ' . $quote_id, 'error');
            return false;
        }
        
        // Allow plugins to perform actions before decline
        do_action('easy_invoice_service_quote_before_decline', $quote, $reason);
        
        // Update quote status to declined
        $update_data = ['status' => 'declined'];
        if (!empty($reason)) {
            $update_data['decline_reason'] = $reason;
        }
        
        $updated = $this->repository->update($quote_id, $update_data);
        
        if ($updated) {
            $this->log('Quote declined successfully: ' . $quote_id);
            
            // Send decline notification
            $this->sendQuoteDeclineNotification($updated, $reason);
            
            // Allow plugins to perform actions after decline
            do_action('easy_invoice_service_quote_declined', $updated, $reason);
            
            return true;
        }
        
        $this->log('Quote decline failed: ' . $quote_id, 'error');
        return false;
    }
    
    /**
     * Send quote to customer
     *
     * @since 1.0.0
     * @param Quote $quote The quote
     * @param string $email The customer email
     * @return bool True if email was sent successfully
     */
    public function sendQuoteToCustomer(Quote $quote, string $email): bool {
        // Allow plugins to modify email data
        $email_data = apply_filters('easy_invoice_service_quote_email_data', [
            'to' => $email,
            'subject' => sprintf(__('Quote #%s from %s', 'easy-invoice'), $quote->getNumber(), get_bloginfo('name')),
            'message' => $this->generateQuoteEmailMessage($quote),
            'headers' => ['Content-Type: text/html; charset=UTF-8']
        ], $quote);
        
        // Allow plugins to handle email sending
        $sent = apply_filters('easy_invoice_service_quote_email_send', null, $email_data, $quote);
        
        if ($sent === null) {
            $sent = $this->sendEmail(
                $email_data['to'],
                $email_data['subject'],
                $email_data['message'],
                $email_data['headers']
            );
        }
        
        if ($sent) {
            // Allow plugins to perform actions after email sent
            do_action('easy_invoice_service_quote_email_sent', $quote, $email, $sent);
        }
        
        return $sent;
    }
    
    /**
     * Send quote acceptance notification
     *
     * @since 1.0.0
     * @param Quote $quote The quote
     * @return bool True if notification was sent successfully
     */
    protected function sendQuoteAcceptanceNotification(Quote $quote): bool {
        $admin_email = get_option('admin_email');
        $subject = sprintf(__('Quote #%s Accepted', 'easy-invoice'), $quote->getNumber());
        
        $message = sprintf(
            '<p>%s</p>',
            __('A quote has been accepted by the customer.', 'easy-invoice')
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Quote Number', 'easy-invoice'),
            $quote->getNumber()
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Customer', 'easy-invoice'),
            $quote->getCustomerName()
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Amount', 'easy-invoice'),
            $this->formatCurrency($this->calculateQuoteTotal($quote), $quote->getCurrencyCode(), $quote->getCurrencyPosition())
        );
        
        // Allow plugins to modify the notification message
        $message = apply_filters('easy_invoice_service_quote_acceptance_notification_message', $message, $quote);
        
        return $this->sendEmail($admin_email, $subject, $message, ['Content-Type: text/html; charset=UTF-8']);
    }
    
    /**
     * Send quote decline notification
     *
     * @since 1.0.0
     * @param Quote $quote The quote
     * @param string $reason The decline reason
     * @return bool True if notification was sent successfully
     */
    protected function sendQuoteDeclineNotification(Quote $quote, string $reason = ''): bool {
        $admin_email = get_option('admin_email');
        $subject = sprintf(__('Quote #%s Declined', 'easy-invoice'), $quote->getNumber());
        
        $message = sprintf(
            '<p>%s</p>',
            __('A quote has been declined by the customer.', 'easy-invoice')
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Quote Number', 'easy-invoice'),
            $quote->getNumber()
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Customer', 'easy-invoice'),
            $quote->getCustomerName()
        );
        
        if (!empty($reason)) {
            $message .= sprintf(
                '<p><strong>%s:</strong> %s</p>',
                __('Reason', 'easy-invoice'),
                esc_html($reason)
            );
        }
        
        // Allow plugins to modify the notification message
        $message = apply_filters('easy_invoice_service_quote_decline_notification_message', $message, $quote, $reason);
        
        return $this->sendEmail($admin_email, $subject, $message, ['Content-Type: text/html; charset=UTF-8']);
    }
    
    /**
     * Generate quote email message
     *
     * @since 1.0.0
     * @param Quote $quote The quote
     * @return string The email message
     */
    protected function generateQuoteEmailMessage(Quote $quote): string {
        $message = sprintf(
            '<p>%s</p>',
            __('Please find attached your quote.', 'easy-invoice')
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Quote Number', 'easy-invoice'),
            $quote->getNumber()
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Amount', 'easy-invoice'),
            $this->formatCurrency($this->calculateQuoteTotal($quote), $quote->getCurrencyCode(), $quote->getCurrencyPosition())
        );
        
        $message .= sprintf(
            '<p><strong>%s:</strong> %s</p>',
            __('Valid Until', 'easy-invoice'),
            $quote->getExpiryDate()
        );
        
        // Allow plugins to modify the email message
        return apply_filters('easy_invoice_service_quote_email_message', $message, $quote);
    }
    
    /**
     * Sanitize quote data
     *
     * @since 1.0.0
     * @param array $data The quote data
     * @return array The sanitized data
     */
    protected function sanitizeQuoteData(array $data): array {
        $sanitization_rules = [
            'title' => 'text_field',
            'description' => 'textarea',
            'number' => 'text_field',
            'issue_date' => 'text_field',
            'expiry_date' => 'text_field',
            'status' => 'text_field',
            'notes' => 'textarea',
            'terms' => 'html',
            'internal_notes' => 'textarea',
            'payment_instructions' => 'textarea',
            'payment_gateways' => 'array',
            'quote_template' => 'text_field',
            'customer_name' => 'text_field',
            'customer_address' => 'textarea',
            'customer_email' => 'email',
            'shipping_name' => 'text_field',
            'shipping_address' => 'textarea',
            'discount_type' => 'text_field',
            'discount_value' => 'float',
            'tax_rate' => 'float',
            'calculation_method' => 'text_field',
            'prices_include_tax' => 'int',
            'currency_code' => 'text_field',
            'currency_position' => 'text_field',
            'footer_text' => 'textarea',
            'items' => 'array',
            'custom_fields' => 'array',
            'decline_reason' => 'textarea'
        ];
        
        return $this->sanitizeData($data, $sanitization_rules);
    }
} 
```
