aboutsummaryrefslogblamecommitdiffstats
path: root/visitors.php
blob: e6c106ae22de7800d384ba24d4556bc0052859fd (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                      
                      
  
                                                                                      







                                                      
                              
























                                                                                                                     
                       









                                                                                                                                 
                                                                                              
                                                      
                                                         
 
                                 






                                                      
                                                           








                                                                            



























                                                                                                                                                                                          


                                              


                                             
                                       





                            
























                                                 














































                                                                                            
 
<?php

$config = array();
define('VERSION', '0.0.1');
define('GIT_REPO', 'https://git.aleteoryx.me/cgit/visitors_dot_php/');


/* CONFIG OPTIONS - COSMETIC */

// Custom CSS: ?string
//
// Will be injected after the builtin CSS. Respects `use_path_info` when being served.

//$config['custom_css'] = 'body { background: red; }';


// Use builtin CSS: bool
//
// Toggles the builtin CSS

$config['builtin_css'] = true;


// Form mode: choice
//
// 0 - Only ask for name
// 1 - Ask for name and website
// 2 - Ask for name, website, and email
// 3 - Ask for name and (website or email)
//
// Any entries in one mode will display fine in any other.

$config['form_mode'] = 0;


// E-Mail display mode: choice
//
// 0 - Show an icon with a `mailto:` link
// 1 - Show an icon with a `mailto:` link, or use the username text as the link if no website is present
// 2 - Show the text `[e-mail]` with a `mailto:` link
// 3 - Show the text `[e-mail]` with a `mailto:` link, or use the username text for the link if no website is present
// 4 - Show the e-mail as escaped text, e.g. 'alyx at aleteoryx dot me'

$config['email_display'] = 0;


// E-Mail icon: ?string
//
// Should be the link to an icon for email display modes 0 and 1. Supports base64. If null or empty, defaults to a builtin image.

//$config['email_icon'] = '/static/my_cool_email_icon.gif';


/* 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

$config['db'] = 'visitors.csv';


/* --- END OF CONFIG, CODE BELOW, PROBABLY DON'T EDIT PAST THIS POINT --- */


// 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 protected function _append_row(array $db_row);
  abstract protected function _list_rows(): array;
  abstract protected function _delete_row(int $id): bool;
}

// notes: `id` is not stable
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, $db_row);
    $this->save();
  }

  protected function _list_rows(): array {
    $data = $this->data;

    foreach($data as $id => &$row) {
      $row['id'] = $id;
    }

    return $data;
  }

  protected function _delete_row(int $id): bool {
    if (!isset($this->data[$id]))
      return false;

    unset($this->data[$id]);
    $this->data = array_values($this->data);
    $this->save();

    return true;
  }

  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 = ['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));
  }
}