From df20936af2d1d7a693cf0a56c9f53fa74320007c Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Thu, 30 May 2024 21:58:54 -0400 Subject: [PATCH] WIP on document conversion --- .gitignore | 1 + src/app-config.php | 20 +-- src/cli-start.php | 6 +- src/composer.json | 23 ++++ src/composer.lock | 49 ++++++++ src/lib/Data.php | 7 ++ src/lib/Domain/Feed.php | 71 +++++++++++ src/lib/Domain/Item.php | 78 ++++++++++++ src/lib/Domain/Table.php | 17 +++ src/lib/Domain/User.php | 17 +++ src/lib/Feed.php | 191 +++++++++++++++++------------ src/lib/FeedItem.php | 33 ++++- src/lib/ItemList.php | 32 +++-- src/lib/Key.php | 4 + src/lib/Security.php | 61 +++++---- src/public/bookmark.php | 16 ++- src/public/docs/feeds.php | 4 + src/public/docs/index.php | 4 + src/public/docs/items.php | 4 + src/public/docs/security-modes.php | 4 + src/public/docs/the-cli.php | 4 + src/public/feed/index.php | 27 ++-- src/public/feed/items.php | 11 +- src/public/feeds.php | 5 + src/public/index.php | 5 + src/public/item.php | 13 +- src/public/search.php | 4 + src/public/user/log-off.php | 2 +- src/public/user/log-on.php | 3 + src/start.php | 51 +++++--- src/user-config.dist.php | 1 + src/util/refresh.php | 11 +- src/util/search.php | 20 +-- src/util/user.php | 79 +++++++----- 34 files changed, 674 insertions(+), 204 deletions(-) create mode 100644 src/composer.json create mode 100644 src/composer.lock create mode 100644 src/lib/Domain/Feed.php create mode 100644 src/lib/Domain/Item.php create mode 100644 src/lib/Domain/Table.php create mode 100644 src/lib/Domain/User.php diff --git a/.gitignore b/.gitignore index f9b4ff3..aaf5ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea +vendor src/data/*.db src/user-config.php diff --git a/src/app-config.php b/src/app-config.php index bf43f2c..c06208c 100644 --- a/src/app-config.php +++ b/src/app-config.php @@ -1,6 +1,9 @@ updated_on) ? null : new DateTimeImmutable($this->updated_on); + } + + /** + * The date/time this feed was last checked + * + * @return DateTimeImmutable|null The last checked date, or null if it is not set + * @throws Exception If the date/time is an invalid format + */ + public function checkedOn(): ?DateTimeImmutable + { + return is_null($this->checked_on) ? null : new DateTimeImmutable($this->checked_on); + } + + /** + * Create a document from the parsed feed + * + * @param \FeedReaderCentral\Feed $feed The parsed feed + * @return static The document constructed from the parsed feed + */ + public static function fromParsed(\FeedReaderCentral\Feed $feed): static + { + $it = new static(); + $it->user_id = $_SESSION[Key::USER_ID]; + $it->url = $feed->url; + $it->title = $feed->title; + $it->updated_on = $feed->updatedOn; + $it->checked_on = Data::formatDate('now'); + + return $it; + } +} diff --git a/src/lib/Domain/Item.php b/src/lib/Domain/Item.php new file mode 100644 index 0000000..e0c17d5 --- /dev/null +++ b/src/lib/Domain/Item.php @@ -0,0 +1,78 @@ +is_read <> 0; + } + + /** + * Is the item bookmarked? + * + * @return bool True if the item is bookmarked, false if not + */ + public function isBookmarked(): bool + { + return $this->is_bookmarked <> 0; + } + + /** + * Create an item document from a parsed feed item + * + * @param int $feedId The ID of the feed to which this item belongs + * @param FeedItem $item The parsed feed item + * @return static The item document + */ + public static function fromFeedItem(int $feedId, FeedItem $item): static + { + $it = new static(); + $it->feed_id = $feedId; + $it->item_guid = $item->guid; + $it->item_link = $item->link; + $it->title = $item->title; + $it->published_on = $item->publishedOn; + $it->updated_on = $item->updatedOn; + $it->content = $item->content; + + return $it; + } +} diff --git a/src/lib/Domain/Table.php b/src/lib/Domain/Table.php new file mode 100644 index 0000000..b9c5a95 --- /dev/null +++ b/src/lib/Domain/Table.php @@ -0,0 +1,17 @@ +title = self::rssValue($channel, 'title'); $feed->url = $url; $feed->updatedOn = Data::formatDate($updatedOn); @@ -267,21 +287,23 @@ class Feed { * @return bool|SQLite3Result The result if the update is successful, false if it failed */ private static function updateItem(int $itemId, FeedItem $item, SQLite3 $db): bool|SQLite3Result { - $query = $db->prepare(<<<'SQL' - UPDATE item - SET title = :title, - published_on = :published, - updated_on = :updated, - content = :content, - is_read = 0 - WHERE id = :id - SQL); - $query->bindValue(':title', $item->title); - $query->bindValue(':published', $item->publishedOn); - $query->bindValue(':updated', $item->updatedOn); - $query->bindValue(':content', $item->content); - $query->bindValue(':id', $itemId); - return $query->execute(); + Patch::byId(Table::ITEM, $itemId, $item->patchFields(), $db); +// $query = $db->prepare(<<<'SQL' +// UPDATE item +// SET title = :title, +// published_on = :published, +// updated_on = :updated, +// content = :content, +// is_read = 0 +// WHERE id = :id +// SQL); +// $query->bindValue(':title', $item->title); +// $query->bindValue(':published', $item->publishedOn); +// $query->bindValue(':updated', $item->updatedOn); +// $query->bindValue(':content', $item->content); +// $query->bindValue(':id', $itemId); +// return $query->execute(); + return true; } /** @@ -293,21 +315,23 @@ class Feed { * @return bool|SQLite3Result The result if the update is successful, false if it failed */ private static function addItem(int $feedId, FeedItem $item, SQLite3 $db): bool|SQLite3Result { - $query = $db->prepare(<<<'SQL' - INSERT INTO item ( - feed_id, item_guid, item_link, title, published_on, updated_on, content - ) VALUES ( - :feed, :guid, :link, :title, :published, :updated, :content - ) - SQL); - $query->bindValue(':feed', $feedId); - $query->bindValue(':guid', $item->guid); - $query->bindValue(':link', $item->link); - $query->bindValue(':title', $item->title); - $query->bindValue(':published', $item->publishedOn); - $query->bindValue(':updated', $item->updatedOn); - $query->bindValue(':content', $item->content); - return $query->execute(); + Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item), $db); +// $query = $db->prepare(<<<'SQL' +// INSERT INTO item ( +// feed_id, item_guid, item_link, title, published_on, updated_on, content +// ) VALUES ( +// :feed, :guid, :link, :title, :published, :updated, :content +// ) +// SQL); +// $query->bindValue(':feed', $feedId); +// $query->bindValue(':guid', $item->guid); +// $query->bindValue(':link', $item->link); +// $query->bindValue(':title', $item->title); +// $query->bindValue(':published', $item->publishedOn); +// $query->bindValue(':updated', $item->updatedOn); +// $query->bindValue(':content', $item->content); +// return $query->execute(); + return true; } /** @@ -321,6 +345,7 @@ class Feed { public static function updateItems(int $feedId, Feed $feed, DateTimeInterface $lastChecked, SQLite3 $db): array { $results = array_map(function ($item) use ($db, $feedId) { + // TODO: convert this query $existsQuery = $db->prepare( 'SELECT id, published_on, updated_on FROM item WHERE feed_id = :feed AND item_guid = :guid'); $existsQuery->bindValue(':feed', $feedId); @@ -357,6 +382,7 @@ class Feed { } try { + // TODO: convert this query $sql = match (PURGE_TYPE) { self::PURGE_READ => 'AND is_read = 1', self::PURGE_BY_DAYS => 'AND date(coalesce(updated_on, published_on)) < date(:oldest)', @@ -401,21 +427,24 @@ class Feed { $itemUpdate = self::updateItems($feedId, $feed, $lastChecked, $db); if (key_exists('error', $itemUpdate)) return $itemUpdate; - $urlUpdate = $url == $feed->url ? '' : ', url = :url'; - $feedUpdate = $db->prepare(<<bindValue(':title', $feed->title); - $feedUpdate->bindValue(':updated', $feed->updatedOn); - $feedUpdate->bindValue(':checked', Data::formatDate('now')); - $feedUpdate->bindValue(':id', $feedId); - if ($urlUpdate != '') $feedUpdate->bindValue(':url', $feed->url); - if (!$feedUpdate->execute()) return Data::error($db); + $patch = ['title' => $feed->title, 'updated_on' => $feed->updatedOn, 'checked_on' => Data::formatDate('now')]; + if ($url == $feed->url) $patch['url'] = $feed->url; + Patch::byId(Table::FEED, $feedId, $patch, $db); +// $urlUpdate = $url == $feed->url ? '' : ', url = :url'; +// $feedUpdate = $db->prepare(<<bindValue(':title', $feed->title); +// $feedUpdate->bindValue(':updated', $feed->updatedOn); +// $feedUpdate->bindValue(':checked', Data::formatDate('now')); +// $feedUpdate->bindValue(':id', $feedId); +// if ($urlUpdate != '') $feedUpdate->bindValue(':url', $feed->url); +// if (!$feedUpdate->execute()) return Data::error($db); return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId, $db); } @@ -432,49 +461,55 @@ class Feed { $feed = $feedExtract['ok']; - $existsQuery = $db->prepare('SELECT COUNT(*) FROM feed WHERE user_id = :user AND url = :url'); - $existsQuery->bindValue(':user', $_SESSION[Key::USER_ID]); - $existsQuery->bindValue(':url', $feed->url); - if (!($exists = $existsQuery->execute())) return Data::error($db); - if ($exists->fetchArray(SQLITE3_NUM)[0] > 0) return ['error' => "Already subscribed to feed $feed->url"]; + $whereUserAndUrl = ' WHERE ' . Query::whereByField(Field::EQ('user_id', ''), '@user') + . ' AND ' . Query::whereByField(Field::EQ('url', ''), '@url'); + $userAndUrlParams = ['@user' => $_SESSION[Key::USER_ID], '@url' => $feed->url]; + if (Custom::scalar('SELECT EXISTS (SELECT 1 FROM ' . Table::FEED . $whereUserAndUrl . ')', $userAndUrlParams, + Results::toExists(...), $db)) { + return ['error' => "Already subscribed to feed $feed->url"]; + } - $query = $db->prepare(<<<'SQL' - INSERT INTO feed ( - user_id, url, title, updated_on, checked_on - ) VALUES ( - :user, :url, :title, :updated, :checked - ) - SQL); - $query->bindValue(':user', $_SESSION[Key::USER_ID]); - $query->bindValue(':url', $feed->url); - $query->bindValue(':title', $feed->title); - $query->bindValue(':updated', $feed->updatedOn); - $query->bindValue(':checked', Data::formatDate('now')); - if (!$query->execute()) return Data::error($db); + Document::insert(Table::FEED, FeedDocument::fromParsed($feed), $db); +// $query = $db->prepare(<<<'SQL' +// INSERT INTO feed ( +// user_id, url, title, updated_on, checked_on +// ) VALUES ( +// :user, :url, :title, :updated, :checked +// ) +// SQL); +// $query->bindValue(':user', $_SESSION[Key::USER_ID]); +// $query->bindValue(':url', $feed->url); +// $query->bindValue(':title', $feed->title); +// $query->bindValue(':updated', $feed->updatedOn); +// $query->bindValue(':checked', Data::formatDate('now')); +// if (!$query->execute()) return Data::error($db); + $doc = Custom::single(Query::selectFromTable(Table::FEED) . $whereUserAndUrl, $userAndUrlParams, + Results::fromData(...), Domain\Feed::class, $db); + if (!$doc) return ['error' => 'Could not retrieve inserted feed']; - $feedId = $db->lastInsertRowID(); - $result = self::updateItems($feedId, $feed, date_create_immutable(WWW_EPOCH), $db); + $result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH), $db); if (key_exists('error', $result)) return $result; - return ['ok' => $feedId]; + return ['ok' => $doc->id]; } /** * Update an RSS feed * - * @param array $existing The existing RSS feed + * @param FeedDocument $existing The existing RSS feed * @param string $url The URL with which the existing feed should be modified * @param SQLite3 $db The database connection on which to execute the update * @return bool[]|string[] [ 'ok' => true ] if successful, [ 'error' => message ] if not */ - public static function update(array $existing, string $url, SQLite3 $db): array { + public static function update(FeedDocument $existing, string $url, SQLite3 $db): array { + // TODO: convert this query (need to make Query\Patch::update visible) $query = $db->prepare('UPDATE feed SET url = :url WHERE id = :id AND user_id = :user'); $query->bindValue(':url', $url); - $query->bindValue(':id', $existing['id']); + $query->bindValue(':id', $existing->id); $query->bindValue(':user', $_SESSION[Key::USER_ID]); if (!$query->execute()) return Data::error($db); - return self::refreshFeed($existing['id'], $url, $db); + return self::refreshFeed($existing->id, $url, $db); } /** @@ -520,12 +555,14 @@ class Feed { * * @param int $feedId The ID of the feed to retrieve * @param SQLite3 $db A database connection to use to retrieve the feed - * @return array|bool The data for the feed if found, false if not found + * @return FeedDocument|false The data for the feed if found, false if not found */ - public static function retrieveById(int $feedId, SQLite3 $db): array|bool { - $query = $db->prepare('SELECT * FROM feed WHERE id = :id AND user_id = :user'); - $query->bindValue(':id', $feedId); - $query->bindValue(':user', $_SESSION[Key::USER_ID]); - return ($result = $query->execute()) ? $result->fetchArray(SQLITE3_ASSOC) : false; + public static function retrieveById(int $feedId, SQLite3 $db): FeedDocument|false { + $doc = Find::byId(Table::FEED, $feedId, FeedDocument::class, $db); + return $doc && $doc->user_id == $_SESSION[Key::USER_ID] ? $doc : false; +// $query = $db->prepare('SELECT * FROM feed WHERE id = :id AND user_id = :user'); +// $query->bindValue(':id', $feedId); +// $query->bindValue(':user', $_SESSION[Key::USER_ID]); +// return ($result = $query->execute()) ? $result->fetchArray(SQLITE3_ASSOC) : false; } } diff --git a/src/lib/FeedItem.php b/src/lib/FeedItem.php index ab6aa04..1b2cd95 100644 --- a/src/lib/FeedItem.php +++ b/src/lib/FeedItem.php @@ -1,4 +1,7 @@ $this->title, + 'published_on' => $this->publishedOn, + 'updated_on' => $this->updatedOn, + 'content' => $this->content, + 'is_read' => 0 + ]; + } + /** * Construct a feed item from an Atom feed's `` tag * * @param DOMNode $node The XML node from which a feed item should be constructed - * @return FeedItem A feed item constructed from the given node + * @return static A feed item constructed from the given node */ - public static function fromAtom(DOMNode $node): FeedItem { + public static function fromAtom(DOMNode $node): static + { $guid = Feed::atomValue($node, 'id'); $link = ''; foreach ($node->getElementsByTagName('link') as $linkElt) { @@ -43,7 +63,7 @@ class FeedItem { } if ($link == '' && str_starts_with($guid, 'http')) $link = $guid; - $item = new FeedItem(); + $item = new static(); $item->guid = $guid; $item->title = Feed::atomValue($node, 'title'); $item->link = $link; @@ -58,14 +78,15 @@ class FeedItem { * Construct a feed item from an RSS feed's `` tag * * @param DOMNode $node The XML node from which a feed item should be constructed - * @return FeedItem A feed item constructed from the given node + * @return static A feed item constructed from the given node */ - public static function fromRSS(DOMNode $node): FeedItem { + public static function fromRSS(DOMNode $node): static + { $itemGuid = Feed::rssValue($node, 'guid'); $updNodes = $node->getElementsByTagNameNS(Feed::ATOM_NS, 'updated'); $encNodes = $node->getElementsByTagNameNS(Feed::CONTENT_NS, 'encoded'); - $item = new FeedItem(); + $item = new static(); $item->guid = $itemGuid == 'guid not found' ? Feed::rssValue($node, 'link') : $itemGuid; $item->title = Feed::rssValue($node, 'title'); $item->link = Feed::rssValue($node, 'link'); diff --git a/src/lib/ItemList.php b/src/lib/ItemList.php index 2f9866b..7697149 100644 --- a/src/lib/ItemList.php +++ b/src/lib/ItemList.php @@ -1,4 +1,9 @@ execute(); if (!$result) { $this->error = Data::error($db)['error']; @@ -56,7 +62,8 @@ class ItemList { * @param array $parameters Parameters to be added to the query (key index 0, value index 1; optional) * @return SQLite3Stmt The query, ready to be executed */ - private static function makeQuery(SQLite3 $db, array $criteria, array $parameters = []): SQLite3Stmt { + private static function makeQuery(SQLite3 $db, array $criteria, array $parameters = []): SQLite3Stmt + { $where = empty($criteria) ? '' : 'AND ' . implode(' AND ', $criteria); $sql = <<linkFeed = true; return $list; @@ -89,7 +97,8 @@ class ItemList { * @param SQLite3 $db The database connection to use to obtain items * @return static An item list with all unread items */ - public static function allUnread(SQLite3 $db): static { + public static function allUnread(SQLite3 $db): static + { $list = new static($db, self::makeQuery($db, ['item.is_read = 0']), 'Unread'); $list->linkFeed = true; return $list; @@ -102,7 +111,8 @@ class ItemList { * @param SQLite3 $db The database connection to use to obtain items * @return static An item list with all items for the given feed */ - public static function allForFeed(int $feedId, SQLite3 $db): static { + public static function allForFeed(int $feedId, SQLite3 $db): static + { $list = new static($db, self::makeQuery($db, ['feed.id = :feed'], [[':feed', $feedId]]), '', "/feed/items?id=$feedId"); $list->showIndicators = true; @@ -116,7 +126,8 @@ class ItemList { * @param SQLite3 $db The database connection to use to obtain items * @return static An item list with unread items for the given feed */ - public static function unreadForFeed(int $feedId, SQLite3 $db): static { + public static function unreadForFeed(int $feedId, SQLite3 $db): static + { return new static($db, self::makeQuery($db, ['feed.id = :feed', 'item.is_read = 0'], [[':feed', $feedId]]), 'Unread', "/feed/items?id=$feedId&unread"); } @@ -128,7 +139,8 @@ class ItemList { * @param SQLite3 $db The database connection to use to obtain items * @return static An item list with bookmarked items for the given feed */ - public static function bookmarkedForFeed(int $feedId, SQLite3 $db): static { + public static function bookmarkedForFeed(int $feedId, SQLite3 $db): static + { return new static($db, self::makeQuery($db, ['feed.id = :feed', 'item.is_bookmarked = 1'], [[':feed', $feedId]]), 'Bookmarked', "/feed/items?id=$feedId&bookmarked"); @@ -142,7 +154,8 @@ class ItemList { * @param SQLite3 $db The database connection to use to obtain items * @return static An item list match the given search terms */ - public static function matchingSearch(string $search, bool $isBookmarked, SQLite3 $db): static { + public static function matchingSearch(string $search, bool $isBookmarked, SQLite3 $db): static + { $where = $isBookmarked ? ['item.is_bookmarked = 1'] : []; $where[] = 'item.id IN (SELECT ROWID FROM item_search WHERE content MATCH :search)'; $list = new static($db, self::makeQuery($db, $where, [[':search', $search]]), @@ -156,7 +169,8 @@ class ItemList { /** * Render this item list */ - public function render(): void { + public function render(): void + { if ($this->isError()) { ?>

Error retrieving list:
error?>prepare('SELECT * FROM frc_user WHERE email = :email'); - $query->bindValue(':email', $email); - $result = $query->execute(); - return $result ? $result->fetchArray(SQLITE3_ASSOC) : false; + public static function findUserByEmail(string $email, SQLite3 $db): User|false { + return Find::firstByField(Table::USER, Field::EQ('email', $email), User::class, $db); +// $query = $db->prepare('SELECT * FROM frc_user WHERE email = :email'); +// $query->bindValue(':email', $email); +// $result = $query->execute(); +// return $result ? $result->fetchArray(SQLITE3_ASSOC) : false; } /** @@ -45,27 +55,32 @@ class Security { * @param SQLite3 $db The data connection to use to add the user */ public static function addUser(string $email, string $password, SQLite3 $db): void { - $query = $db->prepare('INSERT INTO frc_user (email, password) VALUES (:email, :password)'); - $query->bindValue(':email', $email); - $query->bindValue(':password', password_hash($password, self::PW_ALGORITHM)); - $query->execute(); + $user = new User(); + $user->email = $email; + $user->password = $password; + Document::insert(Table::USER, $user, $db); +// $query = $db->prepare('INSERT INTO frc_user (email, password) VALUES (:email, :password)'); +// $query->bindValue(':email', $email); +// $query->bindValue(':password', password_hash($password, self::PW_ALGORITHM)); +// $query->execute(); } /** * Verify a user's password * - * @param array $user The user information retrieved from the database + * @param User $user The user information retrieved from the database * @param string $password The password provided by the user * @param string|null $returnTo The URL to which the user should be redirected * @param SQLite3 $db The database connection to use to verify the user's credentials */ - private static function verifyPassword(array $user, string $password, ?string $returnTo, SQLite3 $db): void { - if (password_verify($password, $user['password'])) { - if (password_needs_rehash($user['password'], self::PW_ALGORITHM)) { - $rehash = $db->prepare('UPDATE frc_user SET password = :hash WHERE id = :id'); - $rehash->bindValue(':hash', password_hash($password, self::PW_ALGORITHM)); - $rehash->bindValue(':id', $user['id']); - $rehash->execute(); + private static function verifyPassword(User $user, string $password, ?string $returnTo, SQLite3 $db): void { + if (password_verify($password, $user->password)) { + if (password_needs_rehash($user->password, self::PW_ALGORITHM)) { + Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)], $db); +// $rehash = $db->prepare('UPDATE frc_user SET password = :hash WHERE id = :id'); +// $rehash->bindValue(':hash', password_hash($password, self::PW_ALGORITHM)); +// $rehash->bindValue(':id', $user['id']); +// $rehash->execute(); } $_SESSION[Key::USER_ID] = $user['id']; $_SESSION[Key::USER_EMAIL] = $user['email']; @@ -104,10 +119,12 @@ class Security { * @param SQLite3 $db The database connection to use in updating the password */ public static function updatePassword(string $email, string $password, SQLite3 $db): void { - $query = $db->prepare('UPDATE frc_user SET password = :password WHERE email = :email'); - $query->bindValue(':password', password_hash($password, self::PW_ALGORITHM)); - $query->bindValue(':email', $email); - $query->execute(); + Patch::byField(Table::USER, Field::EQ('email', $email), + ['password' => password_hash($password, self::PW_ALGORITHM)], $db); +// $query = $db->prepare('UPDATE frc_user SET password = :password WHERE email = :email'); +// $query->bindValue(':password', password_hash($password, self::PW_ALGORITHM)); +// $query->bindValue(':email', $email); +// $query->execute(); } /** diff --git a/src/public/bookmark.php b/src/public/bookmark.php index 4c16617..13e11ce 100644 --- a/src/public/bookmark.php +++ b/src/public/bookmark.php @@ -6,6 +6,12 @@ * This will display a button which will either add or remove a bookmark for a given item. */ +use BitBadger\Documents\SQLite\Patch; +use FeedReaderCentral\Data; +use FeedReaderCentral\Domain\Table; +use FeedReaderCentral\Key; +use FeedReaderCentral\Security; + include '../start.php'; $db = Data::getConnection(); @@ -13,6 +19,7 @@ Security::verifyUser($db); $id = $_GET['id']; +// TODO: adapt query once "by fields" is available $existsQuery = $db->prepare( 'SELECT item.id FROM item INNER JOIN feed ON feed.id = item.feed_id WHERE item.id = :id AND feed.user_id = :user'); $existsQuery->bindValue(':id', $id); @@ -29,10 +36,11 @@ if (key_exists('action', $_GET)) { $flag = 0; } if (isset($flag)) { - $update = $db->prepare('UPDATE item SET is_bookmarked = :flag WHERE id = :id'); - $update->bindValue(':id', $id); - $update->bindValue(':flag', $flag); - if (!$update->execute()) die(Data::error($db)['error']); + Patch::byId(Table::ITEM, $id, ['is_bookmarked' => $flag], $db); +// $update = $db->prepare('UPDATE item SET is_bookmarked = :flag WHERE id = :id'); +// $update->bindValue(':id', $id); +// $update->bindValue(':flag', $flag); +// if (!$update->execute()) die(Data::error($db)['error']); } } diff --git a/src/public/docs/feeds.php b/src/public/docs/feeds.php index 0f9f2e0..527039d 100644 --- a/src/public/docs/feeds.php +++ b/src/public/docs/feeds.php @@ -1,4 +1,8 @@ prepare('DELETE FROM item WHERE feed_id = :feed'); - $itemDelete->bindValue(':feed', $feed['id']); - if (!$itemDelete->execute()) add_error(Data::error($db)['error']); - $feedDelete = $db->prepare('DELETE FROM feed WHERE id = :feed'); - $feedDelete->bindValue(':feed', $feed['id']); - if ($feedDelete->execute()) { + Delete::byField(Table::ITEM, Field::EQ('feed_id', $feed->id), $db); +// $itemDelete = $db->prepare('DELETE FROM item WHERE feed_id = :feed'); +// $itemDelete->bindValue(':feed', $feed['id']); +// if (!$itemDelete->execute()) add_error(Data::error($db)['error']); + Delete::byId(Table::FEED, $feed->id, $db); +// $feedDelete = $db->prepare('DELETE FROM feed WHERE id = :feed'); +// $feedDelete->bindValue(':feed', $feed['id']); +// if ($feedDelete->execute()) { add_info('Feed “' . htmlentities($feed['title']) . '” deleted successfully'); - } else { - add_error(Data::error($db)['error']); - } +// } else { +// add_error(Data::error($db)['error']); +// } frc_redirect('/feeds'); } diff --git a/src/public/feed/items.php b/src/public/feed/items.php index a92269f..f83d20a 100644 --- a/src/public/feed/items.php +++ b/src/public/feed/items.php @@ -5,6 +5,11 @@ * Lists items in a given feed (all, unread, or bookmarked) */ +use FeedReaderCentral\Data; +use FeedReaderCentral\Feed; +use FeedReaderCentral\ItemList; +use FeedReaderCentral\Security; + include '../../start.php'; $db = Data::getConnection(); @@ -13,9 +18,9 @@ Security::verifyUser($db); if (!($feed = Feed::retrieveById($_GET['id'], $db))) not_found(); $list = match (true) { - key_exists('unread', $_GET) => ItemList::unreadForFeed($feed['id'], $db), - key_exists('bookmarked', $_GET) => ItemList::bookmarkedForFeed($feed['id'], $db), - default => ItemList::allForFeed($feed['id'], $db) + key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id, $db), + key_exists('bookmarked', $_GET) => ItemList::bookmarkedForFeed($feed->id, $db), + default => ItemList::allForFeed($feed->id, $db) }; page_head(($list->itemType != '' ? "$list->itemType Items | " : '') . strip_tags($feed['title'])); diff --git a/src/public/feeds.php b/src/public/feeds.php index b765b70..c6af217 100644 --- a/src/public/feeds.php +++ b/src/public/feeds.php @@ -5,11 +5,16 @@ * List feeds and provide links for maintenance actions */ +use FeedReaderCentral\Data; +use FeedReaderCentral\Key; +use FeedReaderCentral\Security; + include '../start.php'; $db = Data::getConnection(); Security::verifyUser($db); +// TODO: adapt query when document list is done $feedQuery = $db->prepare('SELECT * FROM feed WHERE user_id = :user ORDER BY lower(title)'); $feedQuery->bindValue(':user', $_SESSION[Key::USER_ID]); if (!($feedResult = $feedQuery->execute())) { diff --git a/src/public/index.php b/src/public/index.php index 73307e7..d92e2e2 100644 --- a/src/public/index.php +++ b/src/public/index.php @@ -5,6 +5,11 @@ * Displays a list of unread or bookmarked items for the current user */ +use FeedReaderCentral\Data; +use FeedReaderCentral\Feed; +use FeedReaderCentral\ItemList; +use FeedReaderCentral\Security; + include '../start.php'; $db = Data::getConnection(); diff --git a/src/public/item.php b/src/public/item.php index 3633f87..8be3978 100644 --- a/src/public/item.php +++ b/src/public/item.php @@ -6,6 +6,12 @@ * Retrieves and displays an item from a feed belonging to the current user */ +use BitBadger\Documents\SQLite\Patch; +use FeedReaderCentral\Data; +use FeedReaderCentral\Domain\Table; +use FeedReaderCentral\Key; +use FeedReaderCentral\Security; + include '../start.php'; $db = Data::getConnection(); @@ -22,9 +28,10 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') { $isValidQuery->bindValue(':user', $_SESSION[Key::USER_ID]); $isValidResult = $isValidQuery->execute(); if ($isValidResult && $isValidResult->fetchArray(SQLITE3_NUM)[0] == 1) { - $keepUnread = $db->prepare('UPDATE item SET is_read = 0 WHERE id = :id'); - $keepUnread->bindValue(':id', $_POST['id']); - $keepUnread->execute(); + Patch::byId(Table::ITEM, $_POST['id'], ['is_read' => 0], $db); +// $keepUnread = $db->prepare('UPDATE item SET is_read = 0 WHERE id = :id'); +// $keepUnread->bindValue(':id', $_POST['id']); +// $keepUnread->execute(); } $db->close(); frc_redirect($_POST['from']); diff --git a/src/public/search.php b/src/public/search.php index af5e6ab..790a1a5 100644 --- a/src/public/search.php +++ b/src/public/search.php @@ -6,6 +6,10 @@ * Search for items across all feeds */ +use FeedReaderCentral\Data; +use FeedReaderCentral\ItemList; +use FeedReaderCentral\Security; + include '../start.php'; $db = Data::getConnection(); diff --git a/src/public/user/log-off.php b/src/public/user/log-off.php index 73fd811..9c6baea 100644 --- a/src/public/user/log-off.php +++ b/src/public/user/log-off.php @@ -5,6 +5,6 @@ include '../../start.php'; -if (key_exists(Key::USER_ID, $_SESSION)) session_destroy(); +if (key_exists(FeedReaderCentral\Key::USER_ID, $_SESSION)) session_destroy(); frc_redirect('/'); diff --git a/src/public/user/log-on.php b/src/public/user/log-on.php index 3133f0f..9d8fc4f 100644 --- a/src/public/user/log-on.php +++ b/src/public/user/log-on.php @@ -1,6 +1,9 @@ $level, 'message' => $message]; } @@ -25,7 +29,8 @@ function add_message(string $level, string $message): void { * * @param string $message The message to be displayed */ -function add_error(string $message): void { +function add_error(string $message): void +{ add_message('ERROR', $message); } @@ -34,14 +39,22 @@ function add_error(string $message): void { * * @param string $message The message to be displayed */ -function add_info(string $message): void { +function add_info(string $message): void +{ add_message('INFO', $message); } /** @var bool $is_htmx 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); -function nav_link(string $link, bool $isFirst = false) { +/** + * 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 "$sep$link"; } @@ -49,8 +62,9 @@ function nav_link(string $link, bool $isFirst = false) { /** * Render the title bar for the page */ -function title_bar(): void { - $version = display_version();; ?> +function title_bar(): void +{ + $version = display_version(); ?>