PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.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 / includes / Services / QuoteLogService.php

QuoteLogService.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.0, at includes/Services/QuoteLogService.php

497 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Quote Log Service
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\Services;
13
14 use EasyInvoice\Models\QuoteLog;
15
16 /**
17 * Quote Log Service
18 *
19 * Handles quote activity logging using post meta.
20 *
21 * @since 1.0.0
22 */
23 class QuoteLogService extends BaseService {
24
25 /**
26 * Meta key for storing quote logs
27 *
28 * @var string
29 */
30 const LOG_META_KEY = '_quote_logs';
31
32 /**
33 * Constructor
34 *
35 * @since 1.0.0
36 */
37 public function __construct() {
38 parent::__construct('QuoteLogService');
39 }
40
41 /**
42 * Log a quote activity
43 *
44 * @since 1.0.0
45 * @param int $quote_id Quote ID
46 * @param string $action Action type
47 * @param string $description Action description
48 * @param array $additional_data Additional data to store
49 * @return bool True if logged successfully
50 */
51 public function logActivity(int $quote_id, string $action, string $description, array $additional_data = []): bool {
52 try {
53 // Get current user info
54 $current_user = wp_get_current_user();
55 $user_id = $current_user->ID;
56 $user_name = $current_user->display_name ?: $current_user->user_login;
57
58 // Get IP address
59 $ip_address = $this->getClientIp();
60
61 // Get user agent
62 $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
63
64 // Create log entry
65 $log_entry = [
66 'action' => $action,
67 'description' => $description,
68 'user_id' => $user_id,
69 'user_name' => $user_name,
70 'ip_address' => $ip_address,
71 'user_agent' => $user_agent,
72 'additional_data' => json_encode($additional_data),
73 'created_date' => current_time('mysql'),
74 ];
75
76 // Validate log entry structure
77 if (!isset($log_entry['created_date']) || empty($log_entry['created_date'])) {
78 $this->log("Invalid log entry structure for quote {$quote_id}", 'error');
79 return false;
80 }
81
82 // Get existing logs data (raw array, not QuoteLog objects)
83 $existing_logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
84
85 if (!is_array($existing_logs_data)) {
86 $existing_logs_data = [];
87 }
88
89 // Add new log entry
90 $existing_logs_data[] = $log_entry;
91
92 // Store logs in post meta
93 $result = update_post_meta($quote_id, self::LOG_META_KEY, $existing_logs_data);
94
95 if ($result) {
96 $this->log("Quote activity logged: {$action} for quote {$quote_id}");
97
98 // Allow plugins to perform actions after logging
99 do_action('easy_invoice_quote_activity_logged', $quote_id, $action, $description, $additional_data);
100
101 return true;
102 }
103
104 $this->log("Failed to log quote activity: {$action} for quote {$quote_id}", 'error');
105 return false;
106
107 } catch (\Exception $e) {
108 $this->log("Exception while logging quote activity: " . $e->getMessage(), 'error');
109 return false;
110 }
111 }
112
113 /**
114 * Get logs for a quote
115 *
116 * @since 1.0.0
117 * @param int $quote_id Quote ID
118 * @param int $limit Number of logs to return (0 for all)
119 * @return array Array of QuoteLog objects
120 */
121 public function getLogs(int $quote_id, int $limit = 0): array {
122 $logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
123
124 if (!is_array($logs_data)) {
125 return [];
126 }
127
128 // Filter out any non-array entries and ensure proper structure
129 $valid_logs_data = [];
130 $has_corruption = false;
131
132 foreach ($logs_data as $log_data) {
133 if (is_array($log_data) && isset($log_data['created_date']) && !empty($log_data['created_date'])) {
134 $valid_logs_data[] = $log_data;
135 } else {
136 $has_corruption = true;
137 }
138 }
139
140 // If we found corruption, clean it up
141 if ($has_corruption) {
142 $this->cleanupCorruptedLogs($quote_id);
143 }
144
145 if (empty($valid_logs_data)) {
146 return [];
147 }
148
149 // Sort by created date (newest first) - sort the raw data before converting to objects
150 usort($valid_logs_data, function($a, $b) {
151 return strtotime($b['created_date']) - strtotime($a['created_date']);
152 });
153
154 // Apply limit if specified
155 if ($limit > 0) {
156 $valid_logs_data = array_slice($valid_logs_data, 0, $limit);
157 }
158
159 // Convert to QuoteLog objects
160 $logs = [];
161 foreach ($valid_logs_data as $log_data) {
162 try {
163 $logs[] = new QuoteLog($log_data);
164 } catch (\Exception $e) {
165 // Skip invalid log entries
166 continue;
167 }
168 }
169
170 return $logs;
171 }
172
173 /**
174 * Get latest log for a quote
175 *
176 * @since 1.0.0
177 * @param int $quote_id Quote ID
178 * @return QuoteLog|null
179 */
180 public function getLatestLog(int $quote_id): ?QuoteLog {
181 $logs = $this->getLogs($quote_id, 1);
182 return !empty($logs) ? $logs[0] : null;
183 }
184
185 /**
186 * Get logs by action
187 *
188 * @since 1.0.0
189 * @param int $quote_id Quote ID
190 * @param string $action Action type
191 * @return array Array of QuoteLog objects
192 */
193 public function getLogsByAction(int $quote_id, string $action): array {
194 $all_logs = $this->getLogs($quote_id);
195 $filtered_logs = [];
196
197 foreach ($all_logs as $log) {
198 if ($log->getAction() === $action) {
199 $filtered_logs[] = $log;
200 }
201 }
202
203 return $filtered_logs;
204 }
205
206 /**
207 * Clear logs for a quote
208 *
209 * @since 1.0.0
210 * @param int $quote_id Quote ID
211 * @return bool True if cleared successfully
212 */
213 public function clearLogs(int $quote_id): bool {
214 $result = delete_post_meta($quote_id, self::LOG_META_KEY);
215
216 if ($result) {
217 $this->log("Quote logs cleared for quote {$quote_id}");
218 do_action('easy_invoice_quote_logs_cleared', $quote_id);
219 }
220
221 return $result;
222 }
223
224 /**
225 * Clean up corrupted log data
226 *
227 * @since 1.0.0
228 * @param int $quote_id Quote ID
229 * @return bool True if cleaned successfully
230 */
231 public function cleanupCorruptedLogs(int $quote_id): bool {
232 $logs_data = get_post_meta($quote_id, self::LOG_META_KEY, true);
233
234 if (!is_array($logs_data)) {
235 return true; // No data to clean
236 }
237
238 $valid_logs_data = [];
239 foreach ($logs_data as $log_data) {
240 if (is_array($log_data) && isset($log_data['created_date']) && !empty($log_data['created_date'])) {
241 $valid_logs_data[] = $log_data;
242 }
243 }
244
245 // Update with only valid log entries
246 $result = update_post_meta($quote_id, self::LOG_META_KEY, $valid_logs_data);
247
248 if ($result) {
249 $this->log("Cleaned up corrupted logs for quote {$quote_id}");
250 }
251
252 return $result;
253 }
254
255 /**
256 * Get client IP address
257 *
258 * @since 1.0.0
259 * @return string
260 */
261 private function getClientIp(): string {
262 $ip_keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
263
264 foreach ($ip_keys as $key) {
265 if (array_key_exists($key, $_SERVER) === true) {
266 foreach (explode(',', $_SERVER[$key]) as $ip) {
267 $ip = trim($ip);
268 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
269 return $ip;
270 }
271 }
272 }
273 }
274
275 return $_SERVER['REMOTE_ADDR'] ?? '';
276 }
277
278 /**
279 * Log quote creation
280 *
281 * @since 1.0.0
282 * @param int $quote_id Quote ID
283 * @param array $additional_data Additional data
284 * @return bool
285 */
286 public function logCreation(int $quote_id, array $additional_data = []): bool {
287 return $this->logActivity(
288 $quote_id,
289 'created',
290 __('Quote created', 'easy-invoice'),
291 $additional_data
292 );
293 }
294
295 /**
296 * Log quote update
297 *
298 * @since 1.0.0
299 * @param int $quote_id Quote ID
300 * @param array $changes Array of changes made
301 * @param array $additional_data Additional data
302 * @return bool
303 */
304 public function logUpdate(int $quote_id, array $changes = [], array $additional_data = []): bool {
305 $description = __('Quote updated', 'easy-invoice');
306
307 if (!empty($changes)) {
308 $change_descriptions = [];
309 foreach ($changes as $field => $value) {
310 $change_descriptions[] = ucfirst(easy_invoice_str_replace('_', ' ', $field)) . ': ' . $value;
311 }
312 $description .= ' - ' . implode(', ', $change_descriptions);
313 }
314
315 return $this->logActivity(
316 $quote_id,
317 'updated',
318 $description,
319 array_merge(['changes' => $changes], $additional_data)
320 );
321 }
322
323 /**
324 * Log quote acceptance
325 *
326 * @since 1.0.0
327 * @param int $quote_id Quote ID
328 * @param array $additional_data Additional data
329 * @return bool
330 */
331 public function logAcceptance(int $quote_id, array $additional_data = []): bool {
332 return $this->logActivity(
333 $quote_id,
334 'accepted',
335 __('Quote accepted by client', 'easy-invoice'),
336 $additional_data
337 );
338 }
339
340 /**
341 * Log quote decline
342 *
343 * @since 1.0.0
344 * @param int $quote_id Quote ID
345 * @param string $reason Decline reason
346 * @param array $additional_data Additional data
347 * @return bool
348 */
349 public function logDecline(int $quote_id, string $reason = '', array $additional_data = []): bool {
350 $description = __('Quote declined by client', 'easy-invoice');
351 if (!empty($reason)) {
352 $description .= ' - ' . $reason;
353 }
354
355 return $this->logActivity(
356 $quote_id,
357 'declined',
358 $description,
359 array_merge(['reason' => $reason], $additional_data)
360 );
361 }
362
363 /**
364 * Log quote sent
365 *
366 * @since 1.0.0
367 * @param int $quote_id Quote ID
368 * @param string $email Email address
369 * @param array $additional_data Additional data
370 * @return bool
371 */
372 public function logSent(int $quote_id, string $email = '', array $additional_data = []): bool {
373 $description = __('Quote sent to client', 'easy-invoice');
374 if (!empty($email)) {
375 $description .= ' - ' . $email;
376 }
377
378 return $this->logActivity(
379 $quote_id,
380 'sent',
381 $description,
382 array_merge(['email' => $email], $additional_data)
383 );
384 }
385
386 /**
387 * Log quote viewed
388 *
389 * @since 1.0.0
390 * @param int $quote_id Quote ID
391 * @param array $additional_data Additional data
392 * @return bool
393 */
394 public function logViewed(int $quote_id, array $additional_data = []): bool {
395 return $this->logActivity(
396 $quote_id,
397 'viewed',
398 __('Quote viewed by client', 'easy-invoice'),
399 $additional_data
400 );
401 }
402
403 /**
404 * Log status change
405 *
406 * @since 1.0.0
407 * @param int $quote_id Quote ID
408 * @param string $old_status Old status
409 * @param string $new_status New status
410 * @param array $additional_data Additional data
411 * @return bool
412 */
413 public function logStatusChange(int $quote_id, string $old_status, string $new_status, array $additional_data = []): bool {
414 return $this->logActivity(
415 $quote_id,
416 'status_changed',
417 /* translators: %1$s: previous status; %2$s: new status. */
418 sprintf(__('Status changed from %1$s to %2$s', 'easy-invoice'), $old_status, $new_status),
419 array_merge([
420 'old_status' => $old_status,
421 'new_status' => $new_status
422 ], $additional_data)
423 );
424 }
425
426 /**
427 * Log quote to invoice conversion
428 *
429 * @since 1.0.0
430 * @param int $quote_id Quote ID
431 * @param int $invoice_id Invoice ID
432 * @param array $additional_data Additional data
433 * @return bool
434 */
435 public function logConversionToInvoice(int $quote_id, int $invoice_id, array $additional_data = []): bool {
436 return $this->logActivity(
437 $quote_id,
438 'converted_to_invoice',
439 /* translators: %d: invoice id. */
440 sprintf(__('Quote converted to invoice #%d', 'easy-invoice'), $invoice_id),
441 array_merge(['invoice_id' => $invoice_id], $additional_data)
442 );
443 }
444
445 /**
446 * Log quote to invoice duplication
447 *
448 * @since 1.0.0
449 * @param int $quote_id Quote ID
450 * @param int $invoice_id Invoice ID
451 * @param array $additional_data Additional data
452 * @return bool
453 */
454 public function logDuplicationToInvoice(int $quote_id, int $invoice_id, array $additional_data = []): bool {
455 return $this->logActivity(
456 $quote_id,
457 'duplicated_to_invoice',
458 /* translators: %d: invoice id. */
459 sprintf(__('Quote duplicated to invoice #%d', 'easy-invoice'), $invoice_id),
460 array_merge(['invoice_id' => $invoice_id], $additional_data)
461 );
462 }
463
464 /**
465 * Log quote deletion
466 *
467 * @since 1.0.0
468 * @param int $quote_id Quote ID
469 * @param array $additional_data Additional data
470 * @return bool
471 */
472 public function logDeletion(int $quote_id, array $additional_data = []): bool {
473 return $this->logActivity(
474 $quote_id,
475 'deleted',
476 __('Quote deleted', 'easy-invoice'),
477 $additional_data
478 );
479 }
480
481 /**
482 * Log quote restoration
483 *
484 * @since 1.0.0
485 * @param int $quote_id Quote ID
486 * @param array $additional_data Additional data
487 * @return bool
488 */
489 public function logRestoration(int $quote_id, array $additional_data = []): bool {
490 return $this->logActivity(
491 $quote_id,
492 'restored',
493 __('Quote restored', 'easy-invoice'),
494 $additional_data
495 );
496 }
497 }