# yatra/3.0.15/app/Services/EnquiryService.php

Yatra – Travel Booking &amp; Tour Operator Software, version 3.0.15. 526 lines.

- Page: https://pluginprobe.com/plugins/yatra/3.0.15/code/app/Services/EnquiryService.php
- Raw: https://pluginprobe.com/plugins/yatra/3.0.15/raw/app/Services/EnquiryService.php
- Modified: 2026-09-15T11:48:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/yatra/3.0.15/code/app/Services/EnquiryService.php#L10-L20`.

```php
<?php

declare(strict_types=1);

namespace Yatra\Services;

use Yatra\Repositories\EnquiryRepository;
use Yatra\Repositories\TripRepository;

/**
 * Enquiry Service
 * 
 * Contains business logic for customer enquiries.
 * 
 * @package Yatra\Services
 */
class EnquiryService
{
    private EnquiryRepository $enquiryRepository;
    private TripRepository $tripRepository;

    public function __construct()
    {
        $this->enquiryRepository = new EnquiryRepository();
        $this->tripRepository = new TripRepository();
    }

    /**
     * Get paginated enquiries
     * 
     * @param array $filters Filters
     * @return array
     */
    public function getEnquiries(array $filters = []): array
    {
        $result = $this->enquiryRepository->paginate($filters);

        $result['data'] = array_map([$this, 'formatEnquiry'], $result['data']);

        return $result;
    }

    /**
     * Get single enquiry
     * 
     * @param int $id Enquiry ID
     * @return array|null
     */
    public function getEnquiry(int $id): ?array
    {
        $enquiry = $this->enquiryRepository->findWithTrip($id);

        if (!$enquiry) {
            return null;
        }

        return $this->formatEnquiry($enquiry);
    }

    /**
     * Create a new enquiry
     * 
     * @param array $data Enquiry data
     * @return array {success: bool, enquiry_id?: int, message: string}
     */
    public function createEnquiry(array $data): array
    {
        // Normalize common client-side key variants (REST/JS often uses camelCase).
        if ((!isset($data['trip_id']) || $data['trip_id'] === '' || $data['trip_id'] === null)
            && isset($data['tripId'])
            && $data['tripId'] !== ''
            && $data['tripId'] !== null
        ) {
            $data['trip_id'] = $data['tripId'];
        }

        // If trip_id is missing, attempt to derive it from trip slug or the referring URL.
        // This makes enquiry emails resilient even if a client drops hidden fields.
        if (empty($data['trip_id']) || (string) $data['trip_id'] === '0') {
            $candidateSlug = '';
            if (!empty($data['trip_slug'])) {
                $candidateSlug = sanitize_title((string) $data['trip_slug']);
            } elseif (!empty($data['tripSlug'])) {
                $candidateSlug = sanitize_title((string) $data['tripSlug']);
            } else {
                $ref = isset($_SERVER['HTTP_REFERER']) ? (string) $_SERVER['HTTP_REFERER'] : '';
                if ($ref !== '') {
                    $parts = wp_parse_url($ref);
                    $path = isset($parts['path']) ? trim((string) $parts['path'], '/') : '';
                    if ($path !== '') {
                        $segments = array_values(array_filter(explode('/', $path), static fn ($s) => $s !== ''));
                        $tripBase = trim((string) SettingsService::getTripBase(), '/');
                        if ($tripBase !== '' && !empty($segments)) {
                            $baseIndex = array_search($tripBase, $segments, true);
                            if ($baseIndex !== false && isset($segments[$baseIndex + 1])) {
                                $candidateSlug = sanitize_title((string) $segments[$baseIndex + 1]);
                            }
                        }
                    }
                }
            }

            if ($candidateSlug !== '') {
                try {
                    $trip = $this->tripRepository->findBySlug($candidateSlug);
                    if ($trip && !empty($trip->id)) {
                        $data['trip_id'] = (int) $trip->id;
                    }
                } catch (\Throwable $e) {
                    // Ignore; will proceed as general enquiry.
                }
            }
        }

        // Validate required fields
        if (empty($data['name']) || empty($data['email']) || empty($data['message'])) {
            return ['success' => false, 'message' => __('Name, email, and message are required.', 'yatra')];
        }

        // Validate email
        if (!is_email($data['email'])) {
            return ['success' => false, 'message' => __('Please provide a valid email address.', 'yatra')];
        }

        // Validate trip if provided
        if (!empty($data['trip_id'])) {
            $trip = $this->tripRepository->find((int) $data['trip_id']);
            if (!$trip) {
                return ['success' => false, 'message' => __('Trip not found.', 'yatra')];
            }
        }

        // Build metadata for traveler classifications (flat array, no wrapper)
        $classifications = [];
        if (!empty($data['metadata'])) {
            $metaValue = $data['metadata'];
            if (is_string($metaValue)) {
                $decoded = json_decode($metaValue, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    $metaValue = $decoded;
                }
            }
            if (is_array($metaValue)) {
                // Accept both wrapped {classifications: [...]} and direct array [...]
                if (isset($metaValue['classifications']) && is_array($metaValue['classifications'])) {
                    $classifications = $metaValue['classifications'];
                } elseif (array_is_list($metaValue)) {
                    $classifications = $metaValue;
                }
            }
        }

        // Backward compatibility: derive from adults/children if still not provided
        if (empty($classifications) && (isset($data['adults']) || isset($data['children']))) {
            $adults = isset($data['adults']) ? (int) $data['adults'] : null;
            $children = isset($data['children']) ? (int) $data['children'] : null;
            if ($adults !== null) {
                $classifications[] = ['id' => 'adult', 'title' => 'Adult', 'count' => $adults];
            }
            if ($children !== null) {
                $classifications[] = ['id' => 'child', 'title' => 'Child', 'count' => $children];
            }
        }

        // Compute travelers_count from classifications
        $travelersCount = null;
        if (!empty($classifications)) {
            $travelersCount = array_reduce($classifications, function ($carry, $item) {
                $count = isset($item['count']) ? (int) $item['count'] : 0;
                return $carry + $count;
            }, 0);
            if ($travelersCount === 0) {
                $travelersCount = null;
            }
        } elseif (isset($data['adults']) || isset($data['children'])) {
            $travelersCount = ((int) ($data['adults'] ?? 0) + (int) ($data['children'] ?? 0)) ?: null;
        }

        $data['metadata'] = !empty($classifications) ? wp_json_encode($classifications) : null;
        $data['travelers_count'] = $travelersCount;

        // Capture IP and user agent if not provided
        if (empty($data['ip_address'])) {
            $data['ip_address'] = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null;
        }
        if (empty($data['user_agent'])) {
            $data['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? null;
        }

        // Set default status
        if (empty($data['status'])) {
            $data['status'] = 'pending';
        }

        // Create enquiry. The repository throws when the INSERT is rejected;
        // this endpoint is public, so turn that into the same friendly failure
        // every other rejection returns instead of a 500 that echoes the raw
        // database error back to the visitor.
        try {
            $enquiryId = $this->enquiryRepository->create($data);
        } catch (\Throwable $e) {
            error_log('Yatra: failed to create enquiry - ' . $e->getMessage());

            return ['success' => false, 'message' => __('Failed to submit enquiry.', 'yatra')];
        }

        if (!$enquiryId) {
            return ['success' => false, 'message' => __('Failed to submit enquiry.', 'yatra')];
        }
        
        // Load with trip JOIN so listeners (e.g. Pro Email Automation's onEnquiryCreated
        // → buildEnquiryVariables) receive trip_title / trip_slug. Without these, Pro
        // resolves {{trip_name}} to the literal string "General Enquiry".
        $enquiry = $this->enquiryRepository->findWithTrip($enquiryId)
            ?: $this->enquiryRepository->find($enquiryId);

        /**
         * Action: Enquiry created
         * Fires after a new enquiry is successfully created
         * 
         * @param object $enquiry The enquiry object (joined with trip when available)
         * @since 3.0.0
         */
        do_action('yatra_enquiry_created', $enquiry);

        // Core plaintext (wp_mail). Pro Email Automation sends HTML when templates exist — see
        // yatra_send_enquiry_created_* filters in yatra-pro EmailAutomationHooks.
        if (apply_filters('yatra_send_enquiry_created_admin_email', true, $enquiry)) {
            $this->sendAdminNotification($enquiryId);
        }

        if (apply_filters('yatra_send_enquiry_created_customer_email', true, $enquiry)) {
            $this->sendCustomerConfirmation($enquiryId);
        }

        return [
            'success' => true,
            'enquiry_id' => $enquiryId,
            'message' => __('Your enquiry has been submitted successfully. We will get back to you soon.', 'yatra'),
        ];
    }

    /**
     * Update an enquiry
     * 
     * @param int   $id   Enquiry ID
     * @param array $data Enquiry data
     * @return array {success: bool, message: string}
     */
    public function updateEnquiry(int $id, array $data): array
    {
        $enquiry = $this->enquiryRepository->find($id);

        if (!$enquiry) {
            return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
        }

        $updated = $this->enquiryRepository->update($id, $data);

        if (!$updated) {
            return ['success' => false, 'message' => __('Failed to update enquiry.', 'yatra')];
        }

        return [
            'success' => true,
            'message' => __('Enquiry updated successfully.', 'yatra'),
        ];
    }

    /**
     * Respond to an enquiry
     * 
     * @param int    $id       Enquiry ID
     * @param string $response Response message
     * @return array {success: bool, message: string}
     */
    public function respondToEnquiry(int $id, string $response): array
    {
        $enquiry = $this->enquiryRepository->findWithTrip($id);

        if (!$enquiry) {
            return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
        }

        if (empty(trim($response))) {
            return ['success' => false, 'message' => __('Response message is required.', 'yatra')];
        }

        $updated = $this->enquiryRepository->addResponse($id, $response, get_current_user_id());

        if (!$updated) {
            return ['success' => false, 'message' => __('Failed to save response.', 'yatra')];
        }
        
        /**
         * Action: Enquiry responded
         * Fires after admin responds to an enquiry
         * 
         * @param object $enquiry The enquiry object
         * @param string $response The response message
         * @since 3.0.0
         */
        do_action('yatra_enquiry_responded', $enquiry, $response);

        if (apply_filters('yatra_send_enquiry_response_core_email', true, $enquiry, $response)) {
            $this->sendResponseEmail($enquiry, $response);
        }

        return [
            'success' => true,
            'message' => __('Response sent successfully.', 'yatra'),
        ];
    }

    /**
     * Delete an enquiry
     * 
     * @param int $id Enquiry ID
     * @return array {success: bool, message: string}
     */
    public function deleteEnquiry(int $id): array
    {
        $enquiry = $this->enquiryRepository->find($id);

        if (!$enquiry) {
            return ['success' => false, 'message' => __('Enquiry not found.', 'yatra')];
        }

        $deleted = $this->enquiryRepository->delete($id);

        if (!$deleted) {
            return ['success' => false, 'message' => __('Failed to delete enquiry.', 'yatra')];
        }

        return [
            'success' => true,
            'message' => __('Enquiry deleted successfully.', 'yatra'),
        ];
    }

    /**
     * Bulk update status
     * 
     * @param array  $ids    Enquiry IDs
     * @param string $status New status
     * @return array {success: bool, affected: int, message: string}
     */
    public function bulkUpdateStatus(array $ids, string $status): array
    {
        // Allowed statuses for bulk updates. This list is mirrored in the admin UI.
        // 'completed' marks enquiries that have been fully handled, distinct from
        // open/in-progress ones. 'closed' is the "no further action" end state the
        // enquiry edit screen has always offered; it is accepted here too so the
        // list's quick status actions and bulk actions can set it without the
        // operator having to open each enquiry.
        $validStatuses = ['pending', 'read', 'responded', 'completed', 'closed', 'archived', 'spam', 'trash'];

        if (!in_array($status, $validStatuses, true)) {
            return ['success' => false, 'affected' => 0, 'message' => __('Invalid status.', 'yatra')];
        }

        $affected = $this->enquiryRepository->bulkUpdateStatus($ids, $status);

        return [
            'success' => true,
            'affected' => $affected,
            'message' => sprintf(
                /* translators: %d: number of enquiries updated. */
                __('%d enquiries updated.', 'yatra'),
                $affected
            ),
        ];
    }

    /**
     * Bulk delete enquiries
     * 
     * @param array $ids Enquiry IDs
     * @return array {success: bool, affected: int, message: string}
     */
    public function bulkDelete(array $ids): array
    {
        $affected = $this->enquiryRepository->bulkDelete($ids);

        return [
            'success' => true,
            'affected' => $affected,
            'message' => sprintf(
                /* translators: %d: number of enquiries deleted. */
                __('%d enquiries deleted.', 'yatra'),
                $affected
            ),
        ];
    }

    /**
     * Get enquiry statistics
     * 
     * @return array
     */
    public function getStats(): array
    {
        return $this->enquiryRepository->getStats();
    }

    /**
     * Format enquiry for API response
     * 
     * @param object $enquiry Raw enquiry data
     * @return array
     */
    private function formatEnquiry(object $enquiry): array
    {
        $metadata = [];
        if (!empty($enquiry->metadata)) {
            $decoded = json_decode($enquiry->metadata, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $metadata = $decoded;
            }
        }

        $travelersCount = null;
        if (!empty($metadata['classifications']) && is_array($metadata['classifications'])) {
            $travelersCount = array_reduce($metadata['classifications'], function ($carry, $item) {
                $count = isset($item['count']) ? (int) $item['count'] : 0;
                return $carry + $count;
            }, 0);
            if ($travelersCount === 0) {
                $travelersCount = null;
            }
        } elseif (isset($enquiry->travelers_count)) {
            $travelersCount = $enquiry->travelers_count ? (int) $enquiry->travelers_count : null;
        } elseif (isset($enquiry->adults) || isset($enquiry->children)) {
            // Backward compatibility fallback
            $travelersCount = ((int) ($enquiry->adults ?? 0) + (int) ($enquiry->children ?? 0)) ?: null;
        }

        return [
            'id' => (int) $enquiry->id,
            'trip_id' => $enquiry->trip_id ? (int) $enquiry->trip_id : null,
            'trip_title' => $enquiry->trip_title ?? null,
            'trip_slug' => $enquiry->trip_slug ?? null,
            'name' => $enquiry->name,
            'email' => $enquiry->email,
            'phone' => $enquiry->phone,
            'subject' => $enquiry->subject ?? null,
            'message' => $enquiry->message,
            'travel_date' => $enquiry->travel_date ?? null,
            'travelers_count' => $travelersCount,
            'metadata' => $metadata ?: null,
            'status' => $enquiry->status,
            'response' => $enquiry->response_notes ?? ($enquiry->response ?? null),
            'response_notes' => $enquiry->response_notes ?? null,
            'responded_by' => $enquiry->responded_by ? (int) $enquiry->responded_by : null,
            'responded_at' => $enquiry->responded_at ?? null,
            'created_at' => $enquiry->created_at,
            'updated_at' => $enquiry->updated_at,
        ];
    }

    /**
     * Send admin notification for new enquiry
     * 
     * @param int $enquiryId Enquiry ID
     */
    private function sendAdminNotification(int $enquiryId): void
    {
        $enquiry = $this->enquiryRepository->findWithTrip($enquiryId);

        if (!$enquiry) {
            return;
        }

        $adminEmail = sanitize_email((string) SettingsService::getString('admin_email', (string) get_option('admin_email', '')));
        if ($adminEmail === '' || !is_email($adminEmail)) {
            return;
        }

        TransactionalEmailTemplateService::sendIfEnabled(
            TransactionalEmailTemplateService::TYPE_ENQUIRY_ADMIN,
            $adminEmail,
            TransactionalEmailTemplateService::variablesFromEnquiry($enquiry, '')
        );
    }

    /**
     * Send customer confirmation email
     * 
     * @param int $enquiryId Enquiry ID
     */
    private function sendCustomerConfirmation(int $enquiryId): void
    {
        $enquiry = $this->enquiryRepository->findWithTrip($enquiryId);

        if (!$enquiry || empty($enquiry->email)) {
            return;
        }

        TransactionalEmailTemplateService::sendIfEnabled(
            TransactionalEmailTemplateService::TYPE_ENQUIRY_CUSTOMER_RECEIVED,
            (string) $enquiry->email,
            TransactionalEmailTemplateService::variablesFromEnquiry($enquiry, '')
        );
    }

    /**
     * Send response email to customer
     * 
     * @param object $enquiry  Enquiry data
     * @param string $response Response message
     */
    private function sendResponseEmail(object $enquiry, string $response): void
    {
        if (empty($enquiry->email)) {
            return;
        }

        TransactionalEmailTemplateService::sendIfEnabled(
            TransactionalEmailTemplateService::TYPE_ENQUIRY_CUSTOMER_RESPONSE,
            (string) $enquiry->email,
            TransactionalEmailTemplateService::variablesFromEnquiry($enquiry, $response)
        );
    }
}


```
