1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2025-04-18 19:35:50 +00:00
Arsse/lib/Database.php
J. King e6feb8de8d First basic test for User class
As the User class depends on the database and this has yet to be tested (though I'm fairly certain it works), the mock driver also acts as a mock of the required database functions, with both instances sharing a common storage structure. Later test series should separate the two.
2017-02-19 00:22:16 -05:00

277 lines
No EOL
13 KiB
PHP

<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync;
class Database {
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;
protected function cleanName(string $name): string {
return (string) preg_filter("[^0-9a-zA-Z_\.]", "", $name);
}
public function __construct(RuntimeData $data) {
$this->data = $data;
$driver = $data->conf->dbDriver;
$this->db = $driver::create($data, INSTALL);
$ver = $this->db->schemaVersion();
if(!INSTALL && $ver < self::SCHEMA_VERSION) {
$this->db->update(self::SCHEMA_VERSION);
}
}
static public function listDrivers(): array {
$sep = \DIRECTORY_SEPARATOR;
$path = __DIR__.$sep."Db".$sep;
$classes = [];
foreach(glob($path."Driver?*.php") as $file) {
$name = basename($file, ".php");
if(substr($name,-3) != "PDO") {
$name = NS_BASE."Db\\$name";
if(class_exists($name)) {
$classes[$name] = $name::driverName();
}
}
}
return $classes;
}
public function schemaVersion(): int {
return $this->db->schemaVersion();
}
public function dbUpdate(): bool {
if($this->db->schemaVersion() < self::SCHEMA_VERSION) return $this->db->update(self::SCHEMA_VERSION);
return false;
}
public function settingGet(string $key) {
$row = $this->db->prepare("SELECT value, type from newssync_settings where key = ?", "str")->run($key)->get();
if(!$row) return null;
switch($row['type']) {
case "int": return (int) $row['value'];
case "numeric": return (float) $row['value'];
case "text": return $row['value'];
case "json": return json_decode($row['value']);
case "timestamp": return date_create_from_format("!".self::FORMAT_TS, $row['value'], new DateTimeZone("UTC"));
case "date": return date_create_from_format("!".self::FORMAT_DATE, $row['value'], new DateTimeZone("UTC"));
case "time": return date_create_from_format("!".self::FORMAT_TIME, $row['value'], new DateTimeZone("UTC"));
case "bool": return (bool) $row['value'];
case "null": return null;
default: return $row['value'];
}
}
public function settingSet(string $key, $in, string $type = null): bool {
if(!$type) {
switch(gettype($in)) {
case "boolean": $type = "bool"; break;
case "integer": $type = "int"; break;
case "double": $type = "numeric"; break;
case "string":
case "array": $type = "json"; break;
case "resource":
case "unknown type":
case "NULL": $type = "null"; break;
case "object":
if($in instanceof DateTimeInterface) {
$type = "timestamp";
} else {
$type = "text";
}
break;
default: $type = 'null'; break;
}
}
$type = strtolower($type);
switch($type) {
case "integer":
$type = "int";
case "int":
$value =& $in;
break;
case "float":
case "double":
case "real":
$type = "numeric";
case "numeric":
$value =& $in;
break;
case "str":
case "string":
$type = "text";
case "text":
$value =& $in;
break;
case "json":
if(is_array($in) || is_object($in)) {
$value = json_encode($in);
} else {
$value =& $in;
}
break;
case "datetime":
$type = "timestamp";
case "timestamp":
if($in instanceof DateTimeInterface) {
$value = gmdate(self::FORMAT_TS, $in->format("U"));
} else if(is_numeric($in)) {
$value = gmdate(self::FORMAT_TS, $in);
} else {
$value = gmdate(self::FORMAT_TS, gmstrftime($in));
}
break;
case "date":
if($in instanceof DateTimeInterface) {
$value = gmdate(self::FORMAT_DATE, $in->format("U"));
} else if(is_numeric($in)) {
$value = gmdate(self::FORMAT_DATE, $in);
} else {
$value = gmdate(self::FORMAT_DATE, gmstrftime($in));
}
break;
case "time":
if($in instanceof DateTimeInterface) {
$value = gmdate(self::FORMAT_TIME, $in->format("U"));
} else if(is_numeric($in)) {
$value = gmdate(self::FORMAT_TIME, $in);
} else {
$value = gmdate(self::FORMAT_TIME, gmstrftime($in));
}
break;
case "boolean":
case "bit":
$type = "bool";
case "bool":
$value = (int) $in;
break;
case "null":
$value = null;
break;
default:
$type = "text";
$value =& $in;
break;
}
$this->db->prepare("REPLACE INTO newssync_settings(key,value,type) values(?,?,?)", "str", (($type=="null") ? "null" : "str"), "str")->run($key, $value, "text");
}
public function settingRemove(string $key): bool {
$this->db->prepare("DELETE from newssync_settings where key = ?", "str")->run($key);
return true;
}
public function userExists(string $user): bool {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
return (bool) $this->db->prepare("SELECT count(*) from newssync_users where id is ?", "str")->run($user)->getSingle();
}
public function userAdd(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;
if(strlen($password) > 0) $password = password_hash($password, \PASSWORD_DEFAULT);
$this->db->prepare("INSERT INTO newssync_users(id,password) values(?,?)", "str", "str")->run($user,$password);
return true;
}
public function userRemove(string $user): bool {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$this->db->prepare("DELETE from newssync_users where id is ?", "str")->run($user);
return true;
}
public function userList(string $domain = null): array {
if($domain !== null) {
if(!$this->data->user->authorize("@".$domain, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $domain]);
$domain = str_replace(["\\","%","_"],["\\\\", "\\%", "\\_"], $domain);
$domain = "%@".$domain;
$set = $this->db->prepare("SELECT id from newssync_users where id like ?", "str")->run($domain);
} else {
if(!$this->data->user->authorize("", __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => "all users"]);
$set = $this->db->prepare("SELECT id from newssync_users")->run();
}
$out = [];
foreach($set as $row) {
$out[] = $row["id"];
}
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;
if(strlen($password > 0)) $password = password_hash($password, \PASSWORD_DEFAULT);
$this->db->prepare("UPDATE newssync_users set password = ? where id is ?", "str", "str")->run($password, $user);
return true;
}
public function userPropertiesGet(string $user): array {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
$prop = $this->db->prepare("SELECT name,rights from newssync_users where id is ?", "str")->run($user)->get();
if(!$prop) return [];
return $prop;
}
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",
];
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->commit();
return $this->userPropertiesGet($user);
}
public function userRightsGet(string $user): int {
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
return (int) $this->db->prepare("SELECT rights from newssync_users where id is ?", "str")->run($user)->getSingle();
}
public function userRightsSet(string $user, int $rights): bool {
if(!$this->data->user->authorize($user, __FUNCTION__, $rights)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
if(!$this->userExists($user)) return false;
$this->db->prepare("UPDATE newssync_users set rights = ? where id is ?", "int", "str")->run($rights, $user);
return true;
}
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__]);
$this->db->begin();
$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);
$feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle();
}
$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();
return $sub;
}
public function subscriptionRemove(int $id): bool {
$this->db->begin();
$user = $this->db->prepare("SELECT owner from newssync_subscriptions where id is ?", "int")->run($id)->getSingle();
if($user===null) return false;
if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
return (bool) $this->db->prepare("DELETE from newssync_subscriptions where id is ?", "int")->run($id)->changes();
}
}