Store: enforce configurable timezone for store timestamps

This commit is contained in:
AudioCore Bot
2026-03-05 15:27:58 +00:00
parent 20a8582928
commit 93e829bd19
2 changed files with 57 additions and 3 deletions

View File

@@ -19,6 +19,7 @@ class StoreController
public function __construct() public function __construct()
{ {
$this->applyStoreTimezone();
$this->view = new View(__DIR__ . '/views'); $this->view = new View(__DIR__ . '/views');
} }
@@ -171,6 +172,8 @@ class StoreController
$expiryDays = max(1, (int)$expiryDaysRaw); $expiryDays = max(1, (int)$expiryDaysRaw);
$orderPrefixRaw = array_key_exists('store_order_prefix', $_POST) ? (string)$_POST['store_order_prefix'] : (string)($current['store_order_prefix'] ?? 'AC-ORD'); $orderPrefixRaw = array_key_exists('store_order_prefix', $_POST) ? (string)$_POST['store_order_prefix'] : (string)($current['store_order_prefix'] ?? 'AC-ORD');
$orderPrefix = $this->sanitizeOrderPrefix($orderPrefixRaw); $orderPrefix = $this->sanitizeOrderPrefix($orderPrefixRaw);
$timezoneRaw = array_key_exists('store_timezone', $_POST) ? (string)$_POST['store_timezone'] : (string)($current['store_timezone'] ?? 'UTC');
$timezone = $this->normalizeTimezone($timezoneRaw);
$testMode = array_key_exists('store_test_mode', $_POST) ? ((string)$_POST['store_test_mode'] === '1' ? '1' : '0') : (string)($current['store_test_mode'] ?? '1'); $testMode = array_key_exists('store_test_mode', $_POST) ? ((string)$_POST['store_test_mode'] === '1' ? '1' : '0') : (string)($current['store_test_mode'] ?? '1');
$stripeEnabled = array_key_exists('store_stripe_enabled', $_POST) ? ((string)$_POST['store_stripe_enabled'] === '1' ? '1' : '0') : (string)($current['store_stripe_enabled'] ?? '0'); $stripeEnabled = array_key_exists('store_stripe_enabled', $_POST) ? ((string)$_POST['store_stripe_enabled'] === '1' ? '1' : '0') : (string)($current['store_stripe_enabled'] ?? '0');
$stripePublic = trim((string)(array_key_exists('store_stripe_public_key', $_POST) ? $_POST['store_stripe_public_key'] : ($current['store_stripe_public_key'] ?? ''))); $stripePublic = trim((string)(array_key_exists('store_stripe_public_key', $_POST) ? $_POST['store_stripe_public_key'] : ($current['store_stripe_public_key'] ?? '')));
@@ -201,6 +204,7 @@ class StoreController
Settings::set('store_download_limit', (string)$downloadLimit); Settings::set('store_download_limit', (string)$downloadLimit);
Settings::set('store_download_expiry_days', (string)$expiryDays); Settings::set('store_download_expiry_days', (string)$expiryDays);
Settings::set('store_order_prefix', $orderPrefix); Settings::set('store_order_prefix', $orderPrefix);
Settings::set('store_timezone', $timezone);
Settings::set('store_test_mode', $testMode); Settings::set('store_test_mode', $testMode);
Settings::set('store_stripe_enabled', $stripeEnabled); Settings::set('store_stripe_enabled', $stripeEnabled);
Settings::set('store_stripe_public_key', $stripePublic); Settings::set('store_stripe_public_key', $stripePublic);
@@ -2257,6 +2261,7 @@ class StoreController
'store_download_limit' => Settings::get('store_download_limit', '5'), 'store_download_limit' => Settings::get('store_download_limit', '5'),
'store_download_expiry_days' => Settings::get('store_download_expiry_days', '30'), 'store_download_expiry_days' => Settings::get('store_download_expiry_days', '30'),
'store_order_prefix' => Settings::get('store_order_prefix', 'AC-ORD'), 'store_order_prefix' => Settings::get('store_order_prefix', 'AC-ORD'),
'store_timezone' => Settings::get('store_timezone', 'UTC'),
'store_test_mode' => Settings::get('store_test_mode', '1'), 'store_test_mode' => Settings::get('store_test_mode', '1'),
'store_stripe_enabled' => Settings::get('store_stripe_enabled', '0'), 'store_stripe_enabled' => Settings::get('store_stripe_enabled', '0'),
'store_stripe_public_key' => Settings::get('store_stripe_public_key', ''), 'store_stripe_public_key' => Settings::get('store_stripe_public_key', ''),
@@ -2276,6 +2281,39 @@ class StoreController
]; ];
} }
private function normalizeTimezone(string $timezone): string
{
$timezone = trim($timezone);
if ($timezone === '') {
return 'UTC';
}
return in_array($timezone, \DateTimeZone::listIdentifiers(), true) ? $timezone : 'UTC';
}
private function applyStoreTimezone(): void
{
$timezone = $this->normalizeTimezone((string)Settings::get('store_timezone', 'UTC'));
@date_default_timezone_set($timezone);
$db = Database::get();
if (!($db instanceof PDO)) {
return;
}
try {
$tz = new \DateTimeZone($timezone);
$now = new \DateTimeImmutable('now', $tz);
$offset = $tz->getOffset($now);
$sign = $offset < 0 ? '-' : '+';
$offset = abs($offset);
$hours = str_pad((string)intdiv($offset, 3600), 2, '0', STR_PAD_LEFT);
$mins = str_pad((string)intdiv($offset % 3600, 60), 2, '0', STR_PAD_LEFT);
$dbTz = $sign . $hours . ':' . $mins;
$db->exec("SET time_zone = '" . $dbTz . "'");
} catch (Throwable $e) {
}
}
private function ensureSalesChartSchema(): void private function ensureSalesChartSchema(): void
{ {
$db = Database::get(); $db = Database::get();

View File

@@ -67,9 +67,25 @@ ob_start();
</div> </div>
</div> </div>
<div class="label" style="margin-top:12px;">Order Number Prefix</div> <div class="label" style="margin-top:12px;">Order Number Prefix</div>
<input class="input" name="store_order_prefix" value="<?= htmlspecialchars((string)($settings['store_order_prefix'] ?? 'AC-ORD'), ENT_QUOTES, 'UTF-8') ?>" placeholder="AC-ORD"> <input class="input" name="store_order_prefix" value="<?= htmlspecialchars((string)($settings['store_order_prefix'] ?? 'AC-ORD'), ENT_QUOTES, 'UTF-8') ?>" placeholder="AC-ORD">
</div>
<div class="label" style="margin-top:12px;">Store Timezone</div>
<input class="input" name="store_timezone" list="store-timezone-options" value="<?= htmlspecialchars((string)($settings['store_timezone'] ?? 'UTC'), ENT_QUOTES, 'UTF-8') ?>" placeholder="UTC">
<datalist id="store-timezone-options">
<option value="UTC"></option>
<option value="Europe/London"></option>
<option value="Europe/Berlin"></option>
<option value="America/New_York"></option>
<option value="America/Chicago"></option>
<option value="America/Los_Angeles"></option>
<option value="Australia/Sydney"></option>
<option value="Asia/Tokyo"></option>
</datalist>
<div style="margin-top:8px; font-size:12px; color:var(--muted);">
Used for order numbers, store timestamps, and expiry calculations. Invalid values fall back to UTC.
</div>
</div>
<div style="display:flex; justify-content:flex-end;"> <div style="display:flex; justify-content:flex-end;">
<button class="btn" type="submit">Save General Settings</button> <button class="btn" type="submit">Save General Settings</button>