Browse Source

Started integration of PicoFeed

• Integrated PicoFeed into Database->subscriptionAdd
• Added exception handling for feeds
• Added static method for formatting SQL dates into Db/Common
microsub
Dustin Wilson 7 years ago
parent
commit
e5d825d360
  1. 11
      lib/AbstractException.php
  2. 78
      lib/Database.php
  3. 15
      lib/Db/Common.php
  4. 14
      lib/Feed/Exception.php
  5. 12
      locale/en.php

11
lib/AbstractException.php

@ -39,6 +39,17 @@ abstract class AbstractException extends \Exception {
"User/Exception.authMissing" => 10411,
"User/Exception.authFailed" => 10412,
"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) {

78
lib/Database.php

@ -3,13 +3,17 @@ declare(strict_types=1);
namespace JKingWeb\NewsSync;
class Database {
use PicoFeed\Reader\Reader;
use PicoFeed\PicoFeedException;
const SCHEMA_VERSION = 1;
const FORMAT_TS = "Y-m-d h:i:s";
const FORMAT_DATE = "Y-m-d";
const FORMAT_TIME = "h:i:s";
protected $data;
public $db;
private $driver;
protected function cleanName(string $name): string {
return (string) preg_filter("[^0-9a-zA-Z_\.]", "", $name);
@ -17,8 +21,8 @@ class Database {
public function __construct(RuntimeData $data) {
$this->data = $data;
$driver = $data->conf->dbDriver;
$this->db = $driver::create($data, INSTALL);
$this->driver = $data->conf->dbDriver;
$this->db = $this->driver::create($data, INSTALL);
$ver = $this->db->schemaVersion();
if(!INSTALL && $ver < self::SCHEMA_VERSION) {
$this->db->update(self::SCHEMA_VERSION);
@ -36,7 +40,7 @@ class Database {
if(class_exists($name)) {
$classes[$name] = $name::driverName();
}
}
}
}
return $classes;
}
@ -72,12 +76,12 @@ class Database {
switch(gettype($in)) {
case "boolean": $type = "bool"; break;
case "integer": $type = "int"; break;
case "double": $type = "numeric"; break;
case "double": $type = "numeric"; break;
case "string":
case "array": $type = "json"; break;
case "array": $type = "json"; break;
case "resource":
case "unknown type":
case "NULL": $type = "null"; break;
case "NULL": $type = "null"; break;
case "object":
if($in instanceof DateTimeInterface) {
$type = "timestamp";
@ -85,7 +89,7 @@ class Database {
$type = "text";
}
break;
default: $type = 'null'; break;
default: $type = 'null'; break;
}
}
$type = strtolower($type);
@ -113,7 +117,7 @@ class Database {
$value = json_encode($in);
} else {
$value =& $in;
}
}
break;
case "datetime":
$type = "timestamp";
@ -201,13 +205,13 @@ class Database {
}
return $out;
}
public function userPasswordGet(string $user): string {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
if(!$this->userExists($user)) return "";
return (string) $this->db->prepare("SELECT password from newssync_users where id is ?", "str")->run($user)->getSingle();
}
public function userPasswordSet(string $user, string $password = null): bool {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
if(!$this->userExists($user)) return false;
@ -226,13 +230,13 @@ class Database {
public function userPropertiesSet(string $user, array &$properties): array {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$valid = [ // FIXME: add future properties
"name" => "str",
"name" => "str",
];
if(!$this->userExists($user)) return [];
$this->db->begin();
foreach($valid as $prop => $type) {
if(!array_key_exists($prop, $properties)) continue;
$this->db->prepare("UPDATE newssync_users set $prop = ? where id is ?", $type, "str")->run($properties[$prop], $user);
$this->db->prepare("UPDATE newssync_users set $prop = ? where id is ?", $type, "str")->run($properties[$prop], $user);
}
$this->db->commit();
return $this->userPropertiesGet($user);
@ -251,15 +255,55 @@ class Database {
}
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(!$this->userExists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]);
// If the user isn't authorized to perform this action then throw an exception.
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();
// 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");
$feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle();
if($feed===null) {
$this->db->prepare("INSERT INTO newssync_feeds(url,username,password) values(?,?,?)", "str", "str", "str")->run($url, $fetchUser, $fetchPassword);
if ($feed === null) {
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();
}
// Add the feed to a user's subscriptions.
$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();
$this->db->commit();

15
lib/Db/Common.php

@ -5,7 +5,7 @@ use JKingWeb\DrUUID\UUID as UUID;
Trait Common {
protected $transDepth = 0;
public function schemaVersion(): int {
try {
return $this->data->db->settingGet("schema_version");
@ -13,7 +13,7 @@ Trait Common {
return 0;
}
}
public function begin(): bool {
$this->exec("SAVEPOINT newssync_".($this->transDepth));
$this->transDepth += 1;
@ -27,7 +27,7 @@ Trait Common {
$this->transDepth -= 1;
} else {
$this->exec("COMMIT TRANSACTION");
$this->transDepth = 0;
$this->transDepth = 0;
}
return true;
}
@ -70,4 +70,13 @@ Trait Common {
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

@ -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);
}
}

12
locale/en.php

@ -51,4 +51,16 @@ return [
'Exception.JKingWeb/NewsSync/User/Exception.authMissing' => 'Please log in to proceed',
'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/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…
Cancel
Save