| @@ -64,8 +64,55 @@ | ||
| 64 | 64 | * @return array {success: bool, enquiry_id?: int, message: string} |
| 65 | 65 | */ |
| 66 | 66 | public function createEnquiry(array $data): array |
| 67 | 67 | { |
| 68 | + // Normalize common client-side key variants (REST/JS often uses camelCase). | |
| 69 | + if ((!isset($data['trip_id']) || $data['trip_id'] === '' || $data['trip_id'] === null) | |
| 70 | + && isset($data['tripId']) | |
| 71 | + && $data['tripId'] !== '' | |
| 72 | + && $data['tripId'] !== null | |
| 73 | + ) { | |
| 74 | + $data['trip_id'] = $data['tripId']; | |
| 75 | + } | |
| 76 | + | |
| 77 | + // If trip_id is missing, attempt to derive it from trip slug or the referring URL. | |
| 78 | + // This makes enquiry emails resilient even if a client drops hidden fields. | |
| 79 | + if (empty($data['trip_id']) || (string) $data['trip_id'] === '0') { | |
| 80 | + $candidateSlug = ''; | |
| 81 | + if (!empty($data['trip_slug'])) { | |
| 82 | + $candidateSlug = sanitize_title((string) $data['trip_slug']); | |
| 83 | + } elseif (!empty($data['tripSlug'])) { | |
| 84 | + $candidateSlug = sanitize_title((string) $data['tripSlug']); | |
| 85 | + } else { | |
| 86 | + $ref = isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : ''; | |
| 87 | + if ($ref !== '') { | |
| 88 | + $parts = wp_parse_url($ref); | |
| 89 | + $path = isset($parts['path']) ? trim((string) $parts['path'], '/') : ''; | |
| 90 | + if ($path !== '') { | |
| 91 | + $segments = array_values(array_filter(explode('/', $path), static fn ($s) => $s !== '')); | |
| 92 | + $tripBase = trim((string) SettingsService::getTripBase(), '/'); | |
| 93 | + if ($tripBase !== '' && !empty($segments)) { | |
| 94 | + $baseIndex = array_search($tripBase, $segments, true); | |
| 95 | + if ($baseIndex !== false && isset($segments[$baseIndex + 1])) { | |
| 96 | + $candidateSlug = sanitize_title((string) $segments[$baseIndex + 1]); | |
| 97 | + } | |
| 98 | + } | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + if ($candidateSlug !== '') { | |
| 104 | + try { | |
| 105 | + $trip = $this->tripRepository->findBySlug($candidateSlug); | |
| 106 | + if ($trip && !empty($trip->id)) { | |
| 107 | + $data['trip_id'] = (int) $trip->id; | |
| 108 | + } | |
| 109 | + } catch (\Throwable $e) { | |
| 110 | + // Ignore; will proceed as general enquiry. | |
| 111 | + } | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 68 | 115 | // Validate required fields |
| 69 | 116 | if (empty($data['name']) || empty($data['email']) || empty($data['message'])) { |
| 70 | 117 | return ['success' => false, 'message' => __('Name, email, and message are required.', 'yatra')]; |
| 71 | 118 | } |
| @@ -144,23 +191,35 @@ | ||
| 144 | 191 | if (empty($data['status'])) { |
| 145 | 192 | $data['status'] = 'pending'; |
| 146 | 193 | } |
| 147 | 194 | |
| 148 | - // Create enquiry | |
| 149 | - $enquiryId = $this->enquiryRepository->create($data); | |
| 195 | + // Create enquiry. The repository throws when the INSERT is rejected; | |
| 196 | + // this endpoint is public, so turn that into the same friendly failure | |
| 197 | + // every other rejection returns instead of a 500 that echoes the raw | |
| 198 | + // database error back to the visitor. | |
| 199 | + try { | |
| 200 | + $enquiryId = $this->enquiryRepository->create($data); | |
| 201 | + } catch (\Throwable $e) { | |
| 202 | + error_log('Yatra: failed to create enquiry - ' . $e->getMessage()); | |
| 150 | 203 | |
| 204 | + return ['success' => false, 'message' => __('Failed to submit enquiry.', 'yatra')]; | |
| 205 | + } | |
| 206 | + | |
| 151 | 207 | if (!$enquiryId) { |
| 152 | 208 | return ['success' => false, 'message' => __('Failed to submit enquiry.', 'yatra')]; |
| 153 | 209 | } |
| 154 | 210 | |
| 155 | - // Get the full enquiry object for the action | |
| 156 | - $enquiry = $this->enquiryRepository->find($enquiryId); | |
| 157 | - | |
| 211 | + // Load with trip JOIN so listeners (e.g. Pro Email Automation's onEnquiryCreated | |
| 212 | + // → buildEnquiryVariables) receive trip_title / trip_slug. Without these, Pro | |
| 213 | + // resolves {{trip_name}} to the literal string "General Enquiry". | |
| 214 | + $enquiry = $this->enquiryRepository->findWithTrip($enquiryId) | |
| 215 | + ?: $this->enquiryRepository->find($enquiryId); | |
| 216 | + | |
| 158 | 217 | /** |
| 159 | 218 | * Action: Enquiry created |
| 160 | 219 | * Fires after a new enquiry is successfully created |
| 161 | 220 | * |
| 162 | - * @param object $enquiry The enquiry object | |
| 221 | + * @param object $enquiry The enquiry object (joined with trip when available) | |
| 163 | 222 | * @since 3.0.0 |
| 164 | 223 | */ |
| 165 | 224 | do_action('yatra_enquiry_created', $enquiry); |
| 166 | 225 | |
| @@ -288,9 +347,14 @@ | ||
| 288 | 347 | */ |
| 289 | 348 | public function bulkUpdateStatus(array $ids, string $status): array |
| 290 | 349 | { |
| 291 | 350 | // Allowed statuses for bulk updates. This list is mirrored in the admin UI. |
| 292 | - $validStatuses = ['pending', 'read', 'responded', 'archived', 'spam', 'trash']; | |
| 351 | + // 'completed' marks enquiries that have been fully handled, distinct from | |
| 352 | + // open/in-progress ones. 'closed' is the "no further action" end state the | |
| 353 | + // enquiry edit screen has always offered; it is accepted here too so the | |
| 354 | + // list's quick status actions and bulk actions can set it without the | |
| 355 | + // operator having to open each enquiry. | |
| 356 | + $validStatuses = ['pending', 'read', 'responded', 'completed', 'closed', 'archived', 'spam', 'trash']; | |
| 293 | 357 | |
| 294 | 358 | if (!in_array($status, $validStatuses, true)) { |
| 295 | 359 | return ['success' => false, 'affected' => 0, 'message' => __('Invalid status.', 'yatra')]; |
| 296 | 360 | } |
| @@ -299,9 +363,13 @@ | ||
| 299 | 363 | |
| 300 | 364 | return [ |
| 301 | 365 | 'success' => true, |
| 302 | 366 | 'affected' => $affected, |
| 303 | - 'message' => sprintf(__('%d enquiries updated.', 'yatra'), $affected), | |
| 367 | + 'message' => sprintf( | |
| 368 | + /* translators: %d: number of enquiries updated. */ | |
| 369 | + __('%d enquiries updated.', 'yatra'), | |
| 370 | + $affected | |
| 371 | + ), | |
| 304 | 372 | ]; |
| 305 | 373 | } |
| 306 | 374 | |
| 307 | 375 | /** |
| @@ -316,9 +384,13 @@ | ||
| 316 | 384 | |
| 317 | 385 | return [ |
| 318 | 386 | 'success' => true, |
| 319 | 387 | 'affected' => $affected, |
| 320 | - 'message' => sprintf(__('%d enquiries deleted.', 'yatra'), $affected), | |
| 388 | + 'message' => sprintf( | |
| 389 | + /* translators: %d: number of enquiries deleted. */ | |
| 390 | + __('%d enquiries deleted.', 'yatra'), | |
| 391 | + $affected | |
| 392 | + ), | |
| 321 | 393 | ]; |
| 322 | 394 | } |
| 323 | 395 | |
| 324 | 396 | /** |