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 / includes / Repositories / InvoiceRepository.php

InvoiceRepository.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.1, at includes/Repositories/InvoiceRepository.php

618 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Invoice Repository Class
4 *
5 * @package Easy_Invoice
6 * @subpackage Repositories
7 */
8
9 namespace EasyInvoice\Repositories;
10
11 use EasyInvoice\Interfaces\InvoiceRepositoryInterface;
12 use EasyInvoice\Models\Invoice;
13 use WP_Post;
14 use WP_Query;
15
16 /**
17 * InvoiceRepository Class
18 *
19 * Handles data access for invoice objects with extensible architecture for pro features.
20 */
21 class InvoiceRepository implements InvoiceRepositoryInterface {
22
23 /**
24 * The post type name
25 *
26 * @var string
27 */
28 protected $post_type = \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE;
29
30 /**
31 * Find an invoice by ID
32 *
33 * @param int $id The invoice ID
34 * @return Invoice|null The invoice model or null if not found
35 */
36 public function find($id) {
37 $post = get_post($id);
38
39 if (!$post || $post->post_type !== $this->post_type) {
40 return null;
41 }
42
43 $invoice = new Invoice($post);
44
45 // Ensure invoice has proper slug for pretty URLs
46 $invoice->ensureProperSlug();
47
48 // Allow plugins to modify the found invoice
49 return apply_filters('easy_invoice_invoice_found', $invoice, $id);
50 }
51
52 /**
53 * Get all invoices
54 *
55 * @param array $args Optional arguments to filter the results
56 * @return array Array of Invoice models
57 */
58 public function all($args = []) {
59 $default_args = [
60 'post_type' => $this->post_type,
61 'posts_per_page' => -1,
62 'post_status' => 'publish',
63 'orderby' => 'date',
64 'order' => 'DESC',
65 ];
66
67 $query_args = wp_parse_args($args, $default_args);
68
69 // Allow plugins to modify query arguments
70 $query_args = apply_filters('easy_invoice_invoice_query_args', $query_args);
71
72 $query = new WP_Query($query_args);
73 $invoices = [];
74
75 if ($query->have_posts()) {
76 foreach ($query->posts as $post) {
77 $invoice = new Invoice($post);
78
79 // Ensure invoice has proper slug for pretty URLs
80 $invoice->ensureProperSlug();
81
82 $invoices[] = $invoice;
83 }
84 }
85
86 // Allow plugins to modify the results
87 return apply_filters('easy_invoice_invoices_found', $invoices, $query_args);
88 }
89
90 /**
91 * Create a new invoice
92 *
93 * @param array $data Invoice data
94 * @return Invoice|false The created invoice or false on failure
95 */
96 public function create($data) {
97 // Allow plugins to modify the data before creation
98 $data = apply_filters('easy_invoice_invoice_create_data', $data);
99
100 // Create a new invoice object
101 $invoice = new Invoice();
102
103 // Set basic data
104 $invoice->setTitle($data['title'] ?? 'New Invoice');
105
106 // Auto-generate invoice number if not provided
107 // A number the form sent is kept only if nobody has it yet (two
108 // builders opened at once are pre-filled with the same one); the
109 // counter moves past it, or a fresh number is issued.
110 $invoice_number_service = easy_invoice_get_invoice_number_service();
111 $requested = (string) ($data['number'] ?? $data['invoice_number'] ?? '');
112 $data['number'] = $invoice_number_service->claimOrGenerate($requested);
113 unset($data['invoice_number']);
114
115 // Discount timing defaults to "before tax", the first choice the builder
116 // offers. Left empty, the model's arithmetic takes the after-tax branch,
117 // which is what API- and import-created invoices used to get.
118 if (empty($data['discount_calculation_method'])) {
119 $data['discount_calculation_method'] = 'before_tax';
120 }
121
122 // Set invoice data
123 $this->setInvoiceData($invoice, $data, false);
124
125 // A client was given but no customer details: take them from the
126 // client record now, so the object handed back (and the meta saved)
127 // already carries the name and email — callers that email straight
128 // after creating would otherwise see "client email is missing".
129 if (is_numeric($data['client_id'] ?? 0) && (int) ($data['client_id'] ?? 0) > 0 && (empty($data['customer_email']) || empty($data['customer_name'])) && method_exists($invoice, 'populateClientInfo')) {
130 $invoice->populateClientInfo();
131 }
132
133 // Allow plugins to modify the invoice before saving
134 do_action('easy_invoice_invoice_before_create', $invoice, $data);
135
136 // Save the invoice (this will create the post and ensure proper permalinks)
137 if ($invoice->save()) {
138 // Allow plugins to perform actions after creation
139 do_action('easy_invoice_invoice_created', $invoice, $data);
140 return $invoice;
141 }
142
143 return false;
144 }
145
146 /**
147 * Update an existing invoice
148 *
149 * @param int $id The invoice ID
150 * @param array $data The invoice data
151 * @return Invoice|null The updated invoice model or null if not found
152 */
153 public function update($id, $data) {
154 try {
155 $invoice = $this->find($id);
156
157 if (!$invoice) {
158 return null;
159 }
160
161 // Set invoice data
162 $this->setInvoiceData($invoice, $data);
163
164 // Save the invoice
165 $result = $invoice->save();
166
167 return $result ? $invoice : null;
168 } catch (\Exception $e) {
169 return null;
170 }
171 }
172
173 /**
174 * Delete an invoice
175 *
176 * @param int $id The invoice ID
177 * @return bool True if successful, false otherwise
178 */
179 public function delete($id) {
180 $invoice = $this->find($id);
181
182 if (!$invoice) {
183 return false;
184 }
185
186 // Allow plugins to perform actions before deletion
187 do_action('easy_invoice_invoice_before_delete', $invoice);
188
189 $result = wp_delete_post($id, true);
190
191 if ($result) {
192 // Allow plugins to perform actions after deletion
193 do_action('easy_invoice_invoice_deleted', $id);
194 }
195
196 return $result instanceof WP_Post;
197 }
198
199 /**
200 * Find invoices by customer ID
201 *
202 * @param int $customer_id The customer ID
203 * @return array Array of Invoice models
204 */
205 public function findByCustomer($customer_id) {
206 // Invoices record their client as _easy_invoice_client_id; the
207 // _easy_invoice_customer_id key was never written, so this lookup
208 // used to match nothing (the dashboard's "active clients" stayed 0).
209 $args = [
210 'meta_query' => [
211 'relation' => 'OR',
212 [
213 'key' => '_easy_invoice_client_id',
214 'value' => (int) $customer_id,
215 'compare' => '=',
216 ],
217 [
218 'key' => '_easy_invoice_customer_id',
219 'value' => $customer_id,
220 'compare' => '=',
221 ],
222 ],
223 ];
224
225 $invoices = $this->all($args);
226
227 // Allow plugins to modify the filtered results
228 return apply_filters('easy_invoice_invoices_by_customer', $invoices, $customer_id);
229 }
230
231 /**
232 * Find invoices by status
233 *
234 * @param string $status The invoice status
235 * @return array Array of Invoice models
236 */
237 public function findByStatus($status) {
238 $args = [
239 'meta_query' => [
240 [
241 'key' => '_easy_invoice_status',
242 'value' => $status,
243 'compare' => '=',
244 ],
245 ],
246 ];
247
248 $invoices = $this->all($args);
249
250 // Allow plugins to modify the filtered results
251 return apply_filters('easy_invoice_invoices_by_status', $invoices, $status);
252 }
253
254 /**
255 * Find invoices due within a date range
256 *
257 * @param string $start_date The start date in 'Y-m-d' format
258 * @param string $end_date The end date in 'Y-m-d' format
259 * @return array Array of Invoice models
260 */
261 public function findByDueDate($start_date, $end_date = null) {
262 $meta_query = [
263 [
264 'key' => '_easy_invoice_due_date',
265 'value' => $start_date,
266 'compare' => '>=',
267 'type' => 'DATE',
268 ],
269 ];
270
271 if ($end_date) {
272 $meta_query[] = [
273 'key' => '_easy_invoice_due_date',
274 'value' => $end_date,
275 'compare' => '<=',
276 'type' => 'DATE',
277 ];
278 }
279
280 $args = [
281 'meta_query' => $meta_query,
282 ];
283
284 $invoices = $this->all($args);
285
286 // Allow plugins to modify the filtered results
287 return apply_filters('easy_invoice_invoices_by_due_date', $invoices, $start_date, $end_date);
288 }
289
290 /**
291 * Count invoices
292 *
293 * @param array $args Optional arguments to filter the results
294 * @return int Number of invoices
295 */
296 public function count($args = []) {
297 $default_args = [
298 'post_type' => $this->post_type,
299 'post_status' => 'publish',
300 ];
301
302 $query_args = wp_parse_args($args, $default_args);
303
304 // Allow plugins to modify query arguments
305 $query_args = apply_filters('easy_invoice_invoice_count_query_args', $query_args);
306
307 $query = new WP_Query($query_args);
308
309 $count = $query->found_posts;
310
311 // Allow plugins to modify the count
312 return apply_filters('easy_invoice_invoice_count', $count, $query_args);
313 }
314
315 /**
316 * Find an invoice by its invoice number (stored in post meta)
317 *
318 * @param string $number Invoice number (exact match)
319 * @return Invoice|null
320 */
321 public function findByNumber(string $number) {
322 $args = [
323 'post_type' => $this->post_type,
324 'post_status' => ['publish', 'draft', 'private', 'pending'],
325 'posts_per_page' => 1,
326 'meta_query' => [
327 [
328 'key' => \EasyInvoice\Constants\InvoiceMetaKeys::NUMBER,
329 'value' => $number,
330 'compare' => '=',
331 ],
332 ],
333 'orderby' => 'date',
334 'order' => 'DESC',
335 ];
336
337 $query = new WP_Query($args);
338 if (!empty($query->posts)) {
339 $post = $query->posts[0];
340 if ($post && $post->post_type === $this->post_type) {
341 $invoice = new Invoice($post);
342 $invoice->ensureProperSlug();
343 return apply_filters('easy_invoice_invoice_found_by_number', $invoice, $number);
344 }
345 }
346 return null;
347 }
348
349 /**
350 * Find published invoice by ID
351 *
352 * @param int $id The invoice ID
353 * @return Invoice|null The invoice model or null if not found
354 */
355 public function findPublished(int $id) {
356 $post = get_post($id);
357
358 if (!$post || $post->post_type !== $this->post_type || $post->post_status !== 'publish') {
359 return null;
360 }
361
362 $invoice = new Invoice($post);
363
364 // Ensure invoice has proper slug for pretty URLs
365 $invoice->ensureProperSlug();
366
367 return $invoice;
368 }
369
370 /**
371 * Update all existing draft invoices to published status for proper permalinks
372 *
373 * @return int Number of invoices updated
374 */
375 public function updateAllExistingInvoices(): int {
376 global $wpdb;
377
378 // Find all draft invoices
379 $draft_invoices = $wpdb->get_col($wpdb->prepare(
380 "SELECT ID FROM {$wpdb->posts}
381 WHERE post_type = %s
382 AND post_status = 'draft'",
383 $this->post_type
384 ));
385
386 $updated_count = 0;
387
388 foreach ($draft_invoices as $invoice_id) {
389 $invoice = $this->find($invoice_id);
390 if ($invoice) {
391 // Save the invoice (this will publish it and ensure proper permalinks)
392 if ($invoice->save()) {
393 $updated_count++;
394 }
395 }
396 }
397
398 // Allow plugins to perform actions after bulk update
399 do_action('easy_invoice_invoices_bulk_updated', $updated_count);
400
401 return $updated_count;
402 }
403
404 /**
405 * Set invoice data from array
406 *
407 * @param Invoice $invoice
408 * @param array $data
409 * @param bool $preserve_number
410 * @return void
411 */
412 protected function setInvoiceData(Invoice $invoice, array $data, $preserve_number = false) {
413 // Allow plugins to modify the data setting process
414 do_action('easy_invoice_invoice_set_data_before', $invoice, $data);
415
416 // Set basic invoice fields (core fields that have dedicated methods)
417 if (isset($data['title'])) {
418 $invoice->setTitle($data['title']);
419 }
420
421 if (isset($data['invoice_title'])) {
422 $invoice->setTitle($data['invoice_title']);
423 }
424
425 // Always update description if it exists in data (even if empty, to allow clearing)
426 if (array_key_exists('description', $data)) {
427 $invoice->setDescription($data['description'] ?? '');
428 }
429
430 if (array_key_exists('invoice_description', $data)) {
431 $invoice->setDescription($data['invoice_description'] ?? '');
432 }
433
434 // Set invoice number (only if not preserving existing)
435 if (isset($data['invoice_number']) && !empty($data['invoice_number'])) {
436 $invoice->setNumber($data['invoice_number']);
437 } elseif (isset($data['number']) && !empty($data['number'])) {
438 $invoice->setNumber($data['number']);
439 }
440
441 // Set dates
442 if (isset($data['issue_date'])) {
443 $invoice->setIssueDate($data['issue_date']);
444 }
445
446 if (isset($data['invoice_date'])) {
447 $invoice->setIssueDate($data['invoice_date']);
448 }
449
450 if (isset($data['issue-date'])) {
451 $invoice->setIssueDate($data['issue-date']);
452 }
453
454 if (isset($data['due_date'])) {
455 $invoice->setDueDate($data['due_date']);
456 }
457
458 if (isset($data['due-date'])) {
459 $invoice->setDueDate($data['due-date']);
460 }
461
462 // Set status
463 if (isset($data['status'])) {
464 $invoice->setStatus($data['status']);
465 }
466
467 if (isset($data['invoice_status'])) {
468 $invoice->setStatus($data['invoice_status']);
469 }
470
471 if (isset($data['payment_status'])) {
472 $invoice->setStatus($data['payment_status']);
473 }
474
475 // Set notes and terms
476 if (isset($data['notes'])) {
477 $invoice->setNotes($data['notes']);
478 }
479
480 if (isset($data['terms_and_conditions'])) {
481 $invoice->setTerms($data['terms_and_conditions']);
482 }
483
484 if (isset($data['internal_notes'])) {
485 $invoice->setInternalNotes($data['internal_notes']);
486 }
487
488 if (isset($data['payment_instructions'])) {
489 $invoice->setPaymentInstructions($data['payment_instructions']);
490 }
491
492 // Set payment gateways
493 if (isset($data['payment_gateways']) && is_array($data['payment_gateways'])) {
494 $invoice->setPaymentGateways($data['payment_gateways']);
495 } elseif (isset($data['payment_gateways']) && is_string($data['payment_gateways'])) {
496 $gateways = array_filter(explode(',', $data['payment_gateways']));
497 $invoice->setPaymentGateways($gateways);
498 } else {
499 $invoice->setPaymentGateways([]);
500 }
501
502 // Set payment gateways toggle state
503 if (isset($data['payment_gateways_toggle_state'])) {
504 $invoice->setPaymentGatewaysToggleState((string)$data['payment_gateways_toggle_state']);
505 }
506
507 // Set template
508 if (isset($data['invoice_template'])) {
509 $invoice->setTemplate($data['invoice_template']);
510 }
511
512 // Set client information
513 if (isset($data['customer_name'])) {
514 $invoice->setCustomerName($data['customer_name']);
515 }
516
517 if (isset($data['customer_address'])) {
518 $invoice->setCustomerAddress($data['customer_address']);
519 }
520
521 if (isset($data['customer_email'])) {
522 $invoice->setCustomerEmail($data['customer_email']);
523 }
524
525 // Set discount and tax settings
526 if (isset($data['discount_type'])) {
527 $invoice->setDiscountType($data['discount_type']);
528 }
529
530 if (isset($data['discount_value'])) {
531 $invoice->setDiscountValue($data['discount_value']);
532 }
533
534 if (isset($data['discount'])) {
535 $invoice->setDiscountValue($data['discount']);
536 }
537
538 if (isset($data['tax_rate'])) {
539 $invoice->setTaxRate($data['tax_rate']);
540 }
541
542 if (isset($data['discount_calculation_method'])) {
543 $invoice->setDiscountCalculationMethod($data['discount_calculation_method']);
544 } elseif (isset($data['calculation_method'])) {
545 $invoice->setDiscountCalculationMethod($data['calculation_method']);
546 }
547
548 if (isset($data['prices_include_tax'])) {
549 $invoice->setPricesIncludeTax($data['prices_include_tax']);
550 }
551
552 // Set currency settings
553 if (isset($data['currency_code'])) {
554 $invoice->setCurrencyCode($data['currency_code']);
555 }
556
557 if (isset($data['currency_position'])) {
558 $invoice->setCurrencyPosition($data['currency_position']);
559 }
560
561 // Set footer text
562 if (isset($data['footer_text'])) {
563 $invoice->setFooterText($data['footer_text']);
564 }
565
566 // Set client ID
567 if (isset($data['client_id']) && !empty($data['client_id'])) {
568 $invoice->setClientId((int) $data['client_id']);
569 }
570
571 // Set items
572 if (isset($data['items']) && is_array($data['items'])) {
573 $invoice->setItems($data['items']);
574 }
575
576 // Set custom fields
577 if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
578 $invoice->setCustomFields($data['custom_fields']);
579 }
580
581 // Set recurring fields
582 if (isset($data['recurring_enabled'])) {
583 $invoice->setRecurringEnabled((bool) $data['recurring_enabled']);
584 }
585
586 if (isset($data['recurring_frequency'])) {
587 $invoice->setRecurringFrequency($data['recurring_frequency']);
588 }
589
590 if (isset($data['recurring_interval'])) {
591 $invoice->setRecurringInterval((int) $data['recurring_interval']);
592 }
593
594 if (isset($data['recurring_start_date'])) {
595 $invoice->setRecurringStartDate($data['recurring_start_date']);
596 }
597
598 // Use configuration-driven approach for all other fields
599 $form_processor = new \EasyInvoice\Forms\FormProcessor();
600 $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
601
602 // Get all field definitions from all tabs
603 $field_definitions = [];
604 $tabs = $field_registration->getTabs();
605 foreach ($tabs as $tab_id => $tab) {
606 $tab_fields = $field_registration->getFields($tab_id);
607 foreach ($tab_fields as $field) {
608 $field_definitions[] = $field;
609 }
610 }
611
612 // Save form data using field configuration
613 $form_processor->saveFormDataToDatabase($data, $field_definitions, $invoice);
614
615 // Allow plugins to modify the data setting process
616 do_action('easy_invoice_invoice_set_data_after', $invoice, $data);
617 }
618 }