1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Started integration of PicoFeed

• Integrated PicoFeed into Database->subscriptionAdd
• Added exception handling for feeds
• Added static method for formatting SQL dates into Db/Common
This commit is contained in:
Dustin Wilson 2017-02-19 16:02:03 -06:00
parent e6feb8de8d
commit e5d825d360
5 changed files with 110 additions and 20 deletions

View file

@ -39,6 +39,17 @@ abstract class AbstractException extends \Exception {
"User/Exception.authMissing" => 10411, "User/Exception.authMissing" => 10411,
"User/Exception.authFailed" => 10412, "User/Exception.authFailed" => 10412,
"User/Exception.notAuthorized" => 10421, "User/Exception.notAuthorized" => 10421,
"Feed/Exception.invalidCertificate" => 10501,
"Feed/Exception.invalidUrl" => 10502,
"Feed/Exception.maxRedirect" => 10503,
"Feed/Exception.maxSize" => 10504,
"Feed/Exception.timeout" => 10505,
"Feed/Exception.forbidden" => 10506,
"Feed/Exception.unauthorized" => 10507,
"Feed/Exception.malformed" => 10511,
"Feed/Exception.xmlEntity" => 10512,
"Feed/Exception.subscriptionNotFound" => 10521,
"Feed/Exception.unsupportedFeedFormat" => 10522
]; ];
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {

View file

@ -3,6 +3,9 @@ declare(strict_types=1);
namespace JKingWeb\NewsSync; namespace JKingWeb\NewsSync;
class Database { class Database {
use PicoFeed\Reader\Reader;
use PicoFeed\PicoFeedException;
const SCHEMA_VERSION = 1; const SCHEMA_VERSION = 1;
const FORMAT_TS = "Y-m-d h:i:s"; const FORMAT_TS = "Y-m-d h:i:s";
const FORMAT_DATE = "Y-m-d"; const FORMAT_DATE = "Y-m-d";
@ -10,6 +13,7 @@ class Database {
protected $data; protected $data;
public $db; public $db;
private $driver;
protected function cleanName(string $name): string { protected function cleanName(string $name): string {
return (string) preg_filter("[^0-9a-zA-Z_\.]", "", $name); return (string) preg_filter("[^0-9a-zA-Z_\.]", "", $name);
@ -17,8 +21,8 @@ class Database {
public function __construct(RuntimeData $data) { public function __construct(RuntimeData $data) {
$this->data = $data; $this->data = $data;
$driver = $data->conf->dbDriver; $this->driver = $data->conf->dbDriver;
$this->db = $driver::create($data, INSTALL); $this->db = $this->driver::create($data, INSTALL);
$ver = $this->db->schemaVersion(); $ver = $this->db->schemaVersion();
if(!INSTALL && $ver < self::SCHEMA_VERSION) { if(!INSTALL && $ver < self::SCHEMA_VERSION) {
$this->db->update(self::SCHEMA_VERSION); $this->db->update(self::SCHEMA_VERSION);
@ -72,12 +76,12 @@ class Database {
switch(gettype($in)) { switch(gettype($in)) {
case "boolean": $type = "bool"; break; case "boolean": $type = "bool"; break;
case "integer": $type = "int"; break; case "integer": $type = "int"; break;
case "double": $type = "numeric"; break; case "double": $type = "numeric"; break;
case "string": case "string":
case "array": $type = "json"; break; case "array": $type = "json"; break;
case "resource": case "resource":
case "unknown type": case "unknown type":
case "NULL": $type = "null"; break; case "NULL": $type = "null"; break;
case "object": case "object":
if($in instanceof DateTimeInterface) { if($in instanceof DateTimeInterface) {
$type = "timestamp"; $type = "timestamp";
@ -85,7 +89,7 @@ class Database {
$type = "text"; $type = "text";
} }
break; break;
default: $type = 'null'; break; default: $type = 'null'; break;
} }
} }
$type = strtolower($type); $type = strtolower($type);
@ -251,15 +255,55 @@ class Database {
} }
public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int { public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); // If the user isn't authorized to perform this action then throw an exception.
if(!$this->userExists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); if (!$this->data->user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
// If the user doesn't exist throw an exception.
if (!$this->userExists($user)) {
throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]);
}
$this->db->begin(); $this->db->begin();
// If the feed doesn't already exist in the database then add it to the database after determining its validity with picoFeed
$qFeed = $this->db->prepare("SELECT id from newssync_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); $qFeed = $this->db->prepare("SELECT id from newssync_feeds where url is ? and username is ? and password is ?", "str", "str", "str");
$feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle();
if($feed===null) { if ($feed === null) {
$this->db->prepare("INSERT INTO newssync_feeds(url,username,password) values(?,?,?)", "str", "str", "str")->run($url, $fetchUser, $fetchPassword); try {
$reader = new Reader;
$resource = $reader->download($url);
$parser = $reader->getParser(
$resource->getUrl(),
$resource->getContent(),
$resource->getEncoding()
);
$feed = $parser->execute();
} catch (PicoFeedException $e) {
// If there's any error while trying to download or parse the feed then return an exception
throw new Feed\Exception($url, $e);
}
$this->db->prepare("INSERT INTO newssync_feeds(url,title,favicon,source,updated,modified,etag,username,password) values(?,?,?)", "str", "str", "str", "str", "str", "str", "str", "str", "str")->run(
$url,
$feed->title,
// Grab the favicon for the feed. Returns an empty string if it cannot find one
(new PicoFeed\Reader\Favicon)->find($url),
$feed->siteUrl,
// Convert the date formats to ISO 8601 before inserting
$driver::formatDate($feed->date),
$driver::formatDate($resource->getLastModified()),
$resource->getEtag(),
$fetchUser,
$fetchPassword
);
$feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle();
} }
// Add the feed to a user's subscriptions.
$this->db->prepare("INSERT INTO newssync_subscriptions(owner,feed) values(?,?)", "str", "int")->run($user,$feed); $this->db->prepare("INSERT INTO newssync_subscriptions(owner,feed) values(?,?)", "str", "int")->run($user,$feed);
$sub = $this->db->prepare("SELECT id from newssync_subscriptions where owner is ? and feed is ?", "str", "int")->run($user, $feed)->getSingle(); $sub = $this->db->prepare("SELECT id from newssync_subscriptions where owner is ? and feed is ?", "str", "int")->run($user, $feed)->getSingle();
$this->db->commit(); $this->db->commit();

View file

@ -70,4 +70,13 @@ Trait Common {
return $this->prepareArray($query, $paramType); return $this->prepareArray($query, $paramType);
} }
public static function formatDate(string $date): string {
// Force UTC.
$timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
// ISO 8601 with space in the middle instead of T.
$date = date('Y-m-d h:i:sP', strtotime($date));
date_default_timezone_set($timezone);
return $date;
}
} }

14
lib/Feed/Exception.php Normal file
View file

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Feed;
class Exception extends \JKingWeb\NewsSync\AbstractException {
public function __construct($url, \Throwable $e) {
$className = get_class($e);
// Convert the exception thrown by PicoFeed to the one to be thrown here.
$msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className);
// If the message ID doesn't change then it's unknown.
$msgID = ($msgID !== $className) ? lcfirst($msgID) : '';
parent::__construct($msgID, ['url' => $url], $e);
}
}

View file

@ -51,4 +51,16 @@ return [
'Exception.JKingWeb/NewsSync/User/Exception.authMissing' => 'Please log in to proceed', 'Exception.JKingWeb/NewsSync/User/Exception.authMissing' => 'Please log in to proceed',
'Exception.JKingWeb/NewsSync/User/Exception.authFailed' => 'Authentication failed', 'Exception.JKingWeb/NewsSync/User/Exception.authFailed' => 'Authentication failed',
'Exception.JKingWeb/NewsSync/User/ExceptionAuthz.notAuthorized' => 'Authenticated user is not authorized to perform the action "{action}" on behalf of {user}', 'Exception.JKingWeb/NewsSync/User/ExceptionAuthz.notAuthorized' => 'Authenticated user is not authorized to perform the action "{action}" on behalf of {user}',
'Exception.JKingWeb/NewsSync/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
'Exception.JKingWeb/NewsSync/Feed/Exception.invalidURL' => 'Feed URL "{url}" is invalid',
'Exception.JKingWeb/NewsSync/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
'Exception.JKingWeb/NewsSync/Feed/Exception.maxSize' => 'Could not download feed "{url}" because its size exceeds the maximum allowed on its server',
'Exception.JKingWeb/NewsSync/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out',
'Exception.JKingWeb/NewsSync/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',
'Exception.JKingWeb/NewsSync/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials',
'Exception.JKingWeb/NewsSync/Feed/Exception.malformed' => 'Could not parse feed "{url}" because it is malformed',
'Exception.JKingWeb/NewsSync/Feed/Exception.xxe' => 'Refused to parse feed "{url}" because it contains an XXE attack',
'Exception.JKingWeb/NewsSync/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
'Exception.JKingWeb/NewsSync/Feed/Exception.unsupportedFormat' => 'Feed "{url}" is of an unsupported format'
]; ];