Migrate v3 data; WIP on journal items
This commit is contained in:
		
							parent
							
								
									4aa6e832c7
								
							
						
					
					
						commit
						4ea55d4d25
					
				@ -1,3 +1,50 @@
 | 
			
		||||
<?php declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
// TODO: write migration
 | 
			
		||||
use BitBadger\PDODocument\{Configuration, Custom, Definition, Document, Mode};
 | 
			
		||||
use BitBadger\PDODocument\Mapper\ArrayMapper;
 | 
			
		||||
use MyPrayerJournal\{History, Note, Recurrence, RecurrencePeriod, Request, RequestAction, Table};
 | 
			
		||||
 | 
			
		||||
require 'start.php';
 | 
			
		||||
 | 
			
		||||
echo 'Retrieving v3 requests...' . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
Configuration::resetPDO();
 | 
			
		||||
Configuration::$pdoDSN = 'pgsql:host=localhost;user=mpj;password=devpassword;dbname=mpj';
 | 
			
		||||
$reqs = Custom::array('SELECT data FROM mpj.request', [], new ArrayMapper());
 | 
			
		||||
 | 
			
		||||
echo 'Found ' . sizeof($reqs) . ' requests; migrating to v4...' . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
Configuration::resetPDO();
 | 
			
		||||
Configuration::$mode   = Mode::SQLite;
 | 
			
		||||
Configuration::$pdoDSN = 'sqlite:./data/mpj.db';
 | 
			
		||||
 | 
			
		||||
Definition::ensureTable(Table::REQUEST);
 | 
			
		||||
 | 
			
		||||
