PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.0
2.4.3 2.4.4 2.4.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 All 60 releases
easy-invoice / includes / Repositories / QuoteRepository.php

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

584 lines 17.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Quote Repository
4 *
5 * @package EasyInvoice
6 * @author Your Name
7 * @copyright Copyright (c) 2023, Your Company
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace EasyInvoice\Repositories;
13
14 use EasyInvoice\Models\Quote;
15 use EasyInvoice\Constants\PostTypes;
16 use EasyInvoice\Interfaces\QuoteRepositoryInterface;
17
18 /**
19 * Quote Repository
20 *
21 * Handles database operations for quotes with extensible architecture for pro features.
22 *
23 * @since 1.0.0
24 */
25 class QuoteRepository implements QuoteRepositoryInterface {
26
27 /**
28 * Find quote by ID
29 *
30 * @since 1.0.0
31 * @param int $id Quote ID
32 * @return Quote|null
33 */
34 public function find(int $id): ?Quote {
35 $post = get_post($id);
36 if ($post && $post->post_type === PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
37 $quote = new Quote($post);
38
39 // Ensure quote has proper slug for pretty URLs
40 $quote->ensureProperSlug();
41
42 // Allow plugins to modify the found quote
43 return apply_filters('easy_invoice_quote_found', $quote, $id);
44 }
45 return null;
46 }
47
48 /**
49 * Find all quotes
50 *
51 * @since 1.0.0
52 * @param array $args Query arguments
53 * @return array
54 */
55 public function findAll(array $args = []): array {
56 $default_args = [
57 'post_type' => PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
58 'post_status' => ['publish', 'draft', 'private', 'pending'],
59 'posts_per_page' => -1,
60 'orderby' => 'date',
61 'order' => 'DESC'
62 ];
63
64 $query_args = wp_parse_args($args, $default_args);
65
66 // Allow plugins to modify query arguments
67 $query_args = apply_filters('easy_invoice_quote_query_args', $query_args);
68
69 $posts = get_posts($query_args);
70
71 $quotes = [];
72 foreach ($posts as $post) {
73 $quote = new Quote($post);
74
75 // Ensure quote has proper slug for pretty URLs
76 $quote->ensureProperSlug();
77
78 $quotes[] = $quote;
79 }
80
81 // Allow plugins to modify the results
82 return apply_filters('easy_invoice_quotes_found', $quotes, $query_args);
83 }
84
85 /**
86 * Find all published quotes (for public access)
87 *
88 * @since 1.0.0
89 * @param array $args Query arguments
90 * @return array
91 */
92 public function findAllPublished(array $args = []): array {
93 $default_args = [
94 'post_type' => PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
95 'post_status' => 'publish',
96 'posts_per_page' => -1,
97 'orderby' => 'date',
98 'order' => 'DESC'
99 ];
100
101 $query_args = wp_parse_args($args, $default_args);
102
103 // Allow plugins to modify query arguments
104 $query_args = apply_filters('easy_invoice_quote_published_query_args', $query_args);
105
106 $posts = get_posts($query_args);
107
108 $quotes = [];
109 foreach ($posts as $post) {
110 $quote = new Quote($post);
111
112 // Ensure quote has proper slug for pretty URLs
113 $quote->ensureProperSlug();
114
115 $quotes[] = $quote;
116 }
117
118 // Allow plugins to modify the results
119 return apply_filters('easy_invoice_quotes_published_found', $quotes, $query_args);
120 }
121
122 /**
123 * Find quotes by status
124 *
125 * @since 1.0.0
126 * @param string $status Quote status
127 * @return array
128 */
129 public function findByStatus(string $status): array {
130 $quotes = $this->findBy(['status' => $status]);
131
132 // Allow plugins to modify the filtered results
133 return apply_filters('easy_invoice_quotes_by_status', $quotes, $status);
134 }
135
136 /**
137 * Find all quote IDs by status
138 *
139 * @since 1.0.0
140 * @param string $status Quote status
141 * @return array
142 */
143 public function findAllIdsByStatus(string $status): array {
144 global $wpdb;
145
146 $post_ids = $wpdb->get_col($wpdb->prepare(
147 "SELECT p.ID
148 FROM {$wpdb->posts} p
149 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
150 WHERE p.post_type = %s
151 AND pm.meta_key = '_easy_invoice_quote_status'
152 AND pm.meta_value = %s",
153 PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
154 $status
155 ));
156
157 $post_ids = array_map('intval', $post_ids);
158
159 // Allow plugins to modify the results
160 return apply_filters('easy_invoice_quote_ids_by_status', $post_ids, $status);
161 }
162
163 /**
164 * Find quotes by client
165 *
166 * @since 1.0.0
167 * @param int $client_id Client ID
168 * @return array
169 */
170 public function findByClient(int $client_id): array {
171 $quotes = $this->findAll();
172 $filtered_quotes = array_filter($quotes, function($quote) use ($client_id) {
173 return $quote->getClientId() === $client_id;
174 });
175
176 // Allow plugins to modify the filtered results
177 return apply_filters('easy_invoice_quotes_by_client', $filtered_quotes, $client_id);
178 }
179
180 /**
181 * Create new quote
182 *
183 * @since 1.0.0
184 * @param array $data Quote data
185 * @return Quote|null
186 */
187 public function create(array $data): ?Quote {
188 // Allow plugins to modify the data before creation
189 $data = apply_filters('easy_invoice_quote_create_data', $data);
190
191 $quote = new Quote();
192 $this->setQuoteData($quote, $data);
193
194 // Allow plugins to modify the quote before saving
195 do_action('easy_invoice_quote_before_create', $quote, $data);
196
197 if ($quote->save()) {
198 // Allow plugins to perform actions after creation
199 do_action('easy_invoice_quote_created', $quote, $data);
200 return $quote;
201 }
202
203 return null;
204 }
205
206 /**
207 * Update existing quote
208 *
209 * @since 1.0.0
210 * @param int $id Quote ID
211 * @param array $data Quote data
212 * @param Quote|null $existing_quote Optional existing quote object to update
213 * @return Quote|null
214 */
215 public function update(int $id, array $data, ?Quote $existing_quote = null): ?Quote {
216 // Use existing quote object if provided, otherwise find from database
217 $quote = $existing_quote ?: $this->find($id);
218 if (!$quote) {
219 return null;
220 }
221
222 // Prevent quote number from being changed on update
223 unset($data['number'], $data['quote-number']);
224
225 // Allow plugins to modify the data before update
226 $data = apply_filters('easy_invoice_quote_update_data', $data, $quote);
227
228 $this->setQuoteData($quote, $data);
229
230 // Allow plugins to modify the quote before saving
231 do_action('easy_invoice_quote_before_update', $quote, $data);
232
233 if ($quote->save()) {
234 // Allow plugins to perform actions after update
235 do_action('easy_invoice_quote_updated', $quote, $data);
236 return $quote;
237 }
238
239 return null;
240 }
241
242 /**
243 * Delete quote
244 *
245 * @since 1.0.0
246 * @param int $id Quote ID
247 * @return bool
248 */
249 public function delete(int $id): bool {
250 $quote = $this->find($id);
251 if (!$quote) {
252 return false;
253 }
254
255 // Allow plugins to perform actions before deletion
256 do_action('easy_invoice_quote_before_delete', $quote);
257
258 $result = wp_delete_post($id, true);
259
260 if ($result) {
261 // Allow plugins to perform actions after deletion
262 do_action('easy_invoice_quote_deleted', $id);
263 }
264
265 return $result !== false;
266 }
267
268 /**
269 * Force publish quote (for URL fixes)
270 *
271 * @since 1.0.0
272 * @param int $id Quote ID
273 * @return bool
274 */
275 public function forcePublish($id) {
276 $quote = $this->find($id);
277 if (!$quote) {
278 return false;
279 }
280
281 // Save the quote (this will publish it and ensure proper permalinks)
282 return $quote->save();
283 }
284
285 /**
286 * Find published quote by ID
287 *
288 * @since 1.0.0
289 * @param int $id Quote ID
290 * @return Quote|null
291 */
292 public function findPublished(int $id): ?Quote {
293 $post = get_post($id);
294
295 if (!$post || $post->post_type !== PostTypes::EASY_INVOICE_QUOTE_POST_TYPE || $post->post_status !== 'publish') {
296 return null;
297 }
298
299 $quote = new Quote($post);
300
301 // Ensure quote has proper slug for pretty URLs
302 $quote->ensureProperSlug();
303
304 return $quote;
305 }
306
307 /**
308 * Find quotes by criteria
309 *
310 * @param array $criteria Array of criteria to filter by
311 * @return Quote[] Array of Quote objects
312 */
313 public function findBy(array $criteria = []): array {
314 $args = [
315 'post_type' => PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
316 'posts_per_page' => -1,
317 'post_status' => ['publish', 'draft', 'private', 'pending', 'trash']
318 ];
319
320 // Handle status criteria
321 if (isset($criteria['status'])) {
322 $args['meta_query'][] = [
323 'key' => '_easy_invoice_quote_status',
324 'value' => $criteria['status']
325 ];
326 }
327
328 // Handle client criteria
329 if (isset($criteria['client_id'])) {
330 $args['meta_query'][] = [
331 'key' => '_easy_invoice_quote_client_id',
332 'value' => $criteria['client_id']
333 ];
334 }
335
336 // Handle date range
337 if (isset($criteria['date_from'])) {
338 $args['date_query']['after'] = $criteria['date_from'];
339 }
340 if (isset($criteria['date_to'])) {
341 $args['date_query']['before'] = $criteria['date_to'];
342 }
343
344 $posts = get_posts($args);
345 $quotes = [];
346
347 foreach ($posts as $post) {
348 $quotes[] = new Quote($post);
349 }
350
351 return $quotes;
352 }
353
354 /**
355 * Update all existing draft quotes to published status for proper permalinks
356 *
357 * @since 1.0.0
358 * @return int Number of quotes updated
359 */
360 public function updateAllExistingQuotes(): int {
361 global $wpdb;
362
363 // Find all draft quotes
364 $draft_quotes = $wpdb->get_col($wpdb->prepare(
365 "SELECT ID FROM {$wpdb->posts}
366 WHERE post_type = %s
367 AND post_status = 'draft'",
368 PostTypes::EASY_INVOICE_QUOTE_POST_TYPE
369 ));
370
371 $updated_count = 0;
372
373 foreach ($draft_quotes as $quote_id) {
374 $quote = $this->find($quote_id);
375 if ($quote) {
376 // Save the quote (this will publish it and ensure proper permalinks)
377 if ($quote->save()) {
378 $updated_count++;
379 }
380 }
381 }
382
383 // Allow plugins to perform actions after bulk update
384 do_action('easy_invoice_quotes_bulk_updated', $updated_count);
385
386 return $updated_count;
387 }
388
389 /**
390 * Get statistics
391 *
392 * @since 1.0.0
393 * @return array
394 */
395 public function getStatistics(): array {
396 $quotes = $this->findAll();
397
398 $stats = [
399 'total' => count($quotes),
400 'draft' => count($this->findByStatus('draft')),
401 'sent' => count($this->findByStatus('sent')),
402 'accepted' => count($this->findByStatus('accepted')),
403 'declined' => count($this->findByStatus('declined')),
404 'expired' => count($this->findByStatus('expired')),
405 ];
406
407 // Allow plugins to modify statistics
408 return apply_filters('easy_invoice_quote_statistics', $stats, $quotes);
409 }
410
411 /**
412 * Set quote data from array
413 *
414 * @since 1.0.0
415 * @param Quote $quote
416 * @param array $data
417 */
418 private function setQuoteData(Quote $quote, array $data): void {
419 // Allow plugins to modify the data setting process
420 do_action('easy_invoice_quote_set_data_before', $quote, $data);
421
422 // Set basic properties
423 if (isset($data['title'])) {
424 $quote->setTitle($data['title']);
425 }
426
427 if (isset($data['number']) && !empty($data['number'])) {
428 $quote->setNumber($data['number']);
429 }
430
431 if (isset($data['status'])) {
432 $quote->setStatus($data['status']);
433 }
434
435 if (isset($data['issue_date'])) {
436 $quote->setIssueDate($data['issue_date']);
437 }
438
439 if (isset($data['expiry_date'])) {
440 $quote->setExpiryDate($data['expiry_date']);
441 }
442
443 if (isset($data['client_id'])) {
444 $quote->setClientId($data['client_id']);
445 }
446
447 if (isset($data['customer_name'])) {
448 $quote->setCustomerName($data['customer_name']);
449 }
450
451 if (isset($data['customer_email'])) {
452 $quote->setCustomerEmail($data['customer_email']);
453 }
454
455 if (isset($data['customer_address'])) {
456 $quote->setCustomerAddress($data['customer_address']);
457 }
458
459 if (isset($data['notes'])) {
460 $quote->setNotes($data['notes']);
461 }
462
463 if (isset($data['description'])) {
464 $quote->setDescription($data['description']);
465 }
466
467 if (isset($data['terms'])) {
468 $quote->setTerms($data['terms']);
469 }
470
471 if (isset($data['internal_notes'])) {
472 $quote->setInternalNotes($data['internal_notes']);
473 }
474
475 if (isset($data['template'])) {
476 $quote->setTemplate($data['template']);
477 }
478
479 if (isset($data['items']) && is_array($data['items'])) {
480 // Process each item to ensure taxable field is properly set
481 $processed_items = array_map(function($item) {
482 if (is_array($item)) {
483 // Convert taxable field to boolean
484 $taxable = isset($item['taxable']) ? $item['taxable'] : true;
485 if (is_string($taxable)) {
486 $taxable = strtolower($taxable);
487 $taxable = $taxable === '1' || $taxable === 'true' || $taxable === 'yes' || $taxable === 'on';
488 }
489 $item['taxable'] = (bool) $taxable;
490 }
491 return $item;
492 }, $data['items']);
493
494 $quote->setItems($processed_items);
495 }
496
497 if (isset($data['discount_type'])) {
498 $quote->setDiscountType($data['discount_type']);
499 }
500
501 if (isset($data['discount_value'])) {
502 $quote->setDiscountValue($data['discount_value']);
503 }
504
505 if (isset($data['tax_rate'])) {
506 $quote->setTaxRate($data['tax_rate']);
507 }
508
509 if (isset($data['prices_include_tax'])) {
510 $quote->setPricesIncludeTax($data['prices_include_tax']);
511 }
512
513 if (isset($data['discount_calculation_method'])) {
514 $quote->setDiscountCalculationMethod($data['discount_calculation_method']);
515 } else {
516 // Default to before_tax if not set
517 $quote->setDiscountCalculationMethod('before_tax');
518 }
519
520 if (isset($data['currency_code'])) {
521 $quote->setCurrencyCode($data['currency_code']);
522 }
523
524 if (isset($data['currency_position'])) {
525 $quote->setCurrencyPosition($data['currency_position']);
526 }
527
528 // Handle new quote fields
529 if (isset($data['footer_text'])) {
530 $quote->setFooterText($data['footer_text']);
531 }
532
533 if (isset($data['accept_button'])) {
534 $quote->setAcceptButton($data['accept_button']);
535 }
536
537 if (isset($data['accept_action'])) {
538 $quote->setAcceptAction($data['accept_action']);
539 }
540
541 if (isset($data['accept_text'])) {
542 $quote->setAcceptText($data['accept_text']);
543 }
544
545 if (isset($data['accepted_message'])) {
546 $quote->setAcceptedMessage($data['accepted_message']);
547 }
548
549 if (isset($data['declined_message'])) {
550 $quote->setDeclinedMessage($data['declined_message']);
551 }
552
553 // Handle custom fields
554 if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
555 $quote->setCustomFields($data['custom_fields']);
556 }
557
558 // Populate client information if we have a client_id
559 if ($quote->getClientId() > 0) {
560 $this->populateCustomerFromClient($quote, $quote->getClientId());
561 }
562
563 // Allow plugins to modify the data setting process
564 do_action('easy_invoice_quote_set_data_after', $quote, $data);
565 }
566
567 /**
568 * Populate customer information from client
569 *
570 * @since 1.0.0
571 * @param Quote $quote
572 * @param int $client_id
573 */
574 private function populateCustomerFromClient(Quote $quote, int $client_id): void {
575 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
576 $client = $client_repository->find($client_id);
577
578 if ($client) {
579 $quote->setCustomerName($client->getBusinessClientName() ?: '');
580 $quote->setCustomerEmail($client->getEmail() ?: '');
581 $quote->setCustomerAddress($client->getAddress() ?: '');
582 }
583 }
584 }