| 1 |
<?php |
| 2 |
require_once __DIR__ . '/inc/layout.php'; |
| 3 |
require_once __DIR__ . '/inc/report.php'; |
| 4 |
|
| 5 |
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); |
| 6 |
header('Pragma: no-cache'); |
| 7 |
header('Expires: 0'); |
| 8 |
|
| 9 |
require_login(); |
| 10 |
|
| 11 |
$error = ''; |
| 12 |
|
| 13 |
if ($_SERVER['REQUEST_METHOD'] === 'POST') { |
| 14 |
check_csrf(); |
| 15 |
$action = (string)($_POST['action'] ?? ''); |
| 16 |
|
| 17 |
if ($action === 'email') { |
| 18 |
$to = trim((string)($_POST['report_email'] ?? '')); |
| 19 |
$daily = isset($_POST['report_daily']); |
| 20 |
$hour = max(0, min(23, (int)($_POST['report_hour'] ?? 7))); |
| 21 |
$send = ($_POST['do'] ?? '') === 'send'; |
| 22 |
|
| 23 |
if ($to !== '' && !filter_var($to, FILTER_VALIDATE_EMAIL)) { |
| 24 |
$error = 'That email address does not look valid.'; |
| 25 |
} elseif (($daily || $send) && $to === '') { |
| 26 |
$error = 'Enter an email address first.'; |
| 27 |
} else { |
| 28 |
setting_set('report_email', $to); |
| 29 |
setting_set('report_daily', $daily ? '1' : '0'); |
| 30 |
setting_set('report_hour', (string)$hour); |
| 31 |
if ($send) { |
| 32 |
$ok = send_report_email($to, $err); |
| 33 |
setting_set('report_last_status', ($ok ? "Sent to $to" : 'FAILED: ' . $err) . ' — ' . date('Y-m-d H:i')); |
| 34 |
flash($ok ? "Report sent to $to." : "Sending failed: $err"); |
| 35 |
} else { |
| 36 |
flash('Report settings saved.'); |
| 37 |
} |
| 38 |
header('Location: report.php'); |
| 39 |
exit; |
| 40 |
} |
| 41 |
} |
| 42 |
|
| 43 |
if ($action === 'import') { |
| 44 |
$f = $_FILES['csv'] ?? null; |
| 45 |
if (!$f || $f['error'] === UPLOAD_ERR_NO_FILE) { |
| 46 |
$error = 'Choose a CSV file first.'; |
| 47 |
} elseif ($f['error'] !== UPLOAD_ERR_OK) { |
| 48 |
$error = 'Upload failed (error ' . (int)$f['error'] . ') — the file may exceed the server upload limit.'; |
| 49 |
} elseif (!is_uploaded_file($f['tmp_name'])) { |
| 50 |
$error = 'Upload failed.'; |
| 51 |
} else { |
| 52 |
try { |
| 53 |
[$ins, $upd, $skip] = import_csv($f['tmp_name'], isset($_POST['update_existing'])); |
| 54 |
$msg = "Imported $ins new spool" . ($ins === 1 ? '' : 's'); |
| 55 |
if ($upd) { |
| 56 |
$msg .= ", updated $upd"; |
| 57 |
} |
| 58 |
if ($skip) { |
| 59 |
$msg .= ", skipped $skip row" . ($skip === 1 ? '' : 's') . ' (missing type or bad weights)'; |
| 60 |
} |
| 61 |
flash($msg . '.'); |
| 62 |
header('Location: index.php?sort=added'); |
| 63 |
exit; |
| 64 |
} catch (RuntimeException $ex) { |
| 65 |
$error = $ex->getMessage(); |
| 66 |
} |
| 67 |
} |
| 68 |
} |
| 69 |
} |
| 70 |
|
| 71 |
$to = (string)($_POST['report_email'] ?? setting_get('report_email', '')); |
| 72 |
$daily = $_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'email' |
| 73 |
? isset($_POST['report_daily']) |
| 74 |
: setting_get('report_daily') === '1'; |
| 75 |
$hour = max(0, min(23, (int)($_POST['report_hour'] ?? setting_get('report_hour', '7')))); |
| 76 |
$status = setting_get('report_last_status'); |
| 77 |
|
| 78 |
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; |
| 79 |
$base = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])), '/'); |
| 80 |
$cronUrl = $scheme . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . $base . '/cron.php?token=' . cron_token(); |
| 81 |
|
| 82 |
page_header('Report & CSV'); |
| 83 |
?> |
| 84 |
<h1>Email report</h1> |
| 85 |
<?php if ($error): ?> |
| 86 |
<p class="error"><?= e($error) ?></p> |
| 87 |
<?php endif; ?> |
| 88 |
<form method="post"> |
| 89 |
<?= csrf_field() ?> |
| 90 |
<input type="hidden" name="action" value="email"> |
| 91 |
|
| 92 |
<label for="report_email">Send the report to</label> |
| 93 |
<input type="email" id="report_email" name="report_email" value="<?= e($to) ?>" placeholder="you@example.com" autocomplete="email"> |
| 94 |
|
| 95 |
<label class="check"><input type="checkbox" name="report_daily" <?= $daily ? 'checked' : '' ?>> Email the report every morning</label> |
| 96 |
|
| 97 |
<label for="report_hour">Morning send time (server time)</label> |
| 98 |
<select id="report_hour" name="report_hour"> |
| 99 |
<?php for ($h = 0; $h < 24; $h++): ?> |
| 100 |
<option value="<?= $h ?>" <?= $h === $hour ? 'selected' : '' ?>><?= $h ?>:00</option> |
| 101 |
<?php endfor; ?> |
| 102 |
</select> |
| 103 |
|
| 104 |
<div class="actions"> |
| 105 |
<button name="do" value="save">Save settings</button> |
| 106 |
<button name="do" value="send" class="secondary">Save & send now</button> |
| 107 |
</div> |
| 108 |
<?php if ($status): ?> |
| 109 |
<p class="muted">Last report: <?= e($status) ?></p> |
| 110 |
<?php endif; ?> |
| 111 |
<p class="muted">The report lists every spool (lowest first) with the full inventory attached as CSV. |
| 112 |
The morning email goes out on the first visit to the site after the chosen hour; for delivery at |
| 113 |
exactly that time, point a cron job or uptime monitor at:<br> |
| 114 |
<code style="overflow-wrap:anywhere"><?= e($cronUrl) ?></code></p> |
| 115 |
</form> |
| 116 |
|
| 117 |
<h1>CSV export</h1> |
| 118 |
<p class="muted">Download the whole inventory as a spreadsheet-friendly CSV — also handy as a backup.</p> |
| 119 |
<div class="actions"> |
| 120 |
<a class="btn" href="export.php">Download CSV</a> |
| 121 |
</div> |
| 122 |
|
| 123 |
<h1>CSV import</h1> |
| 124 |
<form method="post" enctype="multipart/form-data"> |
| 125 |
<?= csrf_field() ?> |
| 126 |
<input type="hidden" name="action" value="import"> |
| 127 |
|
| 128 |
<label for="csvFile">CSV file</label> |
| 129 |
<input type="file" id="csvFile" name="csv" accept=".csv,text/csv,text/plain" required> |
| 130 |
|
| 131 |
<label class="check"><input type="checkbox" name="update_existing" checked> Update existing spools when a row's id matches (untick to add everything as new)</label> |
| 132 |
|
| 133 |
<div class="actions"> |
| 134 |
<button>Import CSV</button> |
| 135 |
</div> |
| 136 |
<p class="muted">The first row must be a header row. Files from the export above round-trip as-is; |
| 137 |
at minimum a <strong>type</strong> column is required — weights that are missing are assumed |
| 138 |
to be full spools. A database backup is taken before every import.</p> |
| 139 |
</form> |
| 140 |
<?php |
| 141 |
page_footer(); |
| 142 |
|