my-homelab-configs/apps/website/ideas_helper.php

126 lines
3.5 KiB
PHP

<?php
function visitor_ideas_dir(): string {
return getenv('WEBSITE_IDEAS_WRITE_DIR') ?: sys_get_temp_dir() . '/website-ideas';
}
function visitor_ideas_file(): string {
return visitor_ideas_dir() . '/ideas.jsonl';
}
function visitor_ideas_rate_file(): string {
return visitor_ideas_dir() . '/rate.json';
}
function visitor_ideas_ensure_dir(): bool {
$dir = visitor_ideas_dir();
return is_dir($dir) || mkdir($dir, 0750, true);
}
function visitor_idea_clean(string $value, int $maxLength): string {
$value = preg_replace('/<\s*(script|style)\b[^>]*>.*?<\s*\/\s*\1\s*>/is', '', $value) ?? $value;
$value = strip_tags($value);
$value = str_replace(["\r\n", "\r"], "\n", $value);
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? '';
$value = preg_replace('/[ \t]+/u', ' ', $value) ?? $value;
$value = preg_replace("/\n{3,}/", "\n\n", $value) ?? $value;
$value = trim($value);
if (strlen($value) > $maxLength) {
$value = substr($value, 0, $maxLength);
}
return $value;
}
function visitor_ideas_recently_submitted(int $seconds = 45): bool {
if (!visitor_ideas_ensure_dir()) {
return true;
}
$file = visitor_ideas_rate_file();
$key = hash('sha256', $_SERVER['REMOTE_ADDR'] ?? 'unknown');
$now = time();
$handle = fopen($file, 'c+');
if (!$handle) {
return true;
}
flock($handle, LOCK_EX);
$raw = stream_get_contents($handle);
$rates = json_decode($raw ?: '{}', true);
if (!is_array($rates)) {
$rates = [];
}
foreach ($rates as $rateKey => $timestamp) {
if (!is_int($timestamp) && !ctype_digit((string) $timestamp)) {
unset($rates[$rateKey]);
continue;
}
if ($now - (int) $timestamp > 86400) {
unset($rates[$rateKey]);
}
}
$last = (int) ($rates[$key] ?? 0);
$blocked = $now - $last < $seconds;
if (!$blocked) {
$rates[$key] = $now;
rewind($handle);
ftruncate($handle, 0);
fwrite($handle, json_encode($rates, JSON_UNESCAPED_SLASHES));
}
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);
return $blocked;
}
function visitor_ideas_append(string $name, string $idea): bool {
if (!visitor_ideas_ensure_dir()) {
return false;
}
$entry = [
'name' => $name !== '' ? $name : 'Anonymous visitor',
'idea' => $idea,
'created_at' => gmdate('c'),
];
return file_put_contents(
visitor_ideas_file(),
json_encode($entry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n",
FILE_APPEND | LOCK_EX
) !== false;
}
function visitor_ideas_read(int $limit = 6): array {
$file = visitor_ideas_file();
if (!is_readable($file)) {
return [];
}
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$lines) {
return [];
}
$ideas = [];
foreach (array_reverse($lines) as $line) {
$entry = json_decode($line, true);
if (!is_array($entry) || !isset($entry['idea'], $entry['name'], $entry['created_at'])) {
continue;
}
$ideas[] = [
'name' => visitor_idea_clean((string) $entry['name'], 80),
'idea' => visitor_idea_clean((string) $entry['idea'], 600),
'created_at' => visitor_idea_clean((string) $entry['created_at'], 40),
];
if (count($ideas) >= $limit) {
break;
}
}
return $ideas;
}