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

326 lines 18.3 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 -->
136 <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">
137 <div class="max-w-full mx-auto h-full px-4 sm:px-5">
138 <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">
139 <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">
140 <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-invoice-all' ) ); ?>"
141 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">
142 <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">
143 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
144 </svg>
145 <span><?php esc_html_e( 'Back to invoices', 'easy-invoice' ); ?></span>
146 </a>
147 <div class="min-w-0 flex-1 border-l-0 sm:border-l sm:border-gray-200 sm:pl-4">
148 <p class="text-xs font-medium uppercase tracking-wide text-gray-500"><?php esc_html_e( 'Invoice', 'easy-invoice' ); ?></p>
149 <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 ); ?>">
150 <?php echo esc_html( $ei_header_display_title ); ?>
151 </h1>
152 <p class="sr-only"><?php esc_html_e( 'The title above matches the document title field in the editor.', 'easy-invoice' ); ?></p>
153 </div>
154 </div>
155 <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">
156 <?php
157 if ( $invoice && $invoice_id > 0 ) {
158 do_action( 'easy_invoice_builder_before_invoice_actions', $invoice );
159 }
160 ?>
161 <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">
162 <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">
163 <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>
164 </svg>
165 <span><?php echo isset( $_GET['invoice_id'] ) ? esc_html__( 'Update invoice', 'easy-invoice' ) : esc_html__( 'Save invoice', 'easy-invoice' ); ?></span>
166 </button>
167 <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' ) ); ?>">
168 <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">
169 <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>
170 </svg>
171 <span><?php esc_html_e( 'Send invoice', 'easy-invoice' ); ?></span>
172 </button>
173 </div>
174 </div>
175 </div>
176 </header>
177
178 <!-- Main Content -->
179 <div class="flex-grow overflow-y-auto min-h-0">
180 <div class="max-w-full mx-auto px-4 sm:px-5 py-4 sm:py-5">
181 <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' ); ?>">
182 <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>
183 <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>
184 </div>
185 <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>
186 <div class="flex items-start gap-3">
187 <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">
188 <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>
189 </span>
190 <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>
191 <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">
192 <?php esc_html_e( 'Got it', 'easy-invoice' ); ?>
193 </button>
194 </div>
195 </div>
196 <div id="ei-invoice-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
197 <div id="ei-builder-editor" class="min-w-0 ei-builder-panel-editor">
198 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/form.php'; ?>
199 </div>
200 <div id="ei-builder-preview" class="min-w-0 ei-builder-panel-preview hidden lg:block">
201 <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/invoices/live-preview.php'; ?>
202 </div>
203 </div>
204
205 <!-- Add Client Modal -->
206 <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">
207 <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">
208 <div class="flex justify-between items-center mb-4">
209 <h3 id="ei-add-client-title" class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Add new client', 'easy-invoice' ); ?></h3>
210 <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">
211 <span class="sr-only"><?php esc_html_e( 'Close', 'easy-invoice' ); ?></span>
212 <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">
213 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
214 </svg>
215 </button>
216 </div>
217
218 <?php
219 // Unset the $client variable from the foreach loop to ensure clean add form
220 unset($client);
221 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
222 ?>
223 </div>
224 </div>
225
226 <input type="hidden" name="invoice_template" id="invoice_template" value="<?php echo esc_attr($invoice ? $invoice->getTemplate() : 'standard'); ?>" />
227 </div>
228 </div>
229 </div>
230
231 <script type="text/javascript">
232 jQuery(document).ready(function($) {
233 var base = {
234 ajaxUrl: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
235 nonce: '<?php echo esc_js( wp_create_nonce( 'easy_invoice_nonce' ) ); ?>',
236 clientData: <?php echo $client_data_json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
237 isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
238 };
239 window.easyInvoice = $.extend(true, {}, typeof window.easyInvoice === 'object' ? window.easyInvoice : {}, base);
240
241 function t(key, fb) {
242 return (window.easyInvoice && easyInvoice.i18n && easyInvoice.i18n[key]) ? easyInvoice.i18n[key] : fb;
243 }
244
245 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>';
246 var pendingSend = false;
247
248 $(document).on('easy-invoice-saved', function (e, response) {
249 if (!pendingSend || !response || !response.success) {
250 return;
251 }
252 pendingSend = false;
253 if ($('#invoice-id').val() !== '0') {
254 sendInvoiceEmail();
255 }
256 });
257 $(document).on('easy-invoice-save-failed', function () {
258 pendingSend = false;
259 });
260
261 $('#send-invoice-btn').on('click', function (e) {
262 e.preventDefault();
263 if ($('#invoice-id').val() === '0') {
264 pendingSend = true;
265 $('#save-invoice-btn').trigger('click');
266 return;
267 }
268 sendInvoiceEmail();
269 });
270
271 function sendInvoiceEmail() {
272 var invoiceId = $('#invoice-id').val();
273 if (invoiceId === '0') {
274 if (typeof EasyInvoiceToast !== 'undefined') {
275 EasyInvoiceToast.warning(t('save_first_invoice', 'Please save the invoice before sending.'));
276 }
277 return;
278 }
279 var nonce = $('#send-invoice-btn').data('send-nonce') || '<?php echo esc_js( wp_create_nonce( 'easy_invoice_send_invoice_email' ) ); ?>';
280
281 var runAjax = function () {
282 var $btn = $('#send-invoice-btn');
283 var original = $btn.html();
284 $btn.prop('disabled', true).attr('aria-busy', 'true').html('<span class="inline-flex items-center gap-2">' + spin + '<span>' + t('sending', 'Sending…') + '</span></span>');
285 $.ajax({
286 url: window.easyInvoice.ajaxUrl,
287 type: 'POST',
288 data: {
289 action: 'easy_invoice_send_invoice_email',
290 invoice_id: invoiceId,
291 nonce: nonce
292 },
293 success: function (response) {
294 if (response.success) {
295 if (typeof EasyInvoiceToast !== 'undefined') {
296 EasyInvoiceToast.success(t('email_sent_invoice', 'Invoice email sent successfully.'));
297 }
298 } else {
299 var msg = (response.data && response.data.message) ? response.data.message : t('email_error', 'Error sending email.');
300 if (typeof EasyInvoiceToast !== 'undefined') {
301 EasyInvoiceToast.error(msg);
302 }
303 }
304 },
305 error: function () {
306 if (typeof EasyInvoiceToast !== 'undefined') {
307 EasyInvoiceToast.error(t('network_error', 'Error connecting to server.'));
308 }
309 },
310 complete: function () {
311 $btn.prop('disabled', false).removeAttr('aria-busy').html(original);
312 }
313 });
314 };
315
316 if (typeof EasyInvoiceConfirmation !== 'undefined') {
317 EasyInvoiceConfirmation.confirmSendDocument('invoice', runAjax);
318 } else if (window.confirm(t('send_invoice_confirm_browser', 'Send this invoice by email?'))) {
319 runAjax();
320 }
321 }
322 });
323 </script>
324
325
326