PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.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 / invoices / builder.php

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

337 lines 19.0 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
7 // Enqueue CSS and JS files for the builder page
8 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), EASY_INVOICE_VERSION);
9 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), EASY_INVOICE_VERSION, true);
10 wp_enqueue_script('easy-client-manager', plugin_dir_url(__FILE__) . '../../assets/js/client-manager.js', array('jquery'), EASY_INVOICE_VERSION, true);
11
12 use EasyInvoice\Providers\ClientServiceProvider;
13 use EasyInvoice\Providers\InvoiceServiceProvider;
14
15
16 // Get invoice ID if present in URL
17 $invoice_id = isset($_GET['invoice_id']) ? intval($_GET['invoice_id']) : 0;
18 // A stale link (an invoice deleted since, or a quote id pasted here) must not fall
19 // through to the editor and preview as if it were a new invoice.
20 if ( $invoice_id > 0 ) {
21 $ei_builder_post = get_post( $invoice_id );
22 if ( ! $ei_builder_post || \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE !== $ei_builder_post->post_type ) {
23 wp_die(
24 esc_html__( 'That invoice does not exist or has been deleted.', 'easy-invoice' ),
25 esc_html__( 'Invoice not found', 'easy-invoice' ),
26 array( 'back_link' => true, 'response' => 404 )
27 );
28 }
29 }
30 $invoice = null;
31
32 // Check if this is a new invoice (no ID)
33 $is_new_invoice = ($invoice_id === 0);
34
35 // Default values for new invoice
36 $invoice_number_service = easy_invoice_get_invoice_number_service();
37 $invoice_data = array(
38 'number' => $invoice_number_service->getNextNumber(),
39 'date' => current_time('Y-m-d'),
40 'due_date' => wp_date('Y-m-d', strtotime('+30 days')),
41 'client_id' => '',
42 'client_name' => '',
43 'client_email' => '',
44 'client_phone' => '',
45 'client_address' => '',
46 'items' => array(),
47 'notes' => '',
48 'internal_notes' => '',
49 'discount' => 0,
50 'discount_type' => 'percentage',
51 'calculation_method' => 'before_tax',
52 'tax_rate' => 10,
53 'prices_include_tax' => 'no',
54 'payment_method' => 'bank_transfer',
55 'payment_status' => 'unpaid',
56 'currency' => 'USD',
57 'currency_symbol' => '$',
58 'title' => '',
59 'description' => '',
60 'terms' => '',
61 'is_recurring' => 'no',
62 'recurring_frequency' => '',
63 );
64
65 // Load clients for the dropdown
66 $client_repository = ClientServiceProvider::getClientRepository();
67 // The hidden #select-client mirror only needs the document's own client; the picker
68 // searches over AJAX. Loading every client here built a model per user on each open.
69 $clients = [];
70
71 // If editing an existing invoice, load its data
72 if ($invoice_id > 0) {
73 // Get the invoice from repository
74 $invoice_repository = InvoiceServiceProvider::getInvoiceRepository();
75 $invoice = $invoice_repository->find($invoice_id);
76 } else {
77 // Create a temporary WP_Post object for new invoice
78 $empty_post = new WP_Post((object) array(
79 'ID' => 0,
80 'post_author' => get_current_user_id(),
81 'post_date' => current_time('mysql'),
82 'post_date_gmt' => current_time('mysql', 1),
83 'post_title' => $invoice_data['number'],
84 'post_status' => 'auto-draft',
85 'comment_status' => 'closed',
86 'ping_status' => 'closed',
87 'post_name' => '',
88 'post_modified' => current_time('mysql'),
89 'post_modified_gmt' => current_time('mysql', 1),
90 'post_parent' => 0,
91 'guid' => '',
92 'menu_order' => 0,
93 'post_type' => \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
94 'post_mime_type' => '',
95 'comment_count' => 0,
96 'filter' => 'raw',
97 ));
98
99 $invoice = new \EasyInvoice\Models\Invoice($empty_post);
100
101 // Set default values on the invoice object
102 foreach ($invoice_data as $key => $value) {
103 $setter = 'set' . easy_invoice_str_replace('_', '', ucwords($key, '_'));
104 if (method_exists($invoice, $setter)) {
105 // Handle client_id specially to avoid type errors
106 if ($key === 'client_id' && empty($value)) {
107 $invoice->setClientId(0);
108 } else {
109 $invoice->$setter($value);
110 }
111 }
112 }
113
114 // Initialize empty items array
115 $invoice->setItems([]);
116 }
117
118 // Only load custom meta that's not handled by Invoice object
119 if ($invoice) {
120 // These are still accessed from meta as they don't have model methods yet
121 $invoice_data['terms'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_terms', true) : $invoice_data['terms'];
122 $invoice_data['internal_notes'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_internal_notes', true) : $invoice_data['internal_notes'];
123 $invoice_data['payment_method'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_payment_method', true) : $invoice_data['payment_method'];
124 $invoice_data['payment_status'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_payment_status', true) : $invoice_data['payment_status'];
125 $invoice_data['currency'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_currency_code', true) : $invoice_data['currency'];
126 $invoice_data['currency_symbol'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_currency_position', true) : $invoice_data['currency_symbol'];
127 $invoice_data['calculation_method'] = $invoice_id > 0 ? get_post_meta($invoice_id, '_easy_invoice_calculation_method', true) : $invoice_data['calculation_method'];
128 }
129
130 // Prepare invoice items JSON for JavaScript
131 $invoice_items_json = json_encode($invoice ? $invoice->getItems() : []);
132
133 // Create nonce for AJAX calls
134 $admin_nonce = wp_create_nonce('easy_invoice_admin_nonce');
135
136 // Prepare client data for JavaScript
137 $client_data_json = json_encode($client_data ?? null);
138
139 // Start output buffering to capture content
140
141 if ( $invoice_id && $invoice ) {
142 $ei_header_display_title = $invoice->title ?: $invoice->number ?: __( 'Untitled invoice', 'easy-invoice' );
143 } else {
144 $ei_header_display_title = __( 'Create new invoice', 'easy-invoice' );
145 }
146 ?>
147
148 <div id="easy-invoice-content" class="h-screen flex flex-col bg-gray-50">
149 <header class="ei-invoice-builder-header ei-app-header-sync bg-white border-b border-gray-200 shadow-sm w-full z-10 box-border">
150 <div class="max-w-full mx-auto h-full px-4 sm:px-5">
151 <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">
152 <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">
153 <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-invoice-all' ) ); ?>"
154 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">
155 <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">
156 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
157 </svg>
158 <span><?php esc_html_e( 'Back to invoices', 'easy-invoice' ); ?></span>
159 </a>
160 <div class="min-w-0 flex-1 border-l-0 sm:border-l sm:border-gray-200 sm:pl-4">
161 <p class="text-xs font-medium uppercase tracking-wide text-gray-500"><?php esc_html_e( 'Invoice', 'easy-invoice' ); ?></p>
162 <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 ); ?>">
163 <?php echo esc_html( $ei_header_display_title ); ?>
164 </h1>
165 <p class="sr-only"><?php esc_html_e( 'The title above matches the document title field in the editor.', 'easy-invoice' ); ?></p>
166 </div>
167 </div>
168 <div class="ei-builder-header-actions flex w-full min-w-0 flex-wrap items-center justify-end gap-2 lg:w-auto lg:max-w-3xl lg:shrink-0">
169 <?php
170 if ( $invoice && $invoice_id > 0 ) {
171 do_action( 'easy_invoice_builder_before_invoice_actions', $invoice );
172 }
173 ?>
174 <button type="button" id="save-invoice-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">
175 <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">
176 <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>
177 </svg>
178 <span><?php echo isset( $_GET['invoice_id'] ) ? esc_html__( 'Update invoice', 'easy-invoice' ) : esc_html__( 'Save invoice', 'easy-invoice' ); ?></span>
179 </button>
180 <button type="button" id="send-invoice-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_invoice_email' ) ); ?>">
181 <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">
182 <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>
183 </svg>
184 <span><?php esc_html_e( 'Send invoice', 'easy-invoice' ); ?></span>
185 </button>
186 </div>
187 </div>
188 </div>
189 </header>
190
191 <div class="flex-grow overflow-y-auto min-h-0">
192 <div class="max-w-full mx-auto px-4 sm:px-5 py-4 sm:py-5">
193 <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' ); ?>">
194 <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>
195 <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>
196 </div>
197 <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>
198 <div class="flex items-start gap-3">
199 <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">
200 <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>
201 </span>
202 <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>
203 <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">
204 <?php esc_html_e( 'Got it', 'easy-invoice' ); ?>
205 </button>
206 </div>
207 </div>
208 <div id="ei-invoice-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
209 <div id="ei-builder-editor" class="min-w-0 ei-builder-panel-editor">
210 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/form.php'; ?>
211 </div>
212 <div id="ei-builder-preview" class="min-w-0 ei-builder-panel-preview hidden lg:block">
213 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/live-preview.php'; ?>
214 </div>
215 </div>
216
217 <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-add-client-title">
218 <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">
219 <div class="flex justify-between items-center mb-4">
220 <h3 id="ei-add-client-title" class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Add new client', 'easy-invoice' ); ?></h3>
221 <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">
222 <span class="sr-only"><?php esc_html_e( 'Close', 'easy-invoice' ); ?></span>
223 <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">
224 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
225 </svg>
226 </button>
227 </div>
228
229 <?php
230 // Unset the $client variable from the foreach loop to ensure clean add form
231 unset($client);
232 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
233 ?>
234 </div>
235 </div>
236
237 <input type="hidden" name="invoice_template" id="invoice_template" value="<?php echo esc_attr($invoice ? $invoice->getTemplate() : 'standard'); ?>" />
238 </div>
239 </div>
240 </div>
241
242 <script type="text/javascript">
243 jQuery(document).ready(function($) {
244 var base = {
245 ajaxUrl: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
246 nonce: '<?php echo esc_js( wp_create_nonce( 'easy_invoice_nonce' ) ); ?>',
247 clientData: <?php echo $client_data_json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
248 isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
249 };
250 window.easyInvoice = $.extend(true, {}, typeof window.easyInvoice === 'object' ? window.easyInvoice : {}, base);
251
252 function t(key, fb) {
253 return (window.easyInvoice && easyInvoice.i18n && easyInvoice.i18n[key]) ? easyInvoice.i18n[key] : fb;
254 }
255
256 var spin = '<svg class="ei-btn-spinner h-4 w-4 animate-spin 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>';
257 var pendingSend = false;
258
259 $(document).on('easy-invoice-saved', function (e, response) {
260 if (!pendingSend || !response || !response.success) {
261 return;
262 }
263 pendingSend = false;
264 if ($('#invoice-id').val() !== '0') {
265 sendInvoiceEmail();
266 }
267 });
268 $(document).on('easy-invoice-save-failed', function () {
269 pendingSend = false;
270 });
271
272 $('#send-invoice-btn').on('click', function (e) {
273 e.preventDefault();
274 if ($('#invoice-id').val() === '0') {
275 pendingSend = true;
276 $('#save-invoice-btn').trigger('click');
277 return;
278 }
279 sendInvoiceEmail();
280 });
281
282 function sendInvoiceEmail() {
283 var invoiceId = $('#invoice-id').val();
284 if (invoiceId === '0') {
285 if (typeof EasyInvoiceToast !== 'undefined') {
286 EasyInvoiceToast.warning(t('save_first_invoice', 'Please save the invoice before sending.'));
287 }
288 return;
289 }
290 var nonce = $('#send-invoice-btn').data('send-nonce') || '<?php echo esc_js( wp_create_nonce( 'easy_invoice_send_invoice_email' ) ); ?>';
291
292 var runAjax = function () {
293 var $btn = $('#send-invoice-btn');
294 var original = $btn.html();
295 $btn.prop('disabled', true).attr('aria-busy', 'true').html('<span class="inline-flex items-center gap-2">' + spin + '<span>' + t('sending', 'Sending…') + '</span></span>');
296 $.ajax({
297 url: window.easyInvoice.ajaxUrl,
298 type: 'POST',
299 data: {
300 action: 'easy_invoice_send_invoice_email',
301 invoice_id: invoiceId,
302 nonce: nonce
303 },
304 success: function (response) {
305 if (response.success) {
306 if (typeof EasyInvoiceToast !== 'undefined') {
307 EasyInvoiceToast.success(t('email_sent_invoice', 'Invoice email sent successfully.'));
308 }
309 } else {
310 var msg = (response.data && response.data.message) ? response.data.message : t('email_error', 'Error sending email.');
311 if (typeof EasyInvoiceToast !== 'undefined') {
312 EasyInvoiceToast.error(msg);
313 }
314 }
315 },
316 error: function () {
317 if (typeof EasyInvoiceToast !== 'undefined') {
318 EasyInvoiceToast.error(t('network_error', 'Error connecting to server.'));
319 }
320 },
321 complete: function () {
322 $btn.prop('disabled', false).removeAttr('aria-busy').html(original);
323 }
324 });
325 };
326
327 if (typeof EasyInvoiceConfirmation !== 'undefined') {
328 EasyInvoiceConfirmation.confirmSendDocument('invoice', runAjax);
329 } else if (window.confirm(t('send_invoice_confirm_browser', 'Send this invoice by email?'))) {
330 runAjax();
331 }
332 }
333 });
334 </script>
335
336
337