PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / app / Services / EnquiryService.php

EnquiryService.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Services/EnquiryService.php

465 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 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 use Yatra\Repositories\EnquiryRepository;
8 use Yatra\Repositories\TripRepository;
9
10 /**
11 * Enquiry Service
12 *
13 * Contains business logic for customer enquiries.
14 *
15 * @package Yatra\Services
16 */
17 class EnquiryService
18 {
19 private EnquiryRepository $enquiryRepository;
20 private TripRepository $tripRepository;
21
22 public function __construct()
23 {
24 $this->enquiryRepository = new EnquiryRepository();
25 $this->tripRepository = new TripRepository();
26 }
27
28 /**
29 * Get paginated enquiries
30 *
31 * @param array $filters Filters
32 * @return array
33 */
34 public function getEnquiries(array $filters = []): array
35 {
36 $result = $this->enquiryRepository->paginate($filters);
37
38 $result['data'] = array_map([$this, 'formatEnquiry'], $result['data']);
39
40 return $result;
41 }
42
43 /**
44 * Get single enquiry
45 *
46 * @param int $id Enquiry ID
47 * @return array|null
48 */
49 public function getEnquiry(int $id): ?array
50 {
51 $enquiry = $this->enquiryRepository->findWithTrip($id);
52
53 if (!$enquiry) {
54 return null;
55 }
56
57 return $this->formatEnquiry($enquiry);
58 }
59
60 /**
61 * Create a new enquiry
62 *
63 * @param array $data Enquiry data
64 * @return array {success: bool, enquiry_id?: int, message: string}
65 */
66 public function createEnquiry(array $data): array
67 {
68 // Validate required fields
69 if (empty($data['name']) || empty($data['email']) || empty($data['message'])) {
70 return ['success' => false, 'message' => __('Name, email, and message are required.', 'yatra')];
71 }
72
73 // Validate email
74 if (!is_email($data['email'])) {
75 return ['success' => false, 'message' => __('Please provide a valid email address.', 'yatra')];
76 }
77
78 // Validate trip if provided
79 if (!empty($data['trip_id'])) {
80 $trip = $this->tripRepository->find((int) $data['trip_id']);
81 if (!$trip) {
82 return ['success' => false, 'message' => __('Trip not found.', 'yatra')];
83 }
84 }
85
86 // Build metadata for traveler classifications (flat array, no wrapper)
87 $classifications = [];
88 if (!empty($data['metadata'])) {
89 $metaValue = $data['metadata'];
90 if (is_string($metaValue)) {
91 $decoded = json_decode($metaValue, true);
92 if (json_last_error() === JSON_ERROR_NONE) {
93 $metaValue = $decoded;
94 }
95 }
96 if (is_array($metaValue)) {
97 // Accept both wrapped {classifications: [...]} and direct array [...]
98 if (isset($metaValue['classifications']) && is_array($metaValue['classifications'])) {
99 $classifications = $metaValue['classifications'];
100 } elseif (array_is_list($metaValue)) {
101 $classifications = $metaValue;
102 }
103 }
104 }
105
106 // Backward compatibility: derive from adults/children if still not provided
107 if (empty($classifications) && (isset($data['adults']) || isset($data['children']))) {
108 $adults = isset($data['adults']) ? (int) $data['adults'] : null;
109 $children = isset($data['children']) ? (int) $data['children'] : null;
110 if ($adults !== null) {
111 $classifications[] = ['id' => 'adult', 'title' => 'Adult', 'count' => $adults];
112 }
113 if ($children !== null) {
114 $classifications[] = ['id' => 'child', 'title' => 'Child', 'count' => $children];
115 }
116 }
117
118 // Compute travelers_count from classifications
119 $travelersCount = null;
120 if (!empty($classifications)) {
121 $travelersCount = array_reduce($classifications, function ($carry, $item) {
122 $count = isset($item['count']) ? (int) $item['count'] : 0;
123 return $carry + $count;
124 }, 0);
125 if ($travelersCount === 0) {
126 $travelersCount = null;
127 }
128 } elseif (isset($data['adults']) || isset($data['children'])) {
129 $travelersCount = ((int) ($data['adults'] ?? 0) + (int) ($data['children'] ?? 0)) ?: null;
130 }
131
132 $data['metadata'] = !empty($classifications) ? wp_json_encode($classifications) : null;
133 $data['travelers_count'] = $travelersCount;
134
135 // Capture IP and user agent if not provided
136 if (empty($data['ip_address'])) {
137 $data['ip_address'] = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null;
138 }
139 if (empty($data['user_agent'])) {
140 $data['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? null;
141 }
142
143 // Set default status
144 if (empty($data['status'])) {
145 $data['status'] = 'pending';
146 }
147
148 // Create enquiry
149 $enquiryId = $this->enquiryRepository->create($data);
150
151 if (!$enquiryId) {
152 return ['success' => false, 'message' => __('Failed to submit enquiry.', 'yatra')];
153 }
154
155 // Get the full enquiry object for the action
156 $enquiry = $this->enquiryRepository->find($enquiryId);
157
158 /**
159 * Action: Enquiry created
160 * Fires after a new enquiry is successfully created
161 *
162 * @param object $enquiry The enquiry object
163 * @since 3.0.0
164 */
165 do_action('yatra_enquiry_created', $enquiry);
166
167 // Core plaintext (wp_mail). Pro Email Automation sends HTML when templates exist — see
168 // yatra_send_enquiry_created_* filters in yatra-pro EmailAutomationHooks.
169 if (apply_filters('yatra_send_enquiry_created_admin_email', true, $enquiry)) {
170 $this->sendAdminNotification($enquiryId);
171 }
172
173 if (apply_filters('yatra_send_enquiry_created_customer_email', true, $enquiry)) {
174 $this->sendCustomerConfirmation($enquiryId);
175 }
176
177 return [
178 'success' => true,
179 'enquiry_id' => $enquiryId,
180 'message' => __('Your enquiry has been submitted successfully. We will get back to you soon.', 'yatra'),
181 ];
182 }
183
184 /**
185 * Update an enquiry
186 *
187 * @param int $id Enquiry ID
188 * @param array $data Enquiry data
189 * @return array {success: bool, message: string}
190 */
191 public function updateEnquiry(int $id, array $data): array
192 {
193 $enquiry = $this->enquiryRepository->find($id);
194
195 if (!$enquiry) {
196 return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
197 }
198
199 $updated = $this->enquiryRepository->update($id, $data);
200
201 if (!$updated) {
202 return ['success' => false, 'message' => __('Failed to update enquiry.', 'yatra')];
203 }
204
205 return [
206 'success' => true,
207 'message' => __('Enquiry updated successfully.', 'yatra'),
208 ];
209 }
210
211 /**
212 * Respond to an enquiry
213 *
214 * @param int $id Enquiry ID
215 * @param string $response Response message
216 * @return array {success: bool, message: string}
217 */
218 public function respondToEnquiry(int $id, string $response): array
219 {
220 $enquiry = $this->enquiryRepository->findWithTrip($id);
221
222 if (!$enquiry) {
223 return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
224 }
225
226 if (empty(trim($response))) {
227 return ['success' => false, 'message' => __('Response message is required.', 'yatra')];
228 }
229
230 $updated = $this->enquiryRepository->addResponse($id, $response, get_current_user_id());
231
232 if (!$updated) {
233 return ['success' => false, 'message' => __('Failed to save response.', 'yatra')];
234 }
235
236 /**
237 * Action: Enquiry responded
238 * Fires after admin responds to an enquiry
239 *
240 * @param object $enquiry The enquiry object
241 * @param string $response The response message
242 * @since 3.0.0
243 */
244 do_action('yatra_enquiry_responded', $enquiry, $response);
245
246 if (apply_filters('yatra_send_enquiry_response_core_email', true, $enquiry, $response)) {
247 $this->sendResponseEmail($enquiry, $response);
248 }
249
250 return [
251 'success' => true,
252 'message' => __('Response sent successfully.', 'yatra'),
253 ];
254 }
255
256 /**
257 * Delete an enquiry
258 *
259 * @param int $id Enquiry ID
260 * @return array {success: bool, message: string}
261 */
262 public function deleteEnquiry(int $id): array
263 {
264 $enquiry = $this->enquiryRepository->find($id);
265
266 if (!$enquiry) {
267 return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
268 }
269
270 $deleted = $this->enquiryRepository->delete($id);
271
272 if (!$deleted) {
273 return ['success' => false, 'message' => __('Failed to delete enquiry.', 'yatra')];
274 }
275
276 return [
277 'success' => true,
278 'message' => __('Enquiry deleted successfully.', 'yatra'),
279 ];
280 }
281
282 /**
283 * Bulk update status
284 *
285 * @param array $ids Enquiry IDs
286 * @param string $status New status
287 * @return array {success: bool, affected: int, message: string}
288 */
289 public function bulkUpdateStatus(array $ids, string $status): array
290 {
291 // Allowed statuses for bulk updates. This list is mirrored in the admin UI.
292 $validStatuses = ['pending', 'read', 'responded', 'archived', 'spam', 'trash'];
293
294 if (!in_array($status, $validStatuses, true)) {
295 return ['success' => false, 'affected' => 0, 'message' => __('Invalid status.', 'yatra')];
296 }
297
298 $affected = $this->enquiryRepository->bulkUpdateStatus($ids, $status);
299
300 return [
301 'success' => true,
302 'affected' => $affected,
303 'message' => sprintf(__('%d enquiries updated.', 'yatra'), $affected),
304 ];
305 }
306
307 /**
308 * Bulk delete enquiries
309 *
310 * @param array $ids Enquiry IDs
311 * @return array {success: bool, affected: int, message: string}
312 */
313 public function bulkDelete(array $ids): array
314 {
315 $affected = $this->enquiryRepository->bulkDelete($ids);
316
317 return [
318 'success' => true,
319 'affected' => $affected,
320 'message' => sprintf(__('%d enquiries deleted.', 'yatra'), $affected),
321 ];
322 }
323
324 /**
325 * Get enquiry statistics
326 *
327 * @return array
328 */
329 public function getStats(): array
330 {
331 return $this->enquiryRepository->getStats();
332 }
333
334 /**
335 * Format enquiry for API response
336 *
337 * @param object $enquiry Raw enquiry data
338 * @return array
339 */
340 private function formatEnquiry(object $enquiry): array
341 {
342 $metadata = [];
343 if (!empty($enquiry->metadata)) {
344 $decoded = json_decode($enquiry->metadata, true);
345 if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
346 $metadata = $decoded;
347 }
348 }
349
350 $travelersCount = null;
351 if (!empty($metadata['classifications']) && is_array($metadata['classifications'])) {
352 $travelersCount = array_reduce($metadata['classifications'], function ($carry, $item) {
353 $count = isset($item['count']) ? (int) $item['count'] : 0;
354 return $carry + $count;
355 }, 0);
356 if ($travelersCount === 0) {
357 $travelersCount = null;
358 }
359 } elseif (isset($enquiry->travelers_count)) {
360 $travelersCount = $enquiry->travelers_count ? (int) $enquiry->travelers_count : null;
361 } elseif (isset($enquiry->adults) || isset($enquiry->children)) {
362 // Backward compatibility fallback
363 $travelersCount = ((int) ($enquiry->adults ?? 0) + (int) ($enquiry->children ?? 0)) ?: null;
364 }
365
366 return [
367 'id' => (int) $enquiry->id,
368 'trip_id' => $enquiry->trip_id ? (int) $enquiry->trip_id : null,
369 'trip_title' => $enquiry->trip_title ?? null,
370 'trip_slug' => $enquiry->trip_slug ?? null,
371 'name' => $enquiry->name,
372 'email' => $enquiry->email,
373 'phone' => $enquiry->phone,
374 'subject' => $enquiry->subject ?? null,
375 'message' => $enquiry->message,
376 'travel_date' => $enquiry->travel_date ?? null,
377 'travelers_count' => $travelersCount,
378 'metadata' => $metadata ?: null,
379 'status' => $enquiry->status,
380 'response' => $enquiry->response_notes ?? ($enquiry->response ?? null),
381 'response_notes' => $enquiry->response_notes ?? null,
382 'responded_by' => $enquiry->responded_by ? (int) $enquiry->responded_by : null,
383 'responded_at' => $enquiry->responded_at ?? null,
384 'created_at' => $enquiry->created_at,
385 'updated_at' => $enquiry->updated_at,
386 ];
387 }
388
389 /**
390 * Send admin notification for new enquiry
391 *
392 * @param int $enquiryId Enquiry ID
393 */
394 private function sendAdminNotification(int $enquiryId): void
395 {
396 $enquiry = $this->enquiryRepository->findWithTrip($enquiryId);
397
398 if (!$enquiry) {
399 return;
400 }
401
402 $adminEmail = get_option('admin_email');
403 if ($adminEmail === '' || !is_email($adminEmail)) {
404 return;
405 }
406
407 $subject = sprintf(
408 /* translators: 1: site name, 2: customer name */
409 __('💬 [%1$s] New enquiry · %2$s', 'yatra'),
410 get_bloginfo('name'),
411 $enquiry->name
412 );
413
414 $body = EmailTemplateDefaults::renderCoreEnquiryAdminNotificationHtml($enquiry);
415
416 EmailService::send($adminEmail, $subject, $body, ['Content-Type: text/html; charset=UTF-8']);
417 }
418
419 /**
420 * Send customer confirmation email
421 *
422 * @param int $enquiryId Enquiry ID
423 */
424 private function sendCustomerConfirmation(int $enquiryId): void
425 {
426 $enquiry = $this->enquiryRepository->findWithTrip($enquiryId);
427
428 if (!$enquiry || empty($enquiry->email)) {
429 return;
430 }
431
432 $subject = sprintf(
433 __('✉️ [%s] We received your message', 'yatra'),
434 get_bloginfo('name')
435 );
436
437 $body = EmailTemplateDefaults::renderCoreEnquiryCustomerConfirmationHtml($enquiry);
438
439 EmailService::send($enquiry->email, $subject, $body, ['Content-Type: text/html; charset=UTF-8']);
440 }
441
442 /**
443 * Send response email to customer
444 *
445 * @param object $enquiry Enquiry data
446 * @param string $response Response message
447 */
448 private function sendResponseEmail(object $enquiry, string $response): void
449 {
450 if (empty($enquiry->email)) {
451 return;
452 }
453
454 $subject = sprintf(
455 __('💬 [%s] Re: your enquiry', 'yatra'),
456 get_bloginfo('name')
457 );
458
459 $body = EmailTemplateDefaults::renderCoreEnquiryResponseHtml($enquiry, $response);
460
461 EmailService::send($enquiry->email, $subject, $body, ['Content-Type: text/html; charset=UTF-8']);
462 }
463 }
464
465