189 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Web Request Start Script
 | 
						|
 *
 | 
						|
 * This loads the environment needed for a web request
 | 
						|
 *
 | 
						|
 * @author Daniel J. Summers <daniel@bitbadger.solutions>
 | 
						|
 * @license MIT
 | 
						|
 */
 | 
						|
 | 
						|
declare(strict_types=1);
 | 
						|
 | 
						|
use BitBadger\PDODocument\Configuration;
 | 
						|
use FeedReaderCentral\{Key, Security, User};
 | 
						|
 | 
						|
require 'app-config.php';
 | 
						|
 | 
						|
session_start([
 | 
						|
    'name'            => 'FRCSESSION',
 | 
						|
    'use_strict_mode' => true,
 | 
						|
    'cookie_httponly' => true,
 | 
						|
    'cookie_samesite' => 'Strict']);
 | 
						|
 | 
						|
/**
 | 
						|
 * Add a message to be displayed at the top of the page
 | 
						|
 *
 | 
						|
 * @param string $level The level (type) of the message
 | 
						|
 * @param string $message The message itself
 | 
						|
 */
 | 
						|
function add_message(string $level, string $message): void
 | 
						|
{
 | 
						|
    if (!key_exists(Key::UserMsg, $_SESSION)) $_SESSION[Key::UserMsg] = [];
 | 
						|
    $_SESSION[Key::UserMsg][] = ['level' => $level, 'message' => $message];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Add an error message to be displayed at the top of the page
 | 
						|
 *
 | 
						|
 * @param string $message The message to be displayed
 | 
						|
 */
 | 
						|
function add_error(string $message): void
 | 
						|
{
 | 
						|
    add_message('ERROR', $message);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Add an error message to be displayed at the top of the page
 | 
						|
 *
 | 
						|
 * @param string $message The message to be displayed
 | 
						|
 */
 | 
						|
function add_info(string $message): void
 | 
						|
{
 | 
						|
    add_message('INFO', $message);
 | 
						|
}
 | 
						|
 | 
						|
/** True if this request was initiated by htmx, false if not */
 | 
						|
$is_htmx = key_exists('HTTP_HX_REQUEST', $_SERVER) && !key_exists('HTTP_HX_HISTORY_RESTORE_REQUEST', $_SERVER);
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a navigation link in the top right nav bar
 | 
						|
 *
 | 
						|
 * @param string $link The link to be placed
 | 
						|
 * @param bool $isFirst True if this is the first link being placed, false if not
 | 
						|
 */
 | 
						|
function nav_link(string $link, bool $isFirst = false): void
 | 
						|
{
 | 
						|
    $sep = $isFirst ? '' : ' | ';
 | 
						|
    echo "<span>$sep$link</span>";
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Render the title bar for the page
 | 
						|
 */
 | 
						|
function title_bar(): void
 | 
						|
{
 | 
						|
    $version = display_version();
 | 
						|
    echo "<header hx-target=#main hx-push-url=true>"
 | 
						|
        . "<div><a href=/ class=title>Feed Reader Central</a><span class=version>$version</span></div>"
 | 
						|
        . "<nav>";
 | 
						|
    if (key_exists(Key::UserId, $_SESSION)) {
 | 
						|
        nav_link(hx_get('/feeds', 'Feeds'), true);
 | 
						|
        if (User::hasBookmarks()) nav_link(hx_get('/?bookmarked', 'Bookmarked'));
 | 
						|
        nav_link(hx_get('/search', 'Search'));
 | 
						|
        nav_link(hx_get('/docs/', 'Docs'));
 | 
						|
        nav_link('<a href=/user/log-off>Log Off</a>');
 | 
						|
        if ($_SESSION[Key::UserEmail] !== Security::SingleUserEmail) {
 | 
						|
            nav_link($_SESSION[Key::UserEmail]);
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        nav_link(hx_get('/user/log-on', 'Log On'), true);
 | 
						|
        nav_link(hx_get('/docs/', 'Docs'));
 | 
						|
    }
 | 
						|
    echo '</nav></header>'
 | 
						|
        . '<main id=main hx-target=this hx-push-url=true hx-swap="innerHTML show:window:top">';
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Render the page title
 | 
						|
 * @param string $title The title of the page being displayed
 | 
						|
 */
 | 
						|
function page_head(string $title): void
 | 
						|
{
 | 
						|
    global $is_htmx;
 | 
						|
    echo '<!DOCTYPE html><html lang=en>'
 | 
						|
        . "<head><title>$title | Feed Reader Central</title>";
 | 
						|
    if (!$is_htmx) {
 | 
						|
        echo '<meta name=viewport content="width=device-width, initial-scale=1">'
 | 
						|
            . "<meta name=htmx-config content='{\"historyCacheSize\":0}'>"
 | 
						|
            . '<link href=/assets/style.css rel=stylesheet>';
 | 
						|
    }
 | 
						|
    echo '</head><body>';
 | 
						|
    if (!$is_htmx) title_bar();
 | 
						|
    if (sizeof($messages = $_SESSION[Key::UserMsg] ?? []) > 0) {
 | 
						|
        echo '<div class=user_messages>';
 | 
						|
            array_walk($messages, function ($msg) {
 | 
						|
                echo '<div class=user_message>'
 | 
						|
                    . ($msg['level'] === 'INFO' ? '' : "<strong>{$msg['level']}</strong><br>")
 | 
						|
                    . $msg['message'] . '</div>';
 | 
						|
            });
 | 
						|
        echo '</div>';
 | 
						|
        $_SESSION[Key::UserMsg] = [];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Render the end of the page
 | 
						|
 */
 | 
						|
function page_foot(): void
 | 
						|
{
 | 
						|
    global $is_htmx;
 | 
						|
    echo '</main>' . ($is_htmx ? '' : '<script src=/assets/htmx.min.js></script>') . '</body></html>';
 | 
						|
    session_commit();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Redirect the user to the given URL
 | 
						|
 *
 | 
						|
 * @param string $value A local URL to which the user should be redirected
 | 
						|
 */
 | 
						|
function frc_redirect(string $value): never
 | 
						|
{
 | 
						|
    if (str_starts_with($value, 'http')) {
 | 
						|
        http_response_code(400);
 | 
						|
        die();
 | 
						|
    }
 | 
						|
    session_commit();
 | 
						|
    header("Location: $value", true, 303);
 | 
						|
    Configuration::resetPDO();
 | 
						|
    die();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert a date/time string to a date/time in the configured format
 | 
						|
 *
 | 
						|
 * @param string $value The date/time string
 | 
						|
 * @return string The standard format of a date/time, or '(invalid date)' if the date could not be parsed
 | 
						|
 */
 | 
						|
function date_time(string $value): string
 | 
						|
{
 | 
						|
    try {
 | 
						|
        return (new DateTimeImmutable($value))->format(DATE_TIME_FORMAT);
 | 
						|
    } catch (Exception) {
 | 
						|
        return '(invalid date)';
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an anchor tag with both `href` and `hx-get` attributes
 | 
						|
 *
 | 
						|
 * @param string $url The URL to which navigation should occur
 | 
						|
 * @param string $text The text for the link
 | 
						|
 * @param string $extraAttrs Extra attributes for the anchor tag (must be attribute-encoded)
 | 
						|
 * @return string The anchor tag with both `href` and `hx-get` attributes
 | 
						|
 */
 | 
						|
function hx_get(string $url, string $text, string $extraAttrs = ''): string
 | 
						|
{
 | 
						|
    $attrs = $extraAttrs !== '' ? " $extraAttrs" : '';
 | 
						|
    return "<a href=\"$url\" hx-get=\"$url\"$attrs>$text</a>";
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Return a 404 Not Found
 | 
						|
 */
 | 
						|
function not_found(): never
 | 
						|
{
 | 
						|
    http_response_code(404);
 | 
						|
    die('Not Found');
 | 
						|
}
 |