mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-07-29 17:42:34 -05:00
Compare commits
4 Commits
13c0fd6e4c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a8f3a9085b | |||
| 671df18039 | |||
| 3bf7fe7620 | |||
| ffebe87210 |
@@ -7,3 +7,15 @@ 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]
|
||||
|
||||
+19
-2
@@ -39,7 +39,18 @@ $pkg = PACKAGES[$package];
|
||||
$rentalDate = $date;
|
||||
$endDate = date('Y-m-d', strtotime($date . ' +' . $pkg['days'] . ' days'));
|
||||
|
||||
// Check availability
|
||||
// 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')
|
||||
@@ -62,6 +73,9 @@ $stmt = db()->prepare(
|
||||
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'];
|
||||
@@ -127,7 +141,10 @@ $sqId = null;
|
||||
if ($squareToken) {
|
||||
$sqResp = squareApi('POST', '/payments', [
|
||||
'source_id' => $squareToken,
|
||||
'idempotency_key' => $ref . '-dep-' . time(),
|
||||
// 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,
|
||||
|
||||
@@ -1,116 +1,2 @@
|
||||
<?php
|
||||
define('SITE_URL', 'https://www.parkerslingshotrentals.com');
|
||||
|
||||
define('PARKER_DB_HOST', 'localhost');
|
||||
define('PARKER_DB_NAME', 'park_slingshot');
|
||||
define('PARKER_DB_USER', 'park_slingshotuser');
|
||||
define('PARKER_DB_PASS', '4@rxg*8kovxCr7w6');
|
||||
|
||||
define('ADMIN_USER', 'admin');
|
||||
define('ADMIN_PHONE', '(817) 266-2022');
|
||||
define('ADMIN_PASS', '$2y$10$ynnk3RfarOD7VIJizC30kuXqu6tQ3gotNrlp5y33afh5fPOgnAMU6');
|
||||
define('ADMIN_SESSION_KEY', 'parker_admin_auth');
|
||||
|
||||
define('CYBERMAIL_API_KEY', 'sk_live_7f9b0f9a29f6de31a0d229d4af75d56b094ad724fc58a57d');
|
||||
define('MAIL_FROM', 'noreply@orbishosting.com');
|
||||
define('MAIL_FROM_NAME', 'Parker County Slingshot Rentals');
|
||||
define('ADMIN_EMAIL', 'info@parkerslingshotrentals.com');
|
||||
|
||||
define('SQUARE_ACCESS_TOKEN', 'EAAAl3FsAu_2ri8kZE_ENEyi2T_C8HXXm5XQFY6Lbnd8SX6FqYp8J_upUeXNYh7v');
|
||||
define('SQUARE_APP_ID', 'sq0idp-YSM7BU9IVyOWSzpeP-0nzQ');
|
||||
define('SQUARE_LOCATION_ID', 'L8GZYHYKE95CE');
|
||||
define('SQUARE_VERSION', '2024-01-18');
|
||||
define('DEPOSIT_AMOUNT', 45.00); // $45 deposit hold — balance due at pickup
|
||||
|
||||
define('PACKAGES', [
|
||||
'half-day' => ['label' => 'Half Day (4 hrs)', 'amount' => 99.00, 'days' => 0],
|
||||
'full-day' => ['label' => 'Full Day (8 hrs)', 'amount' => 169.00, 'days' => 0],
|
||||
'weekend' => ['label' => 'Weekend (48 hrs)', 'amount' => 299.00, 'days' => 1],
|
||||
]);
|
||||
|
||||
function db(): PDO {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO(
|
||||
'mysql:host=' . PARKER_DB_HOST . ';dbname=' . PARKER_DB_NAME . ';charset=utf8mb4',
|
||||
PARKER_DB_USER, PARKER_DB_PASS,
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function squareApi(string $method, string $path, array $body = []): array {
|
||||
$ch = curl_init('https://connect.squareup.com/v2' . $path);
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . SQUARE_ACCESS_TOKEN,
|
||||
'Content-Type: application/json',
|
||||
'Square-Version: ' . SQUARE_VERSION,
|
||||
];
|
||||
$opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30];
|
||||
if ($method === 'POST') {
|
||||
$opts[CURLOPT_POST] = true;
|
||||
$opts[CURLOPT_POSTFIELDS] = $body ? json_encode($body) : '{}';
|
||||
}
|
||||
curl_setopt_array($ch, $opts);
|
||||
$resp = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return json_decode($resp ?: '{}', true);
|
||||
}
|
||||
|
||||
function generateRef(): string {
|
||||
// 4 random bytes -> 8 hex chars; 'PSR-' + 8 chars = 12, matches booking_ref varchar(12).
|
||||
return 'PSR-' . strtoupper(bin2hex(random_bytes(4)));
|
||||
}
|
||||
|
||||
// Shared by view-doc.php and admin/view-doc.php so the admin-token check and
|
||||
// path-traversal/mime guards live in exactly one place.
|
||||
function verifyAdminToken(string $token): bool {
|
||||
if (!preg_match('/^[a-f0-9]{64}$/', $token)) return false;
|
||||
$stmt = db()->prepare("SELECT token FROM admin_tokens WHERE token=? AND expires_at > NOW()");
|
||||
$stmt->execute([$token]);
|
||||
return (bool)$stmt->fetch();
|
||||
}
|
||||
|
||||
const ALLOWED_DOC_MIMES = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf'];
|
||||
|
||||
function resolveUploadPath(string $root, string $relativePath): ?string {
|
||||
$base = realpath($root . '/uploads');
|
||||
$path = realpath($root . '/' . $relativePath);
|
||||
if (!$path || !$base || !str_starts_with($path, $base . DIRECTORY_SEPARATOR)) {
|
||||
return null;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
function detectAllowedMime(string $path): ?string {
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($path);
|
||||
return isset(ALLOWED_DOC_MIMES[$mime]) ? $mime : null;
|
||||
}
|
||||
|
||||
function sendEmail(string $to, string $toName, string $subject, string $html): bool {
|
||||
$apiKey = defined('CYBERMAIL_API_KEY') ? CYBERMAIL_API_KEY : '';
|
||||
if (!$apiKey || strpos($apiKey, 'YOUR_KEY') !== false) return false;
|
||||
$payload = json_encode([
|
||||
'from' => MAIL_FROM,
|
||||
'to' => $to,
|
||||
'subject' => $subject,
|
||||
'html' => $html,
|
||||
]);
|
||||
$ch = curl_init('https://platform.cyberpersons.com/email/v1/send');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return $code === 202;
|
||||
}
|
||||
require_once dirname(__DIR__) . "/db-secrets.php";
|
||||
|
||||
+1
-1
@@ -1104,7 +1104,7 @@
|
||||
const cardEl = document.getElementById('card-container');
|
||||
if (cardEl) cardEl.style.display = '';
|
||||
try {
|
||||
const payments = Square.payments('sq0idp-YSM7BU9IVyOWSzpeP-0nzQ', 'L8GZYHYKE95CE');
|
||||
const payments = Square.payments('sq0idp-UnXYsN5U7ba8F8C9LfY5ZA', 'LW7MNGFP44QZZ');
|
||||
squareCard = await payments.card({
|
||||
style: {
|
||||
'.input-container': { borderColor: 'rgba(255,255,255,0.12)', borderRadius: '6px' },
|
||||
|
||||
+5
-8
@@ -1,8 +1,5 @@
|
||||
# Block direct web access — documents served only through admin/view-doc.php
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
# 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]
|
||||
|
||||
Reference in New Issue
Block a user