PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.2
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / templates / quotes / builder.php

builder.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.2, at templates/quotes/builder.php

439 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6 use EasyInvoice\Providers\ClientServiceProvider;
7 use EasyInvoice\Providers\QuoteServiceProvider;
8
9 // Enqueue CSS and JS files for the builder page
10 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), EASY_INVOICE_VERSION);
11 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), EASY_INVOICE_VERSION, true);
12 wp_enqueue_script('easy-quote-save', plugin_dir_url(__FILE__) . '../../assets/js/quote-save.js', array('jquery'), EASY_INVOICE_VERSION, true);
13 wp_enqueue_script('easy-client-manager', plugin_dir_url(__FILE__) . '../../assets/js/client-manager.js', array('jquery'), EASY_INVOICE_VERSION, true);
14
15 // Create nonce for AJAX calls
16 $ajax_nonce = wp_create_nonce('easy_invoice_nonce');
17
18 // Localize script for AJAX URL and nonce
19 wp_localize_script('easy-quote-save', 'easyInvoice', array(
20 'ajaxUrl' => admin_url('admin-ajax.php'),
21 'nonce' => $ajax_nonce
22 ));
23
24 // Localize client manager script
25 wp_localize_script('easy-client-manager', 'easyInvoice', array(
26 'ajaxUrl' => admin_url('admin-ajax.php'),
27 'nonce' => $ajax_nonce
28 ));
29
30 // Get quote ID if present in URL
31 $quote_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
32 if ($quote_id) {
33 $post = get_post($quote_id);
34 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
35 wp_die(__('Invalid post type. This builder can only be used for quotes.', 'easy-invoice'), __('Invalid Post Type', 'easy-invoice'), array('back_link' => true));
36 }
37 }
38 $quote = null;
39
40 // Check if this is a new quote (no ID)
41 $is_new_quote = ($quote_id === 0);
42
43 // Default values for new quote
44 $quote_number_service = function_exists('easy_invoice_get_quote_number_service') ? easy_invoice_get_quote_number_service() : null;
45
46 // Get global quote settings
47 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
48 $quote_prefix = $settings_controller::getQuotePrefix();
49 $quote_terms = $settings_controller::getQuoteTermsConditions();
50 $quote_footer = $settings_controller::getQuoteFooterText();
51 $quote_accept_button = get_option('easy_invoice_quote_accept_button', 'yes');
52 $quote_accept_action = get_option('easy_invoice_quote_accept_action', 'email');
53 $quote_accept_text = get_option('easy_invoice_quote_accept_text', __('Accept Quote', 'easy-invoice'));
54 $quote_accepted_message = get_option('easy_invoice_quote_accepted_message', __('Thank you for accepting our quote!', 'easy-invoice'));
55 $quote_declined_message = get_option('easy_invoice_quote_declined_message', __('Thank you for your consideration.', 'easy-invoice'));
56
57 $quote_data = array(
58 'number' => $quote_number_service ? $quote_number_service->getNextNumber() : 'QT-1',
59 'date' => date('Y-m-d'),
60 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
61 'client_id' => 0,
62 'client_name' => '',
63 'client_email' => '',
64 'client_phone' => '',
65 'client_address' => '',
66 'items' => array(),
67 'notes' => '',
68 'internal_notes' => '',
69 'discount' => 0,
70 'discount_type' => 'percentage',
71 'calculation_method' => 'before_tax',
72 'tax_rate' => easy_invoice_get_tax_rate(),
73 'prices_include_tax' => 'no',
74 'status' => 'draft',
75 'currency' => 'USD',
76 'currency_symbol' => '$',
77 'title' => '',
78 'description' => '',
79 'terms' => $quote_terms, // Use global terms setting
80 'footer_text' => $quote_footer, // Use global footer setting
81 'accept_button' => $quote_accept_button, // Use global accept button setting
82 'accept_action' => $quote_accept_action, // Use global accept action setting
83 'accept_text' => $quote_accept_text, // Use global accept text setting
84 'accepted_message' => $quote_accepted_message, // Use global accepted message setting
85 'declined_message' => $quote_declined_message, // Use global declined message setting
86 );
87
88 // Get clients from repository
89 $client_repository = ClientServiceProvider::getClientRepository();
90 $clients = $client_repository->all();
91
92 // Load client data directly in PHP if quote has a client
93 $client_data = null;
94 if ($quote && $quote->getClientId()) {
95 $client = $client_repository->find($quote->getClientId());
96 if ($client) {
97 $client_data = array(
98 'id' => $client->getId(),
99 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
100 'email' => $client->getEmail() ?: '',
101 'phone' => $client->getExtraInfo() ?: '',
102 'company' => $client->getBusinessClientName() ?: '',
103 'address' => $client->getAddress() ?: '',
104 'website' => $client->getWebsite() ?: '',
105 );
106 }
107 }
108
109 // If editing an existing quote, load its data
110 if ($quote_id > 0) {
111 // Get the quote from repository
112 $quote_repository = QuoteServiceProvider::getQuoteRepository();
113 $quote = $quote_repository->find($quote_id);
114
115 if ($quote && $quote->getId()) {
116 // Quote loaded successfully
117 } else {
118 // Failed to load quote
119 }
120 } else {
121 // Create a temporary WP_Post object for new quote
122 $empty_post = new WP_Post((object) array(
123 'ID' => 0,
124 'post_author' => get_current_user_id(),
125 'post_date' => current_time('mysql'),
126 'post_date_gmt' => current_time('mysql', 1),
127 'post_title' => $quote_data['number'],
128 'post_status' => 'auto-draft',
129 'comment_status' => 'closed',
130 'ping_status' => 'closed',
131 'post_name' => '',
132 'post_modified' => current_time('mysql'),
133 'post_modified_gmt' => current_time('mysql', 1),
134 'post_parent' => 0,
135 'guid' => '',
136 'menu_order' => 0,
137 'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
138 'post_mime_type' => '',
139 'comment_count' => 0,
140 'filter' => 'raw',
141 ));
142
143 $quote = new \EasyInvoice\Models\Quote($empty_post);
144
145 // Set default values on the quote object
146 foreach ($quote_data as $key => $value) {
147 $setter = 'set' . easy_invoice_str_replace('_', '', ucwords($key, '_'));
148 if (method_exists($quote, $setter)) {
149 // Handle type-specific setters
150 switch ($setter) {
151 case 'setClientId':
152 $quote->setClientId((int) $value);
153 break;
154 case 'setItems':
155 $quote->setItems((array) $value);
156 break;
157 case 'setSubtotal':
158 case 'setTaxAmount':
159 case 'setDiscountAmount':
160 case 'setTotal':
161 case 'setDiscountValue':
162 case 'setTaxRate':
163 $quote->$setter((float) $value);
164 break;
165 case 'setPricesIncludeTax':
166 $quote->$setter((bool) $value);
167 break;
168 default:
169 $quote->$setter((string) $value);
170 break;
171 }
172 }
173 }
174
175 // Initialize empty items array
176 $quote->setItems([]);
177 }
178
179 // Only load custom meta that's not handled by Quote object
180 if ($quote) {
181 // These are still accessed from meta as they don't have model methods yet
182 $quote_data['terms'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_terms', true) : $quote_data['terms'];
183 $quote_data['internal_notes'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_internal_notes', true) : $quote_data['internal_notes'];
184 $quote_data['currency'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_code', true) : $quote_data['currency'];
185 $quote_data['currency_symbol'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_position', true) : $quote_data['currency_symbol'];
186 $quote_data['calculation_method'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_calculation_method', true) : $quote_data['calculation_method'];
187 }
188
189 // Prepare quote items JSON for JavaScript
190 $quote_items_json = json_encode($quote ? $quote->getItems() : []);
191
192 // Create nonce for AJAX calls
193 $admin_nonce = wp_create_nonce('easy_invoice_admin_nonce');
194
195 // Initialize quote form manager for field configuration
196 $quote_form_manager = new \EasyInvoice\Forms\Quote\QuoteFormManager();
197 $quote_field_config = $quote_form_manager->getFieldConfigForJavaScript();
198
199 // Remove the following code that preloads all templates into JS
200 // $templates = $quote_form_manager->getTemplates();
201 // $template_htmls = [];
202 // $formatter = new \EasyInvoice\Helpers\QuoteFormatter($quote);
203 // foreach ($templates as $template_id => $template_config) {
204 // $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/' . $template_id . '.php';
205 // if (file_exists($template_file)) {
206 // ob_start();
207 // include $template_file;
208 // $template_htmls[$template_id] = ob_get_clean();
209 // } else {
210 // $template_htmls[$template_id] = '<div class="template-not-found"><h3>Template Not Found</h3><p>The template "' . esc_html($template_id) . '" does not exist.</p></div>';
211 // }
212 // }
213
214 ?>
215
216 <script>
217 // Flag to indicate this is a quote form
218 window.isQuoteForm = true;
219
220 // Handle Send Quote button
221 jQuery(document).ready(function($) {
222 $('#send-quote-btn').on('click', function(e) {
223 e.preventDefault();
224
225 // First save the quote if it's not saved yet
226 if ($('#quote-id').val() == '0') {
227 // Quote not saved yet, save it first
228 $('#save-quote-btn').click();
229
230 // Wait for save to complete, then send
231 setTimeout(function() {
232 if ($('#quote-id').val() != '0') {
233 sendQuoteEmail();
234 } else {
235 if (typeof EasyInvoiceToast !== 'undefined') {
236 EasyInvoiceToast.warning('Please save the quote first before sending.');
237 } else {
238 console.log('Please save the quote first before sending.');
239 }
240 }
241 }, 2000);
242 } else {
243 // Quote already saved, send directly
244 sendQuoteEmail();
245 }
246 });
247
248 function sendQuoteEmail() {
249 const quoteId = $('#quote-id').val();
250
251 if (quoteId == '0') {
252 if (typeof EasyInvoiceToast !== 'undefined') {
253 EasyInvoiceToast.warning('Please save the quote first before sending.');
254 } else {
255 console.log('Please save the quote first before sending.');
256 }
257 return;
258 }
259
260 // Use the confirmation system instead of alert
261 if (typeof EasyInvoiceConfirmation !== 'undefined') {
262 EasyInvoiceConfirmation.confirmAction('send email', 'this quote', function() {
263 // Show loading state
264 const $btn = $('#send-quote-btn');
265 const originalText = $btn.html();
266 $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
267
268 // Send AJAX request
269 $.ajax({
270 url: window.easyInvoice.ajaxUrl,
271 type: 'POST',
272 data: {
273 action: 'easy_invoice_send_quote_email',
274 quote_id: quoteId,
275 nonce: '<?php echo wp_create_nonce('easy_invoice_send_quote_email'); ?>'
276 },
277 success: function(response) {
278 if (response.success) {
279 if (typeof EasyInvoiceToast !== 'undefined') {
280 EasyInvoiceToast.success('Quote email sent successfully!');
281 } else {
282 console.log('Quote email sent successfully!');
283 }
284 } else {
285 if (typeof EasyInvoiceToast !== 'undefined') {
286 // Handle both response.data.message and response.data (direct string)
287 const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Error sending email');
288 EasyInvoiceToast.error(errorMessage);
289 } else {
290 const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Unknown error');
291 console.log('Error sending email: ' + errorMessage);
292 }
293 }
294 },
295 error: function() {
296 if (typeof EasyInvoiceToast !== 'undefined') {
297 EasyInvoiceToast.error('Error connecting to server');
298 } else {
299 console.log('Error connecting to server');
300 }
301 },
302 complete: function() {
303 // Reset button
304 $btn.prop('disabled', false).html(originalText);
305 }
306 });
307 });
308 } else {
309 // Fallback to browser confirm if confirmation system not available
310 if (confirm('Send this quote via email?')) {
311 // Show loading state
312 const $btn = $('#send-quote-btn');
313 const originalText = $btn.html();
314 $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
315
316 // Send AJAX request
317 $.ajax({
318 url: window.easyInvoice.ajaxUrl,
319 type: 'POST',
320 data: {
321 action: 'easy_invoice_send_quote_email',
322 quote_id: quoteId,
323 nonce: '<?php echo wp_create_nonce('easy_invoice_send_quote_email'); ?>'
324 },
325 success: function(response) {
326 if (response.success) {
327 alert('Quote email sent successfully!');
328 } else {
329 const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Error sending email');
330 alert(errorMessage);
331 }
332 },
333 error: function() {
334 alert('Error connecting to server');
335 },
336 complete: function() {
337 // Reset button
338 $btn.prop('disabled', false).html(originalText);
339 }
340 });
341 }
342 }
343 }
344 });
345 </script>
346
347 <script>
348 // Quote field configuration and pre-loaded data
349 window.easyInvoice = window.easyInvoice || {};
350 window.easyInvoice.fieldConfig = <?php echo json_encode($quote_field_config); ?>;
351 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
352 window.easyInvoice.ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>';
353 window.easyInvoice.nonce = '<?php echo wp_create_nonce('easy_invoice_nonce'); ?>';
354 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
355 </script>
356
357 <form id="quote-form" method="post">
358 <input type="hidden" id="quote-id" name="quote_id" value="<?php echo $quote_id; ?>">
359 <input type="hidden" id="quote_nonce" name="quote_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
360
361 <div id="easy-invoice-content" class="h-screen flex flex-col">
362 <!-- Header -->
363 <div class="bg-white shadow-sm w-full z-10">
364 <div class="max-w-full mx-auto px-5">
365 <div class="py-3 flex items-center justify-between">
366 <div class="flex items-center">
367 <a href="<?php echo admin_url('admin.php?page=easy-quote-all'); ?>"
368 class="inline-flex items-center text-gray-600 hover:text-gray-900">
369 <i class="fas fa-arrow-left mr-2"></i>
370 <span>Back to Quotes</span>
371 </a>
372 </div>
373 <h1 class="text-lg font-semibold text-gray-800">
374 <?php
375 if ($quote_id && $quote) {
376 $title = $quote->title ?: $quote->number ?: 'Untitled Quote';
377 echo esc_html($title);
378 } else {
379 echo 'Create New Quote';
380 }
381 ?>
382 </h1>
383 <div class="flex items-center space-x-4">
384 <button type="button" id="save-quote-btn" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
385 <i class="fas fa-save mr-2"></i>
386 <span><?php echo (isset($_GET['id']) ? __('Update Quote', 'easy-invoice') : __('Save Quote', 'easy-invoice')); ?></span>
387 </button>
388 <button type="button" id="send-quote-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
389 <i class="fas fa-paper-plane mr-2"></i>
390 <span>Send Quote</span>
391 </button>
392 </div>
393 </div>
394 </div>
395 </div>
396
397 <!-- Main Content -->
398 <div class="flex-grow overflow-y-auto">
399 <div class="max-w-full mx-auto px-5 py-5">
400 <div id="ei-quote-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-8">
401 <!-- Left Panel - Editable Fields -->
402
403 <?php
404 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/form.php';
405
406 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/live-preview.php';
407 ?>
408 </div>
409
410 <!-- Add Client Modal -->
411 <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50">
412 <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white">
413 <div class="flex justify-between items-center mb-4">
414 <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
415 <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
416 <span class="sr-only">Close</span>
417 <i class="fas fa-times"></i>
418 </button>
419 </div>
420
421 <?php
422 // Unset the $client variable from the foreach loop to ensure clean add form
423 unset($client);
424 // Also unset any client variable that might have been set in form.php
425 if (isset($client)) {
426 unset($client);
427 }
428 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
429 ?>
430 </div>
431 </div>
432 </div>
433 </div>
434 </div>
435 </form>
436
437
438
439