From 20ad50928ae13ddfe4c716c4db2ef6a65f3b4df4 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 23 Jun 2024 16:35:55 -0400 Subject: [PATCH] Move files, complete PHP migration The application works the same way as the F# version --- src/MyPrayerJournal/Handlers.fs | 4 +- src/composer.json | 4 +- src/convert-from-v3.php | 3 +- src/lib/Auth.php | 9 + src/lib/{ => Domain}/History.php | 2 +- src/lib/{ => Domain}/Note.php | 2 +- src/lib/{ => Domain}/Recurrence.php | 2 +- src/lib/{ => Domain}/RecurrencePeriod.php | 2 +- src/lib/{ => Domain}/Request.php | 3 +- src/lib/{ => Domain}/RequestAction.php | 2 +- src/lib/UI.php | 238 --------------------- src/lib/UI/Component.php | 175 +++++++++++++++ src/lib/{ => UI}/Layout.php | 21 +- src/lib/UI/RelativeDate.php | 66 ++++++ src/public/components/journal-items.php | 5 +- src/public/components/request/add-note.php | 2 +- src/public/components/request/notes.php | 4 +- src/public/components/request/snooze.php | 2 +- src/public/docs.php | 2 +- src/public/index.php | 2 +- src/public/journal.php | 9 +- src/public/legal/privacy-policy.php | 2 +- src/public/legal/terms-of-service.php | 6 +- src/public/request/cancel-snooze.php | 10 +- src/public/request/edit.php | 9 +- src/public/request/full.php | 5 +- src/public/request/note.php | 3 +- src/public/request/prayed.php | 6 +- src/public/request/save.php | 3 +- src/public/request/show.php | 17 ++ src/public/request/snooze.php | 5 +- src/public/requests/active.php | 8 +- src/public/requests/answered.php | 8 +- src/public/requests/snoozed.php | 8 +- src/public/user/log-off.php | 1 + src/public/user/log-on.php | 2 +- src/start.php | 3 +- 37 files changed, 354 insertions(+), 301 deletions(-) rename src/lib/{ => Domain}/History.php (95%) rename src/lib/{ => Domain}/Note.php (95%) rename src/lib/{ => Domain}/Recurrence.php (97%) rename src/lib/{ => Domain}/RecurrencePeriod.php (93%) rename src/lib/{ => Domain}/Request.php (99%) rename src/lib/{ => Domain}/RequestAction.php (92%) delete mode 100644 src/lib/UI.php create mode 100644 src/lib/UI/Component.php rename src/lib/{ => UI}/Layout.php (91%) create mode 100644 src/lib/UI/RelativeDate.php create mode 100644 src/public/request/show.php diff --git a/src/MyPrayerJournal/Handlers.fs b/src/MyPrayerJournal/Handlers.fs index 4d0d9f5..18a90b1 100644 --- a/src/MyPrayerJournal/Handlers.fs +++ b/src/MyPrayerJournal/Handlers.fs @@ -551,9 +551,9 @@ let routes = [ ] PATCH [ route "" Request.update // done - routef "/%s/cancel-snooze" Request.cancelSnooze + routef "/%s/cancel-snooze" Request.cancelSnooze // done routef "/%s/prayed" Request.prayed // done - routef "/%s/show" Request.show + routef "/%s/show" Request.show // done routef "/%s/snooze" Request.snooze // done ] POST [ diff --git a/src/composer.json b/src/composer.json index 6aad2e7..cc8af6b 100644 --- a/src/composer.json +++ b/src/composer.json @@ -15,7 +15,9 @@ }, "autoload": { "psr-4": { - "MyPrayerJournal\\": "lib/" + "MyPrayerJournal\\": "lib/", + "MyPrayerJournal\\Domain\\": "lib/Domain", + "MyPrayerJournal\\UI\\": "lib/UI" } }, "config": { diff --git a/src/convert-from-v3.php b/src/convert-from-v3.php index 03b3407..3ced31c 100644 --- a/src/convert-from-v3.php +++ b/src/convert-from-v3.php @@ -2,7 +2,8 @@ use BitBadger\PDODocument\{Configuration, Custom, Definition, Document, Mode}; use BitBadger\PDODocument\Mapper\ArrayMapper; -use MyPrayerJournal\{History, Note, Recurrence, RecurrencePeriod, Request, RequestAction, Table}; +use MyPrayerJournal\Domain\{History, Note, Recurrence, RecurrencePeriod, Request, RequestAction}; +use MyPrayerJournal\Table; require 'start.php'; diff --git a/src/lib/Auth.php b/src/lib/Auth.php index d667aad..2d7b2a3 100644 --- a/src/lib/Auth.php +++ b/src/lib/Auth.php @@ -5,10 +5,19 @@ namespace MyPrayerJournal; use Auth0\SDK\Auth0; use Auth0\SDK\Exception\ConfigurationException; +/** + * myPrayerJournal-specific authorization functions + */ class Auth { + /** @var Auth0|null The Auth0 client to use for requests (initialized on first use) */ private static ?Auth0 $auth0 = null; + /** + * Get the current Auth0 client + * + * @return Auth0 The current Auth0 client + */ public static function client(): Auth0 { if (is_null(self::$auth0)) { diff --git a/src/lib/History.php b/src/lib/Domain/History.php similarity index 95% rename from src/lib/History.php rename to src/lib/Domain/History.php index d555e96..27eea02 100644 --- a/src/lib/History.php +++ b/src/lib/Domain/History.php @@ -1,6 +1,6 @@ $name"; - } - - /** - * Render the journal items for the current user - * - * @throws DocumentException If any is encountered - */ - public static function journal(): void - { - Layout::bareHead(); - $reqs = Request::forJournal(); - if ($reqs->hasItems()) { ?> -
 '; - foreach ($reqs->items() as /** @var Request $req */ $req) { ?> -
