PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.1
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.3.1, at templates/quotes/builder.php

410 lines 22.2 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-client-manager', plugin_dir_url(__FILE__) . '../../assets/js/client-manager.js', array('jquery'), EASY_INVOICE_VERSION, true);
13
14 // Create nonce for AJAX calls
15 $ajax_nonce = wp_create_nonce('easy_invoice_nonce');
16
17 // Localize client manager script
18 wp_localize_script('easy-client-manager', 'easyInvoice', array(
19 'ajaxUrl' => admin_url('admin-ajax.php'),
20 'nonce' => $ajax_nonce
21 ));
22
23 // Get quote ID if present in URL
24 $quote_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
25 if ($quote_id) {
26 $post = get_post($quote_id);
27 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
28 wp_die(__('Invalid post type. This builder can only be used for quotes.', 'easy-invoice'), __('Invalid Post Type', 'easy-invoice'), array('back_link' => true));
29 }
30 }
31 $quote = null;
32
33 // Check if this is a new quote (no ID)
34 $is_new_quote = ($quote_id === 0);
35
36 // Default values for new quote
37 $quote_number_service = function_exists('easy_invoice_get_quote_number_service') ? easy_invoice_get_quote_number_service() : null;
38
39 // Get global quote settings
40 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
41 $quote_prefix = $settings_controller::getQuotePrefix();
42 $quote_terms = $settings_controller::getQuoteTermsConditions();
43 $quote_footer = $settings_controller::getQuoteFooterText();
44 $quote_accept_button = get_option('easy_invoice_quote_accept_button', 'yes');
45 $quote_accept_action = get_option('easy_invoice_quote_accept_action', 'email');
46 $quote_accept_text = get_option('easy_invoice_quote_accept_text', __('Accept Quote', 'easy-invoice'));
47 $quote_accepted_message = get_option('easy_invoice_quote_accepted_message', __('Thank you for accepting our quote!', 'easy-invoice'));
48 $quote_declined_message = get_option('easy_invoice_quote_declined_message', __('Thank you for your consideration.', 'easy-invoice'));
49
50 $quote_data = array(
51 'number' => $quote_number_service ? $quote_number_service->getNextNumber() : 'QT-1',
52 'date' => date('Y-m-d'),
53 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
54 'client_id' => 0,
55 'client_name' => '',
56 'client_email' => '',
57 'client_phone' => '',
58 'client_address' => '',
59 'items' => array(),
60 'notes' => '',
61 'internal_notes' => '',
62 'discount' => 0,
63 'discount_type' => 'percentage',
64 'calculation_method' => 'before_tax',
65 'tax_rate' => easy_invoice_get_tax_rate(),
66 'prices_include_tax' => 'no',
67 'status' => 'draft',
68 'currency' => 'USD',
69 'currency_symbol' => '$',
70 'title' => '',
71 'description' => '',
72 'terms' => $quote_terms, // Use global terms setting
73 'footer_text' => $quote_footer, // Use global footer setting
74 'accept_button' => $quote_accept_button, // Use global accept button setting
75 'accept_action' => $quote_accept_action, // Use global accept action setting
76 'accept_text' => $quote_accept_text, // Use global accept text setting
77 'accepted_message' => $quote_accepted_message, // Use global accepted message setting
78 'declined_message' => $quote_declined_message, // Use global declined message setting
79 );
80
81 // Get clients from repository
82 $client_repository = ClientServiceProvider::getClientRepository();
83 $clients = $client_repository->all();
84
85 // Load client data directly in PHP if quote has a client
86 $client_data = null;
87 if ($quote && $quote->getClientId()) {
88 $client = $client_repository->find($quote->getClientId());
89 if ($client) {
90 $client_data = array(
91 'id' => $client->getId(),
92 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
93 'email' => $client->getEmail() ?: '',
94 'phone' => $client->getExtraInfo() ?: '',
95 'company' => $client->getBusinessClientName() ?: '',
96 'address' => $client->getAddress() ?: '',
97 'website' => $client->getWebsite() ?: '',
98 );
99 }
100 }
101
102 // If editing an existing quote, load its data
103 if ($quote_id > 0) {
104 // Get the quote from repository
105 $quote_repository = QuoteServiceProvider::getQuoteRepository();
106 $quote = $quote_repository->find($quote_id);
107
108 if ($quote && $quote->getId()) {
109 // Quote loaded successfully
110 } else {
111 // Failed to load quote
112 }
113 } else {
114 // Create a temporary WP_Post object for new quote
115 $empty_post = new WP_Post((object) array(
116 'ID' => 0,
117 'post_author' => get_current_user_id(),
118 'post_date' => current_time('mysql'),
119 'post_date_gmt' => current_time('mysql', 1),
120 'post_title' => $quote_data['number'],
121 'post_status' => 'auto-draft',
122 'comment_status' => 'closed',
123 'ping_status' => 'closed',
124 'post_name' => '',
125 'post_modified' => current_time('mysql'),
126 'post_modified_gmt' => current_time('mysql', 1),
127 'post_parent' => 0,
128 'guid' => '',
129 'menu_order' => 0,
130 'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
131 'post_mime_type' => '',
132 'comment_count' => 0,
133 'filter' => 'raw',
134 ));
135
136 $quote = new \EasyInvoice\Models\Quote($empty_post);
137
138 // Set default values on the quote object
139 foreach ($quote_data as $key => $value) {
140 $setter = 'set' . easy_invoice_str_replace('_', '', ucwords($key, '_'));
141 if (method_exists($quote, $setter)) {
142 // Handle type-specific setters
143 switch ($setter) {
144 case 'setClientId':
145 $quote->setClientId((int) $value);
146 break;
147 case 'setItems':
148 $quote->setItems((array) $value);
149 break;
150 case 'setSubtotal':
151 case 'setTaxAmount':
152 case 'setDiscountAmount':
153 case 'setTotal':
154 case 'setDiscountValue':
155 case 'setTaxRate':
156 $quote->$setter((float) $value);
157 break;
158 case 'setPricesIncludeTax':
159 $quote->$setter((bool) $value);
160 break;
161 default:
162 $quote->$setter((string) $value);
163 break;
164 }
165 }
166 }
167
168 // Initialize empty items array
169 $quote->setItems([]);
170 }
171
172 // Only load custom meta that's not handled by Quote object
173 if ($quote) {
174 // These are still accessed from meta as they don't have model methods yet
175 $quote_data['terms'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_terms', true) : $quote_data['terms'];
176 $quote_data['internal_notes'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_internal_notes', true) : $quote_data['internal_notes'];
177 $quote_data['currency'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_code', true) : $quote_data['currency'];
178 $quote_data['currency_symbol'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_currency_position', true) : $quote_data['currency_symbol'];
179 $quote_data['calculation_method'] = $quote_id > 0 ? get_post_meta($quote_id, '_easy_invoice_calculation_method', true) : $quote_data['calculation_method'];
180 }
181
182 // Prepare quote items JSON for JavaScript
183 $quote_items_json = json_encode($quote ? $quote->getItems() : []);
184
185 // Create nonce for AJAX calls
186 $admin_nonce = wp_create_nonce('easy_invoice_admin_nonce');
187
188 // Initialize quote form manager for field configuration
189 $quote_form_manager = new \EasyInvoice\Forms\Quote\QuoteFormManager();
190 $quote_field_config = $quote_form_manager->getFieldConfigForJavaScript();
191
192 // Remove the following code that preloads all templates into JS
193 // $templates = $quote_form_manager->getTemplates();
194 // $template_htmls = [];
195 // $formatter = new \EasyInvoice\Helpers\QuoteFormatter($quote);
196 // foreach ($templates as $template_id => $template_config) {
197 // $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/' . $template_id . '.php';
198 // if (file_exists($template_file)) {
199 // ob_start();
200 // include $template_file;
201 // $template_htmls[$template_id] = ob_get_clean();
202 // } else {
203 // $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>';
204 // }
205 // }
206
207 if ( $quote_id && $quote ) {
208 $ei_header_display_title = $quote->title ?: $quote->number ?: __( 'Untitled quote', 'easy-invoice' );
209 } else {
210 $ei_header_display_title = __( 'Create new quote', 'easy-invoice' );
211 }
212
213 ?>
214
215 <script>
216 window.isQuoteForm = true;
217 </script>
218
219 <script>
220 jQuery(function ($) {
221 window.easyInvoice = $.extend(true, {}, typeof window.easyInvoice === 'object' ? window.easyInvoice : {}, {
222 fieldConfig: <?php echo wp_json_encode( $quote_field_config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
223 clientData: <?php echo wp_json_encode( $client_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
224 ajaxUrl: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
225 nonce: '<?php echo esc_js( wp_create_nonce( 'easy_invoice_nonce' ) ); ?>',
226 isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
227 });
228
229 function t(key, fb) {
230 return (window.easyInvoice && easyInvoice.i18n && easyInvoice.i18n[key]) ? easyInvoice.i18n[key] : fb;
231 }
232 var spin = '<svg class="ei-btn-spinner h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
233 var pendingSend = false;
234
235 $(document).on('easy-quote-saved', function (e, response) {
236 if (!pendingSend || !response || !response.success) {
237 return;
238 }
239 pendingSend = false;
240 if ($('#quote-id').val() !== '0') {
241 sendQuoteEmail();
242 }
243 });
244 $(document).on('easy-quote-save-failed', function () {
245 pendingSend = false;
246 });
247
248 $('#send-quote-btn').on('click', function (e) {
249 e.preventDefault();
250 if ($('#quote-id').val() === '0') {
251 pendingSend = true;
252 $('#save-quote-btn').trigger('click');
253 return;
254 }
255 sendQuoteEmail();
256 });
257
258 function sendQuoteEmail() {
259 var quoteId = $('#quote-id').val();
260 if (quoteId === '0') {
261 if (typeof EasyInvoiceToast !== 'undefined') {
262 EasyInvoiceToast.warning(t('save_first_quote', 'Please save the quote before sending.'));
263 }
264 return;
265 }
266 var nonce = $('#send-quote-btn').data('send-nonce') || '<?php echo esc_js( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>';
267
268 var runAjax = function () {
269 var $btn = $('#send-quote-btn');
270 var original = $btn.html();
271 $btn.prop('disabled', true).attr('aria-busy', 'true').html('<span class="inline-flex items-center gap-2">' + spin + '<span>' + t('sending', 'Sending…') + '</span></span>');
272 $.ajax({
273 url: window.easyInvoice.ajaxUrl,
274 type: 'POST',
275 data: {
276 action: 'easy_invoice_send_quote_email',
277 quote_id: quoteId,
278 nonce: nonce
279 },
280 success: function (response) {
281 if (response.success) {
282 if (typeof EasyInvoiceToast !== 'undefined') {
283 EasyInvoiceToast.success(t('email_sent_quote', 'Quote email sent successfully.'));
284 }
285 } else {
286 var msg = (response.data && response.data.message) ? response.data.message : (response.data || t('email_error', 'Error sending email.'));
287 if (typeof EasyInvoiceToast !== 'undefined') {
288 EasyInvoiceToast.error(msg);
289 }
290 }
291 },
292 error: function () {
293 if (typeof EasyInvoiceToast !== 'undefined') {
294 EasyInvoiceToast.error(t('network_error', 'Error connecting to server.'));
295 }
296 },
297 complete: function () {
298 $btn.prop('disabled', false).removeAttr('aria-busy').html(original);
299 }
300 });
301 };
302
303 if (typeof EasyInvoiceConfirmation !== 'undefined') {
304 EasyInvoiceConfirmation.confirmSendDocument('quote', runAjax);
305 } else if (window.confirm(t('send_quote_confirm_browser', 'Send this quote by email?'))) {
306 runAjax();
307 }
308 }
309 });
310 </script>
311
312 <form id="quote-form" method="post">
313 <input type="hidden" id="quote-id" name="quote_id" value="<?php echo $quote_id; ?>">
314 <input type="hidden" id="quote_nonce" name="quote_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
315
316 <div id="easy-invoice-content" class="h-screen flex flex-col bg-gray-50">
317 <header class="ei-quote-builder-header ei-app-header-sync bg-white border-b border-gray-200 shadow-sm w-full z-10 box-border">
318 <div class="max-w-full mx-auto h-full px-4 sm:px-5">
319 <div class="ei-app-header-inner flex flex-col gap-3 py-3 sm:gap-4 lg:flex-row lg:items-center lg:justify-between lg:gap-6 lg:py-0 lg:h-full">
320 <div class="ei-app-header-leading flex min-w-0 flex-1 flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
321 <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-quote-all' ) ); ?>"
322 class="inline-flex shrink-0 items-center gap-2 self-start rounded-md text-sm font-medium text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
323 <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
324 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
325 </svg>
326 <span><?php esc_html_e( 'Back to quotes', 'easy-invoice' ); ?></span>
327 </a>
328 <div class="min-w-0 flex-1 border-l-0 sm:border-l sm:border-gray-200 sm:pl-4">
329 <p class="text-xs font-medium uppercase tracking-wide text-gray-500"><?php esc_html_e( 'Quote', 'easy-invoice' ); ?></p>
330 <h1 id="ei-builder-header-title" class="mt-0.5 truncate text-lg font-semibold leading-tight text-gray-900 sm:text-xl" data-ei-default="<?php echo esc_attr( $ei_header_display_title ); ?>">
331 <?php echo esc_html( $ei_header_display_title ); ?>
332 </h1>
333 <p class="sr-only"><?php esc_html_e( 'The title above matches the document title field in the editor.', 'easy-invoice' ); ?></p>
334 </div>
335 </div>
336 <div class="ei-builder-header-actions flex flex-wrap items-center gap-2 lg:shrink-0">
337 <button type="button" id="save-quote-btn" class="inline-flex items-center gap-2 px-3 sm: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" aria-busy="false">
338 <svg class="h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
339 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
340 </svg>
341 <span><?php echo isset( $_GET['id'] ) ? esc_html__( 'Update quote', 'easy-invoice' ) : esc_html__( 'Save quote', 'easy-invoice' ); ?></span>
342 </button>
343 <button type="button" id="send-quote-btn" class="inline-flex items-center gap-2 px-3 sm: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" data-send-nonce="<?php echo esc_attr( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>">
344 <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
345 <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"></path>
346 </svg>
347 <span><?php esc_html_e( 'Send quote', 'easy-invoice' ); ?></span>
348 </button>
349 </div>
350 </div>
351 </div>
352 </header>
353
354 <div class="flex-grow overflow-y-auto min-h-0">
355 <div class="max-w-full mx-auto px-4 sm:px-5 py-4 sm:py-5">
356 <div class="ei-builder-tabs lg:hidden flex rounded-lg bg-gray-100 p-1 gap-1 mb-4" role="tablist" aria-label="<?php echo esc_attr__( 'Editor panels', 'easy-invoice' ); ?>">
357 <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition bg-white shadow-sm text-indigo-700" data-tab="editor" aria-selected="true"><?php esc_html_e( 'Editor', 'easy-invoice' ); ?></button>
358 <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition text-gray-600 hover:text-gray-800" data-tab="preview" aria-selected="false"><?php esc_html_e( 'Preview', 'easy-invoice' ); ?></button>
359 </div>
360 <div id="ei-builder-shell-hint" class="ei-builder-shell-hint mb-4 hidden rounded-md border border-indigo-100 bg-indigo-50/90 p-3 text-sm text-gray-700 shadow-sm lg:hidden" role="status" hidden>
361 <div class="flex items-start gap-3">
362 <span class="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-indigo-700" aria-hidden="true">
363 <svg class="h-3.5 w-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
364 </span>
365 <p class="min-w-0 flex-1 leading-snug"><?php esc_html_e( 'On smaller screens, use the tabs to switch between the editor and the live preview.', 'easy-invoice' ); ?></p>
366 <button type="button" id="ei-builder-shell-hint-dismiss" class="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-indigo-700 hover:bg-indigo-100 focus:outline-none focus:ring-2 focus:ring-indigo-500">
367 <?php esc_html_e( 'Got it', 'easy-invoice' ); ?>
368 </button>
369 </div>
370 </div>
371 <div id="ei-quote-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
372 <div id="ei-builder-editor" class="min-w-0 ei-builder-panel-editor">
373 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/form.php'; ?>
374 </div>
375 <div id="ei-builder-preview" class="min-w-0 ei-builder-panel-preview hidden lg:block">
376 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/live-preview.php'; ?>
377 </div>
378 </div>
379
380 <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" role="dialog" aria-modal="true" aria-labelledby="ei-quote-add-client-title">
381 <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">
382 <div class="flex justify-between items-center mb-4">
383 <h3 id="ei-quote-add-client-title" class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Add new client', 'easy-invoice' ); ?></h3>
384 <button type="button" class="close-modal rounded-md p-1 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500">
385 <span class="sr-only"><?php esc_html_e( 'Close', 'easy-invoice' ); ?></span>
386 <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
387 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
388 </svg>
389 </button>
390 </div>
391
392 <?php
393 // Unset the $client variable from the foreach loop to ensure clean add form
394 unset($client);
395 // Also unset any client variable that might have been set in form.php
396 if (isset($client)) {
397 unset($client);
398 }
399 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
400 ?>
401 </div>
402 </div>
403 </div>
404 </div>
405 </div>
406 </form>
407
408
409
410