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 / includes / Admin / EasyInvoiceAjax.php

EasyInvoiceAjax.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.2, at includes/Admin/EasyInvoiceAjax.php

1,447 lines 55.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * EasyInvoice AJAX Class
4 *
5 * @package Easy_Invoice
6 * @subpackage Admin
7 */
8
9 namespace EasyInvoice\Admin;
10
11 use EasyInvoice\Models\Invoice;
12 use EasyInvoice\Models\InvoiceItem;
13 use EasyInvoice\Providers\InvoiceServiceProvider;
14 use EasyInvoice\Providers\ClientServiceProvider;
15 use EasyInvoice\Constants\ClientFields;
16
17 /**
18 * EasyInvoiceAjax Class
19 *
20 * Handles all AJAX functionality for the plugin.
21 */
22 class EasyInvoiceAjax {
23 /**
24 * Initialize AJAX handlers
25 */
26 public function init() {
27 // Invoice actions
28 //add_action('wp_ajax_easy_invoice_save', array($this, 'saveInvoice'));
29 add_action('wp_ajax_easy_invoice_delete', array($this, 'deleteInvoice'));
30 add_action('wp_ajax_easy_invoice_get', array($this, 'getInvoice'));
31 add_action('wp_ajax_easy_invoice_save_invoice', array($this, 'saveInvoice'));
32 add_action('wp_ajax_easy_invoice_save_and_send_invoice', array($this, 'saveAndSendInvoice'));
33
34 // Template actions
35
36
37
38 // Document and email actions
39 add_action('wp_ajax_easy_invoice_download_pdf', array($this, 'downloadPdf'));
40 add_action('wp_ajax_easy_invoice_send_email', array($this, 'sendInvoiceEmail'));
41
42 // Single page actions (for public access)
43 add_action('wp_ajax_easy_invoice_download_invoice_pdf', array($this, 'downloadInvoicePdf'));
44 add_action('wp_ajax_easy_invoice_send_invoice_email', array($this, 'sendInvoiceEmailPublic'));
45 add_action('wp_ajax_nopriv_easy_invoice_download_invoice_pdf', array($this, 'downloadInvoicePdf'));
46 add_action('wp_ajax_nopriv_easy_invoice_send_invoice_email', array($this, 'sendInvoiceEmailPublic'));
47
48 // PDF generation actions
49 add_action('wp_ajax_easy_invoice_generate_pdf', array($this, 'generateInvoicePdf'));
50 add_action('wp_ajax_easy_invoice_generate_quote_pdf', array($this, 'generateQuotePdf'));
51 add_action('wp_ajax_nopriv_easy_invoice_generate_pdf', array($this, 'generateInvoicePdf'));
52 add_action('wp_ajax_nopriv_easy_invoice_generate_quote_pdf', array($this, 'generateQuotePdf'));
53
54 // Quote document actions
55 add_action('wp_ajax_easy_invoice_download_quote_pdf', array($this, 'downloadQuotePdf'));
56 add_action('wp_ajax_nopriv_easy_invoice_download_quote_pdf', array($this, 'downloadQuotePdf'));
57 add_action('wp_ajax_easy_invoice_save_quote', array($this, 'saveQuote'));
58
59 // Client actions
60 add_action('wp_ajax_easy_invoice_save_client', array($this, 'saveClient'));
61 add_action('wp_ajax_easy_invoice_delete_client', array($this, 'deleteClient'));
62 add_action('wp_ajax_easy_invoice_get_client', array($this, 'getClient'));
63 add_action('wp_ajax_easy_invoice_add_client', array($this, 'addClient'));
64 add_action('wp_ajax_easy_invoice_update_client', array($this, 'updateClient'));
65 add_action('wp_ajax_easy_invoice_check_email_exists', array($this, 'checkEmailExists'));
66 add_action('wp_ajax_easy_invoice_generate_password', array($this, 'generatePassword'));
67 add_action('wp_ajax_easy_invoice_search_clients', array($this, 'searchClients'));
68 add_action('wp_ajax_easy_invoice_update_invoices_data', array($this, 'updateInvoicesData'));
69 }
70
71 /**
72 * Save invoice
73 */
74 public function saveInvoice() {
75 $this->verifyNonce('easy_invoice_nonce');
76
77 if (!current_user_can('manage_options')) {
78 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
79 }
80
81 // Get the raw invoice data from the form
82 $raw_invoice_data = isset($_POST['invoice_data']) ? $_POST['invoice_data'] : $_POST;
83
84 // Remove non-invoice fields
85 unset($raw_invoice_data['action']);
86 unset($raw_invoice_data['nonce']);
87
88 // Process the invoice data
89 $invoice_form_manager = new \EasyInvoice\Forms\Invoice\InvoiceFormManager();
90 $invoice_data = $invoice_form_manager->processFormData($raw_invoice_data);
91
92 if (!empty($invoice_data['errors'])) {
93 wp_send_json_error([
94 'message' => 'Validation failed',
95 'errors' => $invoice_data['errors']
96 ]);
97 }
98
99 // Handle items separately - process the natural form submission format
100 if (isset($raw_invoice_data['items']) && is_array($raw_invoice_data['items'])) {
101 // Form submits items as items[0][title], items[0][description], etc.
102 // Convert to array of item objects for processing
103 $items_array = [];
104 foreach ($raw_invoice_data['items'] as $index => $item_data) {
105 if (is_array($item_data)) {
106 $items_array[] = $item_data;
107 }
108 }
109
110 // Process items using the dynamic field system
111 $invoice_data['data']['items'] = $invoice_form_manager->processItemsData($items_array);
112 }
113
114 // Handle special fields that might not be in the form definition
115 if (isset($raw_invoice_data['invoice_id'])) {
116 $invoice_data['data']['invoice_id'] = intval($raw_invoice_data['invoice_id']);
117 }
118
119 if (isset($raw_invoice_data['client_id'])) {
120 $invoice_data['data']['client_id'] = intval($raw_invoice_data['client_id']);
121 }
122
123
124
125 $invoice_id = isset($invoice_data['data']['invoice_id']) ? intval($invoice_data['data']['invoice_id']) : 0;
126
127 $repository = InvoiceServiceProvider::getInvoiceRepository();
128
129 if ($invoice_id > 0) {
130 // Update existing invoice - preserve existing invoice number
131 unset($invoice_data['data']['invoice_number']);
132 unset($invoice_data['data']['number']);
133
134 $invoice = $repository->update($invoice_id, $invoice_data['data']);
135
136 if (!$invoice) {
137
138 $this->sendError(__('Failed to update invoice', 'easy-invoice'));
139 }
140
141 // Use FormProcessor to save form data to database
142 $form_processor = new \EasyInvoice\Forms\FormProcessor();
143 $all_fields = $invoice_form_manager->getAllFields();
144 $form_processor->saveFormDataToDatabase($invoice_data['data'], $all_fields, $invoice);
145
146 $message = __('Invoice updated successfully', 'easy-invoice');
147 } else {
148 // Create new invoice - allow auto-generated invoice number to be saved
149 // The invoice number will be auto-generated by the form and included in the data
150
151 $invoice = $repository->create($invoice_data['data']);
152
153
154 if (!$invoice) {
155 $this->sendError(__('Failed to create invoice', 'easy-invoice'));
156 }
157
158 // Use FormProcessor to save form data to database
159 $form_processor = new \EasyInvoice\Forms\FormProcessor();
160 $all_fields = $invoice_form_manager->getAllFields();
161 $form_processor->saveFormDataToDatabase($invoice_data['data'], $all_fields, $invoice);
162
163 $invoice_id = $invoice->getId();
164 $message = __('Invoice created successfully', 'easy-invoice');
165 }
166
167 // Handle items
168 if (isset($invoice_data['data']['items']) && is_array($invoice_data['data']['items'])) {
169 $invoice->setItems($invoice_data['data']['items']);
170 }
171
172 // Prepare response data
173 $response_data = array(
174 'invoice_id' => $invoice_id,
175 'invoice' => $invoice->toArray(),
176 'toast' => array(
177 'type' => 'success',
178 'message' => $message,
179 'options' => array('duration' => 4000)
180 )
181 );
182
183 // Include client data if invoice has a client
184 if ($invoice->getClientId()) {
185 $client_repository = ClientServiceProvider::getClientRepository();
186 $client = $client_repository->find($invoice->getClientId());
187 if ($client) {
188 $response_data['client'] = array(
189 'id' => $client->getId(),
190 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
191 'email' => $client->getEmail() ?: '',
192 'phone' => $client->getExtraInfo() ?: '',
193 'company' => $client->getBusinessClientName() ?: '',
194 'address' => $client->getAddress() ?: '',
195 'website' => $client->getWebsite() ?: '',
196 );
197 }
198 }
199
200 wp_send_json_success($response_data);
201 }
202
203 /**
204 * Save and send invoice
205 */
206 public function saveAndSendInvoice() {
207 $this->verifyNonce('easy_invoice_nonce');
208
209 if (!current_user_can('manage_options')) {
210 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
211 }
212
213 // First save the invoice
214 $this->saveInvoice();
215
216 // If we get here, the invoice was saved successfully
217 // Now send the invoice via email
218 $invoice_id = isset($_POST['invoice_data']['invoice_id']) ? intval($_POST['invoice_data']['invoice_id']) : 0;
219
220 if ($invoice_id > 0) {
221 // Send the invoice via email
222 $result = $this->sendInvoiceEmail($invoice_id);
223
224 if ($result['success']) {
225 $this->sendSuccess(array(
226 'message' => __('Invoice saved and sent successfully', 'easy-invoice'),
227 'invoice_id' => $invoice_id
228 ));
229 } else {
230 $this->sendError($result['message']);
231 }
232 } else {
233 $this->sendError(__('Invalid invoice ID for sending', 'easy-invoice'));
234 }
235 }
236
237 /**
238 * Delete invoice
239 */
240 public function deleteInvoice() {
241 $this->verifyNonce('easy_invoice_nonce');
242
243 if (!current_user_can('manage_options')) {
244 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
245 }
246
247 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
248
249 if ($invoice_id <= 0) {
250 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
251 }
252
253 $repository = InvoiceServiceProvider::getInvoiceRepository();
254 $result = $repository->delete($invoice_id);
255
256 if (!$result) {
257 $this->sendError(__('Failed to delete invoice', 'easy-invoice'));
258 }
259
260 $this->sendSuccess(array(
261 'message' => __('Invoice deleted successfully', 'easy-invoice'),
262 'invoice_id' => $invoice_id,
263 ));
264 }
265
266 /**
267 * Get invoice
268 */
269 public function getInvoice() {
270 $this->verifyNonce('easy_invoice_nonce');
271
272 if (!current_user_can('manage_options')) {
273 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
274 }
275
276 $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;
277
278 if ($invoice_id <= 0) {
279 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
280 }
281
282 $repository = InvoiceServiceProvider::getInvoiceRepository();
283 $invoice = $repository->find($invoice_id);
284
285 if (!$invoice) {
286 $this->sendError(__('Invoice not found', 'easy-invoice'));
287 }
288
289 $this->sendSuccess(array(
290 'invoice' => $invoice->toArray(),
291 ));
292 }
293
294 /**
295 * Save client
296 */
297 public function saveClient() {
298 $this->verifyNonce('easy_invoice_nonce');
299
300 if (!current_user_can('manage_options')) {
301 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
302 }
303
304 $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
305 $client_data = isset($_POST['client_data']) ? $this->sanitizeData($_POST['client_data']) : array();
306
307 if (empty($client_data)) {
308 $this->sendError(__('Invalid client data', 'easy-invoice'));
309 }
310
311 $repository = ClientServiceProvider::getClientRepository();
312
313 if ($client_id > 0) {
314 // Update existing client
315 $client = $repository->update($client_id, $client_data);
316
317 if (!$client) {
318 $this->sendError(__('Failed to update client', 'easy-invoice'));
319 }
320
321 $message = __('Client updated successfully', 'easy-invoice');
322 } else {
323 // Create new client
324 $client = $repository->create($client_data);
325
326 if (!$client) {
327 $this->sendError(__('Failed to create client', 'easy-invoice'));
328 }
329
330 $client_id = $client->getId();
331 $message = __('Client created successfully', 'easy-invoice');
332 }
333
334 $this->sendSuccess(array(
335 'message' => $message,
336 'client_id' => $client_id,
337 'client' => $client->toArray(),
338 ));
339 }
340
341 /**
342 * Delete client
343 */
344 public function deleteClient() {
345 try {
346 $this->verifyNonce('easy_invoice_nonce');
347
348 if (!current_user_can('manage_options')) {
349 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
350 }
351
352 $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
353 $delete_associated_documents = isset($_POST['delete_associated_documents']) ? (bool)$_POST['delete_associated_documents'] : false;
354
355 if ($client_id <= 0) {
356 $this->sendError(__('Invalid client ID', 'easy-invoice'));
357 }
358
359 // Check if the user exists and is not an administrator
360 $user = get_user_by('ID', $client_id);
361 if (!$user) {
362 $this->sendError(__('User not found', 'easy-invoice'));
363 }
364
365 if (in_array('administrator', $user->roles)) {
366 $this->sendError(__('Cannot delete administrator accounts', 'easy-invoice'));
367 }
368
369 global $wpdb;
370
371 // Get counts of associated documents
372 $invoice_count = $wpdb->get_var($wpdb->prepare(
373 "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_client_id' AND meta_value = %d",
374 $client_id
375 ));
376
377 $quote_count = $wpdb->get_var($wpdb->prepare(
378 "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_quote_client_id' AND meta_value = %d",
379 $client_id
380 ));
381
382 $payment_count = $wpdb->get_var($wpdb->prepare(
383 "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_payment_client_id' AND meta_value = %d",
384 $client_id
385 ));
386
387 $total_documents = $invoice_count + $quote_count + $payment_count;
388
389 if ($delete_associated_documents) {
390 // Delete all associated documents
391 $this->log(sprintf('Deleting client %d with all associated documents (%d invoices, %d quotes, %d payments)',
392 $client_id, $invoice_count, $quote_count, $payment_count));
393
394 // Delete invoices
395 if ($invoice_count > 0) {
396 $invoices = $wpdb->get_col($wpdb->prepare(
397 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_client_id' AND meta_value = %d",
398 $client_id
399 ));
400 foreach ($invoices as $invoice_id) {
401 wp_delete_post($invoice_id, true);
402 }
403 }
404
405 // Delete quotes
406 if ($quote_count > 0) {
407 $quotes = $wpdb->get_col($wpdb->prepare(
408 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_quote_client_id' AND meta_value = %d",
409 $client_id
410 ));
411 foreach ($quotes as $quote_id) {
412 wp_delete_post($quote_id, true);
413 }
414 }
415
416 // Delete payments
417 if ($payment_count > 0) {
418 $payments = $wpdb->get_col($wpdb->prepare(
419 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_payment_client_id' AND meta_value = %d",
420 $client_id
421 ));
422 foreach ($payments as $payment_id) {
423 wp_delete_post($payment_id, true);
424 }
425 }
426
427 $message = sprintf(__('Client and all associated documents (%d total) deleted successfully', 'easy-invoice'), $total_documents);
428 } else {
429 // Only remove client associations, preserve documents
430 $this->log(sprintf('Removing client associations for client %d (%d invoices, %d quotes, %d payments)',
431 $client_id, $invoice_count, $quote_count, $payment_count));
432
433 // Remove client associations from invoices
434 if ($invoice_count > 0) {
435 $wpdb->delete(
436 $wpdb->postmeta,
437 ['meta_key' => '_easy_invoice_client_id', 'meta_value' => $client_id]
438 );
439 }
440
441 // Remove client associations from quotes
442 if ($quote_count > 0) {
443 $wpdb->delete(
444 $wpdb->postmeta,
445 ['meta_key' => '_easy_invoice_quote_client_id', 'meta_value' => $client_id]
446 );
447 }
448
449 // Remove client associations from payments
450 if ($payment_count > 0) {
451 $wpdb->delete(
452 $wpdb->postmeta,
453 ['meta_key' => '_easy_payment_client_id', 'meta_value' => $client_id]
454 );
455 }
456
457 $message = sprintf(__('Client deleted successfully. %d documents preserved but client associations removed.', 'easy-invoice'), $total_documents);
458 }
459
460 // Delete the WordPress user
461 require_once(ABSPATH . 'wp-admin/includes/user.php');
462 $result = wp_delete_user($client_id);
463
464 if (!$result) {
465 $this->sendError(__('Failed to delete client', 'easy-invoice'));
466 }
467
468 $this->sendSuccess(array(
469 'message' => $message,
470 'client_id' => $client_id,
471 'documents_deleted' => $delete_associated_documents,
472 'total_documents' => $total_documents
473 ));
474
475 } catch (\Exception $e) {
476 $this->sendError($e->getMessage());
477 }
478 }
479
480 /**
481 * Get client
482 */
483 public function getClient() {
484 $this->verifyNonce('easy_invoice_nonce');
485
486 if (!current_user_can('manage_options')) {
487 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
488 }
489
490 $client_id = isset($_REQUEST['client_id']) ? intval($_REQUEST['client_id']) : 0;
491
492 if ($client_id <= 0) {
493 $this->sendError(__('Invalid client ID', 'easy-invoice'));
494 }
495
496 $repository = ClientServiceProvider::getClientRepository();
497 $client = $repository->find($client_id);
498
499 if (!$client) {
500 $this->sendError(__('Client not found', 'easy-invoice'));
501 }
502
503 $client_data = $client->toArray();
504
505 // Return comprehensive client data in a unified format that works for both form population and display
506 $this->sendSuccess(array(
507 // Form population fields (for invoice-builder.js and invoice-form.js)
508 'name' => $client_data['company_name'] ?? $client_data['contact_name'] ?? '',
509 'email' => $client_data['email'] ?? '',
510 'phone' => $client_data['phone'] ?? '',
511 'company' => $client_data['company_name'] ?? '',
512 'address' => $client_data['billing_address'] ?? '',
513 'website' => $client_data['website'] ?? '',
514
515 // Display fields (for client-manager.js)
516 'business_client_name' => $client->getBusinessClientName(),
517 'username' => $client->getUsername(),
518 'extra_info' => $client->getExtraInfo(),
519 'first_name' => $client->getFirstName(),
520 'last_name' => $client->getLastName(),
521
522 // Raw data for backward compatibility
523 'client' => array(
524 'name' => $client_data['company_name'] ?? $client_data['contact_name'] ?? '',
525 'email' => $client_data['email'] ?? '',
526 'phone' => $client_data['phone'] ?? '',
527 'company' => $client_data['company_name'] ?? '',
528 'address' => $client_data['billing_address'] ?? '',
529 'website' => $client_data['website'] ?? '',
530 )
531 ));
532 }
533
534 /**
535 * Verify nonce
536 *
537 * @param string $action The nonce action
538 */
539 private function verifyNonce($action) {
540 // Check for _nonce (standard format) first
541 if (isset($_REQUEST['_nonce']) && wp_verify_nonce($_REQUEST['_nonce'], $action)) {
542 return;
543 }
544
545 // Also check for 'nonce' (client form format)
546 if (isset($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], $action)) {
547 return;
548 }
549
550 // If we get here, neither nonce format was valid
551 $this->sendError(__('Security check failed', 'easy-invoice'));
552 }
553
554 /**
555 * Sanitize data
556 *
557 * @param array $data The data to sanitize
558 * @return array The sanitized data
559 */
560 private function sanitizeData($data) {
561 if (!is_array($data)) {
562 return array();
563 }
564
565 $sanitized = array();
566
567 // Define fields that should allow HTML (like textarea content)
568 $html_fields = [
569 'invoice_description', 'description', 'notes', 'terms',
570 'internal_notes', 'customer_address'
571 ];
572
573 // Define numeric fields
574 $numeric_fields = [
575 'invoice_id', 'client_id', 'discount_value', 'tax_rate'
576 ];
577
578 foreach ($data as $key => $value) {
579 if (is_array($value)) {
580 $sanitized[$key] = $this->sanitizeData($value);
581 } else if (in_array($key, $html_fields)) {
582 // For HTML fields, use wp_kses to allow certain tags but prevent XSS
583 $sanitized[$key] = wp_kses_post($value);
584 } else if (in_array($key, $numeric_fields)) {
585 // For numeric fields, ensure they're valid numbers
586 $sanitized[$key] = is_numeric($value) ? $value : 0;
587 } else {
588 $sanitized[$key] = sanitize_text_field($value);
589 }
590 }
591
592 return $sanitized;
593 }
594
595 /**
596 * Send success response
597 */
598 private function sendSuccess($data = array()) {
599 // Check if we should suppress global toast
600 $suppress_toast = isset($_POST['suppress_global_toast']) && $_POST['suppress_global_toast'] === 'true';
601
602 // Add toast notification if not already present and not suppressed
603 if (!isset($data['toast']) && !$suppress_toast) {
604 $message = isset($data['message']) ? $data['message'] : __('Operation completed successfully', 'easy-invoice');
605 $data['toast'] = array(
606 'type' => 'success',
607 'message' => $message,
608 'options' => array('duration' => 4000)
609 );
610 }
611
612 // Remove toast data if suppressed
613 if ($suppress_toast && isset($data['toast'])) {
614 unset($data['toast']);
615 }
616
617 wp_send_json_success($data);
618 }
619
620 /**
621 * Send error response
622 */
623 private function sendError($message, $data = array()) {
624 // Add toast notification
625 $data['toast'] = array(
626 'type' => 'error',
627 'message' => $message,
628 'options' => array('duration' => 6000)
629 );
630
631 wp_send_json_error($data);
632 }
633
634 /**
635 * Download invoice as PDF
636 */
637 public function downloadPdf() {
638 // Verify nonce
639 $this->verifyNonce('easy_invoice_nonce');
640
641 // Check if user has required capability
642 if (!current_user_can('edit_posts')) {
643 $this->sendError(__('You do not have permission to download invoices', 'easy-invoice'));
644 }
645
646 // Get invoice ID
647 $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;
648
649 if (!$invoice_id) {
650 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
651 }
652
653 // Get invoice from repository
654 $repository = InvoiceServiceProvider::getInvoiceRepository();
655 $invoice = $repository->find($invoice_id);
656
657 if (!$invoice) {
658 $this->sendError(__('Invoice not found', 'easy-invoice'));
659 }
660
661 // Get invoice data for PDF generation
662 $invoice_data = \EasyInvoice\Includes\Helpers\PdfHelper::getInvoiceDataForPdf($invoice);
663
664 // Return success response with invoice data
665 $this->sendSuccess(array(
666 'message' => __('Invoice data retrieved successfully', 'easy-invoice'),
667 'invoice_data' => $invoice_data
668 ));
669 }
670
671 /**
672 * Send invoice via email
673 */
674 public function sendInvoiceEmail() {
675 $this->verifyNonce('easy_invoice_nonce');
676
677 if (!current_user_can('manage_options')) {
678 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
679 }
680
681 // Get invoice ID from POST data
682 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
683
684 if (!$invoice_id) {
685 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
686 }
687
688 $repository = InvoiceServiceProvider::getInvoiceRepository();
689 $invoice = $repository->find($invoice_id);
690
691 if (!$invoice) {
692 $this->sendError(__('Invoice not found', 'easy-invoice'));
693 }
694
695 // Use EmailManager to send the email
696 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
697 $result = $email_manager->sendInvoiceEmail($invoice, 'new');
698
699 if ($result['success']) {
700 $this->sendSuccess(array(
701 'message' => $result['message']
702 ));
703 } else {
704 $this->sendError($result['message']);
705 }
706 }
707
708 /**
709 * Download quote as PDF
710 */
711 public function downloadQuotePdf() {
712 // Verify nonce
713 $this->verifyNonce('easy_invoice_nonce');
714
715 // Check if user has required capability
716 if (!current_user_can('edit_posts')) {
717 $this->sendError(__('You do not have permission to download quotes', 'easy-invoice'));
718 }
719
720 // Get quote ID
721 $quote_id = isset($_POST['quote_id']) ? intval($_POST['quote_id']) : 0;
722
723 if (!$quote_id) {
724 $this->sendError(__('Invalid quote ID', 'easy-invoice'));
725 }
726
727 // Get quote from repository
728 $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
729 $quote = $repository->find($quote_id);
730
731 if (!$quote) {
732 $this->sendError(__('Quote not found', 'easy-invoice'));
733 }
734
735 // For now, return success response with quote data
736 // PDF generation can be implemented later with actual PDF creation
737 $this->sendSuccess(array(
738 'message' => __('Quote data retrieved successfully', 'easy-invoice'),
739 'quote_data' => $quote->toArray(),
740 'download_url' => add_query_arg(array(
741 'action' => 'easy_invoice_generate_quote_pdf',
742 'quote_id' => $quote_id,
743 'nonce' => wp_create_nonce('generate_quote_pdf')
744 ), admin_url('admin-ajax.php'))
745 ));
746 }
747
748 /**
749 * Save quote
750 */
751 public function saveQuote() {
752 $this->verifyNonce('easy_invoice_nonce');
753
754 if (!current_user_can('manage_options')) {
755 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
756 }
757
758 // Get the raw quote data from the form
759 $raw_quote_data = isset($_POST['quote_data']) ? $_POST['quote_data'] : $_POST;
760
761 // Debug: Log the raw POST data
762 error_log("Raw POST data for quote: " . print_r($_POST, true));
763 error_log("Raw quote data: " . print_r($raw_quote_data, true));
764
765 // Remove non-quote fields
766 unset($raw_quote_data['action']);
767 unset($raw_quote_data['nonce']);
768
769 // Process the quote data
770 $quote_form_manager = new \EasyInvoice\Forms\Quote\QuoteFormManager();
771 $quote_data = $quote_form_manager->processFormData($raw_quote_data);
772
773 if (!empty($quote_data['errors'])) {
774 wp_send_json_error([
775 'message' => 'Validation failed',
776 'errors' => $quote_data['errors']
777 ]);
778 }
779
780 // Handle items separately - process the natural form submission format
781 if (isset($raw_quote_data['items']) && is_array($raw_quote_data['items'])) {
782 // Form submits items as items[0][title], items[0][description], etc.
783 // Convert to array of item objects for processing
784 $items_array = [];
785 foreach ($raw_quote_data['items'] as $index => $item_data) {
786 if (is_array($item_data)) {
787 $items_array[] = $item_data;
788 }
789 }
790
791 // Debug: Log the raw items data
792 error_log("Raw quote items data: " . print_r($raw_quote_data['items'], true));
793 error_log("Processed items array: " . print_r($items_array, true));
794
795 // Process items using the dynamic field system
796 $quote_data['data']['items'] = $quote_form_manager->processItemsData($items_array);
797
798 // Debug: Log the processed items data
799 error_log("Final processed items: " . print_r($quote_data['data']['items'], true));
800 }
801
802 // Handle special fields that might not be in the form definition
803 if (isset($raw_quote_data['quote_id'])) {
804 $quote_data['data']['quote_id'] = intval($raw_quote_data['quote_id']);
805 }
806
807 if (isset($raw_quote_data['client_id'])) {
808 $quote_data['data']['client_id'] = intval($raw_quote_data['client_id']);
809 }
810
811
812
813 $quote_id = isset($quote_data['data']['quote_id']) ? intval($quote_data['data']['quote_id']) : 0;
814
815 $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
816
817 if ($quote_id > 0) {
818 // Update existing quote - preserve existing quote number
819 unset($quote_data['data']['quote_number']);
820 unset($quote_data['data']['number']);
821
822 // Get the existing quote first
823 $quote = $repository->find($quote_id);
824
825 if (!$quote) {
826 $this->sendError(__('Failed to find quote for update', 'easy-invoice'));
827 }
828
829 // Use FormProcessor to save form data to database BEFORE repository update
830 $form_processor = new \EasyInvoice\Forms\FormProcessor();
831 $all_fields = $quote_form_manager->getAllFields();
832 $form_processor->saveFormDataToDatabase($quote_data['data'], $all_fields, $quote);
833
834 // Now update the quote with the processed data, passing the existing quote object
835 $quote = $repository->update($quote_id, $quote_data['data'], $quote);
836
837 if (!$quote) {
838 $this->sendError(__('Failed to update quote', 'easy-invoice'));
839 }
840
841 $message = __('Quote updated successfully', 'easy-invoice');
842 } else {
843 // Create new quote - allow auto-generated quote number to be saved
844 // The quote number will be auto-generated by the form and included in the data
845
846 $quote = $repository->create($quote_data['data']);
847
848 if (!$quote) {
849 $this->sendError(__('Failed to create quote', 'easy-invoice'));
850 }
851
852 // Use FormProcessor to save form data to database
853 $form_processor = new \EasyInvoice\Forms\FormProcessor();
854 $all_fields = $quote_form_manager->getAllFields();
855 $form_processor->saveFormDataToDatabase($quote_data['data'], $all_fields, $quote);
856
857 $quote_id = $quote->getId();
858 $message = __('Quote created successfully', 'easy-invoice');
859 }
860
861 // Handle items
862 if (isset($quote_data['data']['items']) && is_array($quote_data['data']['items'])) {
863 error_log("Setting quote items: " . print_r($quote_data['data']['items'], true));
864 $quote->setItems($quote_data['data']['items']);
865 // Save the quote to persist the items to database
866 error_log("Calling quote->save() to persist items");
867 $quote->save();
868 // Quote save completed
869 }
870
871 // Prepare response data
872 $response_data = array(
873 'quote_id' => $quote_id,
874 'quote' => $quote->toArray(),
875 'toast' => array(
876 'type' => 'success',
877 'message' => $message,
878 'options' => array('duration' => 4000)
879 )
880 );
881
882 // Include client data if quote has a client
883 if ($quote->getClientId()) {
884 $client_repository = ClientServiceProvider::getClientRepository();
885 $client = $client_repository->find($quote->getClientId());
886 if ($client) {
887 $response_data['client'] = array(
888 'id' => $client->getId(),
889 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
890 'email' => $client->getEmail() ?: '',
891 'phone' => $client->getExtraInfo() ?: '',
892 'company' => $client->getBusinessClientName() ?: '',
893 'address' => $client->getAddress() ?: '',
894 'website' => $client->getWebsite() ?: '',
895 );
896 }
897 }
898
899
900 $this->sendSuccess($response_data);
901 }
902
903 /**
904 * Toggle a template as favorite
905 */
906
907
908
909
910 /**
911 * Check if an email already exists for any client
912 */
913 public function checkEmailExists() {
914 $this->verifyNonce('easy_invoice_nonce');
915
916 if (!current_user_can('manage_options')) {
917 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
918 }
919
920 $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
921
922 if (empty($email)) {
923 $this->sendSuccess(array('exists' => false));
924 }
925
926 $repository = ClientServiceProvider::getClientRepository();
927 $existing_clients = $repository->findByEmail($email);
928
929 $this->sendSuccess(array(
930 'exists' => !empty($existing_clients),
931 'count' => count($existing_clients)
932 ));
933 }
934
935 /**
936 * Generate a secure password.
937 */
938 public function generatePassword() {
939 $this->verifyNonce('easy_invoice_nonce');
940
941 if (!current_user_can('manage_options')) {
942 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
943 }
944
945 $password = wp_generate_password(16, true, true);
946
947 // Check if we should suppress global toast
948 $suppress_toast = isset($_POST['suppress_global_toast']) && $_POST['suppress_global_toast'] === 'true';
949
950 $this->sendSuccess(array(
951 'password' => $password,
952 'suppress_toast' => $suppress_toast
953 ));
954 }
955
956 /**
957 * Sanitize invoice items
958 *
959 * @param array $items Raw items data
960 * @return array Sanitized items data
961 */
962 private function sanitizeItems(array $items): array {
963 $sanitized_items = [];
964
965 // Get field configuration for dynamic processing
966 $form_manager = new \EasyInvoice\Forms\Invoice\InvoiceFormManager();
967 $field_config = $form_manager->getItemFields();
968
969 foreach ($items as $item) {
970 if (!is_array($item)) {
971 continue;
972 }
973
974 $sanitized_item = [];
975
976 // Process each field dynamically based on configuration
977 foreach ($field_config as $field) {
978 $field_name = $field['name'] ?? '';
979 $field_type = $field['type'] ?? 'text';
980 $raw_value = $item[$field_name] ?? '';
981
982 // Apply field-specific sanitization
983 switch ($field_type) {
984 case 'text':
985 $sanitized_item[$field_name] = sanitize_text_field($raw_value);
986 break;
987 case 'textarea':
988 $sanitized_item[$field_name] = wp_kses_post($raw_value);
989 break;
990 case 'number':
991 $sanitized_item[$field_name] = is_numeric($raw_value) ? floatval($raw_value) : 0;
992 break;
993 case 'checkbox':
994 $sanitized_item[$field_name] = !empty($raw_value) ? true : false;
995 break;
996 default:
997 $sanitized_item[$field_name] = sanitize_text_field($raw_value);
998 break;
999 }
1000 }
1001
1002 // Handle legacy field names for backward compatibility
1003 if (isset($item['name']) && !isset($sanitized_item['title'])) {
1004 $sanitized_item['title'] = sanitize_text_field($item['name']);
1005 }
1006 if (isset($item['title']) && !isset($sanitized_item['title'])) {
1007 $sanitized_item['title'] = sanitize_text_field($item['title']);
1008 }
1009
1010 // Only add items that have at least a title/name
1011 if (!empty($sanitized_item['title'])) {
1012 $sanitized_items[] = $sanitized_item;
1013 }
1014 }
1015
1016 return $sanitized_items;
1017 }
1018
1019 /**
1020 * Add a new client (specifically for the client form in templates/clients-page.php)
1021 */
1022 public function addClient() {
1023
1024 $this->verifyNonce('easy_invoice_nonce');
1025
1026 if (!current_user_can('manage_options')) {
1027 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
1028 }
1029
1030 // Check if required fields are present
1031 $required_fields = ['business_client_name', 'email', 'username'];
1032 foreach ($required_fields as $field) {
1033 if (!isset($_POST[$field]) || empty($_POST[$field])) {
1034 $this->sendError(__('Missing required field: ' . $field, 'easy-invoice'));
1035 }
1036 }
1037
1038 // Prepare client data
1039 $client_data = [
1040 ClientFields::BUSINESS_CLIENT_NAME => sanitize_text_field($_POST['business_client_name']),
1041 ClientFields::EMAIL => sanitize_email($_POST['email']),
1042 ClientFields::USERNAME => sanitize_user($_POST['username']),
1043 ClientFields::PASSWORD => $_POST['password'],
1044 ClientFields::ADDRESS => sanitize_textarea_field($_POST['address']),
1045 ClientFields::EXTRA_INFO => sanitize_textarea_field($_POST['extra_info']),
1046 ClientFields::FIRST_NAME => sanitize_text_field($_POST['first_name']),
1047 ClientFields::LAST_NAME => sanitize_text_field($_POST['last_name']),
1048 ClientFields::WEBSITE => esc_url_raw($_POST['website']),
1049 ClientFields::PHONE => isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '',
1050 ];
1051
1052
1053
1054 // Basic validation
1055 if (empty($client_data[ClientFields::BUSINESS_CLIENT_NAME]) && (empty($client_data[ClientFields::FIRST_NAME]) || empty($client_data[ClientFields::LAST_NAME]))) {
1056 $this->sendError(__('Please provide a client name or first/last name.', 'easy-invoice'));
1057 }
1058
1059 if (empty($client_data[ClientFields::EMAIL])) {
1060 $this->sendError(__('Email address is required', 'easy-invoice'));
1061 }
1062
1063 $repository = ClientServiceProvider::getClientRepository();
1064
1065 // Create new client
1066 $client = $repository->create($client_data);
1067
1068 if (!$client) {
1069 $this->sendError(__('Failed to create client', 'easy-invoice'));
1070 }
1071
1072 $client_id = $client->getId();
1073
1074 $response_data = array(
1075 'message' => __('Client added successfully', 'easy-invoice'),
1076 'client_id' => $client_id,
1077 'client' => $client->toArray(),
1078 );
1079
1080 $this->sendSuccess($response_data);
1081 }
1082
1083 /**
1084 * Update client from the client edit form
1085 */
1086 public function updateClient() {
1087 $this->verifyNonce('easy_invoice_nonce');
1088
1089 if (!current_user_can('manage_options')) {
1090 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
1091 }
1092
1093 $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
1094
1095 if ($client_id <= 0) {
1096 $this->sendError(__('Invalid client ID', 'easy-invoice'));
1097 }
1098
1099 // Prepare client data
1100 $client_data = [
1101 ClientFields::BUSINESS_CLIENT_NAME => sanitize_text_field($_POST['business_client_name']),
1102 ClientFields::EMAIL => sanitize_email($_POST['email']),
1103 ClientFields::USERNAME => sanitize_user($_POST['username']),
1104 ClientFields::PASSWORD => $_POST['password'], // Keep password as is, don't sanitize
1105 ClientFields::ADDRESS => sanitize_textarea_field($_POST['address']),
1106 ClientFields::PHONE => isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '',
1107 ClientFields::EXTRA_INFO => sanitize_textarea_field($_POST['extra_info']),
1108 ClientFields::FIRST_NAME => sanitize_text_field($_POST['first_name']),
1109 ClientFields::LAST_NAME => sanitize_text_field($_POST['last_name']),
1110 ClientFields::WEBSITE => esc_url_raw($_POST['website'])
1111 ];
1112
1113 // Remove empty values except password (password can be empty for updates)
1114 $client_data = array_filter($client_data, function($value, $key) {
1115 if ($key === ClientFields::PASSWORD) {
1116 return true; // Always include password field
1117 }
1118 return $value !== '';
1119 }, ARRAY_FILTER_USE_BOTH);
1120
1121 if (empty($client_data)) {
1122 $this->sendError(__('No data provided to update.', 'easy-invoice'));
1123 }
1124
1125 $repository = ClientServiceProvider::getClientRepository();
1126
1127 // Update existing client
1128 $client = $repository->update($client_id, $client_data);
1129
1130 if (!$client) {
1131 $this->sendError(__('Failed to update client', 'easy-invoice'));
1132 }
1133
1134 $this->sendSuccess(array(
1135 'message' => __('Client updated successfully', 'easy-invoice'),
1136 'client_id' => $client_id,
1137 'client' => $client->toArray(),
1138 ));
1139 }
1140
1141 /**
1142 * Update invoices data with missing client information and totals
1143 */
1144 public function updateInvoicesData() {
1145 $this->verifyNonce('easy_invoice_admin_nonce');
1146
1147 if (!current_user_can('manage_options')) {
1148 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
1149 }
1150
1151 $repository = InvoiceServiceProvider::getInvoiceRepository();
1152 $client_repository = ClientServiceProvider::getClientRepository();
1153
1154 // Get all invoices
1155 $invoices = $repository->all();
1156 $updated_count = 0;
1157
1158 foreach ($invoices as $invoice) {
1159 $updated = false;
1160
1161 // Check if client data is missing
1162 $client_id = $invoice->getClientId();
1163 if ($client_id > 0) {
1164 $client = $client_repository->find($client_id);
1165 if ($client) {
1166 // Update customer name if missing
1167 $customer_name = $invoice->getCustomerName();
1168 if (empty($customer_name)) {
1169 $customer_name = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
1170 $invoice->setCustomerName($customer_name);
1171 $updated = true;
1172 }
1173
1174 // Update customer email if missing
1175 $customer_email = $invoice->getCustomerEmail();
1176 if (empty($customer_email)) {
1177 $customer_email = $client->getEmail();
1178 $invoice->setCustomerEmail($customer_email);
1179 $updated = true;
1180 }
1181
1182 // Update customer address if missing
1183 $customer_address = $invoice->getCustomerAddress();
1184 if (empty($customer_address)) {
1185 $customer_address = $client->getAddress();
1186 $invoice->setCustomerAddress($customer_address);
1187 $updated = true;
1188 }
1189 }
1190 }
1191
1192 // Check if total is missing or zero
1193 $total = $invoice->getTotal();
1194 if (empty($total) || $total == 0) {
1195 // Recalculate total from items
1196 $items = $invoice->getItems();
1197 if (!empty($items)) {
1198 $subtotal = 0;
1199 foreach ($items as $item) {
1200 if (method_exists($item, 'getAmount')) {
1201 $subtotal += $item->getAmount();
1202 } elseif (isset($item['amount'])) {
1203 $subtotal += $item['amount'];
1204 }
1205 }
1206
1207 // Calculate discount and tax
1208 $discount = $invoice->getDiscountAmount();
1209 $tax = $invoice->getTaxAmount();
1210
1211 $total = $subtotal - $discount + $tax;
1212
1213 // Save the calculated total
1214 $invoice->setMeta('_easy_invoice_total', $total);
1215 $updated = true;
1216 }
1217 }
1218
1219 if ($updated) {
1220 $updated_count++;
1221 }
1222 }
1223
1224 $this->sendSuccess(array(
1225 'message' => sprintf(__('Updated %d invoices with missing data', 'easy-invoice'), $updated_count),
1226 'updated_count' => $updated_count
1227 ));
1228 }
1229
1230 /**
1231 * Download invoice as PDF (public access)
1232 */
1233 public function downloadInvoicePdf() {
1234 // Verify nonce
1235 $this->verifyNonce('easy_invoice_nonce');
1236
1237 // Get invoice ID
1238 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
1239
1240 if (!$invoice_id) {
1241 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
1242 }
1243
1244 // Get invoice from repository (only published invoices for public access)
1245 $repository = InvoiceServiceProvider::getInvoiceRepository();
1246
1247 // For admins, allow access to any invoice status
1248 if (current_user_can('manage_options')) {
1249 $invoice = $repository->find($invoice_id);
1250 } else {
1251 // For non-admins, only allow access to published invoices
1252 $invoice = $repository->findPublished($invoice_id);
1253 }
1254
1255 if (!$invoice) {
1256 $this->sendError(__('Invoice not found', 'easy-invoice'));
1257 }
1258
1259 // Get invoice data for PDF generation
1260 $invoice_data = \EasyInvoice\Includes\Helpers\PdfHelper::getInvoiceDataForPdf($invoice);
1261
1262 // For now, return success response with invoice data
1263 // PDF generation can be implemented later with actual PDF creation
1264 $this->sendSuccess(array(
1265 'message' => __('Invoice data retrieved successfully', 'easy-invoice'),
1266 'invoice_data' => $invoice_data,
1267 'download_url' => add_query_arg(array(
1268 'action' => 'easy_invoice_generate_pdf',
1269 'invoice_id' => $invoice_id,
1270 'nonce' => wp_create_nonce('generate_pdf')
1271 ), admin_url('admin-ajax.php'))
1272 ));
1273 }
1274
1275 /**
1276 * Send invoice via email (public access)
1277 */
1278 public function sendInvoiceEmailPublic() {
1279 $this->verifyNonce('easy_invoice_send_invoice_email');
1280
1281 // Get invoice ID
1282 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
1283
1284 if (!$invoice_id) {
1285 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
1286 }
1287
1288 // Get invoice from repository (only published invoices for public access)
1289 $repository = InvoiceServiceProvider::getInvoiceRepository();
1290
1291 // For admins, allow access to any invoice status
1292 if (current_user_can('manage_options')) {
1293 $invoice = $repository->find($invoice_id);
1294 } else {
1295 // For non-admins, only allow access to published invoices
1296 $invoice = $repository->findPublished($invoice_id);
1297 }
1298
1299 if (!$invoice) {
1300 $this->sendError(__('Invoice not found', 'easy-invoice'));
1301 }
1302
1303 // Use EmailManager to send the email
1304 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1305 $result = $email_manager->sendInvoiceEmail($invoice, 'new');
1306
1307 if ($result['success']) {
1308 $this->sendSuccess(array(
1309 'message' => $result['message']
1310 ));
1311 } else {
1312 $this->sendError($result['message']);
1313 }
1314 }
1315
1316 /**
1317 * Generate invoice PDF
1318 */
1319 public function generateInvoicePdf() {
1320 // Verify nonce
1321 $this->verifyNonce('generate_pdf');
1322
1323 // Get invoice ID
1324 $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;
1325
1326 if (!$invoice_id) {
1327 $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
1328 }
1329
1330 // Get invoice from repository
1331 $repository = InvoiceServiceProvider::getInvoiceRepository();
1332
1333 // For admins, allow access to any invoice status
1334 if (current_user_can('manage_options')) {
1335 $invoice = $repository->find($invoice_id);
1336 } else {
1337 // For non-admins, only allow access to published invoices
1338 $invoice = $repository->findPublished($invoice_id);
1339 }
1340
1341 if (!$invoice) {
1342 $this->sendError(__('Invoice not found', 'easy-invoice'));
1343 }
1344
1345 // Redirect to the invoice single page with PDF generation
1346 $invoice_url = get_permalink($invoice_id);
1347 if ($invoice_url) {
1348 wp_redirect(add_query_arg('auto_download_pdf', '1', $invoice_url));
1349 exit;
1350 } else {
1351 $this->sendError(__('Could not generate invoice URL', 'easy-invoice'));
1352 }
1353 }
1354
1355 /**
1356 * Generate quote PDF
1357 */
1358 public function generateQuotePdf() {
1359 // Verify nonce
1360 $this->verifyNonce('generate_quote_pdf');
1361
1362 // Get quote ID
1363 $quote_id = isset($_REQUEST['quote_id']) ? intval($_REQUEST['quote_id']) : 0;
1364
1365 if (!$quote_id) {
1366 $this->sendError(__('Invalid quote ID', 'easy-invoice'));
1367 }
1368
1369 // Get quote from repository
1370 $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
1371 $quote = $repository->find($quote_id);
1372
1373 if (!$quote) {
1374 $this->sendError(__('Quote not found', 'easy-invoice'));
1375 }
1376
1377 // Redirect to the quote single page with PDF generation
1378 $quote_url = get_permalink($quote_id);
1379 if ($quote_url) {
1380 wp_redirect(add_query_arg('auto_download_pdf', '1', $quote_url));
1381 exit;
1382 } else {
1383 $this->sendError(__('Could not generate quote URL', 'easy-invoice'));
1384 }
1385 }
1386
1387 /**
1388 * Search clients for the dropdown
1389 */
1390 public function searchClients() {
1391 $this->verifyNonce('easy_invoice_nonce');
1392
1393 if (!current_user_can('manage_options')) {
1394 $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
1395 }
1396
1397 $query = isset($_POST['query']) ? sanitize_text_field($_POST['query']) : '';
1398
1399 // Get client repository
1400 $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
1401
1402 // Search clients
1403 $clients = $client_repository->search($query);
1404
1405 if (empty($clients)) {
1406 $this->sendSuccess(array());
1407 }
1408
1409 // Format clients for dropdown
1410 $formatted_clients = array();
1411 foreach ($clients as $client) {
1412 // Get the WordPress user data directly
1413 $user = get_user_by('id', $client->getId());
1414 if (!$user) {
1415 continue;
1416 }
1417
1418 // Use Client model properties first, fallback to WordPress user fields
1419 $business_name = $client->business_client_name ?: '';
1420 $first_name = $client->first_name ?: $user->first_name ?: '';
1421 $last_name = $client->last_name ?: $user->last_name ?: '';
1422 $email = $client->email ?: $user->user_email ?: '';
1423
1424
1425
1426 // Create display name
1427 $client_name = $business_name ?: ($first_name . ' ' . $last_name);
1428 if (empty(trim($client_name))) {
1429 $client_name = $user->display_name ?: 'User ' . $client->getId();
1430 }
1431
1432 // Include all clients, even those with empty emails
1433 $formatted_clients[] = array(
1434 'id' => $client->getId(),
1435 'name' => $client_name,
1436 'email' => $email,
1437 'company' => $business_name,
1438 'phone' => $client->phone ?: '',
1439 'address' => $client->address ?: '',
1440 'website' => $client->website ?: '',
1441 'display_name' => $client_name . ' (' . $email . ')'
1442 );
1443 }
1444
1445 $this->sendSuccess($formatted_clients);
1446 }
1447 }