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:
parent
e6feb8de8d
commit
e5d825d360
5 changed files with 110 additions and 20 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
14
lib/Feed/Exception.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
];
|
];
|
Loading…
Reference in a new issue