-
- -
-

currentText());?> -

- -
-
-
-
-
-
-

'btn btn-primary']); ?> -
-
- $value) echo " $key=\"" . htmlspecialchars($value) . "\""; ?>>setTimezone(new DateTimeZone($_REQUEST['time_zone'])); - echo '' - . self::formatDistance('now', $parsed) . ''; - } - - // Many thanks to date-fns (https://date-fns.org) for this logic - /** - * Format the distance between two dates - * - * @param string|DateTimeImmutable $from The starting date/time - * @param string|DateTimeImmutable $to The ending date/time - * @return string The distance between two dates - * @throws Exception If date/time objects cannot be created - */ - public static function formatDistance(string|DateTimeImmutable $from, string|DateTimeImmutable $to): string - { - $aDay = 1_440.0; - $almost2Days = 2_520.0; - $aMonth = 43_200.0; - $twoMonths = 86_400.0; - - $dtFrom = is_string($from) ? new DateTimeImmutable($from) : $from; - $dtTo = is_string($to) ? new DateTimeImmutable($to) : $to; - $minutes = abs($dtFrom->getTimestamp() - $dtTo->getTimestamp()) / 60; - $months = round($minutes / $aMonth); - $years = round($months / 12); - - $typeAndNumber = match (true) { - $minutes < 1.0 => [FormatDistanceToken::LessThanXMinutes, 1], - $minutes < 45.0 => [FormatDistanceToken::XMinutes, round($minutes)], - $minutes < 90.0 => [FormatDistanceToken::AboutXHours, 1], - $minutes < $aDay => [FormatDistanceToken::AboutXHours, round($minutes / 60)], - $minutes < $almost2Days => [FormatDistanceToken::XDays, 1], - $minutes < $aMonth => [FormatDistanceToken::XDays, round($minutes / $aDay)], - $minutes < $twoMonths => [FormatDistanceToken::AboutXMonths, round($minutes / $aMonth)], - $months < 12 => [FormatDistanceToken::XMonths, round($minutes / $aMonth)], - $months % 12 < 3 => [FormatDistanceToken::AboutXYears, $years], - $months % 12 < 9 => [FormatDistanceToken::OverXYears, $years], - default => [FormatDistanceToken::AlmostXYears, $years] - }; - $format = match ($typeAndNumber[0]) { - FormatDistanceToken::LessThanXMinutes => ['less than a minute', 'less than %d minutes'], - FormatDistanceToken::XMinutes => ['a minute', '%d minutes'], - FormatDistanceToken::AboutXHours => ['about an hour', 'about %d hours'], - FormatDistanceToken::XHours => ['an hour', '%d hours'], - FormatDistanceToken::XDays => ['a day', '%d days'], - FormatDistanceToken::AboutXWeeks => ['about a week', 'about %d weeks'], - FormatDistanceToken::XWeeks => ['a week', '%d weeks'], - FormatDistanceToken::AboutXMonths => ['about a month', 'about %d months'], - FormatDistanceToken::XMonths => ['a month', '%d months'], - FormatDistanceToken::AboutXYears => ['about a year', 'about %d years'], - FormatDistanceToken::XYears => ['a year', '%d years'], - FormatDistanceToken::OverXYears => ['over a year', 'over %d years'], - FormatDistanceToken::AlmostXYears => ['almost a year', 'almost %d years'] - }; - $value = $typeAndNumber[1] == 1 ? $format[0] : sprintf($format[1], $typeAndNumber[1]); - return $dtFrom > $dtTo ? "$value ago" : "in $value"; - } - - public static function requestItem(Request $req): void - { - $btnClass = "btn btn-light mx-2"; - $restoreBtn = fn(string $id, string $link, string $title) => - ''; ?> -
id", self::icon('description'), - ['class' => $btnClass, 'title' => 'View Full Request']); - if (!$req->isAnswered()) { - self::pageLink("/request/edit?id=$req->id", self::icon('edit'), - ['class' => $btnClass, 'title' => 'Edit Request']); - } - if ($req->isSnoozed()) { - echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze'); - } elseif ($req->isPending()) { - echo $restoreBtn($req->id, 'show', 'Show Now'); - } - echo '

