[orbis] Weekly backup 2026-07-07 — 318 files changed, 48597 insertions(+), 7 deletions(-)

This commit is contained in:
DO Server Backup
2026-07-07 15:58:55 +00:00
parent 5fda5a1536
commit fe18800d18
318 changed files with 48597 additions and 7 deletions
@@ -0,0 +1,6 @@
*.log
.DS_Store
*.swp
uploads/*
!uploads/.htaccess
@@ -0,0 +1,21 @@
<Files db.php>
Order deny,allow
Deny from all
</Files>
RewriteEngine On
RewriteRule ^db\.php$ - [F,L]
RewriteCond %{REQUEST_URI} ^/db\.php$
RewriteRule .* - [F,L]
# .git/ was found directly downloadable (leaked GitHub PAT) - same class of
# issue found and fixed on sibling sites this session.
RewriteCond %{REQUEST_URI} ^/\.git
RewriteRule .* - [F,L]
# uploads/ (customer license/insurance docs) must never be directly downloadable -
# same Order/Deny-doesn't-work-on-OpenLiteSpeed gotcha as .git above. Docs are
# served only through view-doc.php / admin/view-doc.php, which read by filesystem
# path via readfile() - blocking direct URL access here doesn't break that flow.
RewriteCond %{REQUEST_URI} ^/uploads/
RewriteRule .* - [F,L]
@@ -0,0 +1,5 @@
<IfModule LiteSpeed>
CacheEnable off
</IfModule>
Header always set Cache-Control "no-store, no-cache, must-revalidate"
Header always set Pragma "no-cache"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
<?php
require_once dirname(__DIR__) . '/db.php';
$token = preg_replace('/[^a-f0-9]/', '', $_GET['_t'] ?? '');
if (!verifyAdminToken($token)) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Unauthorized — please log in to the admin panel first.');
}
$ref = strtoupper(preg_replace('/[^A-Z0-9\-]/', '', $_GET['ref'] ?? ''));
$type = in_array($_GET['type'] ?? '', ['license','insurance']) ? $_GET['type'] : '';
if (!$ref || !$type) {
http_response_code(400);
header('Content-Type: text/plain');
exit('Missing parameters.');
}
$col = $type === 'license' ? 'license_file' : 'insurance_file';
$stmt = db()->prepare("SELECT {$col} AS file_path FROM bookings WHERE booking_ref=?");
$stmt->execute([$ref]);
$row = $stmt->fetch();
if (!$row || !$row['file_path']) {
http_response_code(404);
header('Content-Type: text/plain');
exit('No document on file.');
}
$path = resolveUploadPath(dirname(__DIR__), $row['file_path']);
if (!$path) {
http_response_code(404);
header('Content-Type: text/plain');
exit('File not found.');
}
$mime = detectAllowedMime($path);
if (!$mime) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Invalid file type.');
}
$fname = $type . '-' . $ref . '.' . ALLOWED_DOC_MIMES[$mime];
header('Content-Type: ' . $mime);
header('Content-Disposition: inline; filename="' . $fname . '"');
header('Content-Length: ' . filesize($path));
header('Cache-Control: no-store, no-cache');
readfile($path);
@@ -0,0 +1,47 @@
<?php
require_once __DIR__ . '/db.php';
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: ' . SITE_URL);
header('Cache-Control: no-store, no-cache, must-revalidate');
$month = (int)($_GET['month'] ?? date('n'));
$year = (int)($_GET['year'] ?? date('Y'));
$month = max(1, min(12, $month));
$year = max(date('Y'), min(date('Y') + 2, $year));
$start = sprintf('%04d-%02d-01', $year, $month);
$end = date('Y-m-t', strtotime($start));
// Bookings that overlap this month — including those that start last month and end this month
$booked = db()->prepare(
"SELECT rental_date, end_date FROM bookings
WHERE status IN ('pending','confirmed')
AND rental_date <= ? AND end_date >= ?"
);
$booked->execute([$end, $start]);
$bookedDays = [];
foreach ($booked->fetchAll() as $row) {
$d = new DateTime($row['rental_date']);
$e = new DateTime($row['end_date']);
while ($d <= $e) {
$bookedDays[] = $d->format('Y-m-d');
$d->modify('+1 day');
}
}
// Admin-blocked dates for this month
$blocked = db()->prepare(
"SELECT block_date FROM blocked_dates WHERE block_date BETWEEN ? AND ?"
);
$blocked->execute([$start, $end]);
foreach ($blocked->fetchAll() as $row) {
$bookedDays[] = $row['block_date'];
}
echo json_encode([
'month' => $month,
'year' => $year,
'booked_dates' => array_values(array_unique($bookedDays)),
]);
@@ -0,0 +1,2 @@
RewriteEngine On
RewriteRule .* - [F,L]
@@ -0,0 +1,91 @@
docRoot $VH_ROOT/public_html
vhDomain $VH_NAME
vhAliases www.$VH_NAME
adminEmails admin@parkerslingshotrentals.com
enableGzip 1
enableIpGeo 1
index {
useServer 0
indexFiles index.php, index.html
}
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
useServer 0
logLevel WARN
rollingSize 10M
}
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
useServer 0
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
logHeaders 5
rollingSize 10M
keepDays 10
compressArchive 1
}
scripthandler {
add lsapi:parke1909 php
}
extprocessor parke1909 {
type lsapi
address UDS://tmp/lshttpd/parke1909.sock
maxConns 10
env LSAPI_CHILDREN=10
initTimeout 600
retryTimeout 0
persistConn 1
pcKeepAliveTimeout 1
respBuffer 0
autoStart 1
path /usr/local/lsws/lsphp85/bin/lsphp
extUser parke1909
extGroup parke1909
memSoftLimit 1024M
memHardLimit 1024M
procSoftLimit 400
procHardLimit 500
}
phpIniOverride {
}
module cache {
storagePath /usr/local/lsws/cachedata/$VH_NAME
}
rewrite {
enable 1
autoLoadHtaccess 1
}
context /.well-known/acme-challenge {
location /usr/local/lsws/Example/html/.well-known/acme-challenge
allowBrowse 1
rewrite {
enable 0
}
addDefaultCharset off
phpIniOverride {
}
}
vhssl {
keyFile /etc/letsencrypt/live/parkerslingshotrentals.com/privkey.pem
certFile /etc/letsencrypt/live/parkerslingshotrentals.com/fullchain.pem
certChain 1
sslProtocol 24
enableECDHE 1
renegProtection 1
sslSessionCache 1
enableSpdy 15
enableStapling 1
ocspRespMaxAge 86400
}
@@ -0,0 +1,91 @@
docRoot $VH_ROOT/public_html
vhDomain $VH_NAME
vhAliases www.$VH_NAME
adminEmails admin@parkerslingshotrentals.com
enableGzip 1
enableIpGeo 1
index {
useServer 0
indexFiles index.php, index.html
}
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
useServer 0
logLevel WARN
rollingSize 10M
}
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
useServer 0
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
logHeaders 5
rollingSize 10M
keepDays 10
compressArchive 1
}
scripthandler {
add lsapi:parke1909 php
}
extprocessor parke1909 {
type lsapi
address UDS://tmp/lshttpd/parke1909.sock
maxConns 10
env LSAPI_CHILDREN=10
initTimeout 600
retryTimeout 0
persistConn 1
pcKeepAliveTimeout 1
respBuffer 0
autoStart 1
path /usr/local/lsws/lsphp85/bin/lsphp
extUser parke1909
extGroup parke1909
memSoftLimit 1024M
memHardLimit 1024M
procSoftLimit 400
procHardLimit 500
}
phpIniOverride {
}
module cache {
storagePath /usr/local/lsws/cachedata/$VH_NAME
}
rewrite {
enable 1
autoLoadHtaccess 1
}
context /.well-known/acme-challenge {
location /usr/local/lsws/Example/html/.well-known/acme-challenge
allowBrowse 1
rewrite {
enable 0
}
addDefaultCharset off
phpIniOverride {
}
}
vhssl {
keyFile /etc/letsencrypt/live/parkerslingshotrentals.com/privkey.pem
certFile /etc/letsencrypt/live/parkerslingshotrentals.com/fullchain.pem
certChain 1
sslProtocol 24
enableECDHE 1
renegProtection 1
sslSessionCache 1
enableSpdy 15
enableStapling 1
ocspRespMaxAge 86400
}
@@ -0,0 +1,130 @@
head 1.2;
access;
symbols;
locks
root:1.2; strict;
comment @# @;
1.2
date 2026.05.17.23.58.24; author root; state Exp;
branches;
next 1.1;
1.1
date 2026.05.17.23.58.09; author root; state Exp;
branches;
next ;
desc
@/usr/local/lsws/conf/vhosts/parkerslingshotrentals.com/vhost.conf0
@
1.2
log
@Update
@
text
@docRoot $VH_ROOT/public_html
vhDomain $VH_NAME
vhAliases www.$VH_NAME
adminEmails admin@@parkerslingshotrentals.com
enableGzip 1
enableIpGeo 1
index {
useServer 0
indexFiles index.php, index.html
}
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
useServer 0
logLevel WARN
rollingSize 10M
}
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
useServer 0
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
logHeaders 5
rollingSize 10M
keepDays 10
compressArchive 1
}
scripthandler {
add lsapi:parke1909 php
}
extprocessor parke1909 {
type lsapi
address UDS://tmp/lshttpd/parke1909.sock
maxConns 10
env LSAPI_CHILDREN=10
initTimeout 600
retryTimeout 0
persistConn 1
pcKeepAliveTimeout 1
respBuffer 0
autoStart 1
path /usr/local/lsws/lsphp85/bin/lsphp
extUser parke1909
extGroup parke1909
memSoftLimit 1024M
memHardLimit 1024M
procSoftLimit 400
procHardLimit 500
}
phpIniOverride {
}
module cache {
storagePath /usr/local/lsws/cachedata/$VH_NAME
}
rewrite {
enable 1
autoLoadHtaccess 1
}
context /.well-known/acme-challenge {
location /usr/local/lsws/Example/html/.well-known/acme-challenge
allowBrowse 1
rewrite {
enable 0
}
addDefaultCharset off
phpIniOverride {
}
}
vhssl {
keyFile /etc/letsencrypt/live/parkerslingshotrentals.com/privkey.pem
certFile /etc/letsencrypt/live/parkerslingshotrentals.com/fullchain.pem
certChain 1
sslProtocol 24
enableECDHE 1
renegProtection 1
sslSessionCache 1
enableSpdy 15
enableStapling 1
ocspRespMaxAge 86400
}
@
1.1
log
@Update
@
text
@d79 13
@
@@ -0,0 +1,187 @@
<?php
require_once __DIR__ . '/db.php';
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: ' . SITE_URL . '');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$name = trim(strip_tags($input['name'] ?? ''));
$email = trim(strip_tags($input['email'] ?? ''));
$phone = trim(strip_tags($input['phone'] ?? ''));
$package = trim(strip_tags($input['package'] ?? ''));
$date = trim(strip_tags($input['date'] ?? ''));
$message = trim(strip_tags($input['message'] ?? ''));
$squareToken = trim($input['square_token'] ?? '');
if (!$name || !$email || !$package || !$date) {
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'Name, email, package, and date are required.']); exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'Invalid email address.']); exit;
}
if (!isset(PACKAGES[$package])) {
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'Invalid package.']); exit;
}
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) || strtotime($date) < strtotime('today')) {
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'Invalid or past date.']); exit;
}
$pkg = PACKAGES[$package];
$rentalDate = $date;
$endDate = date('Y-m-d', strtotime($date . ' +' . $pkg['days'] . ' days'));
// Check availability + create booking atomically. A named advisory lock (scoped to the
// requested date range) prevents two concurrent submissions from both passing the
// conflict check before either has inserted, which would otherwise double-book the
// vehicle for overlapping dates.
$lockName = 'psr_booking_' . $rentalDate . '_' . $endDate;
$gotLock = (int) db()->query("SELECT GET_LOCK('" . addslashes($lockName) . "', 5)")->fetchColumn();
if (!$gotLock) {
http_response_code(503);
echo json_encode(['success'=>false,'error'=>'Please try again in a moment.']); exit;
}
try {
$conflict = db()->prepare(
"SELECT id FROM bookings
WHERE status IN ('pending','confirmed')
AND rental_date <= ? AND end_date >= ?"
);
$conflict->execute([$endDate, $rentalDate]);
if ($conflict->fetch()) {
echo json_encode(['success'=>false,'error'=>'Sorry, that date is already booked. Please choose another date.']); exit;
}
$blockedCheck = db()->prepare("SELECT id FROM blocked_dates WHERE block_date BETWEEN ? AND ?");
$blockedCheck->execute([$rentalDate, $endDate]);
if ($blockedCheck->fetch()) {
echo json_encode(['success'=>false,'error'=>'That date is unavailable. Please choose another date.']); exit;
}
// Create booking
$ref = generateRef();
$stmt = db()->prepare(
"INSERT INTO bookings (booking_ref, name, email, phone, package, rental_date, end_date, amount, notes)
VALUES (?,?,?,?,?,?,?,?,?)"
);
$stmt->execute([$ref, $name, $email, $phone, $package, $rentalDate, $endDate, $pkg['amount'], $message]);
} finally {
db()->query("SELECT RELEASE_LOCK('" . addslashes($lockName) . "')");
}
$dateLabel = date('F j, Y', strtotime($rentalDate));
$pkgLabel = $pkg['label'];
$amountLabel = '$' . number_format($pkg['amount'], 2);
$depositLabel = '$' . number_format(DEPOSIT_AMOUNT, 2);
$balance = $pkg['amount'] - DEPOSIT_AMOUNT;
$balanceLabel = '$' . number_format($balance, 2);
// Admin email
$adminHtml = "<div style='max-width:600px;margin:0 auto;font-family:Arial,sans-serif'>
<div style='background:#f97316;padding:20px;text-align:center'>
<h1 style='color:#fff;margin:0;font-size:20px'>New Booking Request — {$ref}</h1>
</div>
<div style='padding:24px;background:#fff;border:1px solid #e5e7eb'>
<table style='width:100%;font-size:15px'>
<tr><td style='color:#6b7280;padding:8px 0;width:110px'>Ref</td><td style='padding:8px 0;font-weight:700'>{$ref}</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Name</td><td style='padding:8px 0'>" . htmlspecialchars($name) . "</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Email</td><td style='padding:8px 0'>" . htmlspecialchars($email) . "</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Phone</td><td style='padding:8px 0'>" . htmlspecialchars($phone ?: '—') . "</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Package</td><td style='padding:8px 0;font-weight:700;color:#f97316'>{$pkgLabel}{$amountLabel}</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Date</td><td style='padding:8px 0;font-weight:700'>{$dateLabel}</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Deposit Hold</td><td style='padding:8px 0'>{$depositLabel} (card held — not charged yet)</td></tr>
<tr><td style='color:#6b7280;padding:8px 0'>Balance Due</td><td style='padding:8px 0;font-weight:700;color:#16a34a'>{$balanceLabel} at pickup</td></tr>
</table>
" . ($message ? "<div style='margin-top:12px;padding:12px;background:#fff7ed;border-left:4px solid #f97316'>" . nl2br(htmlspecialchars($message)) . "</div>" : "") . "
<p style='margin-top:16px;font-size:13px;color:#9ca3af'>Submitted " . date('F j, Y g:i A') . " CT</p>
</div>
</div>";
// Customer confirmation email
$confirmHtml = "<div style='max-width:600px;margin:0 auto;font-family:Arial,sans-serif'>
<div style='background:#0d0d0d;padding:24px;text-align:center'>
<h1 style='color:#f97316;margin:0;font-size:20px'>Parker County Slingshot Rentals</h1>
</div>
<div style='padding:32px;background:#fff'>
<h2 style='margin-top:0;color:#0d0d0d'>Booking Request Received!</h2>
<p style='color:#374151'>Hey " . htmlspecialchars($name) . ", your request is in. We'll confirm availability and reach out within a few hours.</p>
<div style='background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;padding:20px;margin:20px 0'>
<p style='margin:0 0 6px;font-size:13px;color:#9ca3af;text-transform:uppercase;letter-spacing:1px'>Booking Reference</p>
<p style='margin:0 0 16px;font-size:22px;font-weight:700;color:#f97316'>{$ref}</p>
<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Package:</strong> {$pkgLabel}</p>
<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Requested Date:</strong> {$dateLabel}</p>
<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Total:</strong> {$amountLabel}</p>
<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Deposit (card hold today):</strong> {$depositLabel} <span style='font-size:12px;color:#9ca3af'>— not charged until confirmed</span></p>
<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Balance due at pickup:</strong> <span style='font-weight:700;color:#16a34a'>{$balanceLabel}</span></p>
</div>
<div style='margin:20px 0;padding:16px;background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;text-align:center'>
<p style='margin:0 0 10px;font-size:14px;font-weight:700;color:#111'>Next Step: Sign Your Rental Agreement</p>
<p style='margin:0 0 14px;font-size:13px;color:#6b7280'>Once your booking is confirmed you'll sign our digital waiver online — no printer needed. Your link:</p>
<a href='" . SITE_URL . "/waiver.php?ref={$ref}' style='display:inline-block;background:#f97316;color:#fff;text-decoration:none;padding:10px 24px;border-radius:6px;font-weight:700;font-size:14px'>Sign Rental Agreement &rarr;</a>
</div>
<p style='color:#374151'>Questions? Call or text <strong>(817) 266-2022</strong> or reply to this email.</p>
<p style='color:#374151'>Ride on,<br><strong>The Parker County Slingshot Team</strong></p>
</div>
<div style='background:#f3f4f6;padding:16px;text-align:center'>
<p style='margin:0;font-size:12px;color:#9ca3af'>&copy; " . date('Y') . " Parker County Slingshot Rentals &mdash; Weatherford, TX</p>
</div>
</div>";
// Square deposit authorization (delayed capture — hold only, not charged yet)
$depositStatus = null;
$sqId = null;
if ($squareToken) {
$sqResp = squareApi('POST', '/payments', [
'source_id' => $squareToken,
// Stable per booking ref (not time()-suffixed) so a network retry or
// accidental double-submit of this same deposit hold is recognized by
// Square as a duplicate instead of placing a second hold on the card.
'idempotency_key' => $ref . '-dep',
'amount_money' => ['amount' => (int)(DEPOSIT_AMOUNT * 100), 'currency' => 'USD'],
'autocomplete' => false, // hold only — capture when confirmed
'location_id' => SQUARE_LOCATION_ID,
'note' => "Deposit hold — booking {$ref}",
'reference_id' => $ref,
'buyer_email_address' => $email,
]);
if (!empty($sqResp['payment']['id'])) {
$sqId = $sqResp['payment']['id'];
$sqSts = $sqResp['payment']['status']; // APPROVED
db()->prepare("UPDATE bookings SET square_payment_id=?, square_payment_status=? WHERE booking_ref=?")
->execute([$sqId, $sqSts, $ref]);
$depositStatus = $sqSts;
}
}
// Add Square authorization badge to customer email if hold was placed
if ($depositStatus) {
$confirmHtml = str_replace(
"<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Deposit (card hold today):</strong> {$depositLabel}",
"<p style='margin:4px 0;font-size:14px;color:#374151'><strong>Deposit (card hold today):</strong> {$depositLabel} <span style='font-size:12px;color:#16a34a;font-weight:700'>✓ Authorized</span>",
$confirmHtml
);
}
// Add deposit note to admin email if applicable
if ($depositStatus) {
$adminHtml = str_replace(
"<p style='margin-top:16px;font-size:13px;color:#9ca3af'>",
"<p style='margin-top:8px;font-size:13px;color:#16a34a;font-weight:700'>✓ \$" . number_format(DEPOSIT_AMOUNT, 2) . " deposit hold authorized (Square — not yet captured)</p><p style='margin-top:8px;font-size:13px;color:#9ca3af'>",
$adminHtml
);
}
sendEmail(ADMIN_EMAIL, 'Parker Slingshot Admin', "New Booking {$ref}: {$name}{$pkgLabel} on {$dateLabel}", $adminHtml);
sendEmail($email, $name, "Booking Request {$ref} — Parker County Slingshot Rentals", $confirmHtml);
$msg = "Booking request received! Your reference is {$ref}. We'll be in touch shortly.";
if ($depositStatus) $msg .= " A \$" . number_format(DEPOSIT_AMOUNT, 2) . " refundable deposit hold has been placed on your card.";
echo json_encode(['success'=>true,'ref'=>$ref,'deposit_held'=>(bool)$depositStatus,'square_payment_id'=>$sqId,'message'=>$msg]);
@@ -0,0 +1,2 @@
<?php
require_once dirname(__DIR__) . "/db-secrets.php";
@@ -0,0 +1,2 @@
RewriteEngine On
RewriteRule .* - [F,L]
@@ -0,0 +1,208 @@
/*M!999999\- enable the sandbox mode */
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `admin_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `admin_tokens` (
`token` varchar(64) NOT NULL,
`expires_at` datetime NOT NULL,
`created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`token`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `blocked_dates`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `blocked_dates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`block_date` date NOT NULL,
`reason` varchar(200) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `block_date` (`block_date`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `bookings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `bookings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`booking_ref` varchar(12) NOT NULL,
`name` varchar(120) NOT NULL,
`email` varchar(180) NOT NULL,
`phone` varchar(30) DEFAULT NULL,
`package` enum('half-day','full-day','weekend') NOT NULL,
`rental_date` date NOT NULL,
`end_date` date NOT NULL,
`status` enum('pending','confirmed','completed','cancelled') NOT NULL DEFAULT 'pending',
`amount` decimal(8,2) NOT NULL,
`deposit_paid` decimal(8,2) NOT NULL DEFAULT 0.00,
`notes` text DEFAULT NULL,
`admin_notes` text DEFAULT NULL,
`waiver_signed` tinyint(1) NOT NULL DEFAULT 0,
`waiver_signed_at` datetime DEFAULT NULL,
`waiver_ip` varchar(45) DEFAULT NULL,
`waiver_name` varchar(160) DEFAULT NULL,
`waiver_sig` mediumtext DEFAULT NULL,
`insurance_verified` tinyint(1) NOT NULL DEFAULT 0,
`insurance_file` varchar(255) DEFAULT NULL,
`deposit_received` tinyint(1) NOT NULL DEFAULT 0,
`license_verified` tinyint(1) NOT NULL DEFAULT 0,
`license_file` varchar(255) DEFAULT NULL,
`helmet_provided` tinyint(1) NOT NULL DEFAULT 0,
`safety_course` tinyint(1) NOT NULL DEFAULT 0,
`operational_course` tinyint(1) NOT NULL DEFAULT 0,
`slingshot_returned` tinyint(1) NOT NULL DEFAULT 0,
`returned_at` datetime DEFAULT NULL,
`square_payment_id` varchar(64) DEFAULT NULL,
`square_payment_status` varchar(20) DEFAULT NULL,
`square_refund_id` varchar(64) DEFAULT NULL,
`square_customer_id` varchar(64) DEFAULT NULL,
`square_card_id` varchar(64) DEFAULT NULL,
`card_last4` varchar(4) DEFAULT NULL,
`card_brand` varchar(20) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
`updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `booking_ref` (`booking_ref`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_admins`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_admins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_auth_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_auth_tokens` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`token` varchar(64) NOT NULL,
`created_at` datetime DEFAULT current_timestamp(),
`expires_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`),
KEY `idx_token` (`token`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_blocked_dates`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_blocked_dates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`blocked_date` date NOT NULL,
`reason` varchar(255) DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `blocked_date` (`blocked_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_bookings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_bookings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`booking_ref` varchar(20) NOT NULL,
`customer_id` int(11) NOT NULL,
`fleet_id` int(11) DEFAULT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`days` int(11) NOT NULL,
`price_per_day` decimal(10,2) NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`deposit_amount` decimal(10,2) DEFAULT 0.00,
`status` enum('pending','approved','denied','active','completed','cancelled') DEFAULT 'pending',
`payment_status` enum('unpaid','deposit_paid','paid','refunded') DEFAULT 'unpaid',
`payment_method` enum('cash','debit','credit') DEFAULT 'cash',
`license_file` varchar(255) DEFAULT NULL,
`insurance_file` varchar(255) DEFAULT NULL,
`docs_approved` tinyint(1) DEFAULT 0,
`customer_notes` text DEFAULT NULL,
`admin_notes` text DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`square_payment_id` varchar(100) DEFAULT NULL,
`square_terminal_id` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `booking_ref` (`booking_ref`),
KEY `idx_dates` (`start_date`,`end_date`),
KEY `idx_status` (`status`),
KEY `idx_customer` (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_customers`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_customers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password_hash` varchar(255) DEFAULT NULL,
`name` varchar(150) NOT NULL,
`phone` varchar(30) DEFAULT NULL,
`dob` date DEFAULT NULL,
`address` text DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
`is_active` tinyint(1) DEFAULT 1,
`notes` text DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_fleet`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_fleet` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`color` varchar(50) DEFAULT NULL,
`year` int(11) DEFAULT NULL,
`model` varchar(100) DEFAULT NULL,
`is_available` tinyint(1) DEFAULT 1,
`notes` text DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `pcs_settings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `pcs_settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`setting_key` varchar(100) NOT NULL,
`setting_value` text DEFAULT NULL,
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `setting_key` (`setting_key`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
@@ -0,0 +1,79 @@
/*M!999999\- enable the sandbox mode */
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `admin_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `admin_tokens` (
`token` varchar(64) NOT NULL,
`expires_at` datetime NOT NULL,
`created_at` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`token`),
KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `blocked_dates`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `blocked_dates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`block_date` date NOT NULL,
`reason` varchar(200) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `block_date` (`block_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `bookings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `bookings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`booking_ref` varchar(12) NOT NULL,
`name` varchar(120) NOT NULL,
`email` varchar(180) NOT NULL,
`phone` varchar(30) DEFAULT NULL,
`package` enum('half-day','full-day','weekend') NOT NULL,
`rental_date` date NOT NULL,
`end_date` date NOT NULL,
`status` enum('pending','confirmed','completed','cancelled') NOT NULL DEFAULT 'pending',
`amount` decimal(8,2) NOT NULL,
`deposit_paid` decimal(8,2) NOT NULL DEFAULT 0.00,
`notes` text DEFAULT NULL,
`admin_notes` text DEFAULT NULL,
`waiver_signed` tinyint(1) NOT NULL DEFAULT 0,
`waiver_signed_at` datetime DEFAULT NULL,
`waiver_ip` varchar(45) DEFAULT NULL,
`waiver_name` varchar(160) DEFAULT NULL,
`waiver_sig` mediumtext DEFAULT NULL,
`insurance_verified` tinyint(1) NOT NULL DEFAULT 0,
`deposit_received` tinyint(1) NOT NULL DEFAULT 0,
`license_verified` tinyint(1) NOT NULL DEFAULT 0,
`square_payment_id` varchar(64) DEFAULT NULL,
`square_payment_status` varchar(20) DEFAULT NULL,
`square_refund_id` varchar(64) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
`updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `booking_ref` (`booking_ref`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://parkerslingshotrentals.com/sitemap.xml
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://parkerslingshotrentals.com/</loc>
<lastmod>2026-05-19</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
@@ -0,0 +1,187 @@
<?php
require_once __DIR__ . '/db.php';
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
$ref = strtoupper(trim($_GET['ref'] ?? ''));
$type = in_array($_GET['type'] ?? '', ['license','insurance']) ? $_GET['type'] : '';
$error = '';
$done = false;
$booking = null;
if ($ref && $type) {
$stmt = db()->prepare("SELECT id, name, email, booking_ref, rental_date, status FROM bookings WHERE booking_ref=?");
$stmt->execute([$ref]);
$booking = $stmt->fetch();
if (!$booking) $error = 'Booking not found. Please check your confirmation email.';
elseif ($booking['status'] === 'cancelled') $error = 'This booking has been cancelled.';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $booking && !$error) {
$file = $_FILES['doc'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
$error = 'Upload failed — please try again or check file size.';
} else {
$mime = detectAllowedMime($file['tmp_name']);
if (!$mime) {
$error = 'Only JPG, PNG, or PDF files are accepted.';
} elseif ($file['size'] > 10 * 1024 * 1024) {
$error = 'File must be under 10 MB.';
} else {
$ext = ALLOWED_DOC_MIMES[$mime];
$dir = __DIR__ . '/uploads/' . $ref;
if (!is_dir($dir)) mkdir($dir, 0750, true);
$fname = $type . '_' . date('YmdHis') . '.' . $ext;
$dest = $dir . '/' . $fname;
if (move_uploaded_file($file['tmp_name'], $dest)) {
$col = $type === 'license' ? 'license_file' : 'insurance_file';
$rel = 'uploads/' . $ref . '/' . $fname;
db()->prepare("UPDATE bookings SET {$col}=? WHERE booking_ref=?")->execute([$rel, $ref]);
$typeLabel = $type === 'license' ? "Driver's License" : 'Proof of Insurance';
$dateLabel = date('F j, Y', strtotime($booking['rental_date']));
$adminHtml = "<div style='font-family:Arial,sans-serif;max-width:560px;margin:0 auto'>
<div style='background:#f97316;padding:18px;text-align:center'>
<h1 style='color:#fff;margin:0;font-size:18px'>{$typeLabel} Uploaded {$booking['booking_ref']}</h1>
</div>
<div style='padding:24px;background:#fff;border:1px solid #e5e7eb'>
<p><strong>" . htmlspecialchars($booking['name']) . "</strong> uploaded their <strong>{$typeLabel}</strong> for booking <strong>{$booking['booking_ref']}</strong> (rental: {$dateLabel}).</p>
<p style='margin-top:12px;font-size:13px;color:#6b7280'>View it in the admin panel under their booking detail.</p>
<div style='margin-top:16px'><a href='" . SITE_URL . "/admin/' style='display:inline-block;background:#f97316;color:#fff;text-decoration:none;padding:10px 22px;border-radius:6px;font-weight:700;font-size:13px'>Open Admin Panel &rarr;</a></div>
</div>
</div>";
sendEmail(ADMIN_EMAIL, 'Parker Slingshot Admin', "{$typeLabel} Uploaded — {$booking['booking_ref']}: " . $booking['name'], $adminHtml);
$done = true;
} else {
$error = 'Could not save file. Please try again.';
}
}
}
}
$typeLabel = $type === 'license' ? "Driver's License" : ($type === 'insurance' ? 'Proof of Insurance' : '');
$dateLabel = $booking ? date('F j, Y', strtotime($booking['rental_date'])) : '';
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Upload Document Parker County Slingshot Rentals</title>
<meta name="robots" content="noindex,nofollow" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Barlow+Condensed:wght@700;800&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { --orange: #f97316; --black: #0d0d0d; }
body { font-family: 'Inter', sans-serif; background: #f3f4f6; color: #111; }
header { background: var(--black); padding: 1.25rem 2rem; display: flex; align-items: center; justify-content: space-between; }
header a { font-family: 'Barlow Condensed', sans-serif; font-size: 1.3rem; font-weight: 800; color: var(--orange); text-decoration: none; }
header span { font-size: 0.85rem; color: rgba(255,255,255,0.4); }
.wrap { max-width: 560px; margin: 2.5rem auto; padding: 0 1rem 4rem; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 2rem 2.5rem; }
@media (max-width: 560px) { .card { padding: 1.5rem; } }
h1 { font-family: 'Barlow Condensed', sans-serif; font-size: 1.9rem; font-weight: 800; margin-bottom: 0.25rem; }
.booking-badge { display: inline-block; background: #fff7ed; border: 1px solid #fed7aa; border-radius: 8px; padding: 0.6rem 1rem; margin: 1rem 0 1.5rem; }
.booking-badge .ref { font-size: 1.1rem; font-weight: 700; color: var(--orange); }
.booking-badge .meta { font-size: 0.82rem; color: #6b7280; margin-top: 2px; }
.upload-area { border: 2px dashed #d1d5db; border-radius: 10px; padding: 2rem; text-align: center; cursor: pointer; transition: border-color .2s, background .2s; background: #fafafa; position: relative; margin: 1rem 0; }
.upload-area:hover, .upload-area.drag { border-color: var(--orange); background: #fff7ed; }
.upload-area input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.upload-icon { font-size: 2.5rem; margin-bottom: 0.5rem; }
.upload-area p { font-size: 0.9rem; color: #6b7280; margin: 0; }
.upload-area .file-name { font-size: 0.88rem; color: var(--orange); font-weight: 600; margin-top: 0.5rem; display: none; }
.btn { display: block; width: 100%; background: var(--orange); color: #fff; border: none; border-radius: 8px; padding: 0.9rem; font-size: 1rem; font-weight: 700; cursor: pointer; transition: background .2s; margin-top: 1rem; }
.btn:hover { background: #ea580c; }
.btn:disabled { background: #d1d5db; cursor: not-allowed; }
.alert { padding: 0.85rem 1rem; border-radius: 8px; font-size: 0.9rem; margin-bottom: 1.25rem; }
.alert-error { background: rgba(239,68,68,.08); border: 1px solid rgba(239,68,68,.25); color: #dc2626; }
.success-icon { font-size: 3rem; text-align: center; margin-bottom: 1rem; }
.success-box { text-align: center; padding: .5rem 0; }
.success-box h1 { color: #16a34a; margin-bottom: 0.5rem; }
.success-box p { color: #374151; font-size: 0.95rem; max-width: 400px; margin: 0 auto .75rem; }
.hint { font-size: 0.8rem; color: #9ca3af; margin-top: 0.5rem; text-align: center; }
</style>
</head>
<body>
<header>
<a href="/">Parker County Slingshot Rentals</a>
<span>Document Upload</span>
</header>
<div class="wrap">
<?php if (!$ref || !$type || (!$booking && !$error)): ?>
<div class="card">
<h1>Upload Document</h1>
<p style="color:#6b7280;margin-top:.5rem">Invalid or missing upload link. Please use the link from your email or contact us.</p>
</div>
<?php elseif ($error && !$booking): ?>
<div class="card">
<div class="alert alert-error"><?= htmlspecialchars($error) ?></div>
<p style="color:#6b7280;font-size:.9rem">Need help? Call or text <strong>(817) 266-2022</strong>.</p>
</div>
<?php elseif ($done): ?>
<div class="card">
<div class="success-icon"></div>
<div class="success-box">
<h1>Upload Received!</h1>
<p>Thanks, <?= htmlspecialchars($booking['name']) ?>! Your <strong><?= htmlspecialchars($typeLabel) ?></strong> has been submitted for booking <strong><?= htmlspecialchars($booking['booking_ref']) ?></strong>.</p>
<p style="color:#6b7280;font-size:.85rem">We'll review it and still do a quick visual check at pickup. See you on <?= htmlspecialchars($dateLabel) ?>!</p>
</div>
</div>
<?php else: ?>
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<div class="card">
<h1>Upload <?= htmlspecialchars($typeLabel) ?></h1>
<div class="booking-badge">
<div class="ref"><?= htmlspecialchars($booking['booking_ref']) ?></div>
<div class="meta"><?= htmlspecialchars($booking['name']) ?> &mdash; <?= htmlspecialchars($dateLabel) ?></div>
</div>
<p style="color:#374151;font-size:.9rem;margin-bottom:.25rem">
<?php if ($type === 'insurance'): ?>
Please upload a photo or scan of your current auto insurance card. JPG, PNG, or PDF accepted (max 10 MB).
<?php else: ?>
Please upload a photo or scan of the front of your driver's license. JPG, PNG, or PDF accepted (max 10 MB).
<?php endif; ?>
</p>
<p style="color:#9ca3af;font-size:.8rem;margin-bottom:1rem">We'll still do a visual check at pickup this is just for our records.</p>
<form method="post" enctype="multipart/form-data" id="uploadForm">
<div class="upload-area" id="dropZone">
<input type="file" name="doc" id="docInput" accept=".jpg,.jpeg,.png,.pdf" required />
<div class="upload-icon">📎</div>
<p>Tap or drag your file here</p>
<p style="font-size:.78rem;margin-top:4px">JPG &bull; PNG &bull; PDF &bull; max 10 MB</p>
<div class="file-name" id="fileName"></div>
</div>
<button type="submit" class="btn" id="submitBtn">Upload <?= htmlspecialchars($typeLabel) ?></button>
</form>
<p class="hint">Your document is stored securely and only visible to Parker County Slingshot Rentals staff.</p>
</div>
<?php endif; ?>
</div>
<script>
(function(){
const input = document.getElementById('docInput');
const label = document.getElementById('fileName');
const zone = document.getElementById('dropZone');
const btn = document.getElementById('submitBtn');
if (!input) return;
input.addEventListener('change', function() {
if (this.files[0]) {
label.textContent = this.files[0].name;
label.style.display = 'block';
}
});
['dragover','dragenter'].forEach(e => zone.addEventListener(e, ev => { ev.preventDefault(); zone.classList.add('drag'); }));
['dragleave','drop'].forEach(e => zone.addEventListener(e, ev => zone.classList.remove('drag')));
document.getElementById('uploadForm')?.addEventListener('submit', function() {
if (btn) { btn.disabled = true; btn.textContent = 'Uploading…'; }
});
})();
</script>
</body>
</html>
@@ -0,0 +1,5 @@
# Block direct web access - documents served only through view-doc.php / admin/view-doc.php.
# Require all denied / Order deny,allow do NOT work on this OpenLiteSpeed setup -
# only RewriteRule [F,L] actually blocks requests here (confirmed this session).
RewriteEngine On
RewriteRule .* - [F,L]
@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/db.php';
$token = preg_replace('/[^a-f0-9]/', '', $_GET['_t'] ?? '');
if (!verifyAdminToken($token)) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Unauthorized — please log in to the admin panel first.');
}
$ref = strtoupper(preg_replace('/[^A-Z0-9\-]/', '', $_GET['ref'] ?? ''));
$type = in_array($_GET['type'] ?? '', ['license','insurance']) ? $_GET['type'] : '';
if (!$ref || !$type) {
http_response_code(400);
header('Content-Type: text/plain');
exit('Missing parameters.');
}
$col = $type === 'license' ? 'license_file' : 'insurance_file';
$stmt = db()->prepare("SELECT {$col} AS file_path FROM bookings WHERE booking_ref=?");
$stmt->execute([$ref]);
$row = $stmt->fetch();
if (!$row || !$row['file_path']) {
http_response_code(404);
header('Content-Type: text/plain');
exit('Document not found.');
}
$path = resolveUploadPath(__DIR__, $row['file_path']);
if (!$path) {
http_response_code(404);
header('Content-Type: text/plain');
exit('File not found.');
}
$mime = detectAllowedMime($path);
if (!$mime) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Invalid file type.');
}
$fname = $type . '-' . $ref . '.' . ALLOWED_DOC_MIMES[$mime];
header('Content-Type: ' . $mime);
header('Content-Disposition: inline; filename="' . $fname . '"');
header('Content-Length: ' . filesize($path));
header('Cache-Control: private, max-age=3600');
readfile($path);
exit;
@@ -0,0 +1,365 @@
<?php
require_once __DIR__ . '/db.php';
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
$ref = strtoupper(trim($_GET['ref'] ?? ''));
$error = '';
$booking = null;
$signed = false;
if ($ref) {
$stmt = db()->prepare("SELECT * FROM bookings WHERE booking_ref = ?");
$stmt->execute([$ref]);
$booking = $stmt->fetch();
if (!$booking) {
$error = 'Booking reference not found. Please check your confirmation email.';
} elseif ($booking['status'] === 'cancelled') {
$error = 'This booking has been cancelled. Please contact us if you have questions.';
} elseif ($booking['waiver_signed']) {
$signed = true;
}
}
// Handle submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $booking && !$signed) {
$sigName = trim(strip_tags($_POST['sig_name'] ?? ''));
$sigData = $_POST['sig_data'] ?? ''; // base64 canvas PNG
$checks = (array)($_POST['checks'] ?? []);
$required = ['age','license','insurance','rules','damage','waiver'];
$missing = array_diff($required, $checks);
if (!$sigName) {
$error = 'Please type your full name to sign.';
} elseif ($missing) {
$error = 'Please check all required boxes before signing.';
} elseif (!$sigData || !preg_match('#^data:image/png;base64,[A-Za-z0-9+/]+={0,2}$#', $sigData) || strlen($sigData) > 300000) {
$error = 'Please draw your signature in the box above.';
} else {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
$ip = explode(',', $ip)[0];
db()->prepare(
"UPDATE bookings SET waiver_signed=1, waiver_signed_at=NOW(), waiver_ip=?, waiver_name=?, waiver_sig=? WHERE booking_ref=?"
)->execute([trim($ip), $sigName, $sigData, $ref]);
$pkg = PACKAGES[$booking['package']] ?? ['label'=>$booking['package']];
$dateLabel = date('F j, Y', strtotime($booking['rental_date']));
$adminHtml = "<div style='font-family:Arial,sans-serif;max-width:600px;margin:0 auto'>
<div style='background:#f97316;padding:20px;text-align:center'>
<h1 style='color:#fff;margin:0;font-size:18px'>Waiver Signed {$ref}</h1>
</div>
<div style='padding:24px;background:#fff;border:1px solid #e5e7eb'>
<p><strong>" . htmlspecialchars($booking['name']) . "</strong> signed the rental waiver for booking <strong>{$ref}</strong>.</p>
<table style='width:100%;font-size:14px'>
<tr><td style='color:#6b7280;padding:6px 0;width:110px'>Package</td><td style='padding:6px 0'>" . htmlspecialchars($pkg['label']) . "</td></tr>
<tr><td style='color:#6b7280;padding:6px 0'>Date</td><td style='padding:6px 0'>{$dateLabel}</td></tr>
<tr><td style='color:#6b7280;padding:6px 0'>Signed by</td><td style='padding:6px 0'>" . htmlspecialchars($sigName) . "</td></tr>
<tr><td style='color:#6b7280;padding:6px 0'>IP</td><td style='padding:6px 0'>" . htmlspecialchars($ip) . "</td></tr>
<tr><td style='color:#6b7280;padding:6px 0'>Timestamp</td><td style='padding:6px 0'>" . date('F j, Y g:i A') . " CT</td></tr>
</table>
<img src='" . htmlspecialchars($sigData) . "' style='margin-top:16px;border:1px solid #e5e7eb;border-radius:6px;max-width:100%;height:auto' alt='Signature' />
</div>
</div>";
$custHtml = "<div style='font-family:Arial,sans-serif;max-width:600px;margin:0 auto'>
<div style='background:#0d0d0d;padding:24px;text-align:center'>
<h1 style='color:#f97316;margin:0;font-size:20px'>Parker County Slingshot Rentals</h1>
</div>
<div style='padding:32px;background:#fff'>
<h2 style='margin-top:0'>Waiver Signed You're All Set!</h2>
<p style='color:#374151'>Hey " . htmlspecialchars($booking['name']) . ", your rental agreement for booking <strong>{$ref}</strong> is signed and on file. See you on <strong>{$dateLabel}</strong>!</p>
<p style='color:#374151'>Remember to bring:</p>
<ul style='color:#374151'>
<li>Valid driver's license</li>
<li>Proof of personal auto insurance</li>
</ul>
<p style='color:#374151'>Questions? Call or text <strong>" . ADMIN_PHONE . "</strong>.</p>
<p style='color:#374151'>Ride on,<br><strong>The Parker County Slingshot Team</strong></p>
</div>
<div style='background:#f3f4f6;padding:16px;text-align:center'>
<p style='margin:0;font-size:12px;color:#9ca3af'>&copy; " . date('Y') . " Parker County Slingshot Rentals &mdash; Weatherford, TX</p>
</div>
</div>";
sendEmail(ADMIN_EMAIL, 'Parker Slingshot Admin', "Waiver Signed: {$ref}" . $booking['name'], $adminHtml);
sendEmail($booking['email'], $booking['name'], "Rental Agreement Signed — {$ref}", $custHtml);
$signed = true;
}
}
$pkgLabel = $booking ? (PACKAGES[$booking['package']]['label'] ?? $booking['package']) : '';
$dateLabel = $booking ? date('F j, Y', strtotime($booking['rental_date'])) : '';
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rental Agreement Parker County Slingshot Rentals</title>
<meta name="robots" content="noindex,nofollow" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Barlow+Condensed:wght@700;800&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { --orange: #f97316; --orange-dark: #ea580c; --black: #0d0d0d; }
body { font-family: 'Inter', sans-serif; background: #f3f4f6; color: #111; line-height: 1.6; }
header { background: var(--black); padding: 1.25rem 2rem; display: flex; align-items: center; justify-content: space-between; }
header a { font-family: 'Barlow Condensed', sans-serif; font-size: 1.3rem; font-weight: 800; color: var(--orange); text-decoration: none; }
header span { font-size: 0.85rem; color: rgba(255,255,255,0.4); }
.wrap { max-width: 760px; margin: 2.5rem auto; padding: 0 1rem 4rem; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 2rem 2.5rem; margin-bottom: 1.5rem; }
@media (max-width: 600px) { .card { padding: 1.5rem; } }
h1 { font-family: 'Barlow Condensed', sans-serif; font-size: 2rem; font-weight: 800; margin-bottom: 0.25rem; }
h2 { font-size: 1rem; font-weight: 700; color: var(--orange); text-transform: uppercase; letter-spacing: 1px; margin: 1.75rem 0 0.75rem; }
p { color: #374151; font-size: 0.95rem; margin-bottom: 0.75rem; }
.booking-banner { background: #fff7ed; border: 1px solid #fed7aa; border-radius: 10px; padding: 1.25rem 1.5rem; margin-bottom: 1.75rem; display: flex; gap: 2rem; flex-wrap: wrap; }
.bb-item { font-size: 0.9rem; }
.bb-label { color: #9ca3af; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 0.2rem; }
.bb-value { font-weight: 700; color: #111; }
.bb-ref { color: var(--orange); font-size: 1.1rem; }
.clause { display: flex; gap: 0.85rem; align-items: flex-start; padding: 0.85rem 0; border-bottom: 1px solid #f3f4f6; }
.clause:last-child { border-bottom: none; }
.clause input[type=checkbox] { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--orange); flex-shrink: 0; cursor: pointer; }
.clause label { font-size: 0.92rem; color: #374151; cursor: pointer; }
.clause label strong { color: #111; }
.sig-wrap { border: 2px dashed #d1d5db; border-radius: 8px; overflow: hidden; position: relative; background: #fafafa; margin-top: 0.5rem; }
#sigCanvas { display: block; width: 100%; height: 160px; cursor: crosshair; touch-action: none; }
.sig-clear { position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.06); border: none; border-radius: 6px; padding: 4px 10px; font-size: 0.78rem; cursor: pointer; color: #6b7280; }
.sig-clear:hover { background: rgba(239,68,68,0.1); color: #ef4444; }
.sig-hint { font-size: 0.78rem; color: #9ca3af; margin-top: 0.4rem; }
input[type=text] { width: 100%; border: 1px solid #d1d5db; border-radius: 8px; padding: 0.75rem 1rem; font-size: 1rem; font-family: inherit; outline: none; transition: border-color 0.2s; margin-top: 0.4rem; }
input[type=text]:focus { border-color: var(--orange); }
.btn-sign { display: block; width: 100%; background: var(--orange); color: white; border: none; border-radius: 8px; padding: 1rem; font-size: 1.05rem; font-weight: 700; cursor: pointer; transition: background 0.2s; margin-top: 1.5rem; }
.btn-sign:hover { background: var(--orange-dark); }
.btn-sign:disabled { background: #d1d5db; cursor: not-allowed; }
.alert { padding: 0.9rem 1.1rem; border-radius: 8px; font-size: 0.9rem; margin-bottom: 1rem; }
.alert-error { background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.25); color: #dc2626; }
.alert-success { background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.25); color: #16a34a; }
.success-icon { font-size: 3rem; text-align: center; margin-bottom: 1rem; }
.success-box { text-align: center; padding: 1rem 0; }
.success-box h1 { color: #16a34a; margin-bottom: 0.5rem; }
.success-box p { color: #374151; max-width: 480px; margin: 0 auto 1rem; }
.checklist { text-align: left; display: inline-block; margin: 1rem auto; }
.checklist li { padding: 0.35rem 0; color: #374151; font-size: 0.95rem; list-style: none; }
.checklist li::before { content: '✓ '; color: #16a34a; font-weight: 700; }
.back-link { text-align: center; margin-top: 1.5rem; }
.back-link a { color: var(--orange); text-decoration: none; font-weight: 600; font-size: 0.9rem; }
</style>
</head>
<body>
<header>
<a href="/">Parker County Slingshot Rentals</a>
<span>Rental Agreement</span>
</header>
<div class="wrap">
<?php if (!$ref || $error === 'Booking reference not found. Please check your confirmation email.'): ?>
<!-- No valid ref show lookup form -->
<div class="card">
<h1>Rental Agreement</h1>
<p style="margin:0.75rem 0 1.5rem;color:#6b7280;">Enter your booking reference from your confirmation email to access your rental agreement.</p>
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="get" action="/waiver.php">
<label style="font-weight:600;font-size:0.9rem;display:block;margin-bottom:0.4rem;">Booking Reference</label>
<input type="text" name="ref" placeholder="PSR-XXXXXX" value="<?= htmlspecialchars($ref) ?>" style="text-transform:uppercase" maxlength="12" required />
<button type="submit" class="btn-sign" style="margin-top:1rem;">Look Up My Booking</button>
</form>
</div>
<?php elseif ($signed): ?>
<!-- Already signed or just signed -->
<div class="card">
<div class="success-icon"></div>
<div class="success-box">
<h1>You're All Set!</h1>
<p>Your rental agreement for booking <strong><?= htmlspecialchars($ref) ?></strong> is signed and on file. We'll see you on <strong><?= htmlspecialchars($dateLabel) ?></strong>!</p>
<p style="color:#6b7280;font-size:0.88rem;">A confirmation was sent to <?= htmlspecialchars($booking['email']) ?>.</p>
<p style="font-weight:700;margin-top:1rem;margin-bottom:0.25rem;">Remember to bring:</p>
<ul class="checklist">
<li>Valid driver's license</li>
<li>Proof of personal auto insurance</li>
</ul>
</div>
<div class="back-link"><a href="/"> Back to parkerslingshotrentals.com</a></div>
</div>
<?php elseif ($booking): ?>
<!-- Waiver form -->
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<div class="card">
<h1>Rental Agreement</h1>
<p style="color:#6b7280;margin-top:0.25rem;margin-bottom:1.5rem;">Please read and sign the agreement below for your upcoming rental.</p>
<div class="booking-banner">
<div class="bb-item"><div class="bb-label">Booking Ref</div><div class="bb-value bb-ref"><?= htmlspecialchars($booking['booking_ref']) ?></div></div>
<div class="bb-item"><div class="bb-label">Name</div><div class="bb-value"><?= htmlspecialchars($booking['name']) ?></div></div>
<div class="bb-item"><div class="bb-label">Package</div><div class="bb-value"><?= htmlspecialchars($pkgLabel) ?></div></div>
<div class="bb-item"><div class="bb-label">Rental Date</div><div class="bb-value"><?= htmlspecialchars($dateLabel) ?></div></div>
</div>
<h2>Rental Terms & Conditions</h2>
<p>This Rental Agreement ("Agreement") is entered into between Parker County Slingshot Rentals ("Company") and the renter identified above ("Renter"). By signing below, Renter agrees to all terms stated herein.</p>
<h2>Eligibility Requirements</h2>
<p>Renter must be at least 25 years of age and hold a valid Class C driver's license (or equivalent) issued by a U.S. state or territory. Renter must not have any DUI/DWI convictions within the past 5 years.</p>
<h2>Insurance Requirement</h2>
<p>Renter is required to carry and provide proof of valid personal auto insurance at the time of vehicle pickup. The Company maintains a fleet insurance policy covering the vehicle; however, Renter's personal insurance is primary for liability arising from Renter's operation of the vehicle. Renter accepts financial responsibility for any deductible, damages, or losses not covered by either policy.</p>
<h2>Vehicle Use & Rules</h2>
<p>Renter agrees to operate the Polaris Slingshot in a safe, lawful manner and specifically agrees to:</p>
<ul style="color:#374151;font-size:0.92rem;padding-left:1.25rem;margin-bottom:0.75rem;">
<li style="margin-bottom:0.35rem;">Obey all applicable traffic laws and speed limits</li>
<li style="margin-bottom:0.35rem;">Never operate the vehicle under the influence of alcohol, drugs, or any impairing substance</li>
<li style="margin-bottom:0.35rem;">Never allow an unauthorized third party to operate the vehicle</li>
<li style="margin-bottom:0.35rem;">Wear the provided DOT-approved helmet at all times while operating the vehicle</li>
<li style="margin-bottom:0.35rem;">Not take the vehicle off paved roads or outside the approved driving area</li>
<li>Return the vehicle at the agreed-upon time and location in the same condition it was received</li>
</ul>
<h2>Damage & Security Deposit</h2>
<p>A refundable security deposit is required at pickup. Renter is financially responsible for any damage to the vehicle, including but not limited to collision damage, tire damage, interior damage, and any fines or citations incurred during the rental period. The Company reserves the right to apply the security deposit toward any such costs.</p>
<h2>Assumption of Risk & Release of Liability</h2>
<p>Renter acknowledges that operating a Polaris Slingshot involves inherent risks including, but not limited to, physical injury or death. Renter voluntarily assumes all such risks and, to the fullest extent permitted by Texas law, releases and holds harmless Parker County Slingshot Rentals, its owners, employees, and agents from any and all claims, damages, or liability arising out of Renter's use of the vehicle.</p>
<h2>Cancellation Policy</h2>
<p>Cancellations made more than 24 hours before the rental start time are fully refunded. Cancellations within 24 hours of the rental start time are subject to a 50% cancellation fee.</p>
</div>
<form method="post" action="/waiver.php?ref=<?= urlencode($ref) ?>" id="waiverForm">
<input type="hidden" name="sig_data" id="sigDataInput" />
<div class="card">
<h2 style="margin-top:0">Acknowledgments</h2>
<p style="margin-bottom:1rem;color:#6b7280;font-size:0.88rem;">Check each box to confirm you have read and agree to that section.</p>
<div class="clause">
<input type="checkbox" name="checks[]" value="age" id="ck_age" required />
<label for="ck_age"><strong>Age & License:</strong> I confirm I am 25 years of age or older and hold a valid driver's license.</label>
</div>
<div class="clause">
<input type="checkbox" name="checks[]" value="insurance" id="ck_ins" required />
<label for="ck_ins"><strong>Insurance:</strong> I will provide proof of valid personal auto insurance at pickup and understand it is required.</label>
</div>
<div class="clause">
<input type="checkbox" name="checks[]" value="rules" id="ck_rules" required />
<label for="ck_rules"><strong>Vehicle Rules:</strong> I agree to operate the Slingshot safely, lawfully, and sober, and to wear the provided helmet at all times.</label>
</div>
<div class="clause">
<input type="checkbox" name="checks[]" value="damage" id="ck_dmg" required />
<label for="ck_dmg"><strong>Damage Responsibility:</strong> I understand I am financially responsible for any damage or fines incurred during my rental period.</label>
</div>
<div class="clause">
<input type="checkbox" name="checks[]" value="license" id="ck_lic" required />
<label for="ck_lic"><strong>License Verification:</strong> I consent to Parker County Slingshot Rentals verifying my driver's license at pickup.</label>
</div>
<div class="clause">
<input type="checkbox" name="checks[]" value="waiver" id="ck_waiver" required />
<label for="ck_waiver"><strong>Assumption of Risk:</strong> I have read and understand the Assumption of Risk and Release of Liability section and agree to its terms.</label>
</div>
</div>
<div class="card">
<h2 style="margin-top:0">Your Signature</h2>
<label style="font-weight:600;font-size:0.9rem;display:block;margin-bottom:0.2rem;">Full Legal Name</label>
<input type="text" name="sig_name" id="sigName" placeholder="Type your full name as it appears on your license" required />
<p style="margin:1.25rem 0 0.4rem;font-weight:600;font-size:0.9rem;">Draw Your Signature</p>
<div class="sig-wrap">
<canvas id="sigCanvas"></canvas>
<button type="button" class="sig-clear" id="clearBtn">Clear</button>
</div>
<p class="sig-hint">Use your mouse or finger to sign in the box above.</p>
<p style="margin-top:1.5rem;font-size:0.82rem;color:#9ca3af;">
By clicking "Sign &amp; Submit" below, you confirm that you have read this entire Rental Agreement, that all acknowledgments above are checked, and that your typed name and drawn signature constitute a legally binding electronic signature under the ESIGN Act and Texas law. Signed: <span id="sigDateDisplay"><?= date('F j, Y') ?></span>.
</p>
<button type="submit" class="btn-sign" id="submitBtn">Sign &amp; Submit Rental Agreement</button>
</div>
</form>
<?php endif; ?>
</div><!-- /.wrap -->
<script>
(function() {
const canvas = document.getElementById('sigCanvas');
if (!canvas) return;
// Size canvas to its CSS width
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * window.devicePixelRatio;
canvas.height = rect.height * window.devicePixelRatio;
canvas.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio);
}
resizeCanvas();
const ctx = canvas.getContext('2d');
let drawing = false;
let hasDrawn = false;
ctx.strokeStyle = '#111';
ctx.lineWidth = 2.2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
function getPos(e) {
const r = canvas.getBoundingClientRect();
const src = e.touches ? e.touches[0] : e;
return { x: src.clientX - r.left, y: src.clientY - r.top };
}
function startDraw(e) {
e.preventDefault();
drawing = true;
const p = getPos(e);
ctx.beginPath();
ctx.moveTo(p.x, p.y);
}
function draw(e) {
if (!drawing) return;
e.preventDefault();
const p = getPos(e);
ctx.lineTo(p.x, p.y);
ctx.stroke();
hasDrawn = true;
}
function endDraw() { drawing = false; }
canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDraw);
canvas.addEventListener('mouseleave', endDraw);
canvas.addEventListener('touchstart', startDraw, { passive: false });
canvas.addEventListener('touchmove', draw, { passive: false });
canvas.addEventListener('touchend', endDraw);
document.getElementById('clearBtn').addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hasDrawn = false;
document.getElementById('sigDataInput').value = '';
});
document.getElementById('waiverForm').addEventListener('submit', function(e) {
if (!hasDrawn) {
e.preventDefault();
alert('Please draw your signature before submitting.');
canvas.style.borderColor = '#ef4444';
return;
}
// Export canvas to base64 PNG
document.getElementById('sigDataInput').value = canvas.toDataURL('image/png');
});
})();
</script>
</body>
</html>