foreach ($reqs as $reqJson) {
 | 
			
		||||
    $req     = json_decode($reqJson['data']);
 | 
			
		||||
    $notes   = array_map(fn(stdClass $note) => new Note($note->asOf, $note->notes), $req->notes ?? []);
 | 
			
		||||
    $history = array_map(fn(stdClass $hist) =>
 | 
			
		||||
        new History(
 | 
			
		||||
            asOf:   $hist->asOf,
 | 
			
		||||
            action: RequestAction::from($hist->status),
 | 
			
		||||
            text:   property_exists($hist, 'text') ? $hist->text : null),
 | 
			
		||||
        $req->history);
 | 
			
		||||
    $recurParts  = explode(' ', $req->recurrence);
 | 
			
		||||
    $recurPeriod = RecurrencePeriod::from(end($recurParts));
 | 
			
		||||
    $recur       = match ($recurPeriod) {
 | 
			
		||||
        RecurrencePeriod::Immediate => new Recurrence(RecurrencePeriod::Immediate),
 | 
			
		||||
        default                     => new Recurrence($recurPeriod, (int)$recurParts[0])
 | 
			
		||||
    };
 | 
			
		||||
    $v4Req = new Request(
 | 
			
		||||
        id:           $req->id,
 | 
			
		||||
        enteredOn:    $req->enteredOn,
 | 
			
		||||
        userId:       $req->userId,
 | 
			
		||||
        snoozedUntil: property_exists($req, 'snoozedUntil') ? $req->snoozedUntil : null,
 | 
			
		||||
        showAfter:    property_exists($req, 'showAfter')    ? $req->showAfter    : null,
 | 
			
		||||
        recurrence:   $recur,
 | 
			
		||||
        history:      $history,
 | 
			
		||||
        notes:        $notes);
 | 
			
		||||
    Document::insert(Table::REQUEST, $v4Req);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
echo PHP_EOL . 'done' . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,16 @@ class Auth
 | 
			
		||||
        return self::$auth0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the logged on user information
 | 
			
		||||
     *
 | 
			
		||||
     * @return array|null The user information (null if no user is logged on)
 | 
			
		||||
     */
 | 
			
		||||
    public static function user(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        return self::client()->getUser();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiate a log on with Auth0
 | 
			
		||||
     *
 | 
			
		||||
@ -50,4 +60,20 @@ class Auth
 | 
			
		||||
        header('Location: ' . self::client()->logout($_ENV['AUTH0_BASE_URL']));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Require a user be logged on
 | 
			
		||||
     *
 | 
			
		||||
     * @param bool $redirect Whether to redirect to log on if there is not a user logged on
 | 
			
		||||
     * @return void If it returns, there is a user logged on; if not, we will be redirected to log on
 | 
			
		||||
     * @throws ConfigurationException If the Auth0 client is not configured correctly
 | 
			
		||||
     */
 | 
			
		||||
    public static function requireUser(bool $redirect = true): void
 | 
			
		||||
    {
 | 
			
		||||
        if (is_null(self::user())) {
 | 
			
		||||
            if ($redirect) self::logOn();
 | 
			
		||||
            http_response_code(403);
 | 
			
		||||
            die('Not Authorized');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,12 @@
 | 
			
		||||
 | 
			
		||||
namespace MyPrayerJournal;
 | 
			
		||||
 | 
			
		||||
use JsonSerializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A record of an action taken on a request
 | 
			
		||||
 */
 | 
			
		||||
class History
 | 
			
		||||
class History implements JsonSerializable
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $asOf The date/time this entry was made
 | 
			
		||||
@ -13,4 +15,11 @@ class History
 | 
			
		||||
     * @param string|null $text The text for this history entry (optional)
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(public string $asOf, public RequestAction $action, public ?string $text = null) { }
 | 
			
		||||
 | 
			
		||||
    public function jsonSerialize(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        $values = ['asOf' => $this->asOf, 'action' => $this->action->value];
 | 
			
		||||
        if (!is_null($this->text)) $values['text'] = $this->text;
 | 
			
		||||
        return $values;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -114,4 +114,15 @@ class Layout
 | 
			
		||||
        </footer><?php
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a card when there are no results found
 | 
			
		||||
    public static function noResults(string $heading, string $link, string $buttonText, string $text): void
 | 
			
		||||
    { ?>
 | 
			
		||||
        <div class=card>
 | 
			
		||||
            <h5 class=card-header><?=$heading?></h5>
 | 
			
		||||
            <div class="card-body text-center">
 | 
			
		||||
                <p class=card-text><?=$text?></p><?php
 | 
			
		||||
                page_link($link, $buttonText, ['class' => 'btn btn-primary']); ?>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div><?php
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,14 +2,23 @@
 | 
			
		||||
 | 
			
		||||
namespace MyPrayerJournal;
 | 
			
		||||
 | 
			
		||||
use JsonSerializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The recurrence for a prayer request
 | 
			
		||||
 */
 | 
			
		||||
class Recurrence
 | 
			
		||||
class Recurrence implements JsonSerializable
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @param RecurrencePeriod $period The recurrence period
 | 
			
		||||
     * @param int|null $interval How many of the periods will pass before the request is visible again
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(public RecurrencePeriod $period, public ?int $interval = null) { }
 | 
			
		||||
 | 
			
		||||
    public function jsonSerialize(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        $values = ['period' => $this->period->value];
 | 
			
		||||
        if (!is_null($this->interval)) $values['interval'] = $this->interval;
 | 
			
		||||
        return $values;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,17 +5,17 @@ namespace MyPrayerJournal;
 | 
			
		||||
/**
 | 
			
		||||
 * The type of recurrence a request can have
 | 
			
		||||
 */
 | 
			
		||||
enum RecurrencePeriod
 | 
			
		||||
enum RecurrencePeriod: string
 | 
			
		||||
{
 | 
			
		||||
    /** Requests, once prayed, are available again immediately */
 | 
			
		||||
    case Immediate;
 | 
			
		||||
    case Immediate = 'Immediate';
 | 
			
		||||
 | 
			
		||||
    /** Requests, once prayed, appear again in a number of hours */
 | 
			
		||||
    case Hours;
 | 
			
		||||
    case Hours = 'Hours';
 | 
			
		||||
 | 
			
		||||
    /** Requests, once prayed, appear again in a number of days */
 | 
			
		||||
    case Days;
 | 
			
		||||
    case Days = 'Days';
 | 
			
		||||
 | 
			
		||||
    /** Requests, once prayed, appear again in a number of weeks */
 | 
			
		||||
    case Weeks;
 | 
			
		||||
    case Weeks = 'Weeks';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,12 +4,13 @@ namespace MyPrayerJournal;
 | 
			
		||||
 | 
			
		||||
use BitBadger\PDODocument\{DocumentException, Find};
 | 
			
		||||
use Exception;
 | 
			
		||||
use JsonSerializable;
 | 
			
		||||
use Visus\Cuid2\Cuid2;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A prayer request
 | 
			
		||||
 */
 | 
			
		||||
class Request
 | 
			
		||||
class Request implements JsonSerializable
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $id The ID for the request
 | 
			
		||||
@ -18,8 +19,8 @@ class Request
 | 
			
		||||
     * @param string|null $snoozedUntil The date/time the snooze expires for this request (null = not snoozed)
 | 
			
		||||
     * @param string|null $showAfter The date/time the current recurrence period is over (null = immediate)
 | 
			
		||||
     * @param Recurrence $recurrence The recurrence for this request
 | 
			
		||||
     * @param array|History[] $history The history of this request
 | 
			
		||||
     * @param array|Note[] $notes Notes regarding this request
 | 
			
		||||
     * @param History[] $history The history of this request
 | 
			
		||||
     * @param Note[] $notes Notes regarding this request
 | 
			
		||||
     * @throws Exception If the ID generation fails
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(public string $id = '', public string $enteredOn = '', public string $userId = '',
 | 
			
		||||
@ -44,4 +45,19 @@ class Request
 | 
			
		||||
        $req = Find::byId(Table::REQUEST, $id, self::class);
 | 
			
		||||
        return ($req && $req->userId == $_SESSION['user_id']) ? $req : false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function jsonSerialize(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        $values = [
 | 
			
		||||
            'id'         => $this->id,
 | 
			
		||||
            'enteredOn'  => $this->enteredOn,
 | 
			
		||||
            'userId'     => $this->userId,
 | 
			
		||||
            'recurrence' => $this->recurrence,
 | 
			
		||||
            'history'    => $this->history,
 | 
			
		||||
            'notes'      => $this->notes
 | 
			
		||||
        ];
 | 
			
		||||
        if (!is_null($this->snoozedUntil)) $values['snoozedUntil'] = $this->snoozedUntil;
 | 
			
		||||
        if (!is_null($this->showAfter))    $values['showAfter']    = $this->showAfter;
 | 
			
		||||
        return $values;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,17 +5,17 @@ namespace MyPrayerJournal;
 | 
			
		||||
/**
 | 
			
		||||
 * An action taken on a prayer request
 | 
			
		||||
 */
 | 
			
		||||
enum RequestAction
 | 
			
		||||
enum RequestAction: string
 | 
			
		||||
{
 | 
			
		||||
    /** The request was created */
 | 
			
		||||
    case Created;
 | 
			
		||||
    case Created = 'Created';
 | 
			
		||||
 | 
			
		||||
    /** The request was marked as having been prayed for */
 | 
			
		||||
    case Prayed;
 | 
			
		||||
    case Prayed = 'Prayed';
 | 
			
		||||
 | 
			
		||||
    /** The request was updated */
 | 
			
		||||
    case Updated;
 | 
			
		||||
    case Updated = 'Updated';
 | 
			
		||||
 | 
			
		||||
    /** The request was marked as answered */
 | 
			
		||||
    case Answered;
 | 
			
		||||
    case Answered = 'Answered';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								src/public/components/journal-items.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/public/components/journal-items.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
use BitBadger\PDODocument\{Custom, Query};
 | 
			
		||||
use BitBadger\PDODocument\Mapper\DocumentMapper;
 | 
			
		||||
use MyPrayerJournal\{Auth,History,Layout,Request,Table};
 | 
			
		||||
 | 
			
		||||
require '../../start.php';
 | 
			
		||||
 | 
			
		||||
Auth::requireUser(false);
 | 
			
		||||
bare_head();
 | 
			
		||||
 | 
			
		||||
$reqs = Custom::list(
 | 
			
		||||
    Query::selectFromTable(Table::REQUEST) . " WHERE data->>'userId' = :userId AND data->>'$.history[0].action' <> 'Answered'",
 | 
			
		||||
    [':userId' => Auth::user()['sub']], new DocumentMapper(Request::class));
 | 
			
		||||
 | 
			
		||||
if ($reqs->hasItems()) { ?>
 | 
			
		||||
    <section id=journalItems class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3" hx-target=this
 | 
			
		||||
             hx-swap=outerHTML aria-label="Prayer Requests"><?php
 | 
			
		||||
        $spacer = '<span> </span>';
 | 
			
		||||
        foreach ($reqs->items() as /** @var Request $req */ $req) {
 | 
			
		||||
            $withText = array_filter($req->history, fn($hist) => isset($hist->text));
 | 
			
		||||
            $text = $withText[array_key_first($withText)]->text; ?>
 | 
			
		||||
            <div class=col>
 | 
			
		||||
                <div class="card h-100">
 | 
			
		||||
                    <div class="card-header p-0 d-flex" role=toolbar><?php
 | 
			
		||||
                        page_link("/request/edit?id=$req->id", icon('edit'),
 | 
			
		||||
                            ['class' => 'btn btn-secondary', 'title' => 'Edit Request']); ?>
 | 
			
		||||
                        <?=$spacer?>
 | 
			
		||||
                        <button type=button class="btn btn-secondary" title="Add Notes" data-bs-toggle=modal
 | 
			
		||||
                                data-bs-target=#notesModal hx-get="/components/request/add-notes?id=<?=$req->id?>"
 | 
			
		||||
                                hx-target=#notesBody hx-swap=innerHTML><?=icon('comment');?></button>
 | 
			
		||||
                        <?=$spacer?>
 | 
			
		||||
                        <button type=button class="btn btn-secondary" title="Snooze Request" data-bs-toggle=modal
 | 
			
		||||
                                data-bs-target=#snoozeModal hx-get="/components/request/snooze?id=<?=$req->id?>"
 | 
			
		||||
                                hx-target=#snoozeBody hx-swap=innerHTML><?=icon('schedule');?></button>
 | 
			
		||||
                        <div class=flex-grow-1></div>
 | 
			
		||||
                        <button type=button class="btn btn-success w-25" hx-patch="/request/prayed?id=<?=$req->id?>"
 | 
			
		||||
                                title="Mark as Prayed"><?=icon('done');?></button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class=card-body>
 | 
			
		||||
                        <p class=request-text><?=htmlentities($text);?>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-footer text-end text-muted px-1 py-0">
 | 
			
		||||
                        <em>last activity <?=$req->history[0]->asOf?></em>
 | 
			
		||||
                        <?php /*
 | 
			
		||||
 TODO: relative time
 | 
			
		||||
  [] [
 | 
			
		||||
            match req.LastPrayed with
 | 
			
		||||
            | Some dt -> str "last prayed ";   relativeDate dt       now tz
 | 
			
		||||
            | None    -> str "last activity "; relativeDate req.AsOf now tz
 | 
			
		||||
            ] */ ?>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div><?php
 | 
			
		||||
        } ?>
 | 
			
		||||
    </section><?php
 | 
			
		||||
} else {
 | 
			
		||||
    Layout::noResults('No Active Requests', '/request/edit?id=new', 'Add a Request', <<<'TEXT'
 | 
			
		||||
        You have no requests to be shown; see the “Active” link above for snoozed or deferred requests, and
 | 
			
		||||
        the “Answered” link for answered requests
 | 
			
		||||
        TEXT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bare_foot();
 | 
			
		||||
							
								
								
									
										48
									
								
								src/public/journal.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/public/journal.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
<?php declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
use MyPrayerJournal\Auth;
 | 
			
		||||
 | 
			
		||||
require '../start.php';
 | 
			
		||||
 | 
			
		||||
Auth::requireUser();
 | 
			
		||||
 | 
			
		||||
$user = Auth::user();
 | 
			
		||||
$name = $user['first_name'] ?? 'Your';
 | 
			
		||||
page_head('Welcome'); ?>
 | 
			
		||||
<article class="container-fluid mt-3">
 | 
			
		||||
    <h2 class=pb-3><?=$name?><?=$name == 'Your' ? '' : '’s'?> Prayer Journal</h2>
 | 
			
		||||
    <p class="pb-3 text-center"><?php
 | 
			
		||||
        page_link('/request/new/edit', icon('add_box') . ' Add a Prayer Request', ['class' => 'btn btn-primary']); ?>
 | 
			
		||||
    <p hx-get=/components/journal-items hx-swap=outerHTML hx-trigger=load hx-target=this>
 | 
			
		||||
        Loading your prayer journal…
 | 
			
		||||
    <div id=notesModal class="modal fade" tabindex=-1 aria-labelledby=nodesModalLabel aria-hidden=true>
 | 
			
		||||
        <div class="modal-dialog modal-dialog-scrollable">
 | 
			
		||||
            <div class=modal-content>
 | 
			
		||||
                <div class=modal-header>
 | 
			
		||||
                    <h5 class=modal-title id=nodesModalLabel>Add Notes to Prayer Request</h5>
 | 
			
		||||
                    <button type=button class=btn-close data-bs-dismiss=modal aria-label=Close></button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class=modal-body id=notesBody></div>
 | 
			
		||||
                <div class=modal-footer>
 | 
			
		||||
                    <button type=button id=notesDismiss class="btn btn-secondary" data-bs-dismiss=modal>Close</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id=snoozeModal class="modal fade" tabindex=-1 aria-labelledby=snoozeModalLabel aria-hidden=true>
 | 
			
		||||
        <div class="modal-dialog modal-sm">
 | 
			
		||||
            <div class=modal-content>
 | 
			
		||||
                <div class=modal-header>
 | 
			
		||||
                    <h5 class=modal-title id=snoozeModalLabel>Snooze Prayer Request</h5>
 | 
			
		||||
                    <button type=button class=btn-close data-bs-dismiss=modal aria-label=Close></button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class=modal-body id=snoozeBody></div>
 | 
			
		||||
                <div class=modal-footer>
 | 
			
		||||
                    <button type=button id=snoozeDismiss class="btn btn-secondary" data-bs-dismiss=modal>Close</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</article><?php
 | 
			
		||||
page_foot();
 | 
			
		||||
 | 
			
		||||
@ -7,5 +7,5 @@ require '../../../start.php';
 | 
			
		||||
Auth::client()->exchange($_ENV['AUTH0_BASE_URL'] . '/user/log-on/success');
 | 
			
		||||
 | 
			
		||||
// TODO: get the possible redirect URL
 | 
			
		||||
header('Location: /');
 | 
			
		||||
header('Location: /journal');
 | 
			
		||||
exit();
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
<?php declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
use BitBadger\PDODocument\{Configuration, Definition, Mode};
 | 
			
		||||
use Dotenv\Dotenv;
 | 
			
		||||
use MyPrayerJournal\{Auth, Layout};
 | 
			
		||||
use MyPrayerJournal\{Auth, Layout, Table};
 | 
			
		||||
 | 
			
		||||
require __DIR__ . '/vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
@ -16,6 +17,10 @@ if (!is_null($auth0_session)) {
 | 
			
		||||
    $_SESSION['user_id'] = $auth0_session->user['sub'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Configuration::$pdoDSN = 'sqlite:' . implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', 'mpj.db']);
 | 
			
		||||
Configuration::$mode   = Mode::SQLite;
 | 
			
		||||
Definition::ensureTable(Table::REQUEST);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Is this an htmx request?
 | 
			
		||||
 *
 | 
			
		||||
@ -33,6 +38,16 @@ function page_link(string $href, string $text, array $attrs = []): void
 | 
			
		||||
    echo ">$text</a>";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generate a material icon
 | 
			
		||||
 *
 | 
			
		||||
 * @param string $name The name of the material icon
 | 
			
		||||
 * @return string The material icon wrapped in a `span` tag
 | 
			
		||||
 */
 | 
			
		||||
function icon(string $name): string {
 | 
			
		||||
    return "<span class=material-icons>$name</span>";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function page_head(string $title): void
 | 
			
		||||
{
 | 
			
		||||
    Layout::htmlHead($title);
 | 
			
		||||
@ -42,6 +57,11 @@ function page_head(string $title): void
 | 
			
		||||
    echo '<main role=main>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function bare_head(): void
 | 
			
		||||
{
 | 
			
		||||
    echo '<!DOCTYPE html><html lang=en><head><title></title></head><body>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function page_foot(): void
 | 
			
		||||
{
 | 
			
		||||
    echo '</main>';
 | 
			
		||||
@ -51,3 +71,8 @@ function page_foot(): void
 | 
			
		||||
    }
 | 
			
		||||
    echo '</body></html>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function bare_foot(): void
 | 
			
		||||
{
 | 
			
		||||
    echo '</body></html>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user