getDefaultTabsArray(); // Apply filter to allow pro plugins to modify tabs $all_tabs = apply_filters('easy_invoice_quote_form_tabs', $all_tabs); // Register all tabs from the array foreach ($all_tabs as $tab_id => $tab_config) { $this->registerTab($tab_id, $tab_config); } } /** * Get default tabs array * * @since 1.0.0 * @return array */ private function getDefaultTabsArray(): array { return [ 'quote-tab' => [ 'label' => __('Quote Details', 'easy-invoice'), 'icon' => 'fas fa-file-invoice', 'active' => true, 'order' => 1 ], 'items-tab' => [ 'label' => __('Items', 'easy-invoice'), 'icon' => 'fas fa-list', 'active' => false, 'order' => 2 ], 'client-tab' => [ 'label' => __('Client', 'easy-invoice'), 'icon' => 'fas fa-user', 'active' => false, 'order' => 3 ], 'discounts-taxes-tab' => [ 'label' => __('Discounts & Taxes', 'easy-invoice'), 'icon' => 'fas fa-percentage', 'active' => false, 'order' => 4 ], 'payments-tab' => [ 'label' => __('Payment', 'easy-invoice'), 'icon' => 'fas fa-credit-card', 'active' => false, 'order' => 5 ] ]; } /** * Register default fields * * @since 1.0.0 * @return void */ public function registerDefaultFields(): void { // Define all fields in a single array for easy modification by pro plugins $all_fields = $this->getDefaultFieldsArray(); // Apply filter to allow pro plugins to modify fields $all_fields = apply_filters('easy_invoice_quote_form_fields', $all_fields); // Register all fields from the array foreach ($all_fields as $field_config) { $tab_id = $field_config['tab_id'] ?? ''; unset($field_config['tab_id']); // Remove tab_id from config if (!empty($tab_id)) { $this->registerField($tab_id, $field_config); } } } /** * Get default fields array * * @since 1.0.0 * @return array */ private function getDefaultFieldsArray(): array { return [ // Quote Tab Fields [ 'tab_id' => 'quote-tab', 'id' => 'quote_template', 'type' => 'hidden', 'label' => __('Quote Template', 'easy-invoice'), 'name' => 'quote_template', 'required' => false, 'grid_cols' => 'sm:col-span-6', 'order' => 0, 'default_value' => 'standard', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function($value) { return !empty(trim($value)) ? true : __('Quote template is required.', 'easy-invoice'); } ], [ 'tab_id' => 'quote-tab', 'id' => 'quote_title', 'type' => 'text', 'label' => __('Quote Title', 'easy-invoice'), 'name' => 'title', 'placeholder' => __('Enter quote title', 'easy-invoice'), 'required' => true, 'grid_cols' => 'sm:col-span-6', 'order' => 1, 'default_value' => __('New Quote', 'easy-invoice'), 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function($value) { return !empty(trim($value)) ? true : __('Quote title is required.', 'easy-invoice'); } ], [ 'tab_id' => 'quote-tab', 'id' => 'quote_description', 'type' => 'textarea', 'label' => __('Quote Description', 'easy-invoice'), 'name' => 'description', 'placeholder' => __('Enter quote description or additional details', 'easy-invoice'), 'required' => false, 'grid_cols' => 'sm:col-span-6', 'rows' => 3, 'order' => 2, 'description' => __('Optional description or additional details about this quote.', 'easy-invoice'), 'sanitize_callback' => 'sanitize_textarea_field', ], [ 'tab_id' => 'quote-tab', 'id' => 'quote-number', 'type' => 'text', 'label' => __('Quote Number', 'easy-invoice'), 'name' => 'number', 'placeholder' => __('QT-001', 'easy-invoice'), 'required' => true, 'readonly' => true, 'description' => __('This is the unique number for your quote. It cannot be changed.', 'easy-invoice'), 'grid_cols' => 'sm:col-span-3', 'order' => 3, 'default_value' => function() { // For new quotes, show the next number without incrementing // For existing quotes, this will be overridden by the quote object's getNumber() method if (class_exists('\\EasyInvoice\\Services\\QuoteNumberService')) { $service = new \EasyInvoice\Services\QuoteNumberService(); return $service->getNextNumber(); } return 'QT-' . str_pad(time(), 6, '0', STR_PAD_LEFT); }, 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function($value) { return !empty(trim($value)) ? true : __('Quote number is required.', 'easy-invoice'); } ], [ 'tab_id' => 'quote-tab', 'id' => 'quote-date', 'type' => 'date', 'label' => __('Quote Date', 'easy-invoice'), 'name' => 'issue_date', 'required' => true, 'grid_cols' => 'sm:col-span-3', 'order' => 4, 'default_value' => date('Y-m-d'), 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function($value) { return !empty($value) && strtotime($value) ? true : __('Please enter a valid quote date.', 'easy-invoice'); } ], [ 'tab_id' => 'quote-tab', 'id' => 'expiry-date', 'type' => 'date', 'label' => __('Expiry Date', 'easy-invoice'), 'name' => 'expiry_date', 'required' => true, 'grid_cols' => 'sm:col-span-3', 'order' => 5, 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function($value) { return !empty($value) && strtotime($value) ? true : __('Please enter a valid expiry date.', 'easy-invoice'); } ], [ 'tab_id' => 'quote-tab', 'id' => 'status', 'type' => 'select', 'label' => __('Status', 'easy-invoice'), 'name' => 'status', 'required' => true, 'grid_cols' => 'sm:col-span-3', 'options' => [ 'available' => __('Available', 'easy-invoice'), 'draft' => __('Draft', 'easy-invoice'), 'accepted' => __('Accepted', 'easy-invoice'), 'declined' => __('Declined', 'easy-invoice'), 'cancelled' => __('Cancelled', 'easy-invoice'), 'expired' => __('Expired', 'easy-invoice'), 'sent' => __('Sent', 'easy-invoice') ], 'order' => 5 ], [ 'tab_id' => 'quote-tab', 'id' => 'notes', 'type' => 'textarea', 'label' => __('Notes', 'easy-invoice'), 'name' => 'notes', 'placeholder' => __('Additional notes for the client', 'easy-invoice'), 'grid_cols' => 'sm:col-span-6', 'rows' => 3, 'section' => 'notes', 'order' => 6, 'description' => __('These notes will be visible to your client on the quote.', 'easy-invoice'), ], [ 'tab_id' => 'quote-tab', 'id' => 'internal_notes', 'type' => 'textarea', 'label' => __('Internal Notes', 'easy-invoice'), 'name' => 'internal_notes', 'placeholder' => __('Internal notes (not visible to client)', 'easy-invoice'), 'grid_cols' => 'sm:col-span-6', 'rows' => 3, 'section' => 'notes', 'order' => 7, 'description' => __('Only you and your team can see these notes. Clients will not see them.', 'easy-invoice'), ], [ 'tab_id' => 'quote-tab', 'id' => 'terms', 'type' => 'textarea', 'label' => __('Quote Terms', 'easy-invoice'), 'name' => 'terms', 'placeholder' => __('Quote terms and conditions', 'easy-invoice'), 'grid_cols' => 'sm:col-span-6', 'rows' => 3, 'section' => 'notes', 'order' => 8, 'default_value' => function() { return \EasyInvoice\Controllers\SettingsController::getQuoteTermsConditions(); }, 'description' => __('Specify the terms and conditions for this quote.', 'easy-invoice'), ], // Client Tab Fields [ 'tab_id' => 'client-tab', 'id' => 'client_id', 'type' => 'select', 'label' => __('Select Client', 'easy-invoice'), 'name' => 'client_id', 'required' => false, 'grid_cols' => 'sm:col-span-6', 'options' => $this->getClientOptions(), 'order' => 1, 'description' => __('Choose an existing client or add a new one.', 'easy-invoice'), ], // Discounts & Taxes Tab Fields [ 'tab_id' => 'discounts-taxes-tab', 'name' => 'discount_type', 'type' => 'select', 'label' => __('Discount Type', 'easy-invoice'), 'grid_cols' => 'sm:col-span-2', 'section' => 'discount', 'options' => [ 'none' => __('No Discount', 'easy-invoice'), 'percentage' => __('Percentage', 'easy-invoice'), 'fixed' => __('Fixed Amount', 'easy-invoice') ], 'order' => 1, 'description' => __('Choose how the discount should be applied to this quote.', 'easy-invoice'), ], [ 'tab_id' => 'discounts-taxes-tab', 'name' => 'discount_value', 'type' => 'number', 'label' => __('Discount Value', 'easy-invoice'), 'placeholder' => '0.00', 'grid_cols' => 'sm:col-span-2', 'section' => 'discount', 'min' => '0', 'step' => '0.01', 'order' => 2, 'sanitize_callback' => 'floatval', 'validate_callback' => function($value) { return is_numeric($value) && $value >= 0 ? true : __('Discount value must be a non-negative number.', 'easy-invoice'); }, 'description' => __('Enter the discount amount or percentage for this quote.', 'easy-invoice'), ], [ 'tab_id' => 'discounts-taxes-tab', 'name' => 'discount_calculation_method', 'type' => 'select', 'label' => __('Calculation Method', 'easy-invoice'), 'grid_cols' => 'sm:col-span-2', 'section' => 'tax', 'options' => [ 'before_tax' => __('Before Tax', 'easy-invoice'), 'after_tax' => __('After Tax', 'easy-invoice') ], 'default_value' => 'before_tax', 'order' => 3, 'description' => __('Apply discount before tax (reduces taxable amount) or after tax (tax on full amount)', 'easy-invoice'), ], [ 'tab_id' => 'discounts-taxes-tab', 'name' => 'tax_rate', 'type' => 'number', 'label' => __('Tax Rate (%)', 'easy-invoice'), 'placeholder' => '0.00', 'grid_cols' => 'sm:col-span-3', 'section' => 'tax', 'min' => '0', 'max' => '100', 'step' => '0.01', 'order' => 4, 'default_value' => function() { return easy_invoice_get_tax_rate(); }, 'sanitize_callback' => 'floatval', 'validate_callback' => function($value) { return is_numeric($value) && $value >= 0 && $value <= 100 ? true : __('Tax rate must be between 0 and 100.', 'easy-invoice'); }, 'description' => __('The tax rate that will be applied to taxable items in this quote.', 'easy-invoice'), ], [ 'tab_id' => 'discounts-taxes-tab', 'name' => 'prices_include_tax', 'type' => 'select', 'label' => __('Price Includes Tax', 'easy-invoice'), 'grid_cols' => 'sm:col-span-3', 'section' => 'tax', 'options' => [ 'no' => __('No', 'easy-invoice'), 'yes' => __('Yes', 'easy-invoice') ], 'order' => 5, 'description' => __('Choose whether the prices in this quote include tax or not.', 'easy-invoice'), ], // Payments Tab Fields // Currency Section in Payment Tab [ 'tab_id' => 'payments-tab', 'id' => 'currency_code', 'type' => 'select', 'label' => __('Currency', 'easy-invoice'), 'name' => 'currency_code', 'grid_cols' => 'sm:col-span-3', 'section' => 'currency', 'options' => array_merge( ['global' => __('Use from Global Settings', 'easy-invoice')], \EasyInvoice\Helpers\CurrencyHelper::getCurrencyOptions() ), 'default_value' => 'global', 'order' => 1, 'description' => __('Select the currency for this quote or use global settings.', 'easy-invoice'), ], [ 'tab_id' => 'payments-tab', 'id' => 'currency_position', 'type' => 'select', 'label' => __('Currency Symbol Position', 'easy-invoice'), 'name' => 'currency_position', 'grid_cols' => 'sm:col-span-3', 'section' => 'currency', 'options' => [ 'global' => __('Use from Global Settings', 'easy-invoice'), 'before' => __('Before Amount', 'easy-invoice'), 'after' => __('After Amount', 'easy-invoice'), ], 'default_value' => 'global', 'order' => 2, 'description' => __('Choose where to display the currency symbol or use global setting.', 'easy-invoice'), ], ]; } /** * Register default templates * * @since 1.0.0 * @return void */ public function registerDefaultTemplates(): void { // Define all templates in a single array for easy modification by pro plugins $all_templates = $this->getDefaultTemplatesArray(); // Apply filter to allow pro plugins to modify templates $all_templates = apply_filters('easy_invoice_quote_templates', $all_templates); // Register all templates from the array foreach ($all_templates as $template_id => $template_config) { $this->registerTemplate($template_id, $template_config); } } /** * Get default templates array * * @since 1.0.0 * @return array */ private function getDefaultTemplatesArray(): array { // Check if this is the free version (no pro plugin active) $is_free_version = !easy_invoice_has_pro(); return [ 'standard' => [ 'name' => __('Standard', 'easy-invoice'), 'description' => __('A clean, professional quote template', 'easy-invoice'), 'icon' => 'fas fa-file-invoice', 'order' => 1, 'is_free' => true ], 'legacy' => [ 'name' => __('Legacy', 'easy-invoice'), 'description' => __('Clean, minimalist design with traditional business layout', 'easy-invoice'), 'icon' => 'fas fa-file-alt', 'order' => 2, 'is_free' => true ], 'modern' => [ 'name' => __('Modern', 'easy-invoice'), 'description' => __('A contemporary quote template', 'easy-invoice'), 'icon' => 'fas fa-star', 'order' => 3, 'is_free' => !$is_free_version ], 'minimal' => [ 'name' => __('Minimal', 'easy-invoice'), 'description' => __('A simple, clean quote template', 'easy-invoice'), 'icon' => 'fas fa-minus', 'order' => 4, 'is_free' => !$is_free_version ] ]; } /** * Register default item fields * * @since 1.0.0 * @return void */ public function registerDefaultItemFields(): void { // Define all item fields in a single array for easy modification by pro plugins $all_item_fields = $this->getDefaultItemFieldsArray(); // Apply filter to allow pro plugins to modify item fields $all_item_fields = apply_filters('easy_invoice_quote_item_fields', $all_item_fields); // Register all item fields from the array foreach ($all_item_fields as $field_config) { $this->registerItemField($field_config); } } /** * Get default item fields array * * @since 1.0.0 * @return array */ private function getDefaultItemFieldsArray(): array { $fields = [ [ 'id' => 'title', 'type' => 'text', 'label' => __('Item Title', 'easy-invoice'), 'name' => 'title', 'placeholder' => __('Enter item title', 'easy-invoice'), 'required' => true, 'container_class' => 'sm:col-span-6', 'order' => 1, 'description' => __('A short name for this item (e.g. Service Fee, Product Name).', 'easy-invoice'), ], [ 'id' => 'description', 'type' => 'textarea', 'label' => __('Description', 'easy-invoice'), 'name' => 'description', 'placeholder' => __('Enter item description', 'easy-invoice'), 'container_class' => 'sm:col-span-6', 'rows' => 3, 'order' => 2, 'description' => __('Details about this item for your client.', 'easy-invoice'), ], [ 'id' => 'quantity', 'type' => 'number', 'label' => __('Quantity', 'easy-invoice'), 'name' => 'quantity', 'placeholder' => '0', 'required' => true, 'container_class' => 'sm:col-span-1', 'min' => '0', 'step' => '0.01', 'default_value' => '0', 'order' => 3, 'description' => __('How many units of this item are being billed?', 'easy-invoice'), ], [ 'id' => 'price', 'type' => 'number', 'label' => __('Price', 'easy-invoice'), 'name' => 'price', 'placeholder' => '0.00', 'required' => true, 'container_class' => 'sm:col-span-1', 'min' => '0', 'step' => '0.01', 'default_value' => '0.00', 'order' => 4, 'description' => __('Unit price for this item.', 'easy-invoice'), ] ]; // Add adjust field only if setting allows it if (\EasyInvoice\Controllers\SettingsController::shouldShowQuoteAdjustField()) { $fields[] = [ 'id' => 'adjust_percentage', 'type' => 'number', 'label' => __('Adjust (%)', 'easy-invoice'), 'name' => 'adjust_percentage', 'placeholder' => '0.00', 'container_class' => 'sm:col-span-1', 'min' => '-100', 'max' => '1000', 'step' => '0.01', 'default_value' => '0.00', 'order' => 5, 'description' => __('Percentage adjustment to apply to this item (positive for markup, negative for discount).', 'easy-invoice'), ]; $current_order = 5; // Adjust field is present } else { $current_order = 4; // Adjust field is not present, start after price field } // Calculate order for remaining fields $fields[] = [ 'id' => 'total', 'type' => 'number', 'label' => __('Total', 'easy-invoice'), 'name' => 'total', 'placeholder' => '0.00', 'container_class' => 'sm:col-span-1', 'min' => '0', 'step' => '0.01', 'default_value' => '0.00', 'readonly' => true, 'order' => $current_order + 1, 'description' => __('Total amount for this item (Quantity x Price) with adjustment applied.', 'easy-invoice'), ]; $fields[] = [ 'id' => 'taxable', 'type' => 'checkbox', 'label' => __('Taxable', 'easy-invoice'), 'name' => 'taxable', 'container_class' => 'sm:col-span-6', 'default_value' => '1', 'order' => $current_order + 2, 'description' => __('Check if this item is subject to tax.', 'easy-invoice'), ]; return $fields; } /** * Get client options for select field * * @since 1.0.0 * @return array */ private function getClientOptions(): array { $options = ['' => __('Select a client', 'easy-invoice')]; // Get clients from repository $client_repository = new \EasyInvoice\Repositories\ClientRepository(); $clients = $client_repository->all(); foreach ($clients as $client) { $client_name = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()); $options[$client->getId()] = $client_name; } return $options; } /** * Get the filter name for tabs * * @since 1.0.0 * @return string */ protected function getTabFilterName(): string { return 'easy_invoice_quote_register_tab'; } /** * Get the filter name for fields * * @since 1.0.0 * @return string */ protected function getFieldFilterName(): string { return 'easy_invoice_quote_register_field'; } /** * Get the filter name for templates * * @since 1.0.0 * @return string */ protected function getTemplateFilterName(): string { return 'easy_invoice_quote_register_template'; } /** * Get the filter name for item fields * * @since 1.0.0 * @return string */ protected function getItemFieldFilterName(): string { return 'easy_invoice_quote_register_item_field'; } }