Documents and Documentation (beta 1) #23
|
@ -5,7 +5,7 @@
|
||||||
use BitBadger\Documents\SQLite\Configuration;
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use FeedReaderCentral\Data;
|
use FeedReaderCentral\Data;
|
||||||
|
|
||||||
const FRC_VERSION = '1.0.0-alpha7';
|
const FRC_VERSION = '1.0.0-beta1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drop .0 or .0.0 from the end of the version to format it for display
|
* Drop .0 or .0.0 from the end of the version to format it for display
|
||||||
|
@ -24,15 +24,6 @@ function display_version(): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
//spl_autoload_register(function ($class) {
|
|
||||||
// $file = implode(DIRECTORY_SEPARATOR, [__DIR__, 'lib', "$class.php"]);
|
|
||||||
// if (file_exists($file)) {
|
|
||||||
// require $file;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
//});
|
|
||||||
|
|
||||||
require 'user-config.php';
|
require 'user-config.php';
|
||||||
|
|
||||||
Configuration::useDbFileName(implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', DATABASE_NAME]));
|
Configuration::useDbFileName(implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', DATABASE_NAME]));
|
||||||
|
|
20
src/composer.lock
generated
20
src/composer.lock
generated
|
@ -12,10 +12,16 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/documents-common",
|
"url": "https://git.bitbadger.solutions/bit-badger/documents-common",
|
||||||
"reference": "30d3ad0621485d0797f2483424a6199ab0021c97"
|
"reference": "4aecbfe3e8030fe7ddc0391ee715d6766cbb9c6e"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"time": "2024-06-01T02:26:15+00:00"
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"BitBadger\\Documents\\": "./",
|
||||||
|
"BitBadger\\Documents\\Query\\": "./Query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time": "2024-06-02T02:11:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bit-badger/documents-sqlite",
|
"name": "bit-badger/documents-sqlite",
|
||||||
|
@ -23,14 +29,20 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite",
|
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite",
|
||||||
"reference": "009ea77b7510fd13936ec4927e2390bccd5d5e70"
|
"reference": "ac34dbf481287526b6d044fd7699568e0ee92805"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"bit-badger/documents-common": "dev-conversion",
|
"bit-badger/documents-common": "dev-conversion",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"time": "2024-06-01T02:28:46+00:00"
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"BitBadger\\Documents\\SQLite\\": "./",
|
||||||
|
"BitBadger\\Documents\\SQLite\\Query\\": "./Query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time": "2024-06-02T02:18:12+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|
|
@ -14,18 +14,8 @@ use SQLite3;
|
||||||
/**
|
/**
|
||||||
* A centralized place for data access for the application
|
* A centralized place for data access for the application
|
||||||
*/
|
*/
|
||||||
class Data {
|
class Data
|
||||||
|
{
|
||||||
/**
|
|
||||||
* Obtain a new connection to the database
|
|
||||||
* @return SQLite3 A new connection to the database
|
|
||||||
*/
|
|
||||||
public static function getConnection(): SQLite3 {
|
|
||||||
$db = new SQLite3(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'data', DATABASE_NAME]));
|
|
||||||
$db->exec('PRAGMA foreign_keys = ON;');
|
|
||||||
return $db;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the search index and synchronization triggers for the item table
|
* Create the search index and synchronization triggers for the item table
|
||||||
*
|
*
|
||||||
|
@ -71,51 +61,17 @@ class Data {
|
||||||
{
|
{
|
||||||
$tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name'));
|
$tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name'));
|
||||||
$db = Configuration::dbConn();
|
$db = Configuration::dbConn();
|
||||||
// $tables = array();
|
|
||||||
// $tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type = 'table'");
|
|
||||||
// while ($table = $tableQuery->fetchArray(SQLITE3_NUM)) $tables[] = $table[0];
|
|
||||||
if (!in_array(Table::USER, $tables)) {
|
if (!in_array(Table::USER, $tables)) {
|
||||||
Definition::ensureTable(Table::USER, $db);
|
Definition::ensureTable(Table::USER, $db);
|
||||||
Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $db);
|
Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $db);
|
||||||
// $db->exec(<<<'SQL'
|
|
||||||
// CREATE TABLE frc_user (
|
|
||||||
// id INTEGER NOT NULL PRIMARY KEY,
|
|
||||||
// email TEXT NOT NULL,
|
|
||||||
// password TEXT NOT NULL)
|
|
||||||
// SQL);
|
|
||||||
// $db->exec('CREATE INDEX idx_user_email ON frc_user (email)');
|
|
||||||
}
|
}
|
||||||
if (!in_array(Table::FEED, $tables)) {
|
if (!in_array(Table::FEED, $tables)) {
|
||||||
Definition::ensureTable(Table::FEED, $db);
|
Definition::ensureTable(Table::FEED, $db);
|
||||||
Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $db);
|
Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $db);
|
||||||
// $db->exec(<<<'SQL'
|
|
||||||
// CREATE TABLE feed (
|
|
||||||
// id INTEGER NOT NULL PRIMARY KEY,
|
|
||||||
// user_id INTEGER NOT NULL,
|
|
||||||
// url TEXT NOT NULL,
|
|
||||||
// title TEXT,
|
|
||||||
// updated_on TEXT,
|
|
||||||
// checked_on TEXT,
|
|
||||||
// FOREIGN KEY (user_id) REFERENCES frc_user (id))
|
|
||||||
// SQL);
|
|
||||||
}
|
}
|
||||||
if (!in_array(Table::ITEM, $tables)) {
|
if (!in_array(Table::ITEM, $tables)) {
|
||||||
Definition::ensureTable(Table::ITEM, $db);
|
Definition::ensureTable(Table::ITEM, $db);
|
||||||
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db);
|
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db);
|
||||||
// $db->exec(<<<'SQL'
|
|
||||||
// CREATE TABLE item (
|
|
||||||
// id INTEGER NOT NULL PRIMARY KEY,
|
|
||||||
// feed_id INTEGER NOT NULL,
|
|
||||||
// title TEXT NOT NULL,
|
|
||||||
// item_guid TEXT NOT NULL,
|
|
||||||
// item_link TEXT NOT NULL,
|
|
||||||
// published_on TEXT NOT NULL,
|
|
||||||
// updated_on TEXT,
|
|
||||||
// content TEXT NOT NULL,
|
|
||||||
// is_read BOOLEAN NOT NULL DEFAULT 0,
|
|
||||||
// is_bookmarked BOOLEAN NOT NULL DEFAULT 0,
|
|
||||||
// FOREIGN KEY (feed_id) REFERENCES feed (id))
|
|
||||||
// SQL);
|
|
||||||
self::createSearchIndex($db);
|
self::createSearchIndex($db);
|
||||||
}
|
}
|
||||||
$db->close();
|
$db->close();
|
||||||
|
@ -127,21 +83,12 @@ class Data {
|
||||||
* @param ?string $value The date/time to be parsed and formatted
|
* @param ?string $value The date/time to be parsed and formatted
|
||||||
* @return string|null The date/time in `DateTimeInterface::ATOM` format, or `null` if the input cannot be parsed
|
* @return string|null The date/time in `DateTimeInterface::ATOM` format, or `null` if the input cannot be parsed
|
||||||
*/
|
*/
|
||||||
public static function formatDate(?string $value): ?string {
|
public static function formatDate(?string $value): ?string
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
return $value ? (new DateTimeImmutable($value))->format(DateTimeInterface::ATOM) : null;
|
return $value ? (new DateTimeImmutable($value))->format(DateTimeInterface::ATOM) : null;
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the last SQLite error message as a result array
|
|
||||||
*
|
|
||||||
* @param SQLite3 $db The database connection on which the error has occurred
|
|
||||||
* @return string[] ['error' => message] for last SQLite error message
|
|
||||||
*/
|
|
||||||
public static function error(SQLite3 $db): array {
|
|
||||||
return ['error' => 'SQLite error: ' . $db->lastErrorMsg()];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,25 +33,19 @@ class Feed
|
||||||
/** @var int Purge items in number greater than the specified number of items to keep */
|
/** @var int Purge items in number greater than the specified number of items to keep */
|
||||||
public const int PURGE_BY_COUNT = 3;
|
public const int PURGE_BY_COUNT = 3;
|
||||||
|
|
||||||
// ***** PROPERTIES *****
|
/**
|
||||||
|
* Constructor
|
||||||
/** @var int The ID of the feed */
|
*
|
||||||
public int $id = 0;
|
* @param int $id The ID of the feed
|
||||||
|
* @param int $user_id The ID of the user to whom this subscription belongs
|
||||||
/** @var int The ID of the user to whom this subscription belongs */
|
* @param string $url The URL of the feed
|
||||||
public int $user_id = 0;
|
* @param string|null $title The title of this feed
|
||||||
|
* @param string|null $updated_on The date/time items in this feed were last updated
|
||||||
/** @var string The URL of the feed */
|
* @param string|null $checked_on The date/time this feed was last checked
|
||||||
public string $url = '';
|
*/
|
||||||
|
public function __construct(public int $id = 0, public int $user_id = 0, public string $url = '',
|
||||||
/** @var string|null The title of this feed */
|
public ?string $title = null, public ?string $updated_on = null,
|
||||||
public ?string $title = null;
|
public ?string $checked_on = null) { }
|
||||||
|
|
||||||
/** @var string|null The date/time items in this feed were last updated */
|
|
||||||
public ?string $updated_on = null;
|
|
||||||
|
|
||||||
/** @var string|null The date/time this feed was last checked */
|
|
||||||
public ?string $checked_on = null;
|
|
||||||
|
|
||||||
// ***** STATIC FUNCTIONS *****
|
// ***** STATIC FUNCTIONS *****
|
||||||
|
|
||||||
|
|
|
@ -14,35 +14,24 @@ use BitBadger\Documents\SQLite\Parameters;
|
||||||
*/
|
*/
|
||||||
class Item
|
class Item
|
||||||
{
|
{
|
||||||
/** @var int The ID of this item in the Feed Reader Central database */
|
/**
|
||||||
public int $id = 0;
|
* Constructor
|
||||||
|
*
|
||||||
/** @var int The ID of the feed to which this item belongs */
|
* @param int $id The ID of this item in the Feed Reader Central database
|
||||||
public int $feed_id = 0;
|
* @param int $feed_id The ID of the feed to which this item belongs
|
||||||
|
* @param string $title The title of this item
|
||||||
/** @var string The title of this item */
|
* @param string $item_guid The Globally Unique ID (GUID) for this item (an attribute in the feed XML)
|
||||||
public string $title = '';
|
* @param string $item_link The link to the item on its original site
|
||||||
|
* @param string $published_on The date/time this item was published
|
||||||
/** @var string The Globally Unique ID (GUID) for this item (an attribute in the feed XML) */
|
* @param string|null $updated_on The date/time this item was last updated
|
||||||
public string $item_guid = '';
|
* @param string $content The content for this item
|
||||||
|
* @param int $is_read 1 if the item has been read, 0 if not
|
||||||
/** @var string The link to the item on its original site */
|
* @param int $is_bookmarked 1 if the item is bookmarked, 0 if not
|
||||||
public string $item_link = '';
|
*/
|
||||||
|
public function __construct(public int $id = 0, public int $feed_id = 0, public string $title = '',
|
||||||
/** @var string The date/time this item was published */
|
public string $item_guid = '', public string $item_link = '',
|
||||||
public string $published_on = '';
|
public string $published_on = '', public ?string $updated_on = null,
|
||||||
|
public string $content = '', public int $is_read = 0, public int $is_bookmarked = 0) { }
|
||||||
/** @var string|null The date/time this item was last updated */
|
|
||||||
public ?string $updated_on = null;
|
|
||||||
|
|
||||||
/** @var string The content for this item */
|
|
||||||
public string $content = '';
|
|
||||||
|
|
||||||
/** @var int 1 if the item has been read, 0 if not */
|
|
||||||
public int $is_read = 0;
|
|
||||||
|
|
||||||
/** @var int 1 if the item is bookmarked, 0 if not */
|
|
||||||
public int $is_bookmarked = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has the item been read?
|
* Has the item been read?
|
||||||
|
@ -73,16 +62,14 @@ class Item
|
||||||
*/
|
*/
|
||||||
public static function fromFeedItem(int $feedId, ParsedItem $item): static
|
public static function fromFeedItem(int $feedId, ParsedItem $item): static
|
||||||
{
|
{
|
||||||
$it = new static();
|
return new static(
|
||||||
$it->feed_id = $feedId;
|
feed_id: $feedId,
|
||||||
$it->item_guid = $item->guid;
|
title: $item->title,
|
||||||
$it->item_link = $item->link;
|
item_guid: $item->guid,
|
||||||
$it->title = $item->title;
|
item_link: $item->link,
|
||||||
$it->published_on = $item->publishedOn;
|
published_on: $item->publishedOn,
|
||||||
$it->updated_on = $item->updatedOn;
|
updated_on: $item->updatedOn,
|
||||||
$it->content = $item->content;
|
content: $item->content);
|
||||||
|
|
||||||
return $it;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ItemList {
|
||||||
{
|
{
|
||||||
$result = $query->execute();
|
$result = $query->execute();
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
$this->error = Data::error($db)['error'];
|
$this->error = 'SQLite error: ' . $db->lastErrorMsg();
|
||||||
} else {
|
} else {
|
||||||
$this->items = $result;
|
$this->items = $result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ namespace FeedReaderCentral;
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
use BitBadger\Documents\Field;
|
use BitBadger\Documents\Field;
|
||||||
use BitBadger\Documents\SQLite\Patch;
|
use BitBadger\Documents\SQLite\Patch;
|
||||||
use SQLite3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security functions
|
* Security functions
|
||||||
*/
|
*/
|
||||||
class Security {
|
class Security
|
||||||
|
{
|
||||||
/** @var int Run as a single user requiring no password */
|
/** @var int Run as a single user requiring no password */
|
||||||
public const int SINGLE_USER = 0;
|
public const int SINGLE_USER = 0;
|
||||||
|
|
||||||
|
@ -35,17 +34,16 @@ class Security {
|
||||||
* @param User $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 $password The password provided by the user
|
||||||
* @param string|null $returnTo The URL to which the user should be redirected
|
* @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
|
|
||||||
* @throws DocumentException if any is encountered
|
* @throws DocumentException if any is encountered
|
||||||
*/
|
*/
|
||||||
private static function verifyPassword(User $user, string $password, ?string $returnTo, SQLite3 $db): void
|
private static function verifyPassword(User $user, string $password, ?string $returnTo): void
|
||||||
{
|
{
|
||||||
if (password_verify($password, $user->password)) {
|
if (password_verify($password, $user->password)) {
|
||||||
if (password_needs_rehash($user->password, self::PW_ALGORITHM)) {
|
if (password_needs_rehash($user->password, self::PW_ALGORITHM)) {
|
||||||
Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)], $db);
|
Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||||
}
|
}
|
||||||
$_SESSION[Key::USER_ID] = $user['id'];
|
$_SESSION[Key::USER_ID] = $user->id;
|
||||||
$_SESSION[Key::USER_EMAIL] = $user['email'];
|
$_SESSION[Key::USER_EMAIL] = $user->email;
|
||||||
frc_redirect($returnTo ?? '/');
|
frc_redirect($returnTo ?? '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,10 +54,9 @@ class Security {
|
||||||
* @param string $email The e-mail address for the user (cannot be the single-user mode user)
|
* @param string $email The e-mail address for the user (cannot be the single-user mode user)
|
||||||
* @param string $password The password provided by the user
|
* @param string $password The password provided by the user
|
||||||
* @param string|null $returnTo The URL to which the user should be redirected
|
* @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
|
|
||||||
* @throws DocumentException If any is encountered
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
public static function logOnUser(string $email, string $password, ?string $returnTo, SQLite3 $db): void {
|
public static function logOnUser(string $email, string $password, ?string $returnTo): void {
|
||||||
if (SECURITY_MODEL == self::SINGLE_USER_WITH_PASSWORD) {
|
if (SECURITY_MODEL == self::SINGLE_USER_WITH_PASSWORD) {
|
||||||
$dbEmail = self::SINGLE_USER_EMAIL;
|
$dbEmail = self::SINGLE_USER_EMAIL;
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,7 +67,7 @@ class Security {
|
||||||
$dbEmail = $email;
|
$dbEmail = $email;
|
||||||
}
|
}
|
||||||
$user = User::findByEmail($dbEmail);
|
$user = User::findByEmail($dbEmail);
|
||||||
if ($user) self::verifyPassword($user, $password, $returnTo, $db);
|
if ($user) self::verifyPassword($user, $password, $returnTo);
|
||||||
add_error('Invalid credentials; log on unsuccessful');
|
add_error('Invalid credentials; log on unsuccessful');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,40 +76,37 @@ class Security {
|
||||||
*
|
*
|
||||||
* @param string $email The e-mail address of the user whose password should be updated
|
* @param string $email The e-mail address of the user whose password should be updated
|
||||||
* @param string $password The new password for this user
|
* @param string $password The new password for this user
|
||||||
* @param SQLite3 $db The database connection to use in updating the password
|
|
||||||
* @throws DocumentException If any is encountered
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
public static function updatePassword(string $email, string $password, SQLite3 $db): void {
|
public static function updatePassword(string $email, string $password): void {
|
||||||
Patch::byFields(Table::USER, [Field::EQ('email', $email)],
|
Patch::byFields(Table::USER, [Field::EQ('email', $email)],
|
||||||
['password' => password_hash($password, self::PW_ALGORITHM)], $db);
|
['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log on the single user
|
* Log on the single user
|
||||||
*
|
*
|
||||||
* @param SQLite3 $db The data connection to use to retrieve the user
|
|
||||||
* @throws DocumentException If any is encountered
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
private static function logOnSingleUser(SQLite3 $db): void {
|
private static function logOnSingleUser(): void {
|
||||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
User::add(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD, $db);
|
User::add(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD);
|
||||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||||
}
|
}
|
||||||
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo'], $db);
|
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that user is logged on
|
* Verify that user is logged on
|
||||||
*
|
*
|
||||||
* @param SQLite3 $db The data connection to use if required
|
|
||||||
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
|
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
|
||||||
* @throws DocumentException If any is encountered
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
public static function verifyUser(SQLite3 $db, bool $redirectIfAnonymous = true): void {
|
public static function verifyUser(bool $redirectIfAnonymous = true): void {
|
||||||
if (key_exists(Key::USER_ID, $_SESSION)) return;
|
if (key_exists(Key::USER_ID, $_SESSION)) return;
|
||||||
|
|
||||||
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser($db);
|
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser();
|
||||||
|
|
||||||
if (SECURITY_MODEL != self::SINGLE_USER_WITH_PASSWORD && SECURITY_MODEL != self::MULTI_USER) {
|
if (SECURITY_MODEL != self::SINGLE_USER_WITH_PASSWORD && SECURITY_MODEL != self::MULTI_USER) {
|
||||||
die('Unrecognized security model (' . SECURITY_MODEL . ')');
|
die('Unrecognized security model (' . SECURITY_MODEL . ')');
|
||||||
|
|
|
@ -3,8 +3,12 @@ namespace FeedReaderCentral;
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
use BitBadger\Documents\Field;
|
use BitBadger\Documents\Field;
|
||||||
|
use BitBadger\Documents\Query;
|
||||||
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
use BitBadger\Documents\SQLite\Document;
|
use BitBadger\Documents\SQLite\Document;
|
||||||
use BitBadger\Documents\SQLite\Find;
|
use BitBadger\Documents\SQLite\Find;
|
||||||
|
use BitBadger\Documents\SQLite\Parameters;
|
||||||
|
use BitBadger\Documents\SQLite\Results;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,14 +16,14 @@ use SQLite3;
|
||||||
*/
|
*/
|
||||||
class User
|
class User
|
||||||
{
|
{
|
||||||
/** @var int The ID of the user */
|
/**
|
||||||
public int $id = 0;
|
* Constructor
|
||||||
|
*
|
||||||
/** @var string The e-mail address for the user */
|
* @param int $id The ID of the user
|
||||||
public string $email = '';
|
* @param string $email The e-mail address for the user
|
||||||
|
* @param string $password The password for the user
|
||||||
/** @var string The password for the user */
|
*/
|
||||||
public string $password = '';
|
public function __construct(public int $id = 0, public string $email = '', public string $password = '') { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a user by their e=mail address
|
* Find a user by their e=mail address
|
||||||
|
@ -38,15 +42,34 @@ class User
|
||||||
*
|
*
|
||||||
* @param string $email The e-mail address for the user
|
* @param string $email The e-mail address for the user
|
||||||
* @param string $password The user's password
|
* @param string $password The user's password
|
||||||
* @param SQLite3 $db The data connection to use to add the user
|
|
||||||
* @throws DocumentException If any is encountered
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
public static function add(string $email, string $password, SQLite3 $db): void
|
public static function add(string $email, string $password): void
|
||||||
{
|
{
|
||||||
$user = new User();
|
Document::insert(Table::USER, new User(email: $email, password: $password));
|
||||||
$user->email = $email;
|
|
||||||
$user->password = $password;
|
|
||||||
Document::insert(Table::USER, $user, $db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this user have any bookmarked items?
|
||||||
|
* @return bool True if the user has any bookmarked items, false if not
|
||||||
|
* @throws DocumentException If any is encountered
|
||||||
|
*/
|
||||||
|
public static function hasBookmarks(): bool
|
||||||
|
{
|
||||||
|
$userField = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
|
||||||
|
$userField->qualifier = Table::FEED;
|
||||||
|
$bookField = Field::EQ('is_bookmarked', 1, '@book');
|
||||||
|
$bookField->qualifier = Table::ITEM;
|
||||||
|
$fields = [$userField, $bookField];
|
||||||
|
|
||||||
|
$item = Table::ITEM;
|
||||||
|
$feed = Table::FEED;
|
||||||
|
$where = Query::whereByFields($fields);
|
||||||
|
return Custom::scalar(<<<SQL
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM $item INNER JOIN $feed ON $item.data->>'feed_id' = $feed.data->>'id'
|
||||||
|
WHERE $where)
|
||||||
|
SQL, Parameters::addFields($fields, []), Results::toExists(...));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Find;
|
use BitBadger\Documents\SQLite\Find;
|
||||||
use BitBadger\Documents\SQLite\Patch;
|
use BitBadger\Documents\SQLite\Patch;
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Item;
|
use FeedReaderCentral\Item;
|
||||||
use FeedReaderCentral\Key;
|
use FeedReaderCentral\Key;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
@ -17,12 +17,12 @@ use FeedReaderCentral\Table;
|
||||||
|
|
||||||
include '../start.php';
|
include '../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
$id = $_GET['id'];
|
$id = $_GET['id'];
|
||||||
|
|
||||||
// TODO: adapt query once "by fields" is available
|
// TODO: adapt query once "by fields" is available
|
||||||
|
$db = Configuration::dbConn();
|
||||||
$existsQuery = $db->prepare(
|
$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');
|
'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);
|
$existsQuery->bindValue(':id', $id);
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Security;
|
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
FeedReaderCentral\Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
page_head('Feeds | Documentation'); ?>
|
page_head('Feeds | Documentation'); ?>
|
||||||
<h1>Feeds</h1>
|
<h1>Feeds</h1>
|
||||||
|
@ -75,4 +71,3 @@ php-cli util/refresh.php all</pre>
|
||||||
add <code>nice -n 1</code> (with a trailing space) before the path to the script.
|
add <code>nice -n 1</code> (with a trailing space) before the path to the script.
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Security;
|
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
FeedReaderCentral\Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
page_head('Documentation'); ?>
|
page_head('Documentation'); ?>
|
||||||
<h1>Documentation Home</h1>
|
<h1>Documentation Home</h1>
|
||||||
|
@ -35,4 +31,3 @@ page_head('Documentation'); ?>
|
||||||
that can be performed via its command line interface.
|
that can be performed via its command line interface.
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Security;
|
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
FeedReaderCentral\Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
page_head('Items | Documentation'); ?>
|
page_head('Items | Documentation'); ?>
|
||||||
<h1>Items</h1>
|
<h1>Items</h1>
|
||||||
|
@ -70,4 +66,3 @@ page_head('Items | Documentation'); ?>
|
||||||
others; if the items seem to move around in the list after a refresh, this is likely the cause.
|
others; if the items seem to move around in the list after a refresh, this is likely the cause.
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Security;
|
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
FeedReaderCentral\Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
page_head('Security Modes | Documentation'); ?>
|
page_head('Security Modes | Documentation'); ?>
|
||||||
<h1>Configuring Security Modes</h1>
|
<h1>Configuring Security Modes</h1>
|
||||||
|
@ -63,4 +59,3 @@ page_head('Security Modes | Documentation'); ?>
|
||||||
<p><code>php-cli util/user.php reset-single-password</code>
|
<p><code>php-cli util/user.php reset-single-password</code>
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Security;
|
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
FeedReaderCentral\Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
page_head('About the CLI | Documentation'); ?>
|
page_head('About the CLI | Documentation'); ?>
|
||||||
<h1>About the CLI</h1>
|
<h1>About the CLI</h1>
|
||||||
|
@ -25,4 +21,3 @@ page_head('About the CLI | Documentation'); ?>
|
||||||
<p><code>php-cli util/some-process.php command option1 option2</code>
|
<p><code>php-cli util/some-process.php command option1 option2</code>
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -7,24 +7,23 @@
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
use BitBadger\Documents\Field;
|
use BitBadger\Documents\Field;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Delete;
|
use BitBadger\Documents\SQLite\Delete;
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
use FeedReaderCentral\Table;
|
use FeedReaderCentral\Table;
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
$feedId = $_GET['id'] ?? '';
|
$feedId = $_GET['id'] ?? '';
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
try {
|
try {
|
||||||
if (!($feed = Feed::retrieveById($feedId))) not_found();
|
if (!($feed = Feed::retrieveById($feedId))) not_found();
|
||||||
Delete::byFields(Table::ITEM, [Field::EQ('feed_id', $feed->id)], $db);
|
Delete::byFields(Table::ITEM, [Field::EQ('feed_id', $feed->id)]);
|
||||||
Delete::byId(Table::FEED, $feed->id, $db);
|
Delete::byId(Table::FEED, $feed->id);
|
||||||
add_info('Feed “' . htmlentities($feed->title) . '” deleted successfully');
|
add_info('Feed “' . htmlentities($feed->title) . '” deleted successfully');
|
||||||
$db->close();
|
$db->close();
|
||||||
frc_redirect('/feeds');
|
frc_redirect('/feeds');
|
||||||
|
@ -33,6 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$db = Configuration::dbConn();
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
try {
|
try {
|
||||||
$isNew = $_POST['id'] == 'new';
|
$isNew = $_POST['id'] == 'new';
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
* Lists items in a given feed (all, unread, or bookmarked)
|
* Lists items in a given feed (all, unread, or bookmarked)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
use FeedReaderCentral\ItemList;
|
use FeedReaderCentral\ItemList;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
if (!($feed = Feed::retrieveById($_GET['id']))) not_found();
|
if (!($feed = Feed::retrieveById($_GET['id']))) not_found();
|
||||||
|
|
||||||
|
$db = Configuration::dbConn();
|
||||||
$list = match (true) {
|
$list = match (true) {
|
||||||
key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id, $db),
|
key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id, $db),
|
||||||
key_exists('bookmarked', $_GET) => ItemList::bookmarkedForFeed($feed->id, $db),
|
key_exists('bookmarked', $_GET) => ItemList::bookmarkedForFeed($feed->id, $db),
|
||||||
|
|
|
@ -10,7 +10,6 @@ use BitBadger\Documents\Field;
|
||||||
use BitBadger\Documents\JsonMapper;
|
use BitBadger\Documents\JsonMapper;
|
||||||
use BitBadger\Documents\Query;
|
use BitBadger\Documents\Query;
|
||||||
use BitBadger\Documents\SQLite\Custom;
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
use FeedReaderCentral\Key;
|
use FeedReaderCentral\Key;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
@ -18,8 +17,7 @@ use FeedReaderCentral\Table;
|
||||||
|
|
||||||
include '../start.php';
|
include '../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
// TODO: adapt query when document list is done
|
// TODO: adapt query when document list is done
|
||||||
$field = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
|
$field = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
|
||||||
|
@ -52,4 +50,3 @@ page_head('Your Feeds'); ?>
|
||||||
}); ?>
|
}); ?>
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -5,16 +5,16 @@
|
||||||
* Displays a list of unread or bookmarked items for the current user
|
* Displays a list of unread or bookmarked items for the current user
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
use FeedReaderCentral\ItemList;
|
use FeedReaderCentral\ItemList;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
|
||||||
include '../start.php';
|
include '../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
|
$db = Configuration::dbConn();
|
||||||
if (key_exists('refresh', $_GET)) {
|
if (key_exists('refresh', $_GET)) {
|
||||||
$refreshResult = Feed::refreshAll($db);
|
$refreshResult = Feed::refreshAll($db);
|
||||||
if (key_exists('ok', $refreshResult)) {
|
if (key_exists('ok', $refreshResult)) {
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Delete;
|
use BitBadger\Documents\SQLite\Delete;
|
||||||
use BitBadger\Documents\SQLite\Patch;
|
use BitBadger\Documents\SQLite\Patch;
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Item;
|
use FeedReaderCentral\Item;
|
||||||
use FeedReaderCentral\Key;
|
use FeedReaderCentral\Key;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
@ -17,16 +17,14 @@ use FeedReaderCentral\Table;
|
||||||
|
|
||||||
include '../start.php';
|
include '../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
try {
|
try {
|
||||||
// "Keep as New" button sends a POST request to reset the is_read flag before going back to the item list
|
// "Keep as New" button sends a POST request to reset the is_read flag before going back to the item list
|
||||||
if (Item::retrieveByIdForUser($_POST['id'])) {
|
if (Item::retrieveByIdForUser($_POST['id'])) {
|
||||||
Patch::byId(Table::ITEM, $_POST['id'], ['is_read' => 0], $db);
|
Patch::byId(Table::ITEM, $_POST['id'], ['is_read' => 0]);
|
||||||
}
|
}
|
||||||
$db->close();
|
|
||||||
frc_redirect($_POST['from']);
|
frc_redirect($_POST['from']);
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
add_error("$ex");
|
add_error("$ex");
|
||||||
|
@ -38,16 +36,16 @@ $from = $_GET['from'] ?? '/';
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
try {
|
try {
|
||||||
if (Item::retrieveByIdForUser($_GET['id'])) {
|
if (Item::retrieveByIdForUser($_GET['id'])) {
|
||||||
Delete::byId(Table::ITEM, $_GET['id'], $db);
|
Delete::byId(Table::ITEM, $_GET['id']);
|
||||||
}
|
}
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
add_error("$ex");
|
add_error("$ex");
|
||||||
}
|
}
|
||||||
$db->close();
|
|
||||||
frc_redirect($from);
|
frc_redirect($from);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: convert this query
|
// TODO: convert this query
|
||||||
|
$db = Configuration::dbConn();
|
||||||
$query = $db->prepare(<<<'SQL'
|
$query = $db->prepare(<<<'SQL'
|
||||||
SELECT item.title AS item_title, item.item_link, item.published_on, item.updated_on, item.content,
|
SELECT item.title AS item_title, item.item_link, item.published_on, item.updated_on, item.content,
|
||||||
feed.title AS feed_title
|
feed.title AS feed_title
|
||||||
|
|
|
@ -6,18 +6,18 @@
|
||||||
* Search for items across all feeds
|
* Search for items across all feeds
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use FeedReaderCentral\ItemList;
|
use FeedReaderCentral\ItemList;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
|
||||||
include '../start.php';
|
include '../start.php';
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser();
|
||||||
Security::verifyUser($db);
|
|
||||||
|
|
||||||
$search = $_GET['search'] ?? '';
|
$search = $_GET['search'] ?? '';
|
||||||
$items = $_GET['items'] ?? 'all';
|
$items = $_GET['items'] ?? 'all';
|
||||||
|
|
||||||
|
$db = Configuration::dbConn();
|
||||||
if ($search != '') {
|
if ($search != '') {
|
||||||
$list = ItemList::matchingSearch($search, $items == 'bookmarked', $db);
|
$list = ItemList::matchingSearch($search, $items == 'bookmarked', $db);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
include '../../start.php';
|
include '../../start.php';
|
||||||
|
|
||||||
use FeedReaderCentral\Data;
|
|
||||||
use FeedReaderCentral\Key;
|
use FeedReaderCentral\Key;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
|
||||||
$db = Data::getConnection();
|
Security::verifyUser(redirectIfAnonymous: false);
|
||||||
Security::verifyUser($db, redirectIfAnonymous: false);
|
|
||||||
|
|
||||||
// Users already logged on have no need of this page
|
// Users already logged on have no need of this page
|
||||||
if (key_exists(Key::USER_ID, $_SESSION)) frc_redirect('/');
|
if (key_exists(Key::USER_ID, $_SESSION)) frc_redirect('/');
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
Security::logOnUser($_POST['email'] ?? '', $_POST['password'], $_POST['returnTo'] ?? null, $db);
|
Security::logOnUser($_POST['email'] ?? '', $_POST['password'], $_POST['returnTo'] ?? null);
|
||||||
// If we're still here, something didn't work; preserve the returnTo parameter
|
// If we're still here, something didn't work; preserve the returnTo parameter
|
||||||
$_GET['returnTo'] = $_POST['returnTo'];
|
$_GET['returnTo'] = $_POST['returnTo'];
|
||||||
}
|
}
|
||||||
|
@ -41,4 +39,3 @@ page_head('Log On'); ?>
|
||||||
</form>
|
</form>
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
$db->close();
|
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use BitBadger\Documents\Field;
|
||||||
|
use BitBadger\Documents\Query;
|
||||||
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
|
use BitBadger\Documents\SQLite\Parameters;
|
||||||
|
use BitBadger\Documents\SQLite\Results;
|
||||||
use FeedReaderCentral\Data;
|
use FeedReaderCentral\Data;
|
||||||
use FeedReaderCentral\Key;
|
use FeedReaderCentral\Key;
|
||||||
use FeedReaderCentral\Security;
|
use FeedReaderCentral\Security;
|
||||||
|
use FeedReaderCentral\Table;
|
||||||
|
use FeedReaderCentral\User;
|
||||||
|
|
||||||
require 'app-config.php';
|
require 'app-config.php';
|
||||||
|
|
||||||
|
@ -69,27 +76,13 @@ function title_bar(): void
|
||||||
<div><a href=/ class=title>Feed Reader Central</a><span class=version><?=$version?></span></div>
|
<div><a href=/ class=title>Feed Reader Central</a><span class=version><?=$version?></span></div>
|
||||||
<nav><?php
|
<nav><?php
|
||||||
if (key_exists(Key::USER_ID, $_SESSION)) {
|
if (key_exists(Key::USER_ID, $_SESSION)) {
|
||||||
$db = Data::getConnection();
|
nav_link(hx_get('/feeds', 'Feeds'), true);
|
||||||
try {
|
if (User::hasBookmarks()) nav_link(hx_get('/?bookmarked', 'Bookmarked'));
|
||||||
$bookQuery = $db->prepare(<<<'SQL'
|
nav_link(hx_get('/search', 'Search'));
|
||||||
SELECT EXISTS(
|
nav_link(hx_get('/docs/', 'Docs'));
|
||||||
SELECT 1
|
nav_link('<a href=/user/log-off>Log Off</a>');
|
||||||
FROM item INNER JOIN feed ON item.feed_id = feed.id
|
if ($_SESSION[Key::USER_EMAIL] != Security::SINGLE_USER_EMAIL) {
|
||||||
WHERE feed.user_id = :id AND item.is_bookmarked = 1)
|
nav_link($_SESSION[Key::USER_EMAIL]);
|
||||||
SQL);
|
|
||||||
$bookQuery->bindValue(':id', $_SESSION[Key::USER_ID]);
|
|
||||||
$bookResult = $bookQuery->execute();
|
|
||||||
$hasBookmarks = $bookResult && $bookResult->fetchArray(SQLITE3_NUM)[0];
|
|
||||||
nav_link(hx_get('/feeds', 'Feeds'), true);
|
|
||||||
if ($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::USER_EMAIL] != Security::SINGLE_USER_EMAIL) {
|
|
||||||
nav_link($_SESSION[Key::USER_EMAIL]);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
$db->close();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nav_link(hx_get('/user/log-on', 'Log On'), true);
|
nav_link(hx_get('/user/log-on', 'Log On'), true);
|
||||||
|
|
120
src/util/db-update.php
Normal file
120
src/util/db-update.php
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use BitBadger\Documents\ArrayMapper;
|
||||||
|
use BitBadger\Documents\DocumentException;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
|
use BitBadger\Documents\SQLite\Document;
|
||||||
|
use FeedReaderCentral\Data;
|
||||||
|
use FeedReaderCentral\Feed;
|
||||||
|
use FeedReaderCentral\Item;
|
||||||
|
use FeedReaderCentral\Table;
|
||||||
|
use FeedReaderCentral\User;
|
||||||
|
|
||||||
|
require __DIR__ . '/../cli-start.php';
|
||||||
|
|
||||||
|
cli_title('DATABASE UPDATE');
|
||||||
|
|
||||||
|
if ($argc < 2) display_help();
|
||||||
|
|
||||||
|
switch ($argv[1]) {
|
||||||
|
case 'check':
|
||||||
|
check_status();
|
||||||
|
break;
|
||||||
|
case 'run':
|
||||||
|
run_update();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printfn('Unrecognized option "%s"', $argv[1]);
|
||||||
|
display_help();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the options for this utility and exit
|
||||||
|
*/
|
||||||
|
function display_help(): never
|
||||||
|
{
|
||||||
|
printfn('Options:');
|
||||||
|
printfn(' - check');
|
||||||
|
printfn(' Check to see if the configured database has been updated');
|
||||||
|
printfn(' - run');
|
||||||
|
printfn(' Run the beta1 database storage update');
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function json_column_exists(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$table = Custom::single("SELECT sql FROM sqlite_master WHERE tbl_name='frc_user'", [], new ArrayMapper());
|
||||||
|
return $table && substr_compare(strtolower($table['sql']), 'data text not null', 0) >= 0;
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR $ex");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_status(): void
|
||||||
|
{
|
||||||
|
if (json_column_exists()) {
|
||||||
|
printfn('The database has already been updated');
|
||||||
|
} else {
|
||||||
|
printfn('The database has yet to be updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_update(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Configuration::dbConn();
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR: Cannot obtain a connection to the database\n $ex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
printfn('Removing search index...');
|
||||||
|
Custom::nonQuery('DROP TRIGGER item_ai', [], $db);
|
||||||
|
Custom::nonQuery('DROP TRIGGER item_au', [], $db);
|
||||||
|
Custom::nonQuery('DROP TRIGGER item_ad', [], $db);
|
||||||
|
Custom::nonQuery('DROP TABLE item_search', [], $db);
|
||||||
|
printfn('Moving old tables...');
|
||||||
|
Custom::nonQuery('ALTER TABLE item RENAME TO old_item', [], $db);
|
||||||
|
Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', [], $db);
|
||||||
|
Custom::nonQuery('ALTER TABLE frc_user RENAME TO old_user', [], $db);
|
||||||
|
printfn('Creating new tables...');
|
||||||
|
Data::ensureDb();
|
||||||
|
printfn('Migrating users...');
|
||||||
|
$users = $db->query('SELECT * FROM old_user');
|
||||||
|
if (!$users) throw new DocumentException('Could not retrieve users');
|
||||||
|
while ($user = $users->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']), $db);
|
||||||
|
}
|
||||||
|
printfn('Migrating feeds...');
|
||||||
|
$feeds = $db->query('SELECT * FROM old_feed');
|
||||||
|
if (!$feeds) throw new DocumentException('Could not retrieve feeds');
|
||||||
|
while ($feed = $feeds->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
Document::insert(Table::FEED,
|
||||||
|
new Feed($feed['id'], $feed['user_id'], $feed['url'], $feed['title'], $feed['updated_on'],
|
||||||
|
$feed['checked_on']), $db);
|
||||||
|
}
|
||||||
|
printfn('Migrating items...');
|
||||||
|
$items = $db->query('SELECT * FROM old_item');
|
||||||
|
if (!$items) throw new DocumentException('Could not retrieve items');
|
||||||
|
while ($item = $items->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
Document::insert(Table::ITEM,
|
||||||
|
new Item($item['id'], $item['feed_id'], $item['title'], $item['item_guid'], $item['item_link'],
|
||||||
|
$item['published_on'], $item['updated_on'], $item['content'], $item['is_read'],
|
||||||
|
$item['is_bookmarked']), $db);
|
||||||
|
}
|
||||||
|
printfn('Dropping old tables...');
|
||||||
|
Custom::nonQuery('DROP TABLE old_item', [], $db);
|
||||||
|
Custom::nonQuery('DROP TABLE old_feed', [], $db);
|
||||||
|
Custom::nonQuery('DROP TABLE old_user', [], $db);
|
||||||
|
printfn(PHP_EOL. 'Migration complete!');
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR $ex");
|
||||||
|
} finally {
|
||||||
|
$db->close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Find;
|
use BitBadger\Documents\SQLite\Find;
|
||||||
use FeedReaderCentral\Data;
|
use FeedReaderCentral\Data;
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
|
@ -36,7 +37,12 @@ function display_help(): never
|
||||||
|
|
||||||
function refresh_all(): void
|
function refresh_all(): void
|
||||||
{
|
{
|
||||||
$db = Data::getConnection();
|
try {
|
||||||
|
$db = Configuration::dbConn();
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR: Cannot obtain a connection to the database\n $ex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$users = [];
|
$users = [];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Custom;
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
use BitBadger\Documents\SQLite\Results;
|
use BitBadger\Documents\SQLite\Results;
|
||||||
use FeedReaderCentral\Data;
|
use FeedReaderCentral\Data;
|
||||||
|
@ -37,7 +38,12 @@ function display_help(): never
|
||||||
*/
|
*/
|
||||||
function rebuild_index(): void
|
function rebuild_index(): void
|
||||||
{
|
{
|
||||||
$db = Data::getConnection();
|
try {
|
||||||
|
$db = Configuration::dbConn();
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR: Cannot obtain a connection to the database\n $ex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$hasIndex = Custom::scalar("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'", [],
|
$hasIndex = Custom::scalar("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'", [],
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use BitBadger\Documents\DocumentException;
|
use BitBadger\Documents\DocumentException;
|
||||||
use BitBadger\Documents\Field;
|
use BitBadger\Documents\Field;
|
||||||
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Count;
|
use BitBadger\Documents\SQLite\Count;
|
||||||
use BitBadger\Documents\SQLite\Delete;
|
use BitBadger\Documents\SQLite\Delete;
|
||||||
use BitBadger\Documents\SQLite\Patch;
|
use BitBadger\Documents\SQLite\Patch;
|
||||||
|
@ -96,8 +97,6 @@ function add_user(): void
|
||||||
{
|
{
|
||||||
global $argv;
|
global $argv;
|
||||||
|
|
||||||
$db = Data::getConnection();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure there is not already a user with this e-mail address
|
// Ensure there is not already a user with this e-mail address
|
||||||
$user = User::findByEmail($argv[2]);
|
$user = User::findByEmail($argv[2]);
|
||||||
|
@ -106,13 +105,11 @@ function add_user(): void
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
User::add($argv[2], $argv[3], $db);
|
User::add($argv[2], $argv[3]);
|
||||||
|
|
||||||
printfn('User "%s" with password "%s" added successfully', $argv[2], $argv[3]);
|
printfn('User "%s" with password "%s" added successfully', $argv[2], $argv[3]);
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
printfn("$ex");
|
printfn("$ex");
|
||||||
} finally {
|
|
||||||
$db->close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +129,6 @@ function display_user(string $email): string
|
||||||
*/
|
*/
|
||||||
function set_password(string $email, string $password): void
|
function set_password(string $email, string $password): void
|
||||||
{
|
{
|
||||||
$db = Data::getConnection();
|
|
||||||
try {
|
try {
|
||||||
$displayUser = display_user($email);
|
$displayUser = display_user($email);
|
||||||
|
|
||||||
|
@ -143,15 +139,13 @@ function set_password(string $email, string $password): void
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Security::updatePassword($email, $password, $db);
|
Security::updatePassword($email, $password);
|
||||||
|
|
||||||
$msg = $email == Security::SINGLE_USER_EMAIL && $password == Security::SINGLE_USER_PASSWORD
|
$msg = $email == Security::SINGLE_USER_EMAIL && $password == Security::SINGLE_USER_PASSWORD
|
||||||
? 'reset' : sprintf('set to "%s"', $password);
|
? 'reset' : sprintf('set to "%s"', $password);
|
||||||
printfn('%s password %s successfully', init_cap($displayUser), $msg);
|
printfn('%s password %s successfully', init_cap($displayUser), $msg);
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
printfn("$ex");
|
printfn("$ex");
|
||||||
} finally {
|
|
||||||
$db->close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +156,12 @@ function set_password(string $email, string $password): void
|
||||||
*/
|
*/
|
||||||
function delete_user(string $email): void
|
function delete_user(string $email): void
|
||||||
{
|
{
|
||||||
$db = Data::getConnection();
|
try {
|
||||||
|
$db = Configuration::dbConn();
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR: Cannot obtain a connection to the database\n $ex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$displayUser = display_user($email);
|
$displayUser = display_user($email);
|
||||||
|
@ -214,7 +213,12 @@ function migrate_single_user(): void
|
||||||
{
|
{
|
||||||
global $argv;
|
global $argv;
|
||||||
|
|
||||||
$db = Data::getConnection();
|
try {
|
||||||
|
$db = Configuration::dbConn();
|
||||||
|
} catch (DocumentException $ex) {
|
||||||
|
printfn("ERR: Cannot obtain a connection to the database\n $ex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!$single = User::findByEmail(Security::SINGLE_USER_EMAIL)) {
|
if (!$single = User::findByEmail(Security::SINGLE_USER_EMAIL)) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user