[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,62 @@
<?php
require_once __DIR__ . '/functions.php';
class AdminAuth {
private const MAX_ATTEMPTS = 5;
private const LOCKOUT_MINUTES = 15;
public static function attempt(string $password): bool {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (self::isLockedOut($ip)) {
return false;
}
if (password_verify($password, ADMIN_PASSWORD_HASH)) {
db()->query("DELETE FROM login_attempts WHERE ip_address = :ip", ['ip' => $ip]);
$_SESSION['wt_admin'] = true;
session_regenerate_id(true);
return true;
}
self::recordFailure($ip);
return false;
}
public static function isLockedOut(string $ip): bool {
$row = db()->fetch("SELECT * FROM login_attempts WHERE ip_address = :ip", ['ip' => $ip]);
if (!$row || (int) $row['attempts'] < self::MAX_ATTEMPTS) {
return false;
}
$lockoutUntil = strtotime($row['last_attempt']) + self::LOCKOUT_MINUTES * 60;
return time() < $lockoutUntil;
}
private static function recordFailure(string $ip): void {
$row = db()->fetch("SELECT * FROM login_attempts WHERE ip_address = :ip", ['ip' => $ip]);
if ($row) {
$lockoutUntil = strtotime($row['last_attempt']) + self::LOCKOUT_MINUTES * 60;
// Reset the counter once the previous lockout window has fully expired.
$attempts = (time() < $lockoutUntil) ? (int) $row['attempts'] + 1 : 1;
db()->update('login_attempts', ['attempts' => $attempts, 'last_attempt' => date('Y-m-d H:i:s')], 'ip_address = :ip', ['ip' => $ip]);
} else {
db()->insert('login_attempts', ['ip_address' => $ip, 'attempts' => 1, 'last_attempt' => date('Y-m-d H:i:s')]);
}
}
public static function isLoggedIn(): bool {
return !empty($_SESSION['wt_admin']);
}
public static function require(): void {
if (!self::isLoggedIn()) {
header('Location: /admin/login.php');
exit;
}
}
public static function logout(): void {
unset($_SESSION['wt_admin']);
session_regenerate_id(true);
}
}
@@ -0,0 +1,57 @@
<?php
require_once __DIR__ . '/../../includes/config.php';
class Database {
private static ?PDO $pdo = null;
public static function get(): PDO {
if (self::$pdo === null) {
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4';
self::$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return self::$pdo;
}
public function query(string $sql, array $params = []): PDOStatement {
$stmt = self::get()->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function fetch(string $sql, array $params = []): ?array {
$row = $this->query($sql, $params)->fetch();
return $row ?: null;
}
public function fetchAll(string $sql, array $params = []): array {
return $this->query($sql, $params)->fetchAll();
}
public function insert(string $table, array $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$this->query("INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})", $data);
return self::get()->lastInsertId();
}
public function update(string $table, array $data, string $where, array $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
return $this->query($sql, array_merge($data, $whereParams))->rowCount();
}
}
function db(): Database {
static $instance = null;
if ($instance === null) {
$instance = new Database();
}
return $instance;
}
@@ -0,0 +1,2 @@
</body>
</html>
@@ -0,0 +1,95 @@
<?php
require_once __DIR__ . '/db.php';
session_start();
function formatCurrency(float $amount): string {
return '$' . number_format($amount, 2);
}
function generateToken(): string {
return bin2hex(random_bytes(20));
}
/**
* Work weeks run Friday-Thursday (Sat/Sun excluded entirely, not tracked).
* Returns the Friday date (Y-m-d) that the given date's work week belongs to.
*/
function getWeekStart(string $dateStr): string {
$d = new DateTime($dateStr);
$dow = (int) $d->format('N'); // 1=Mon ... 5=Fri, 6=Sat, 7=Sun
if ($dow === 5) {
// Friday itself
return $d->format('Y-m-d');
} elseif ($dow === 6) {
// Saturday - previous day is Friday
$d->modify('-1 day');
return $d->format('Y-m-d');
} elseif ($dow === 7) {
// Sunday - 2 days back is Friday
$d->modify('-2 days');
return $d->format('Y-m-d');
} else {
// Mon(1)->Fri is 3 days prior; Tue(2)->4; Wed(3)->5; Thu(4)->6
$daysBack = $dow + 2;
$d->modify("-{$daysBack} days");
return $d->format('Y-m-d');
}
}
/**
* Returns the 5 workdate strings (Y-m-d) for the week starting on $weekStart (a Friday),
* in order: Friday, Monday, Tuesday, Wednesday, Thursday.
*/
function getWeekDates(string $weekStart): array {
$fri = new DateTime($weekStart);
$dates = [$fri->format('Y-m-d')];
$d = clone $fri;
$d->modify('+3 days'); // Monday
for ($i = 0; $i < 4; $i++) {
$dates[] = $d->format('Y-m-d');
$d->modify('+1 day');
}
return $dates;
}
function dayLabel(string $dateStr): string {
$d = new DateTime($dateStr);
return $d->format('D, M j');
}
function currentWeekStart(): string {
return getWeekStart(date('Y-m-d'));
}
function nextWeekStart(string $weekStart): string {
$d = new DateTime($weekStart);
$d->modify('+7 days');
return $d->format('Y-m-d');
}
/**
* Compute the dollar amount owed for a set of day_entries rows.
*/
function calcOwed(array $entries): float {
$total = 0.0;
foreach ($entries as $e) {
if ($e['status'] === 'full') $total += FULL_DAY_RATE;
elseif ($e['status'] === 'half') $total += HALF_DAY_RATE;
}
return $total;
}
function e(string $s): string {
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
/**
* Validates a Y-m-d date string strictly (rejects malformed input that
* would otherwise throw when passed to `new DateTime()`).
*/
function isValidDateStr(?string $s): bool {
if (!$s) return false;
$d = DateTime::createFromFormat('Y-m-d', $s);
return $d !== false && $d->format('Y-m-d') === $s;
}
@@ -0,0 +1,14 @@
<?php $pageTitle = $pageTitle ?? 'ChuckCo Time Keeper'; ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="apple-mobile-web-app-title" content="ChuckCo">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<title><?= e($pageTitle) ?></title>
<link rel="stylesheet" href="/assets/style.css?v=<?= filemtime(__DIR__ . '/../assets/style.css') ?>">
</head>
<body>
<div class="brand-strip">👷 <span>Chuck<b>Co</b> Time Keeper</span></div>