:`. // Only set one, or neither. This option only applies for email_display modes 0 and 1. //$config['email_link'] = '/static/my_cool_email_icon.gif'; //$config['email_icon'] = 'image/png:...'; /* CONFIG OPTIONS - BACKEND */ // Send assets via PATH_INFO: bool // // If true, assets like CSS, GIFs, etc, will be served from URLs like `/visitors.php/foo.css`. // Otherwise, inline them into a single HTML document. // Only enable this if your webserver supports PATH_INFO. $config['use_path_info'] = false; // Database path: string // // The path to the database file, in one of 3 formats. // // *.json - use a JSON array // *.jsonl - use newline-delimited JSON objects (jsonlines) // *.csv - use CSV // *.db, *.sqlite, *.sqlite3 - use Sqlite3 // // If the file extension is unknown, CSV is picked as default. $config['db'] = 'visitors.csv'; /* --- END OF CONFIG, CODE BELOW, PROBABLY DON'T EDIT PAST THIS POINT --- */ /* CONFIG FIXING */ if (!isset($config['email_link'])) { // stolen from $config['email_icon'] ??= 'image/gif:R0lGODlhEAAQALMEAAAAADExY2NjMWNjzv///5ycY5yc/86cY87O///OnP//zv///wAAAAAAAAAAAAAAACH5BAEAAAQALAAAAAAQABAAAARfkJBCqy1CyrK690qmcUd5KKiSiNuSvio7LWVinqs2w2kuDaSbLTYgDAwcHmplCAwWBprplTA0nwiDspp1LhBZq9gKvn6z4DS6izWo1W6z+w2/Hsd46yBACAD+gIGABBEAOw=='; if (!$config['use_path_info']) { [$mime, $base64] = explode(':', $config['email_icon'], 2); $config['email_link'] = 'data:'.$mime.';base64,'.$base64; unset($config['email_icon']); } } /* DATABASES */ // db_row: ['id' => int (only for list_rows), 'name' => string, 'message' => string, 'email' => ?string, 'website' => ?string, 'timestamp' => string (or DateTimeInterface for list_rows)] // message is pre-escaped at insert time. // timestamp is DateTimeInterface::ISO8601_EXPANDED format. abstract class Database { public function append_row(string $name, string $message, ?string $email = NULL, ?string $website = NULL) { $timestamp = date(DATE_ISO8601_EXPANDED); $this->_append_row(compact('name', 'message', 'email', 'website', 'timestamp')); } public function list_rows(): array { $data = $this->_list_rows(); foreach($data as &$row) { $row['timestamp'] = DateTime::createFromFormat(DATE_ISO8601_EXPANDED, $row['timestamp']); } return $data; } public function delete_row(int $id): bool { return $this->_delete_row($id); } abstract public function __construct(string $file); abstract protected function _append_row(array $db_row); abstract protected function _list_rows(): array; abstract protected function _delete_row(int $id): bool; } // sigh, refactor later, it wont be a perf issue for any realistic number of entries. abstract class FileDatabase extends Database { protected string $file; protected array $data; public function __construct(string $file) { if (file_exists($file)) $this->data = $this->load($file); else $this->data = array(); $this->file = $file; } protected function _append_row(array $db_row) { array_unshift($this->data, array(...$db_row, 'id' => count($this->data) ? max(array_map(fn($a) => $a['id'], $this->data)) : 0)); $this->save(); } protected function _list_rows(): array { $data = $this->data; return $data; } protected function _delete_row(int $id): bool { foreach(array_keys($this->data) as $key) if ($this->data[$key]['id'] === $id) { unset($this->data[$key]); $this->save(); return true; } return false; } abstract protected function save(); abstract protected function load(string $file): array; } // notes: `id` is not stable final class JsonDatabase extends FileDatabase { protected function save() { file_put_contents($this->file, json_encode($this->data)); } protected function load(string $file): array { return json_decode(file_get_contents($file), associative: true); } } // notes: `id` is not stable final class JsonlDatabase extends FileDatabase { protected function save() { $fp = fopen($this->file, 'w'); foreach($data as $row) { fwrite($fp, json_encode($row)."\n"); } fclose($fp); } protected function load(string $file): array { return array_map(fn($s) => json_decode($s, associative: true), file($file)); } } // notes: `id` is not stable final class CsvDatabase extends FileDatabase { private const array KEY_ORDER = ['id', 'name', 'message', 'email', 'website', 'timestamp']; protected function save() { $fp = fopen($this->file, 'w'); foreach($this->data as $row) { fputcsv($fp, array_map(fn($k) => $row[$k]??'', self::KEY_ORDER)); } fclose($fp); } protected function load(string $file): array { return array_map(fn($s) => array_combine(self::KEY_ORDER, str_getcsv($s)), file($file)); } } final class SqliteDatabase extends Database { private Sqlite3 $handle; public function __construct(string $file) { $this->handle = new Sqlite3($file); if (!$this->handle->querySingle('SELECT count(*) FROM sqlite_master')) $this->handle->exec('CREATE TABLE visitors (id INTEGER PRIMARY KEY, name TEXT NOT NULL, message TEXT NOT NULL, email TEXT, website TEXT, timestamp TEXT NOT NULL)'); } protected function _append_row(array $db_row) { $stmt = $this->handle->prepare('INSERT INTO visitors (name, message, email, website, timestamp) VALUES (:name, :message, :email, :website, :timestamp)'); foreach($db_row as $key => $value) $stmt->bindValue($key, $value); $stmt->execute(); } protected function _list_rows(): array { $rows = array(); $res = $this->handle->query('SELECT * FROM visitors ORDER BY id DESC'); while ($row = $res->fetchArray(SQLITE3_ASSOC)) $rows[] = $row; return $rows; } protected function _delete_row(int $id): bool { $stmt = $this->handle->prepare('SELECT count(*) FROM visitors WHERE id=:id'); $stmt->bindValue('id', $id); if (!$stmt->execute()->fetchArray(SQLITE3_NUM)[0]) return false;; $stmt = $this->handle->prepare('DELETE FROM visitors WHERE id=:id'); $stmt->bindValue('id', $id); $stmt->execute(); return true; } } function get_database_for_file(string $file): Database { $ext = preg_replace('/.+\.([^.]+)$/', '${1}', $file); return match ($ext) { 'db', 'sqlite', 'sqlite3' => new SqliteDatabase($file), 'csv' => new CsvDatabase($file), 'json' => new JsonDatabase($file), 'jsonl' => new JsonlDatabase($file), default => new CsvDatabase($file) }; } $db = get_database_for_file($config['db']); //$db->append_row('foo', 'bar'); /* ACTUAL UI */ $ed_1_or_3 = $config['email_display'] == 1 || $config['email_display'] == 3; ?>

Guestbook

list_rows() as $row): $author_link = $row['website'] ? ''.$row['name'].'' : (($row['email'] && $ed_1_or_3) ? ''.$row['name'].'' : $row['name']); $email_element = $row['email'] && (($row['website'] && $ed_1_or_3) || !$ed_1_or_3) ? match ($config['email_display']) { 0, 1 => 'e-mail icon', 2, 3 => '[e-mail]', 4 => 'mail: '.str_replace(['@', '.'], [' at ', ' dot '], $row['email']).'' } : ''; ?>
data-visit-id= data-visit-name="" data-visit-message="" data-visit-email="" data-visit-website="">