Add user maintenance CLI (#9)
- Add CLI infrastructure - Add user to index page query - Strip tags from title - Move item parsing to FeedItem
This commit is contained in:
		
							parent
							
								
									7b21b86550
								
							
						
					
					
						commit
						c1790b58fd
					
				
							
								
								
									
										17
									
								
								src/app-config.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/app-config.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/** The current Feed Reader Central version */
 | 
			
		||||
const FRC_VERSION = '1.0.0-alpha4';
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
Data::ensureDb();
 | 
			
		||||
							
								
								
									
										26
									
								
								src/cli-start.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/cli-start.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
require 'app-config.php';
 | 
			
		||||
 | 
			
		||||
if (php_sapi_name() != 'cli') {
 | 
			
		||||
    http_response_code(404);
 | 
			
		||||
    die('Not Found');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Print a line with a newline character at the end (printf + newline = printfn; surprised this isn't built in!)
 | 
			
		||||
 *
 | 
			
		||||
 * @param string $format The format string
 | 
			
		||||
 * @param mixed ...$values The values for the placeholders in the string
 | 
			
		||||
 */
 | 
			
		||||
function printfn(string $format, mixed ...$values): void {
 | 
			
		||||
    printf($format . PHP_EOL, ...$values);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display a title for a CLI run
 | 
			
		||||
 *
 | 
			
		||||
 * @param string $title The title to display on the command line
 | 
			
		||||
 */
 | 
			
		||||
function cli_title(string $title): void {
 | 
			
		||||
    printfn("$title | Feed Reader Central v%s" . PHP_EOL, FRC_VERSION);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								src/lib/Feed.php
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								src/lib/Feed.php
									
									
									
									
									
								
							@ -22,6 +22,61 @@ class FeedItem {
 | 
			
		||||
 | 
			
		||||
    /** @var string The content for the item */
 | 
			
		||||
    public string $content = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a feed item from an Atom feed's `<entry>` 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
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromAtom(DOMNode $node): FeedItem {
 | 
			
		||||
        $guid = Feed::atomValue($node, 'id');
 | 
			
		||||
        $link = '';
 | 
			
		||||
        foreach ($node->getElementsByTagName('link') as $linkElt) {
 | 
			
		||||
            if ($linkElt->hasAttributes()) {
 | 
			
		||||
                $relAttr = $linkElt->attributes->getNamedItem('rel');
 | 
			
		||||
                if ($relAttr && $relAttr->value == 'alternate') {
 | 
			
		||||
                    $link = $linkElt->attributes->getNamedItem('href')->value;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if ($link == '' && str_starts_with($guid, 'http')) $link = $guid;
 | 
			
		||||
 | 
			
		||||
        $item              = new FeedItem();
 | 
			
		||||
        $item->guid        = $guid;
 | 
			
		||||
        $item->title       = Feed::atomValue($node, 'title');
 | 
			
		||||
        $item->link        = $link;
 | 
			
		||||
        $item->publishedOn = Data::formatDate(Feed::atomValue($node, 'published'));
 | 
			
		||||
        $item->updatedOn   = Data::formatDate(Feed::atomValue($node, 'updated'));
 | 
			
		||||
        $item->content     = Feed::atomValue($node, 'content');
 | 
			
		||||
 | 
			
		||||
        return $item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a feed item from an RSS feed's `<item>` 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
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromRSS(DOMNode $node): FeedItem {
 | 
			
		||||
        $itemGuid = Feed::rssValue($node, 'guid');
 | 
			
		||||
        $updNodes = $node->getElementsByTagNameNS(Feed::ATOM_NS,    'updated');
 | 
			
		||||
        $encNodes = $node->getElementsByTagNameNS(Feed::CONTENT_NS, 'encoded');
 | 
			
		||||
 | 
			
		||||
        $item              = new FeedItem();
 | 
			
		||||
        $item->guid        = $itemGuid == 'guid not found' ? Feed::rssValue($node, 'link') : $itemGuid;
 | 
			
		||||
        $item->title       = Feed::rssValue($node, 'title');
 | 
			
		||||
        $item->link        = Feed::rssValue($node, 'link');
 | 
			
		||||
        $item->publishedOn = Data::formatDate(Feed::rssValue($node, 'pubDate'));
 | 
			
		||||
        $item->updatedOn   = Data::formatDate($updNodes->length > 0 ? $updNodes->item(0)->textContent : null);
 | 
			
		||||
        $item->content     = $encNodes->length > 0
 | 
			
		||||
            ? $encNodes->item(0)->textContent
 | 
			
		||||
            : Feed::rssValue($node, 'description');
 | 
			
		||||
 | 
			
		||||
        return $item;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -87,11 +142,11 @@ class Feed {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the value of a child element by its tag name for an RSS feed
 | 
			
		||||
     *
 | 
			
		||||
     * @param DOMElement $element The parent element
 | 
			
		||||
     * @param DOMNode $element The parent element
 | 
			
		||||
     * @param string $tagName The name of the tag whose value should be obtained
 | 
			
		||||
     * @return string The value of the element (or "[element] not found" if that element does not exist)
 | 
			
		||||
     */
 | 
			
		||||
    private static function rssValue(DOMElement $element, string $tagName): string {
 | 
			
		||||
    public static function rssValue(DOMNode $element, string $tagName): string {
 | 
			
		||||
        $tags = $element->getElementsByTagName($tagName);
 | 
			
		||||
        return $tags->length == 0 ? "$tagName not found" : $tags->item(0)->textContent;
 | 
			
		||||
    }
 | 
			
		||||
@ -109,34 +164,19 @@ class Feed {
 | 
			
		||||
            return ['error' => "Channel element not found ($channel->nodeType)"];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $feed = new Feed();
 | 
			
		||||
        $feed->title = self::rssValue($channel, 'title');
 | 
			
		||||
        $feed->url   = $url;
 | 
			
		||||
 | 
			
		||||
        // The Atom namespace provides a lastBuildDate, which contains the last time an item in the feed was updated; if
 | 
			
		||||
        // that is not present, use the pubDate element instead
 | 
			
		||||
        $feed->updatedOn = self::rssValue($channel, 'lastBuildDate');
 | 
			
		||||
        if ($feed->updatedOn == 'lastBuildDate not found') {
 | 
			
		||||
            $feed->updatedOn = self::rssValue($channel, 'pubDate');
 | 
			
		||||
            if ($feed->updatedOn == 'pubDate not found') $feed->updatedOn = null;
 | 
			
		||||
        $updatedOn = self::rssValue($channel, 'lastBuildDate');
 | 
			
		||||
        if ($updatedOn == 'lastBuildDate not found') {
 | 
			
		||||
            $updatedOn = self::rssValue($channel, 'pubDate');
 | 
			
		||||
            if ($updatedOn == 'pubDate not found') $updatedOn = null;
 | 
			
		||||
        }
 | 
			
		||||
        $feed->updatedOn = Data::formatDate($feed->updatedOn);
 | 
			
		||||
 | 
			
		||||
        foreach ($channel->getElementsByTagName('item') as $xmlItem) {
 | 
			
		||||
            $itemGuid = self::rssValue($xmlItem, 'guid');
 | 
			
		||||
            $updNodes = $xmlItem->getElementsByTagNameNS(Feed::ATOM_NS,    'updated');
 | 
			
		||||
            $encNodes = $xmlItem->getElementsByTagNameNS(Feed::CONTENT_NS, 'encoded');
 | 
			
		||||
            $item     = new FeedItem();
 | 
			
		||||
            $item->guid        = $itemGuid == 'guid not found' ? self::rssValue($xmlItem, 'link') : $itemGuid;
 | 
			
		||||
            $item->title       = self::rssValue($xmlItem, 'title');
 | 
			
		||||
            $item->link        = self::rssValue($xmlItem, 'link');
 | 
			
		||||
            $item->publishedOn = Data::formatDate(self::rssValue($xmlItem, 'pubDate'));
 | 
			
		||||
            $item->updatedOn   = Data::formatDate($updNodes->length > 0 ? $updNodes->item(0)->textContent : null);
 | 
			
		||||
            $item->content     = $encNodes->length > 0
 | 
			
		||||
                ? $encNodes->item(0)->textContent
 | 
			
		||||
                : self::rssValue($xmlItem, 'description');
 | 
			
		||||
            $feed->items[] = $item;
 | 
			
		||||
        }
 | 
			
		||||
        $feed            = new Feed();
 | 
			
		||||
        $feed->title     = self::rssValue($channel, 'title');
 | 
			
		||||
        $feed->url       = $url;
 | 
			
		||||
        $feed->updatedOn = Data::formatDate($updatedOn);
 | 
			
		||||
        foreach ($channel->getElementsByTagName('item') as $item) $feed->items[] = FeedItem::fromRSS($item);
 | 
			
		||||
 | 
			
		||||
        return ['ok' => $feed];
 | 
			
		||||
    }
 | 
			
		||||
@ -147,11 +187,11 @@ class Feed {
 | 
			
		||||
     * (Atom feeds can have type attributes on nearly any value. For our purposes, types "text" and "html" will work as
 | 
			
		||||
     * regular string values; for "xhtml", though, we will need to get the `<div>` and extract its contents instead.)
 | 
			
		||||
     *
 | 
			
		||||
     * @param DOMElement $element The parent element
 | 
			
		||||
     * @param DOMNode $element The parent element
 | 
			
		||||
     * @param string $tagName The name of the tag whose value should be obtained
 | 
			
		||||
     * @return string The value of the element (or "[element] not found" if that element does not exist)
 | 
			
		||||
     */
 | 
			
		||||
    private static function atomValue(DOMElement $element, string $tagName): string {
 | 
			
		||||
    public static function atomValue(DOMNode $element, string $tagName): string {
 | 
			
		||||
        $tags = $element->getElementsByTagName($tagName);
 | 
			
		||||
        if ($tags->length == 0) return "$tagName not found";
 | 
			
		||||
        $tag = $tags->item(0);
 | 
			
		||||
@ -172,39 +212,15 @@ class Feed {
 | 
			
		||||
     * @return array|Feed[] ['ok' => feed]
 | 
			
		||||
     */
 | 
			
		||||
    private static function fromAtom(DOMDocument $xml, string $url): array {
 | 
			
		||||
        /** @var DOMElement $root */
 | 
			
		||||
        $root = $xml->getElementsByTagNameNS(self::ATOM_NS, 'feed')->item(0);
 | 
			
		||||
        $feed = new Feed();
 | 
			
		||||
        $feed->title = self::atomValue($root, 'title');
 | 
			
		||||
        $feed->url   = $url;
 | 
			
		||||
        $root      = $xml->getElementsByTagNameNS(self::ATOM_NS, 'feed')->item(0);
 | 
			
		||||
        $updatedOn = self::atomValue($root, 'updated');
 | 
			
		||||
        if ($updatedOn == 'pubDate not found') $updatedOn = null;
 | 
			
		||||
 | 
			
		||||
        $feed->updatedOn = self::atomValue($root, 'updated');
 | 
			
		||||
        if ($feed->updatedOn == 'pubDate not found') $feed->updatedOn = null;
 | 
			
		||||
        $feed->updatedOn = Data::formatDate($feed->updatedOn);
 | 
			
		||||
 | 
			
		||||
        foreach ($root->getElementsByTagName('entry') as $xmlItem) {
 | 
			
		||||
            $guid = self::atomValue($xmlItem, 'id');
 | 
			
		||||
            $link = '';
 | 
			
		||||
            foreach ($xmlItem->getElementsByTagName('link') as $linkElt) {
 | 
			
		||||
                if ($linkElt->hasAttributes()) {
 | 
			
		||||
                    $relAttr = $linkElt->attributes->getNamedItem('rel');
 | 
			
		||||
                    if ($relAttr && $relAttr->value == 'alternate') {
 | 
			
		||||
                        $link = $linkElt->attributes->getNamedItem('href')->value;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($link == '' && str_starts_with($guid, 'http')) $link = $guid;
 | 
			
		||||
 | 
			
		||||
            $item = new FeedItem();
 | 
			
		||||
            $item->guid        = $guid;
 | 
			
		||||
            $item->title       = self::atomValue($xmlItem, 'title');
 | 
			
		||||
            $item->link        = $link;
 | 
			
		||||
            $item->publishedOn = Data::formatDate(self::atomValue($xmlItem, 'published'));
 | 
			
		||||
            $item->updatedOn   = Data::formatDate(self::atomValue($xmlItem, 'updated'));
 | 
			
		||||
            $item->content     = self::atomValue($xmlItem, 'content');
 | 
			
		||||
            $feed->items[] = $item;
 | 
			
		||||
        }
 | 
			
		||||
        $feed            = new Feed();
 | 
			
		||||
        $feed->title     = self::atomValue($root, 'title');
 | 
			
		||||
        $feed->url       = $url;
 | 
			
		||||
        $feed->updatedOn = Data::formatDate($updatedOn);
 | 
			
		||||
        foreach ($root->getElementsByTagName('entry') as $entry) $feed->items[] = FeedItem::fromAtom($entry);
 | 
			
		||||
 | 
			
		||||
        return ['ok' => $feed];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -84,6 +84,20 @@ class Security {
 | 
			
		||||
        add_error('Invalid credentials; log on unsuccessful');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the password for the given user
 | 
			
		||||
     *
 | 
			
		||||
     * @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 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, PASSWORD_DEFAULT));
 | 
			
		||||
        $query->bindValue(':email',    $email);
 | 
			
		||||
        $query->execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Log on the single user
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -19,14 +19,17 @@ if (array_key_exists('refresh', $_GET)) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$result = $db->query(<<<'SQL'
 | 
			
		||||
$query = $db->prepare(<<<'SQL'
 | 
			
		||||
    SELECT item.id, item.title AS item_title, coalesce(item.updated_on, item.published_on) AS as_of,
 | 
			
		||||
           feed.title AS feed_title
 | 
			
		||||
      FROM item
 | 
			
		||||
           INNER JOIN feed ON feed.id = item.feed_id
 | 
			
		||||
     WHERE item.is_read = 0
 | 
			
		||||
     WHERE feed.user_id = :userId 
 | 
			
		||||
       AND item.is_read = 0
 | 
			
		||||
     ORDER BY coalesce(item.updated_on, item.published_on) DESC
 | 
			
		||||
    SQL);
 | 
			
		||||
$query->bindValue(':userId', $_SESSION[Key::USER_ID]);
 | 
			
		||||
$result = $query->execute();
 | 
			
		||||
$item   = $result ? $result->fetchArray(SQLITE3_ASSOC) : false;
 | 
			
		||||
 | 
			
		||||
page_head('Welcome'); ?>
 | 
			
		||||
@ -34,7 +37,7 @@ page_head('Welcome'); ?>
 | 
			
		||||
<article><?php
 | 
			
		||||
if ($item) {
 | 
			
		||||
    while ($item) { ?>
 | 
			
		||||
        <p><a href=/item?id=<?=$item['id']?>><?=$item['item_title']?></a><br>
 | 
			
		||||
        <p><a href=/item?id=<?=$item['id']?>><?=strip_tags($item['item_title'])?></a><br>
 | 
			
		||||
            <?=htmlentities($item['feed_title'])?><br><small><em><?=date_time($item['as_of'])?></em></small><?php
 | 
			
		||||
        $item = $result->fetchArray(SQLITE3_ASSOC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ $updated   = isset($item['updated_on']) ? date_time($item['updated_on']) : null;
 | 
			
		||||
 | 
			
		||||
page_head(htmlentities("{$item['item_title']} | {$item['feed_title']}")); ?>
 | 
			
		||||
<h1 class=item_heading>
 | 
			
		||||
    <a href="<?=$item['item_link']?>" target=_blank rel=noopener><?=$item['item_title']?></a><br>
 | 
			
		||||
    <a href="<?=$item['item_link']?>" target=_blank rel=noopener><?=strip_tags($item['item_title'])?></a><br>
 | 
			
		||||
</h1>
 | 
			
		||||
<div class=item_published>
 | 
			
		||||
    From <strong><?=htmlentities($item['feed_title'])?></strong><br>
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
use JetBrains\PhpStorm\NoReturn;
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
Data::ensureDb();
 | 
			
		||||
require 'app-config.php';
 | 
			
		||||
 | 
			
		||||
session_start([
 | 
			
		||||
    'name'            => 'FRCSESSION',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										177
									
								
								src/util/user.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/util/user.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
<?php
 | 
			
		||||
use JetBrains\PhpStorm\NoReturn;
 | 
			
		||||
 | 
			
		||||
require __DIR__ . '/../cli-start.php';
 | 
			
		||||
 | 
			
		||||
cli_title('USER MAINTENANCE');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display the options for this utility and exit
 | 
			
		||||
 */
 | 
			
		||||
#[NoReturn]
 | 
			
		||||
function display_help(): void {
 | 
			
		||||
    printfn('Options:');
 | 
			
		||||
    printfn(' - add-user [e-mail] [password]');
 | 
			
		||||
    printfn('     Adds a new user to this instance');
 | 
			
		||||
    printfn(' - set-password [e-mail] [password]');
 | 
			
		||||
    printfn('     Sets the password for the given user');
 | 
			
		||||
    printfn(' - delete-user [e-mail]');
 | 
			
		||||
    printfn('     Deletes a user and all their data' . PHP_EOL);
 | 
			
		||||
    printfn('To assist with migrating from single-user to multi-user mode:');
 | 
			
		||||
    printfn(' - migrate-single-user [e-mail] [password]');
 | 
			
		||||
    printfn('     Changes the e-mail address and password for the single-user mode user');
 | 
			
		||||
    printfn(' - remove-single-user');
 | 
			
		||||
    printfn('     Removes the single-user mode user and its data');
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if ($argc < 2) display_help();
 | 
			
		||||
 | 
			
		||||
switch ($argv[1]) {
 | 
			
		||||
    case 'add-user':
 | 
			
		||||
        if ($argc < 4) {
 | 
			
		||||
            printfn('Missing parameters: add-user requires e-mail and password');
 | 
			
		||||
            exit(-1);
 | 
			
		||||
        }
 | 
			
		||||
        add_user();
 | 
			
		||||
        break;
 | 
			
		||||
    case 'set-password':
 | 
			
		||||
        if ($argc < 4) {
 | 
			
		||||
            printfn('Missing parameters: set-password requires e-mail and password');
 | 
			
		||||
            exit(-1);
 | 
			
		||||
        }
 | 
			
		||||
        set_password();
 | 
			
		||||
        break;
 | 
			
		||||
    case 'delete-user':
 | 
			
		||||
        if ($argc < 3) {
 | 
			
		||||
            printfn('Missing parameters: delete-user requires e-mail address');
 | 
			
		||||
            exit(-1);
 | 
			
		||||
        }
 | 
			
		||||
        delete_user();
 | 
			
		||||
        break;
 | 
			
		||||
    case 'migrate-single-user':
 | 
			
		||||
        printfn('TODO: single-user migration');
 | 
			
		||||
        break;
 | 
			
		||||
    case 'remove-single-user':
 | 
			
		||||
        printfn('TODO: single-user removal');
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        printfn('Unrecognized option "%s"', $argv[1]);
 | 
			
		||||
        display_help();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new user
 | 
			
		||||
 */
 | 
			
		||||
function add_user(): void {
 | 
			
		||||
    global $argv;
 | 
			
		||||
    $db = Data::getConnection();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        // Ensure there is not already a user with this e-mail address
 | 
			
		||||
        $existsQuery = $db->prepare('SELECT COUNT(*) FROM frc_user WHERE email = :email');
 | 
			
		||||
        $existsQuery->bindValue(':email', $argv[2]);
 | 
			
		||||
        $existsResult = $existsQuery->execute();
 | 
			
		||||
        if (!$existsResult) {
 | 
			
		||||
            printfn('SQLite error: %s', $db->lastErrorMsg());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ($existsResult->fetchArray(SQLITE3_NUM)[0] != 0) {
 | 
			
		||||
            printfn('A user with e-mail address "%s" already exists', $argv[2]);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Security::addUser($argv[2], $argv[3], $db);
 | 
			
		||||
 | 
			
		||||
        printfn('User "%s" with password "%s" added successfully', $argv[2], $argv[3]);
 | 
			
		||||
    } finally {
 | 
			
		||||
        $db->close();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set a user's password
 | 
			
		||||
 */
 | 
			
		||||
function set_password(): void {
 | 
			
		||||
    global $argv;
 | 
			
		||||
 | 
			
		||||
    $db = Data::getConnection();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        // Ensure this user exists
 | 
			
		||||
        $existsQuery = $db->prepare('SELECT COUNT(*) FROM frc_user WHERE email = :email');
 | 
			
		||||
        $existsQuery->bindValue(':email', $argv[2]);
 | 
			
		||||
        $existsResult = $existsQuery->execute();
 | 
			
		||||
        if (!$existsResult) {
 | 
			
		||||
            printfn('SQLite error: %s', $db->lastErrorMsg());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ($existsResult->fetchArray(SQLITE3_NUM)[0] == 0) {
 | 
			
		||||
            printfn('No user exists with e-mail address "%s"', $argv[2]);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Security::updatePassword($argv[2], $argv[3], $db);
 | 
			
		||||
 | 
			
		||||
        printfn('User "%s" password set to "%s" successfully', $argv[2], $argv[3]);
 | 
			
		||||
    } finally {
 | 
			
		||||
        $db->close();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Delete a user
 | 
			
		||||
 */
 | 
			
		||||
function delete_user(): void {
 | 
			
		||||
    global $argv;
 | 
			
		||||
 | 
			
		||||
    $db = Data::getConnection();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        // Get the ID for the provided e-mail address
 | 
			
		||||
        $idQuery = $db->prepare('SELECT id FROM frc_user WHERE email = :email');
 | 
			
		||||
        $idQuery->bindValue(':email', $argv[2]);
 | 
			
		||||
        $idResult = $idQuery->execute();
 | 
			
		||||
        if (!$idResult) {
 | 
			
		||||
            printfn('SQLite error: %s', $db->lastErrorMsg());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $id = $idResult->fetchArray(SQLITE3_NUM);
 | 
			
		||||
        if (!$id) {
 | 
			
		||||
            printfn('No user exists with e-mail address "%s"', $argv[2]);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $feedCountQuery = $db->prepare('SELECT COUNT(*) FROM feed WHERE user_id = :user');
 | 
			
		||||
        $feedCountQuery->bindValue(':user', $id[0]);
 | 
			
		||||
        $feedCountResult = $feedCountQuery->execute();
 | 
			
		||||
        if (!$feedCountResult) {
 | 
			
		||||
            printfn('SQLite error: %s', $db->lastErrorMsg());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $feedCount = $feedCountResult->fetchArray(SQLITE3_NUM);
 | 
			
		||||
 | 
			
		||||
        $proceed = readline("Delete user \"$argv[2]\" and their $feedCount[0] feed(s)? (y/N)" . PHP_EOL);
 | 
			
		||||
        if (!$proceed || !str_starts_with(strtolower($proceed), 'y')) {
 | 
			
		||||
            printfn('Deletion canceled');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $itemDelete = $db->prepare('DELETE FROM item WHERE feed_id IN (SELECT id FROM feed WHERE user_id = :user)');
 | 
			
		||||
        $itemDelete->bindValue(':user', $id[0]);
 | 
			
		||||
        $itemDelete->execute();
 | 
			
		||||
 | 
			
		||||
        $feedDelete = $db->prepare('DELETE FROM feed WHERE user_id = :user');
 | 
			
		||||
        $feedDelete->bindValue(':user', $id[0]);
 | 
			
		||||
        $feedDelete->execute();
 | 
			
		||||
 | 
			
		||||
        $userDelete = $db->prepare('DELETE FROM frc_user WHERE id = :user');
 | 
			
		||||
        $userDelete->bindValue(':user', $id[0]);
 | 
			
		||||
        $userDelete->execute();
 | 
			
		||||
 | 
			
		||||
        printfn('User "%s" deleted successfully', $argv[2]);
 | 
			
		||||
    } finally {
 | 
			
		||||
        $db->close();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user