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

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