' . $req->currentText(); - if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?> -
- isSnoozed(): - echo 'Snooze expires '; self::relativeDate($req->snoozedUntil); - break; - case $req->isPending(): - echo 'Request appears next '; self::relativeDate($req->showAfter); - break; - default: - echo 'Answered '; self::relativeDate($req->history[0]->asOf); - } ?> - -

$reqs The list of requests to render - * @throws Exception If date/time instances are not valid - */ - public static function requestList(DocumentList $reqs): void - { - echo '
'; - foreach ($reqs->items() as $req) self::requestItem($req); - echo '
'; - } -} - -enum FormatDistanceToken -{ - case LessThanXMinutes; - case XMinutes; - case AboutXHours; - case XHours; - case XDays; - case AboutXWeeks; - case XWeeks; - case AboutXMonths; - case XMonths; - case AboutXYears; - case XYears; - case OverXYears; - case AlmostXYears; -} diff --git a/src/lib/UI/Component.php b/src/lib/UI/Component.php new file mode 100644 index 0000000..cecb247 --- /dev/null +++ b/src/lib/UI/Component.php @@ -0,0 +1,175 @@ +$name"; + } + + /** + * Render the journal items for the current user + * + * @throws DocumentException If any is encountered + */ + public static function journal(): void + { + Layout::bareHead(); + $reqs = Request::forJournal(); + if ($reqs->hasItems()) { ?> +
 '; + foreach ($reqs->items() as /** @var Request $req */ $req) { ?> +
+
+ +
+

currentText());?> +

+ +
+
+
+
+
+
+

+ 'btn btn-primary'])?> +
+
$acc . sprintf(' %s="%s"', $key, $attrs[$key]), ''); + return sprintf('%s', $href, + $href, $extraAttrs, $text); + } + + /** + * Create a relative date with a tooltip with the actual date/time + * + * @return string The HTML for the relative date + * @throws Exception If the date/time cannot be parsed + */ + public static function relativeDate(string $date): string + { + $parsed = new DateTimeImmutable($date); + $inZone = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone'])); + return sprintf('%s', date_format($inZone, 'l, F j, Y \a\t g:ia T'), + RelativeDate::between('now', $parsed)); + } + + /** + * Render a request list item + * + * @param Request $req The request on which the list items should be based + * @throws Exception If date/time values cannot be parsed + */ + public static function requestItem(Request $req): void + { + $btnClass = "btn btn-light mx-2"; + $restoreBtn = fn(string $id, string $link, string $title) => sprintf( + '', + $btnClass, $link, $id, htmlspecialchars($title), $id, self::icon('restore')); ?> +
id?>>id", self::icon('description'), + ['class' => $btnClass, 'title' => 'View Full Request']); + if (!$req->isAnswered()) { + echo self::pageLink("/request/edit?id=$req->id", self::icon('edit'), + ['class' => $btnClass, 'title' => 'Edit Request']); + } + if ($req->isSnoozed()) { + echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze'); + } elseif ($req->isPending()) { + echo $restoreBtn($req->id, 'show', 'Show Now'); + } + echo '

' . $req->currentText(); + if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?> +
+ isSnoozed()) { + echo 'Snooze expires ' . self::relativeDate($req->snoozedUntil); + } elseif ($req->isPending()) { + echo 'Request appears next ' . self::relativeDate($req->showAfter); + } else { + echo 'Answered ' . self::relativeDate($req->history[0]->asOf); + } ?> + +

$reqs The list of requests to render + * @throws Exception If date/time instances are not valid + */ + public static function requestList(DocumentList $reqs): void + { + echo '
'; + foreach ($reqs->items() as $req) self::requestItem($req); + echo '
'; + } +} diff --git a/src/lib/Layout.php b/src/lib/UI/Layout.php similarity index 91% rename from src/lib/Layout.php rename to src/lib/UI/Layout.php index 11abe8a..b25e64c 100644 --- a/src/lib/Layout.php +++ b/src/lib/UI/Layout.php @@ -1,9 +1,10 @@ ['class' => 'is-active-route'], default => [] }; - echo '