241 lines
7.4 KiB
PHP
241 lines
7.4 KiB
PHP
<?php
|
|
// This is the initialization code that needs to be run before anything else.
|
|
// - define paths
|
|
// - confirm /storage directory exists and is writable
|
|
// - make sure database is ready
|
|
// - load classes
|
|
|
|
// Define all the important paths
|
|
define('APP_ROOT', dirname(dirname(__FILE__)));
|
|
define('SRC_DIR', APP_ROOT . '/src');
|
|
define('STORAGE_DIR', APP_ROOT . '/storage');
|
|
define('TEMPLATES_DIR', APP_ROOT . '/templates');
|
|
define('TICKS_DIR', STORAGE_DIR . '/ticks');
|
|
define('DATA_DIR', STORAGE_DIR . '/db');
|
|
define('CSS_UPLOAD_DIR', STORAGE_DIR . '/upload/css');
|
|
define('DB_FILE', DATA_DIR . '/tkr.sqlite');
|
|
|
|
// Define an exception for validation errors
|
|
class SetupException extends Exception {
|
|
private $setupIssue;
|
|
|
|
public function __construct(string $message, string $setupIssue = '', int $code = 0, Throwable $previous = null) {
|
|
parent::__construct($message, $code, $previous);
|
|
$this->setupIssue = $setupIssue;
|
|
}
|
|
|
|
public function getSetupIssue(): string {
|
|
return $this->setupIssue;
|
|
}
|
|
}
|
|
|
|
function handle_setup_exception(SetupException $e){
|
|
switch ($e->getSetupIssue()){
|
|
case 'storage_missing':
|
|
case 'storage_permissions':
|
|
case 'directory_creation':
|
|
case 'directory_permissions':
|
|
case 'database_connection':
|
|
case 'table_creation':
|
|
// Unrecoverable errors.
|
|
// Show error message and exit
|
|
http_response_code(500);
|
|
echo "<h1>Configuration Error</h1>";
|
|
echo "<p>" . Util::escape_html($e->getSetupIssue) . '-' . Util::escape_html($e->getMessage()) . "</p>";
|
|
exit;
|
|
case 'table_contents':
|
|
// Recoverable error.
|
|
// Redirect to setup if we aren't already headed there.
|
|
// NOTE: Just read directly from init.php instead of
|
|
// trying to use the config object. This is the initial
|
|
// setup. It shouldn't assume anything can be loaded.
|
|
$init = require APP_ROOT . '/config/init.php';
|
|
$currentPath = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
|
|
|
if (strpos($currentPath, 'setup') === false) {
|
|
header('Location: ' . $init['base_path'] . 'setup');
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Main validation function
|
|
// Any failures will throw a SetupException
|
|
function confirm_setup(): void {
|
|
validate_storage_dir();
|
|
validate_storage_subdirs();
|
|
validate_tables();
|
|
validate_table_contents();
|
|
}
|
|
|
|
// Make sure the storage/ directory exists and is writable
|
|
function validate_storage_dir(): void{
|
|
if (!is_dir(STORAGE_DIR)) {
|
|
throw new SetupException(
|
|
STORAGE_DIR . "does not exist. Please check your installation.",
|
|
'storage_missing'
|
|
);
|
|
}
|
|
|
|
if (!is_writable(STORAGE_DIR)) {
|
|
throw new SetupException(
|
|
STORAGE_DIR . "is not writable. Exiting.",
|
|
'storage_permissions'
|
|
);
|
|
}
|
|
}
|
|
|
|
// validate that the required storage subdirectories exist
|
|
// attempt to create them if they don't
|
|
function validate_storage_subdirs(): void {
|
|
$storageSubdirs = array();
|
|
$storageSubdirs[] = CSS_UPLOAD_DIR;
|
|
$storageSubdirs[] = DATA_DIR;
|
|
$storageSubdirs[] = TICKS_DIR;
|
|
|
|
foreach($storageSubdirs as $storageSubdir){
|
|
if (!is_dir($storageSubdir)) {
|
|
if (!mkdir($storageSubdir, 0770, true)) {
|
|
throw new SetupException(
|
|
"Failed to create required directory: $dir",
|
|
'directory_creation'
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!is_writable($storageSubdir)) {
|
|
if (!chmod($storageSubdir, 0770)) {
|
|
throw new SetupException(
|
|
"Required directory is not writable: $dir",
|
|
'directory_permissions'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function get_db(): PDO {
|
|
try {
|
|
// SQLite will just create this if it doesn't exist.
|
|
$db = new PDO("sqlite:" . DB_FILE);
|
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
} catch (PDOException $e) {
|
|
throw new SetupException(
|
|
"Database connection failed: " . $e->getMessage(),
|
|
'database_connection',
|
|
0,
|
|
$e
|
|
);
|
|
}
|
|
|
|
return $db;
|
|
}
|
|
|
|
function create_tables(): void {
|
|
$db = get_db();
|
|
|
|
try {
|
|
// user table
|
|
$db->exec("CREATE TABLE IF NOT EXISTS user (
|
|
id INTEGER PRIMARY KEY,
|
|
username TEXT NOT NULL,
|
|
display_name TEXT NOT NULL,
|
|
password_hash TEXT NULL,
|
|
about TEXT NULL,
|
|
website TEXT NULL,
|
|
mood TEXT NULL
|
|
)");
|
|
|
|
// settings table
|
|
$db->exec("CREATE TABLE IF NOT EXISTS settings (
|
|
id INTEGER PRIMARY KEY,
|
|
site_title TEXT NOT NULL,
|
|
site_description TEXT NULL,
|
|
base_url TEXT NOT NULL,
|
|
base_path TEXT NOT NULL,
|
|
items_per_page INTEGER NOT NULL,
|
|
css_id INTEGER NULL
|
|
)");
|
|
|
|
// css table
|
|
$db->exec("CREATE TABLE IF NOT EXISTS css (
|
|
id INTEGER PRIMARY KEY,
|
|
filename TEXT UNIQUE NOT NULL,
|
|
description TEXT NULL
|
|
)");
|
|
|
|
// mood table
|
|
$db->exec("CREATE TABLE IF NOT EXISTS emoji(
|
|
id INTEGER PRIMARY KEY,
|
|
emoji TEXT UNIQUE NOT NULL,
|
|
description TEXT NOT NULL
|
|
)");
|
|
} catch (PDOException $e) {
|
|
throw new SetupException(
|
|
"Table creation failed: " . $e->getMessage(),
|
|
'table_creation',
|
|
0,
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
|
|
// make sure all tables exist
|
|
// attempt to create them if they don't
|
|
function validate_tables(): void {
|
|
$appTables = array();
|
|
$appTables[] = "settings";
|
|
$appTables[] = "user";
|
|
$appTables[] = "css";
|
|
$appTables[] = "emoji";
|
|
|
|
$db = get_db();
|
|
|
|
foreach ($appTables as $appTable){
|
|
$stmt = $db->prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
|
$stmt->execute([$appTable]);
|
|
if (!$stmt->fetch()){
|
|
// At least one table doesn't exist.
|
|
// Try creating tables (hacky, but I have 4 total tables)
|
|
// Will throw an exception if it fails
|
|
create_tables();
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure tables that need to be seeded have been
|
|
function validate_table_contents(): void {
|
|
$db = get_db();
|
|
|
|
// make sure required tables (user, settings) are populated
|
|
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
|
$settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn();
|
|
|
|
// If either required table has no records and we aren't on /admin,
|
|
// redirect to /admin to complete setup
|
|
if ($user_count === 0 || $settings_count === 0){
|
|
throw new SetupException(
|
|
"Required tables aren't populated. Please complete setup",
|
|
'table_contents',
|
|
);
|
|
};
|
|
}
|
|
|
|
// Load all classes from the src/ directory
|
|
function load_classes(): void {
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator(SRC_DIR)
|
|
);
|
|
|
|
// load base classes first
|
|
require_once SRC_DIR . '/Controller/Controller.php';
|
|
|
|
// load everything else
|
|
foreach ($iterator as $file) {
|
|
if ($file->isFile() && fnmatch('*.php', $file->getFilename())) {
|
|
require_once $file;
|
|
}
|
|
}
|
|
}
|