mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 05:02:40 +00:00
Converted all hard tabs to soft tabs
This commit is contained in:
parent
8afa1cb5bb
commit
c5fac33398
33 changed files with 1666 additions and 1666 deletions
10
.gitattributes
vendored
10
.gitattributes
vendored
|
@ -10,13 +10,13 @@
|
|||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
{
|
||||
"name": "jkingweb/arsse",
|
||||
"type": "library",
|
||||
"description": "TODO",
|
||||
"keywords": ["rss"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "J. King",
|
||||
"email": "jking@jkingweb.ca",
|
||||
"homepage": "https://jkingweb.ca/"
|
||||
},
|
||||
{
|
||||
"name": "Dustin Wilson",
|
||||
"email": "dustin@dustinwilson.com",
|
||||
"homepage": "https://dustinwilson.com/"
|
||||
}
|
||||
"name": "jkingweb/arsse",
|
||||
"type": "library",
|
||||
"description": "TODO",
|
||||
"keywords": ["rss"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "J. King",
|
||||
"email": "jking@jkingweb.ca",
|
||||
"homepage": "https://jkingweb.ca/"
|
||||
},
|
||||
{
|
||||
"name": "Dustin Wilson",
|
||||
"email": "dustin@dustinwilson.com",
|
||||
"homepage": "https://dustinwilson.com/"
|
||||
}
|
||||
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0.0",
|
||||
"jkingweb/druuid": "^3.0.0",
|
||||
"phpseclib/phpseclib": "^2.0.4",
|
||||
"webmozart/glob": "^4.1.0",
|
||||
"fguillot/picoFeed": ">=0.1.31"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsStream": "^1.6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JKingWeb\\NewsSync\\": "lib/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"JKingWeb\\NewsSync\\Test\\": "tests/lib/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0.0",
|
||||
"jkingweb/druuid": "^3.0.0",
|
||||
"phpseclib/phpseclib": "^2.0.4",
|
||||
"webmozart/glob": "^4.1.0",
|
||||
"fguillot/picoFeed": ">=0.1.31"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsStream": "^1.6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JKingWeb\\NewsSync\\": "lib/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"JKingWeb\\NewsSync\\Test\\": "tests/lib/"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,57 +4,57 @@ namespace JKingWeb\NewsSync;
|
|||
|
||||
abstract class AbstractException extends \Exception {
|
||||
|
||||
const CODES = [
|
||||
"Exception.uncoded" => -1,
|
||||
"Exception.invalid" => 1, // this exception MUST NOT have a message string defined
|
||||
"Exception.unknown" => 10000,
|
||||
"Lang/Exception.defaultFileMissing" => 10101,
|
||||
"Lang/Exception.fileMissing" => 10102,
|
||||
"Lang/Exception.fileUnreadable" => 10103,
|
||||
"Lang/Exception.fileCorrupt" => 10104,
|
||||
"Lang/Exception.stringMissing" => 10105,
|
||||
"Db/Exception.extMissing" => 10201,
|
||||
"Db/Exception.fileMissing" => 10202,
|
||||
"Db/Exception.fileUnusable" => 10203,
|
||||
"Db/Exception.fileUnreadable" => 10204,
|
||||
"Db/Exception.fileUnwritable" => 10205,
|
||||
"Db/Exception.fileUncreatable" => 10206,
|
||||
"Db/Exception.fileCorrupt" => 10207,
|
||||
"Db/Update/Exception.tooNew" => 10211,
|
||||
"Db/Update/Exception.fileMissing" => 10212,
|
||||
"Db/Update/Exception.fileUnusable" => 10213,
|
||||
"Db/Update/Exception.fileUnreadable" => 10214,
|
||||
"Db/Update/Exception.manual" => 10215,
|
||||
"Db/Update/Exception.manualOnly" => 10216,
|
||||
"Conf/Exception.fileMissing" => 10302,
|
||||
"Conf/Exception.fileUnusable" => 10303,
|
||||
"Conf/Exception.fileUnreadable" => 10304,
|
||||
"Conf/Exception.fileUnwritable" => 10305,
|
||||
"Conf/Exception.fileUncreatable" => 10306,
|
||||
"Conf/Exception.fileCorrupt" => 10307,
|
||||
"User/Exception.functionNotImplemented" => 10401,
|
||||
"User/Exception.doesNotExist" => 10402,
|
||||
"User/Exception.alreadyExists" => 10403,
|
||||
"User/Exception.authMissing" => 10411,
|
||||
"User/Exception.authFailed" => 10412,
|
||||
"User/Exception.notAuthorized" => 10421,
|
||||
];
|
||||
const CODES = [
|
||||
"Exception.uncoded" => -1,
|
||||
"Exception.invalid" => 1, // this exception MUST NOT have a message string defined
|
||||
"Exception.unknown" => 10000,
|
||||
"Lang/Exception.defaultFileMissing" => 10101,
|
||||
"Lang/Exception.fileMissing" => 10102,
|
||||
"Lang/Exception.fileUnreadable" => 10103,
|
||||
"Lang/Exception.fileCorrupt" => 10104,
|
||||
"Lang/Exception.stringMissing" => 10105,
|
||||
"Db/Exception.extMissing" => 10201,
|
||||
"Db/Exception.fileMissing" => 10202,
|
||||
"Db/Exception.fileUnusable" => 10203,
|
||||
"Db/Exception.fileUnreadable" => 10204,
|
||||
"Db/Exception.fileUnwritable" => 10205,
|
||||
"Db/Exception.fileUncreatable" => 10206,
|
||||
"Db/Exception.fileCorrupt" => 10207,
|
||||
"Db/Update/Exception.tooNew" => 10211,
|
||||
"Db/Update/Exception.fileMissing" => 10212,
|
||||
"Db/Update/Exception.fileUnusable" => 10213,
|
||||
"Db/Update/Exception.fileUnreadable" => 10214,
|
||||
"Db/Update/Exception.manual" => 10215,
|
||||
"Db/Update/Exception.manualOnly" => 10216,
|
||||
"Conf/Exception.fileMissing" => 10302,
|
||||
"Conf/Exception.fileUnusable" => 10303,
|
||||
"Conf/Exception.fileUnreadable" => 10304,
|
||||
"Conf/Exception.fileUnwritable" => 10305,
|
||||
"Conf/Exception.fileUncreatable" => 10306,
|
||||
"Conf/Exception.fileCorrupt" => 10307,
|
||||
"User/Exception.functionNotImplemented" => 10401,
|
||||
"User/Exception.doesNotExist" => 10402,
|
||||
"User/Exception.alreadyExists" => 10403,
|
||||
"User/Exception.authMissing" => 10411,
|
||||
"User/Exception.authFailed" => 10412,
|
||||
"User/Exception.notAuthorized" => 10421,
|
||||
];
|
||||
|
||||
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
|
||||
if($msgID=="") {
|
||||
$msg = "Exception.unknown";
|
||||
$code = 10000;
|
||||
} else {
|
||||
$class = get_called_class();
|
||||
$codeID = str_replace("\\", "/", str_replace(NS_BASE, "", $class)).".$msgID";
|
||||
if(!array_key_exists($codeID, self::CODES)) {
|
||||
throw new Exception("uncoded");
|
||||
} else {
|
||||
$code = self::CODES[$codeID];
|
||||
$msg = "Exception.".str_replace("\\", "/", $class).".$msgID";
|
||||
}
|
||||
$msg = Lang::msg($msg, $vars);
|
||||
}
|
||||
parent::__construct($msg, $code, $e);
|
||||
}
|
||||
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
|
||||
if($msgID=="") {
|
||||
$msg = "Exception.unknown";
|
||||
$code = 10000;
|
||||
} else {
|
||||
$class = get_called_class();
|
||||
$codeID = str_replace("\\", "/", str_replace(NS_BASE, "", $class)).".$msgID";
|
||||
if(!array_key_exists($codeID, self::CODES)) {
|
||||
throw new Exception("uncoded");
|
||||
} else {
|
||||
$code = self::CODES[$codeID];
|
||||
$msg = "Exception.".str_replace("\\", "/", $class).".$msgID";
|
||||
}
|
||||
$msg = Lang::msg($msg, $vars);
|
||||
}
|
||||
parent::__construct($msg, $code, $e);
|
||||
}
|
||||
}
|
104
lib/Conf.php
104
lib/Conf.php
|
@ -3,64 +3,64 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync;
|
||||
|
||||
class Conf {
|
||||
public $lang = "en";
|
||||
|
||||
public $dbDriver = Db\DriverSQLite3::class;
|
||||
public $dbSQLite3File = BASE."newssync.db";
|
||||
public $dbSQLite3Key = "";
|
||||
public $dbSQLite3AutoUpd = true;
|
||||
public $dbPostgreSQLHost = "localhost";
|
||||
public $dbPostgreSQLUser = "newssync";
|
||||
public $dbPostgreSQLPass = "";
|
||||
public $dbPostgreSQLPort = 5432;
|
||||
public $dbPostgreSQLDb = "newssync";
|
||||
public $dbPostgreSQLSchema = "";
|
||||
public $dbPostgreSQLAutoUpd = false;
|
||||
public $dbMySQLHost = "localhost";
|
||||
public $dbMySQLUser = "newssync";
|
||||
public $dbMySQLPass = "";
|
||||
public $dbMySQLPort = 3306;
|
||||
public $dbMySQLDb = "newssync";
|
||||
public $dbMySQLAutoUpd = false;
|
||||
public $lang = "en";
|
||||
|
||||
public $userDriver = User\DriverInternal::class;
|
||||
public $userAuthPreferHTTP = false;
|
||||
public $userComposeNames = true;
|
||||
public $dbDriver = Db\DriverSQLite3::class;
|
||||
public $dbSQLite3File = BASE."newssync.db";
|
||||
public $dbSQLite3Key = "";
|
||||
public $dbSQLite3AutoUpd = true;
|
||||
public $dbPostgreSQLHost = "localhost";
|
||||
public $dbPostgreSQLUser = "newssync";
|
||||
public $dbPostgreSQLPass = "";
|
||||
public $dbPostgreSQLPort = 5432;
|
||||
public $dbPostgreSQLDb = "newssync";
|
||||
public $dbPostgreSQLSchema = "";
|
||||
public $dbPostgreSQLAutoUpd = false;
|
||||
public $dbMySQLHost = "localhost";
|
||||
public $dbMySQLUser = "newssync";
|
||||
public $dbMySQLPass = "";
|
||||
public $dbMySQLPort = 3306;
|
||||
public $dbMySQLDb = "newssync";
|
||||
public $dbMySQLAutoUpd = false;
|
||||
|
||||
public $simplepieCache = BASE.".cache";
|
||||
public $userDriver = User\DriverInternal::class;
|
||||
public $userAuthPreferHTTP = false;
|
||||
public $userComposeNames = true;
|
||||
|
||||
public $simplepieCache = BASE.".cache";
|
||||
|
||||
|
||||
public function __construct(string $import_file = "") {
|
||||
if($import_file != "") $this->importFile($import_file);
|
||||
}
|
||||
public function __construct(string $import_file = "") {
|
||||
if($import_file != "") $this->importFile($import_file);
|
||||
}
|
||||
|
||||
public function importFile(string $file): self {
|
||||
if(!file_exists($file)) throw new Conf\Exception("fileMissing", $file);
|
||||
if(!is_readable($file)) throw new Conf\Exception("fileUnreadable", $file);
|
||||
try {
|
||||
ob_start();
|
||||
$arr = (@include $file);
|
||||
} catch(\Throwable $e) {
|
||||
$arr = null;
|
||||
} finally {
|
||||
ob_end_clean();
|
||||
}
|
||||
if(!is_array($arr)) throw new Conf\Exception("fileCorrupt", $file);
|
||||
return $this->import($arr);
|
||||
}
|
||||
public function importFile(string $file): self {
|
||||
if(!file_exists($file)) throw new Conf\Exception("fileMissing", $file);
|
||||
if(!is_readable($file)) throw new Conf\Exception("fileUnreadable", $file);
|
||||
try {
|
||||
ob_start();
|
||||
$arr = (@include $file);
|
||||
} catch(\Throwable $e) {
|
||||
$arr = null;
|
||||
} finally {
|
||||
ob_end_clean();
|
||||
}
|
||||
if(!is_array($arr)) throw new Conf\Exception("fileCorrupt", $file);
|
||||
return $this->import($arr);
|
||||
}
|
||||
|
||||
public function import(array $arr): self {
|
||||
foreach($arr as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function import(array $arr): self {
|
||||
foreach($arr as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function export(string $file = ""): string {
|
||||
// TODO
|
||||
}
|
||||
public function export(string $file = ""): string {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->export();
|
||||
}
|
||||
public function __toString(): string {
|
||||
return $this->export();
|
||||
}
|
||||
}
|
504
lib/Database.php
504
lib/Database.php
|
@ -3,275 +3,275 @@ 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;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 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 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 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 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 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 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", "str")->run($user,$password,$admin);
|
||||
return true;
|
||||
}
|
||||
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", "str")->run($user,$password,$admin);
|
||||
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 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);
|
||||
$this->db->prepare("UPDATE newssync_users set password = ? where id is ?", "str", "str")->run($password, $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);
|
||||
$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 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 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 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__)) 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 userRightsSet(string $user, int $rights): bool {
|
||||
if(!$this->data->user->authorize($user, __FUNCTION__)) 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 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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -4,70 +4,70 @@ namespace JKingWeb\NewsSync\Db;
|
|||
use JKingWeb\DrUUID\UUID as UUID;
|
||||
|
||||
Trait Common {
|
||||
protected $transDepth = 0;
|
||||
|
||||
public function schemaVersion(): int {
|
||||
try {
|
||||
return $this->data->db->settingGet("schema_version");
|
||||
} catch(\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function begin(): bool {
|
||||
$this->exec("SAVEPOINT newssync_".($this->transDepth));
|
||||
$this->transDepth += 1;
|
||||
return true;
|
||||
}
|
||||
protected $transDepth = 0;
|
||||
|
||||
public function schemaVersion(): int {
|
||||
try {
|
||||
return $this->data->db->settingGet("schema_version");
|
||||
} catch(\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function begin(): bool {
|
||||
$this->exec("SAVEPOINT newssync_".($this->transDepth));
|
||||
$this->transDepth += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function commit(bool $all = false): bool {
|
||||
if($this->transDepth==0) return false;
|
||||
if(!$all) {
|
||||
$this->exec("RELEASE SAVEPOINT newssync_".($this->transDepth - 1));
|
||||
$this->transDepth -= 1;
|
||||
} else {
|
||||
$this->exec("COMMIT TRANSACTION");
|
||||
$this->transDepth = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function commit(bool $all = false): bool {
|
||||
if($this->transDepth==0) return false;
|
||||
if(!$all) {
|
||||
$this->exec("RELEASE SAVEPOINT newssync_".($this->transDepth - 1));
|
||||
$this->transDepth -= 1;
|
||||
} else {
|
||||
$this->exec("COMMIT TRANSACTION");
|
||||
$this->transDepth = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rollback(bool $all = false): bool {
|
||||
if($this->transDepth==0) return false;
|
||||
if(!$all) {
|
||||
$this->exec("ROLLBACK TRANSACTION TO SAVEPOINT newssync_".($this->transDepth - 1));
|
||||
// rollback to savepoint does not collpase the savepoint
|
||||
$this->commit();
|
||||
$this->transDepth -= 1;
|
||||
if($this->transDepth==0) $this->exec("ROLLBACK TRANSACTION");
|
||||
} else {
|
||||
$this->exec("ROLLBACK TRANSACTION");
|
||||
$this->transDepth = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function rollback(bool $all = false): bool {
|
||||
if($this->transDepth==0) return false;
|
||||
if(!$all) {
|
||||
$this->exec("ROLLBACK TRANSACTION TO SAVEPOINT newssync_".($this->transDepth - 1));
|
||||
// rollback to savepoint does not collpase the savepoint
|
||||
$this->commit();
|
||||
$this->transDepth -= 1;
|
||||
if($this->transDepth==0) $this->exec("ROLLBACK TRANSACTION");
|
||||
} else {
|
||||
$this->exec("ROLLBACK TRANSACTION");
|
||||
$this->transDepth = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function lock(): bool {
|
||||
if($this->schemaVersion() < 1) return true;
|
||||
if($this->isLocked()) return false;
|
||||
$uuid = UUID::mintStr();
|
||||
if(!$this->data->db->settingSet("lock", $uuid)) return false;
|
||||
sleep(1);
|
||||
if($this->data->db->settingGet("lock") != $uuid) return false;
|
||||
return true;
|
||||
}
|
||||
public function lock(): bool {
|
||||
if($this->schemaVersion() < 1) return true;
|
||||
if($this->isLocked()) return false;
|
||||
$uuid = UUID::mintStr();
|
||||
if(!$this->data->db->settingSet("lock", $uuid)) return false;
|
||||
sleep(1);
|
||||
if($this->data->db->settingGet("lock") != $uuid) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unlock(): bool {
|
||||
return $this->data->db->settingRemove("lock");
|
||||
}
|
||||
public function unlock(): bool {
|
||||
return $this->data->db->settingRemove("lock");
|
||||
}
|
||||
|
||||
public function isLocked(): bool {
|
||||
if($this->schemaVersion() < 1) return false;
|
||||
return ($this->query("SELECT count(*) from newssync_settings where key = 'lock'")->getSingle() > 0);
|
||||
}
|
||||
public function isLocked(): bool {
|
||||
if($this->schemaVersion() < 1) return false;
|
||||
return ($this->query("SELECT count(*) from newssync_settings where key = 'lock'")->getSingle() > 0);
|
||||
}
|
||||
|
||||
public function prepare(string $query, string ...$paramType): Statement {
|
||||
return $this->prepareArray($query, $paramType);
|
||||
}
|
||||
public function prepare(string $query, string ...$paramType): Statement {
|
||||
return $this->prepareArray($query, $paramType);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,15 +3,15 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
Trait CommonPDO {
|
||||
public function query(string $query): Result {
|
||||
return new ResultPDO($this->db->query($query));
|
||||
}
|
||||
public function query(string $query): Result {
|
||||
return new ResultPDO($this->db->query($query));
|
||||
}
|
||||
|
||||
public function prepareArray(string $query, array $paramTypes): Statement {
|
||||
return new StatementPDO($query, $paramTypes);
|
||||
}
|
||||
public function prepareArray(string $query, array $paramTypes): Statement {
|
||||
return new StatementPDO($query, $paramTypes);
|
||||
}
|
||||
|
||||
public function prepare(string $query, string ...$paramType): Statement {
|
||||
return $this->prepareArray($query, $paramType);
|
||||
}
|
||||
public function prepare(string $query, string ...$paramType): Statement {
|
||||
return $this->prepareArray($query, $paramType);
|
||||
}
|
||||
}
|
|
@ -3,48 +3,48 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
Trait CommonSQLite3 {
|
||||
|
||||
static public function driverName(): string {
|
||||
return "SQLite 3";
|
||||
}
|
||||
|
||||
static public function driverName(): string {
|
||||
return "SQLite 3";
|
||||
}
|
||||
|
||||
public function schemaVersion(): int {
|
||||
return $this->query("PRAGMA user_version")->getSingle();
|
||||
}
|
||||
public function schemaVersion(): int {
|
||||
return $this->query("PRAGMA user_version")->getSingle();
|
||||
}
|
||||
|
||||
public function update(int $to): bool {
|
||||
$ver = $this->schemaVersion();
|
||||
if(!$this->data->conf->dbSQLite3AutoUpd) throw new Update\Exception("manual", ['version' => $ver, 'driver_name' => $this->driverName()]);
|
||||
if($ver >= $to) throw new Update\Exception("tooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
||||
$sep = \DIRECTORY_SEPARATOR;
|
||||
$path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep;
|
||||
$this->lock();
|
||||
$this->begin();
|
||||
for($a = $ver; $a < $to; $a++) {
|
||||
$this->begin();
|
||||
try {
|
||||
$file = $path.$a.".sql";
|
||||
if(!file_exists($file)) throw new Update\Exception("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
if(!is_readable($file)) throw new Update\Exception("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
$sql = @file_get_contents($file);
|
||||
if($sql===false) throw new Update\Exception("fileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
$this->exec($sql);
|
||||
} catch(\Throwable $e) {
|
||||
// undo any partial changes from the failed update
|
||||
$this->rollback();
|
||||
// commit any successful updates if updating by more than one version
|
||||
$this->commit(true);
|
||||
// throw the error received
|
||||
throw $e;
|
||||
}
|
||||
$this->commit();
|
||||
}
|
||||
$this->unlock();
|
||||
$this->commit();
|
||||
return true;
|
||||
}
|
||||
public function update(int $to): bool {
|
||||
$ver = $this->schemaVersion();
|
||||
if(!$this->data->conf->dbSQLite3AutoUpd) throw new Update\Exception("manual", ['version' => $ver, 'driver_name' => $this->driverName()]);
|
||||
if($ver >= $to) throw new Update\Exception("tooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
||||
$sep = \DIRECTORY_SEPARATOR;
|
||||
$path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep;
|
||||
$this->lock();
|
||||
$this->begin();
|
||||
for($a = $ver; $a < $to; $a++) {
|
||||
$this->begin();
|
||||
try {
|
||||
$file = $path.$a.".sql";
|
||||
if(!file_exists($file)) throw new Update\Exception("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
if(!is_readable($file)) throw new Update\Exception("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
$sql = @file_get_contents($file);
|
||||
if($sql===false) throw new Update\Exception("fileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]);
|
||||
$this->exec($sql);
|
||||
} catch(\Throwable $e) {
|
||||
// undo any partial changes from the failed update
|
||||
$this->rollback();
|
||||
// commit any successful updates if updating by more than one version
|
||||
$this->commit(true);
|
||||
// throw the error received
|
||||
throw $e;
|
||||
}
|
||||
$this->commit();
|
||||
}
|
||||
$this->unlock();
|
||||
$this->commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function exec(string $query): bool {
|
||||
return (bool) $this->db->exec($query);
|
||||
}
|
||||
public function exec(string $query): bool {
|
||||
return (bool) $this->db->exec($query);
|
||||
}
|
||||
}
|
|
@ -2,29 +2,29 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
interface Driver {
|
||||
// returns an instance of a class implementing this interface. Implemented as a static method so that classes may return their PDO equivalents instead of themselves
|
||||
static function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver;
|
||||
// returns a human-friendly name for the driver (for display in installer, for example)
|
||||
static function driverName(): string;
|
||||
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
||||
function schemaVersion(): int;
|
||||
// begin a real or synthetic transactions, with real or synthetic nesting
|
||||
function begin(): bool;
|
||||
// commit either the latest or all pending nested transactions; use of this method should assume a partial commit is a no-op
|
||||
function commit(bool $all = false): bool;
|
||||
// rollback either the latest or all pending nested transactions; use of this method should assume a partial rollback will not work
|
||||
function rollback(bool $all = false): bool;
|
||||
// attempt to advise other processes that they should not attempt to access the database; used during live upgrades
|
||||
function lock(): bool;
|
||||
function unlock(): bool;
|
||||
function isLocked(): bool;
|
||||
// attempt to perform an in-place upgrade of the database schema; this may be a no-op which always throws an exception
|
||||
function update(int $to): bool;
|
||||
// execute one or more unsanitized SQL queries and return an indication of success
|
||||
function exec(string $query): bool;
|
||||
// perform a single unsanitized query and return a result set
|
||||
function query(string $query): Result;
|
||||
// ready a prepared statement for later execution
|
||||
function prepare(string $query, string ...$paramType): Statement;
|
||||
interface Driver {
|
||||
// returns an instance of a class implementing this interface. Implemented as a static method so that classes may return their PDO equivalents instead of themselves
|
||||
static function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver;
|
||||
// returns a human-friendly name for the driver (for display in installer, for example)
|
||||
static function driverName(): string;
|
||||
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
||||
function schemaVersion(): int;
|
||||
// begin a real or synthetic transactions, with real or synthetic nesting
|
||||
function begin(): bool;
|
||||
// commit either the latest or all pending nested transactions; use of this method should assume a partial commit is a no-op
|
||||
function commit(bool $all = false): bool;
|
||||
// rollback either the latest or all pending nested transactions; use of this method should assume a partial rollback will not work
|
||||
function rollback(bool $all = false): bool;
|
||||
// attempt to advise other processes that they should not attempt to access the database; used during live upgrades
|
||||
function lock(): bool;
|
||||
function unlock(): bool;
|
||||
function isLocked(): bool;
|
||||
// attempt to perform an in-place upgrade of the database schema; this may be a no-op which always throws an exception
|
||||
function update(int $to): bool;
|
||||
// execute one or more unsanitized SQL queries and return an indication of success
|
||||
function exec(string $query): bool;
|
||||
// perform a single unsanitized query and return a result set
|
||||
function query(string $query): Result;
|
||||
// ready a prepared statement for later execution
|
||||
function prepare(string $query, string ...$paramType): Statement;
|
||||
}
|
|
@ -3,57 +3,57 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
class DriverSQLite3 implements Driver {
|
||||
use Common, CommonSQLite3 {
|
||||
CommonSQLite3::schemaVersion insteadof Common;
|
||||
}
|
||||
|
||||
protected $db;
|
||||
protected $data;
|
||||
|
||||
private function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
|
||||
$this->data = $data;
|
||||
$file = $data->conf->dbSQLite3File;
|
||||
// if the file exists (or we're initializing the database), try to open it and set initial options
|
||||
try {
|
||||
$this->db = new \SQLite3($file, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, $data->conf->dbSQLite3Key);
|
||||
$this->db->enableExceptions(true);
|
||||
$this->exec("PRAGMA journal_mode = wal");
|
||||
$this->exec("PRAGMA foreign_keys = yes");
|
||||
} catch(\Throwable $e) {
|
||||
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
|
||||
if(!file_exists($file)) {
|
||||
if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file));
|
||||
throw new Exception("fileMissing", $file);
|
||||
}
|
||||
if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file);
|
||||
if(!is_readable($file)) throw new Exception("fileUnreadable", $file);
|
||||
if(!is_writable($file)) throw new Exception("fileUnwritable", $file);
|
||||
// otherwise the database is probably corrupt
|
||||
throw new Exception("fileCorrupt", $mainfile);
|
||||
}
|
||||
}
|
||||
use Common, CommonSQLite3 {
|
||||
CommonSQLite3::schemaVersion insteadof Common;
|
||||
}
|
||||
|
||||
protected $db;
|
||||
protected $data;
|
||||
|
||||
private function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
|
||||
$this->data = $data;
|
||||
$file = $data->conf->dbSQLite3File;
|
||||
// if the file exists (or we're initializing the database), try to open it and set initial options
|
||||
try {
|
||||
$this->db = new \SQLite3($file, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, $data->conf->dbSQLite3Key);
|
||||
$this->db->enableExceptions(true);
|
||||
$this->exec("PRAGMA journal_mode = wal");
|
||||
$this->exec("PRAGMA foreign_keys = yes");
|
||||
} catch(\Throwable $e) {
|
||||
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
|
||||
if(!file_exists($file)) {
|
||||
if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file));
|
||||
throw new Exception("fileMissing", $file);
|
||||
}
|
||||
if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file);
|
||||
if(!is_readable($file)) throw new Exception("fileUnreadable", $file);
|
||||
if(!is_writable($file)) throw new Exception("fileUnwritable", $file);
|
||||
// otherwise the database is probably corrupt
|
||||
throw new Exception("fileCorrupt", $mainfile);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->db->close();
|
||||
unset($this->db);
|
||||
}
|
||||
public function __destruct() {
|
||||
$this->db->close();
|
||||
unset($this->db);
|
||||
}
|
||||
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver {
|
||||
// check to make sure required extensions are loaded
|
||||
if(class_exists("SQLite3")) {
|
||||
return new self($data, $install);
|
||||
} else if(class_exists("PDO") && in_array("sqlite",\PDO::getAvailableDrivers())) {
|
||||
return new DriverSQLite3PDO($data, $install);
|
||||
} else {
|
||||
throw new Exception("extMissing", self::driverName());
|
||||
}
|
||||
}
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver {
|
||||
// check to make sure required extensions are loaded
|
||||
if(class_exists("SQLite3")) {
|
||||
return new self($data, $install);
|
||||
} else if(class_exists("PDO") && in_array("sqlite",\PDO::getAvailableDrivers())) {
|
||||
return new DriverSQLite3PDO($data, $install);
|
||||
} else {
|
||||
throw new Exception("extMissing", self::driverName());
|
||||
}
|
||||
}
|
||||
|
||||
public function query(string $query): Result {
|
||||
return new ResultSQLite3($this->db->query($query), $this->db->changes());
|
||||
}
|
||||
public function query(string $query): Result {
|
||||
return new ResultSQLite3($this->db->query($query), $this->db->changes());
|
||||
}
|
||||
|
||||
public function prepareArray(string $query, array $paramTypes): Statement {
|
||||
return new StatementSQLite3($this->db, $this->db->prepare($query), $paramTypes);
|
||||
}
|
||||
public function prepareArray(string $query, array $paramTypes): Statement {
|
||||
return new StatementSQLite3($this->db, $this->db->prepare($query), $paramTypes);
|
||||
}
|
||||
}
|
|
@ -3,26 +3,26 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
class DriverSQLite3 implements Driver {
|
||||
use CommonPDO, CommonSQLite3;
|
||||
|
||||
protected $db;
|
||||
|
||||
private function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
|
||||
// FIXME: stub
|
||||
}
|
||||
use CommonPDO, CommonSQLite3;
|
||||
|
||||
protected $db;
|
||||
|
||||
private function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
|
||||
// FIXME: stub
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
// FIXME: stub
|
||||
}
|
||||
public function __destruct() {
|
||||
// FIXME: stub
|
||||
}
|
||||
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver {
|
||||
// check to make sure required extensions are loaded
|
||||
if(class_exists("PDO") && in_array("sqlite",\PDO::getAvailableDrivers())) {
|
||||
return new self($data, $install);
|
||||
} else if(class_exists("SQLite3")) {
|
||||
return new DriverSQLite3($data, $install);
|
||||
} else {
|
||||
throw new Exception("extMissing", self::driverName());
|
||||
}
|
||||
}
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false): Driver {
|
||||
// check to make sure required extensions are loaded
|
||||
if(class_exists("PDO") && in_array("sqlite",\PDO::getAvailableDrivers())) {
|
||||
return new self($data, $install);
|
||||
} else if(class_exists("SQLite3")) {
|
||||
return new DriverSQLite3($data, $install);
|
||||
} else {
|
||||
throw new Exception("extMissing", self::driverName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
interface Result extends \Iterator {
|
||||
function current();
|
||||
function key();
|
||||
function next();
|
||||
function rewind();
|
||||
function valid();
|
||||
function current();
|
||||
function key();
|
||||
function next();
|
||||
function rewind();
|
||||
function valid();
|
||||
|
||||
function get();
|
||||
function getSingle();
|
||||
function changes();
|
||||
function get();
|
||||
function getSingle();
|
||||
function changes();
|
||||
}
|
|
@ -3,62 +3,62 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
class ResultSQLite3 implements Result {
|
||||
protected $st;
|
||||
protected $set;
|
||||
protected $pos = 0;
|
||||
protected $cur = null;
|
||||
protected $rows = 0;
|
||||
protected $st;
|
||||
protected $set;
|
||||
protected $pos = 0;
|
||||
protected $cur = null;
|
||||
protected $rows = 0;
|
||||
|
||||
public function __construct($result, $changes, $statement = null) {
|
||||
$this->st = $statement; //keeps the statement from being destroyed, invalidating the result set
|
||||
$this->set = $result;
|
||||
$this->rows = $changes;
|
||||
}
|
||||
public function __construct($result, $changes, $statement = null) {
|
||||
$this->st = $statement; //keeps the statement from being destroyed, invalidating the result set
|
||||
$this->set = $result;
|
||||
$this->rows = $changes;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->set->finalize();
|
||||
unset($this->set);
|
||||
}
|
||||
public function __destruct() {
|
||||
$this->set->finalize();
|
||||
unset($this->set);
|
||||
}
|
||||
|
||||
public function valid() {
|
||||
$this->cur = $this->set->fetchArray(\SQLITE3_ASSOC);
|
||||
return ($this->cur !== false);
|
||||
}
|
||||
public function valid() {
|
||||
$this->cur = $this->set->fetchArray(\SQLITE3_ASSOC);
|
||||
return ($this->cur !== false);
|
||||
}
|
||||
|
||||
public function next() {
|
||||
$this->cur = null;
|
||||
$this->pos += 1;
|
||||
}
|
||||
public function next() {
|
||||
$this->cur = null;
|
||||
$this->pos += 1;
|
||||
}
|
||||
|
||||
public function current() {
|
||||
return $this->cur;
|
||||
}
|
||||
public function current() {
|
||||
return $this->cur;
|
||||
}
|
||||
|
||||
public function key() {
|
||||
return $this->pos;
|
||||
}
|
||||
public function key() {
|
||||
return $this->pos;
|
||||
}
|
||||
|
||||
public function rewind() {
|
||||
$this->pos = 0;
|
||||
$this->cur = null;
|
||||
$this->set->reset();
|
||||
}
|
||||
public function rewind() {
|
||||
$this->pos = 0;
|
||||
$this->cur = null;
|
||||
$this->set->reset();
|
||||
}
|
||||
|
||||
public function getSingle() {
|
||||
$this->next();
|
||||
if($this->valid()) {
|
||||
$keys = array_keys($this->cur);
|
||||
return $this->cur[array_shift($keys)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function getSingle() {
|
||||
$this->next();
|
||||
if($this->valid()) {
|
||||
$keys = array_keys($this->cur);
|
||||
return $this->cur[array_shift($keys)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
$this->next();
|
||||
return ($this->valid() ? $this->cur : null);
|
||||
}
|
||||
public function get() {
|
||||
$this->next();
|
||||
return ($this->valid() ? $this->cur : null);
|
||||
}
|
||||
|
||||
public function changes() {
|
||||
return $this->rows;
|
||||
}
|
||||
public function changes() {
|
||||
return $this->rows;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
interface Statement {
|
||||
function __invoke(&...$values); // alias of run()
|
||||
function run(&...$values): Result;
|
||||
function runArray(array &$values): Result;
|
||||
function __invoke(&...$values); // alias of run()
|
||||
function run(&...$values): Result;
|
||||
function runArray(array &$values): Result;
|
||||
}
|
|
@ -3,71 +3,71 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Db;
|
||||
|
||||
class StatementSQLite3 implements Statement {
|
||||
protected $db;
|
||||
protected $st;
|
||||
protected $types;
|
||||
protected $db;
|
||||
protected $st;
|
||||
protected $types;
|
||||
|
||||
public function __construct($db, $st, array $bindings = null) {
|
||||
$this->db = $db;
|
||||
$this->st = $st;
|
||||
$this->types = [];
|
||||
foreach($bindings as $binding) {
|
||||
switch(trim(strtolower($binding))) {
|
||||
case "int":
|
||||
case "integer":
|
||||
$this->types[] = \SQLITE3_INTEGER; break;
|
||||
case "float":
|
||||
case "double":
|
||||
case "real":
|
||||
case "numeric":
|
||||
$this->types[] = \SQLITE3_FLOAT; break;
|
||||
case "date":
|
||||
case "time":
|
||||
case "datetime":
|
||||
case "timestamp":
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
case "blob":
|
||||
case "bin":
|
||||
case "binary":
|
||||
$this->types[] = \SQLITE3_BLOB; break;
|
||||
case "text":
|
||||
case "string":
|
||||
case "str":
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
case "bool":
|
||||
case "boolean":
|
||||
case "bit":
|
||||
$this->types[] = \SQLITE3_INTEGER; break;
|
||||
default:
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public function __construct($db, $st, array $bindings = null) {
|
||||
$this->db = $db;
|
||||
$this->st = $st;
|
||||
$this->types = [];
|
||||
foreach($bindings as $binding) {
|
||||
switch(trim(strtolower($binding))) {
|
||||
case "int":
|
||||
case "integer":
|
||||
$this->types[] = \SQLITE3_INTEGER; break;
|
||||
case "float":
|
||||
case "double":
|
||||
case "real":
|
||||
case "numeric":
|
||||
$this->types[] = \SQLITE3_FLOAT; break;
|
||||
case "date":
|
||||
case "time":
|
||||
case "datetime":
|
||||
case "timestamp":
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
case "blob":
|
||||
case "bin":
|
||||
case "binary":
|
||||
$this->types[] = \SQLITE3_BLOB; break;
|
||||
case "text":
|
||||
case "string":
|
||||
case "str":
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
case "bool":
|
||||
case "boolean":
|
||||
case "bit":
|
||||
$this->types[] = \SQLITE3_INTEGER; break;
|
||||
default:
|
||||
$this->types[] = \SQLITE3_TEXT; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->st->close();
|
||||
unset($this->st);
|
||||
}
|
||||
public function __destruct() {
|
||||
$this->st->close();
|
||||
unset($this->st);
|
||||
}
|
||||
|
||||
public function __invoke(&...$values) {
|
||||
return $this->runArray($values);
|
||||
}
|
||||
public function __invoke(&...$values) {
|
||||
return $this->runArray($values);
|
||||
}
|
||||
|
||||
public function run(&...$values): Result {
|
||||
return $this->runArray($values);
|
||||
}
|
||||
public function run(&...$values): Result {
|
||||
return $this->runArray($values);
|
||||
}
|
||||
|
||||
public function runArray(array &$values = null): Result {
|
||||
$this->st->clear();
|
||||
$l = sizeof($values);
|
||||
for($a = 0; $a < $l; $a++) {
|
||||
if($values[$a]===null) {
|
||||
$type = \SQLITE3_NULL;
|
||||
} else {
|
||||
$type = (array_key_exists($a,$this->types)) ? $this->types[$a] : \SQLITE3_TEXT;
|
||||
}
|
||||
$this->st->bindParam($a+1, $values[$a], $type);
|
||||
}
|
||||
return new ResultSQLite3($this->st->execute(), $this->db->changes(), $this);
|
||||
}
|
||||
public function runArray(array &$values = null): Result {
|
||||
$this->st->clear();
|
||||
$l = sizeof($values);
|
||||
for($a = 0; $a < $l; $a++) {
|
||||
if($values[$a]===null) {
|
||||
$type = \SQLITE3_NULL;
|
||||
} else {
|
||||
$type = (array_key_exists($a,$this->types)) ? $this->types[$a] : \SQLITE3_TEXT;
|
||||
}
|
||||
$this->st->bindParam($a+1, $values[$a], $type);
|
||||
}
|
||||
return new ResultSQLite3($this->st->execute(), $this->db->changes(), $this);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync;
|
||||
|
||||
class ExceptionFatal extends AbstractException {
|
||||
public function __construct($msg = "", $code = 0, $e = null) {
|
||||
\Exception::__construct($msg, $code, $e);
|
||||
}
|
||||
public function __construct($msg = "", $code = 0, $e = null) {
|
||||
\Exception::__construct($msg, $code, $e);
|
||||
}
|
||||
}
|
294
lib/Lang.php
294
lib/Lang.php
|
@ -4,161 +4,161 @@ namespace JKingWeb\NewsSync;
|
|||
use \Webmozart\Glob\Glob;
|
||||
|
||||
class Lang {
|
||||
const DEFAULT = "en";
|
||||
const REQUIRED = [
|
||||
'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php',
|
||||
'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable' => 'Insufficient permissions to read language file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt' => 'Language file "{0}" is corrupt or does not conform to expected format',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing' => 'Message string "{msgID}" missing from all loaded language files ({fileList})',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})',
|
||||
];
|
||||
const DEFAULT = "en";
|
||||
const REQUIRED = [
|
||||
'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php',
|
||||
'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable' => 'Insufficient permissions to read language file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt' => 'Language file "{0}" is corrupt or does not conform to expected format',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing' => 'Message string "{msgID}" missing from all loaded language files ({fileList})',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})',
|
||||
];
|
||||
|
||||
static public $path = BASE."locale".DIRECTORY_SEPARATOR;
|
||||
static protected $requirementsMet = false;
|
||||
static protected $synched = false;
|
||||
static protected $wanted = self::DEFAULT;
|
||||
static protected $locale = "";
|
||||
static protected $loaded = [];
|
||||
static protected $strings = self::REQUIRED;
|
||||
static public $path = BASE."locale".DIRECTORY_SEPARATOR;
|
||||
static protected $requirementsMet = false;
|
||||
static protected $synched = false;
|
||||
static protected $wanted = self::DEFAULT;
|
||||
static protected $locale = "";
|
||||
static protected $loaded = [];
|
||||
static protected $strings = self::REQUIRED;
|
||||
|
||||
protected function __construct() {}
|
||||
protected function __construct() {}
|
||||
|
||||
static public function set(string $locale, bool $immediate = false): string {
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
if($locale==self::$wanted) return $locale;
|
||||
if($locale != "") {
|
||||
$list = self::listFiles();
|
||||
if(!in_array(self::DEFAULT, $list)) throw new Lang\Exception("defaultFileMissing", self::DEFAULT);
|
||||
self::$wanted = self::match($locale, $list);
|
||||
} else {
|
||||
self::$wanted = "";
|
||||
}
|
||||
self::$synched = false;
|
||||
if($immediate) self::load();
|
||||
return self::$wanted;
|
||||
}
|
||||
static public function set(string $locale, bool $immediate = false): string {
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
if($locale==self::$wanted) return $locale;
|
||||
if($locale != "") {
|
||||
$list = self::listFiles();
|
||||
if(!in_array(self::DEFAULT, $list)) throw new Lang\Exception("defaultFileMissing", self::DEFAULT);
|
||||
self::$wanted = self::match($locale, $list);
|
||||
} else {
|
||||
self::$wanted = "";
|
||||
}
|
||||
self::$synched = false;
|
||||
if($immediate) self::load();
|
||||
return self::$wanted;
|
||||
}
|
||||
|
||||
static public function get(): string {
|
||||
return (self::$locale=="") ? self::DEFAULT : self::$locale;
|
||||
}
|
||||
static public function get(): string {
|
||||
return (self::$locale=="") ? self::DEFAULT : self::$locale;
|
||||
}
|
||||
|
||||
static public function dump(): array {
|
||||
return self::$strings;
|
||||
}
|
||||
static public function dump(): array {
|
||||
return self::$strings;
|
||||
}
|
||||
|
||||
static public function msg(string $msgID, $vars = null): string {
|
||||
// if we're trying to load the system default language and it fails, we have a chicken and egg problem, so we catch the exception and load no language file instead
|
||||
if(!self::$synched) try {self::load();} catch(Lang\Exception $e) {
|
||||
if(self::$wanted==self::DEFAULT) {
|
||||
self::set("", true);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if(!array_key_exists($msgID, self::$strings)) throw new Lang\Exception("stringMissing", ['msgID' => $msgID, 'fileList' => implode(", ",self::$loaded)]);
|
||||
// variables fed to MessageFormatter must be contained in array
|
||||
$msg = self::$strings[$msgID];
|
||||
if($vars===null) {
|
||||
return $msg;
|
||||
} else if(!is_array($vars)) {
|
||||
$vars = [$vars];
|
||||
}
|
||||
$msg = \MessageFormatter::formatMessage(self::$locale, $msg, $vars);
|
||||
if($msg===false) throw new Lang\Exception("stringInvalid", ['msgID' => $msgID, 'fileList' => implode(", ",self::$loaded)]);
|
||||
return $msg;
|
||||
}
|
||||
static public function msg(string $msgID, $vars = null): string {
|
||||
// if we're trying to load the system default language and it fails, we have a chicken and egg problem, so we catch the exception and load no language file instead
|
||||
if(!self::$synched) try {self::load();} catch(Lang\Exception $e) {
|
||||
if(self::$wanted==self::DEFAULT) {
|
||||
self::set("", true);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if(!array_key_exists($msgID, self::$strings)) throw new Lang\Exception("stringMissing", ['msgID' => $msgID, 'fileList' => implode(", ",self::$loaded)]);
|
||||
// variables fed to MessageFormatter must be contained in array
|
||||
$msg = self::$strings[$msgID];
|
||||
if($vars===null) {
|
||||
return $msg;
|
||||
} else if(!is_array($vars)) {
|
||||
$vars = [$vars];
|
||||
}
|
||||
$msg = \MessageFormatter::formatMessage(self::$locale, $msg, $vars);
|
||||
if($msg===false) throw new Lang\Exception("stringInvalid", ['msgID' => $msgID, 'fileList' => implode(", ",self::$loaded)]);
|
||||
return $msg;
|
||||
}
|
||||
|
||||
static public function list(string $locale = ""): array {
|
||||
$out = [];
|
||||
$files = self::listFiles();
|
||||
foreach($files as $tag) {
|
||||
$out[$tag] = \Locale::getDisplayName($tag, ($locale=="") ? $tag : $locale);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
static public function list(string $locale = ""): array {
|
||||
$out = [];
|
||||
$files = self::listFiles();
|
||||
foreach($files as $tag) {
|
||||
$out[$tag] = \Locale::getDisplayName($tag, ($locale=="") ? $tag : $locale);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
static public function match(string $locale, array $list = null): string {
|
||||
if($list===null) $list = self::listFiles();
|
||||
$default = (self::$locale=="") ? self::DEFAULT : self::$locale;
|
||||
return \Locale::lookup($list,$locale, true, $default);
|
||||
}
|
||||
static public function match(string $locale, array $list = null): string {
|
||||
if($list===null) $list = self::listFiles();
|
||||
$default = (self::$locale=="") ? self::DEFAULT : self::$locale;
|
||||
return \Locale::lookup($list,$locale, true, $default);
|
||||
}
|
||||
|
||||
static protected function checkRequirements(): bool {
|
||||
if(!extension_loaded("intl")) throw new ExceptionFatal("The \"Intl\" extension is required, but not loaded");
|
||||
self::$requirementsMet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static protected function listFiles(): array {
|
||||
$out = glob(self::$path."*.php");
|
||||
// built-in glob doesn't work with vfsStream (and this other glob doesn't seem to work with Windows paths), so we try both
|
||||
if(empty($out)) $out = Glob::glob(self::$path."*.php");
|
||||
$out = array_map(function($file) {
|
||||
$file = str_replace(DIRECTORY_SEPARATOR, "/", $file);
|
||||
$file = substr($file, strrpos($file, "/")+1);
|
||||
return strtolower(substr($file,0,strrpos($file,".")));
|
||||
},$out);
|
||||
natsort($out);
|
||||
return $out;
|
||||
}
|
||||
static protected function checkRequirements(): bool {
|
||||
if(!extension_loaded("intl")) throw new ExceptionFatal("The \"Intl\" extension is required, but not loaded");
|
||||
self::$requirementsMet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static protected function load(): bool {
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
// if we've requested no locale (""), just load the fallback strings and return
|
||||
if(self::$wanted=="") {
|
||||
self::$strings = self::REQUIRED;
|
||||
self::$locale = self::$wanted;
|
||||
self::$synched = true;
|
||||
return true;
|
||||
}
|
||||
// decompose the requested locale from specific to general, building a list of files to load
|
||||
$tags = \Locale::parseLocale(self::$wanted);
|
||||
$files = [];
|
||||
while(sizeof($tags) > 0) {
|
||||
$files[] = strtolower(\Locale::composeLocale($tags));
|
||||
$tag = array_pop($tags);
|
||||
}
|
||||
// include the default locale as the base if the most general locale requested is not the default
|
||||
if($tag != self::DEFAULT) $files[] = self::DEFAULT;
|
||||
// save the list of files to be loaded for later reference
|
||||
$loaded = $files;
|
||||
// reduce the list of files to be loaded to the minimum necessary (e.g. if we go from "fr" to "fr_ca", we don't need to load "fr" or "en")
|
||||
$files = [];
|
||||
foreach($loaded as $file) {
|
||||
if($file==self::$locale) break;
|
||||
$files[] = $file;
|
||||
}
|
||||
// if we need to load all files, start with the fallback strings
|
||||
$strings = [];
|
||||
if($files==$loaded) {
|
||||
$strings[] = self::REQUIRED;
|
||||
} else {
|
||||
// otherwise start with the strings we already have if we're going from e.g. "fr" to "fr_ca"
|
||||
$strings[] = self::$strings;
|
||||
}
|
||||
// read files in reverse order
|
||||
$files = array_reverse($files);
|
||||
foreach($files as $file) {
|
||||
if(!file_exists(self::$path."$file.php")) throw new Lang\Exception("fileMissing", $file);
|
||||
if(!is_readable(self::$path."$file.php")) throw new Lang\Exception("fileUnreadable", $file);
|
||||
try {
|
||||
ob_start();
|
||||
$arr = (include self::$path."$file.php");
|
||||
} catch(\Throwable $e) {
|
||||
$arr = null;
|
||||
} finally {
|
||||
ob_end_clean();
|
||||
}
|
||||
if(!is_array($arr)) throw new Lang\Exception("fileCorrupt", $file);
|
||||
$strings[] = $arr;
|
||||
}
|
||||
// apply the results and return
|
||||
self::$strings = call_user_func_array("array_replace_recursive", $strings);
|
||||
self::$loaded = $loaded;
|
||||
self::$locale = self::$wanted;
|
||||
return true;
|
||||
}
|
||||
static protected function listFiles(): array {
|
||||
$out = glob(self::$path."*.php");
|
||||
// built-in glob doesn't work with vfsStream (and this other glob doesn't seem to work with Windows paths), so we try both
|
||||
if(empty($out)) $out = Glob::glob(self::$path."*.php");
|
||||
$out = array_map(function($file) {
|
||||
$file = str_replace(DIRECTORY_SEPARATOR, "/", $file);
|
||||
$file = substr($file, strrpos($file, "/")+1);
|
||||
return strtolower(substr($file,0,strrpos($file,".")));
|
||||
},$out);
|
||||
natsort($out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
static protected function load(): bool {
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
// if we've requested no locale (""), just load the fallback strings and return
|
||||
if(self::$wanted=="") {
|
||||
self::$strings = self::REQUIRED;
|
||||
self::$locale = self::$wanted;
|
||||
self::$synched = true;
|
||||
return true;
|
||||
}
|
||||
// decompose the requested locale from specific to general, building a list of files to load
|
||||
$tags = \Locale::parseLocale(self::$wanted);
|
||||
$files = [];
|
||||
while(sizeof($tags) > 0) {
|
||||
$files[] = strtolower(\Locale::composeLocale($tags));
|
||||
$tag = array_pop($tags);
|
||||
}
|
||||
// include the default locale as the base if the most general locale requested is not the default
|
||||
if($tag != self::DEFAULT) $files[] = self::DEFAULT;
|
||||
// save the list of files to be loaded for later reference
|
||||
$loaded = $files;
|
||||
// reduce the list of files to be loaded to the minimum necessary (e.g. if we go from "fr" to "fr_ca", we don't need to load "fr" or "en")
|
||||
$files = [];
|
||||
foreach($loaded as $file) {
|
||||
if($file==self::$locale) break;
|
||||
$files[] = $file;
|
||||
}
|
||||
// if we need to load all files, start with the fallback strings
|
||||
$strings = [];
|
||||
if($files==$loaded) {
|
||||
$strings[] = self::REQUIRED;
|
||||
} else {
|
||||
// otherwise start with the strings we already have if we're going from e.g. "fr" to "fr_ca"
|
||||
$strings[] = self::$strings;
|
||||
}
|
||||
// read files in reverse order
|
||||
$files = array_reverse($files);
|
||||
foreach($files as $file) {
|
||||
if(!file_exists(self::$path."$file.php")) throw new Lang\Exception("fileMissing", $file);
|
||||
if(!is_readable(self::$path."$file.php")) throw new Lang\Exception("fileUnreadable", $file);
|
||||
try {
|
||||
ob_start();
|
||||
$arr = (include self::$path."$file.php");
|
||||
} catch(\Throwable $e) {
|
||||
$arr = null;
|
||||
} finally {
|
||||
ob_end_clean();
|
||||
}
|
||||
if(!is_array($arr)) throw new Lang\Exception("fileCorrupt", $file);
|
||||
$strings[] = $arr;
|
||||
}
|
||||
// apply the results and return
|
||||
self::$strings = call_user_func_array("array_replace_recursive", $strings);
|
||||
self::$loaded = $loaded;
|
||||
self::$locale = self::$wanted;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,22 +3,22 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\Lang;
|
||||
|
||||
class Exception extends \JKingWeb\NewsSync\AbstractException {
|
||||
static $test = false; // used during PHPUnit testing only
|
||||
static $test = false; // used during PHPUnit testing only
|
||||
|
||||
function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
|
||||
if(!self::$test) {
|
||||
parent::__construct($msgID, $vars, $e);
|
||||
} else {
|
||||
$codeID = "Lang/Exception.$msgID";
|
||||
if(!array_key_exists($codeID,self::CODES)) {
|
||||
$code = -1;
|
||||
$msg = "Exception.".str_replace("\\","/",parent::class).".uncoded";
|
||||
$vars = $msgID;
|
||||
} else {
|
||||
$code = self::CODES[$codeID];
|
||||
$msg = "Exception.".str_replace("\\","/",__CLASS__).".$msgID";
|
||||
}
|
||||
\Exception::__construct($msg, $code, $e);
|
||||
}
|
||||
}
|
||||
function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
|
||||
if(!self::$test) {
|
||||
parent::__construct($msgID, $vars, $e);
|
||||
} else {
|
||||
$codeID = "Lang/Exception.$msgID";
|
||||
if(!array_key_exists($codeID,self::CODES)) {
|
||||
$code = -1;
|
||||
$msg = "Exception.".str_replace("\\","/",parent::class).".uncoded";
|
||||
$vars = $msgID;
|
||||
} else {
|
||||
$code = self::CODES[$codeID];
|
||||
$msg = "Exception.".str_replace("\\","/",__CLASS__).".$msgID";
|
||||
}
|
||||
\Exception::__construct($msg, $code, $e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,14 +3,14 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync;
|
||||
|
||||
class RuntimeData {
|
||||
public $conf;
|
||||
public $db;
|
||||
public $auth;
|
||||
public $conf;
|
||||
public $db;
|
||||
public $auth;
|
||||
|
||||
public function __construct(Conf $conf) {
|
||||
$this->conf = $conf;
|
||||
Lang::set($conf->lang);
|
||||
$this->db = new Database($this);
|
||||
$this->user = new User($this);
|
||||
}
|
||||
public function __construct(Conf $conf) {
|
||||
$this->conf = $conf;
|
||||
Lang::set($conf->lang);
|
||||
$this->db = new Database($this);
|
||||
$this->user = new User($this);
|
||||
}
|
||||
}
|
434
lib/User.php
434
lib/User.php
|
@ -3,241 +3,241 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync;
|
||||
|
||||
class User {
|
||||
public $id = null;
|
||||
public $id = null;
|
||||
|
||||
protected $data;
|
||||
protected $u;
|
||||
protected $authz = true;
|
||||
protected $existSupported = 0;
|
||||
protected $authzSupported = 0;
|
||||
|
||||
static public function listDrivers(): array {
|
||||
$sep = \DIRECTORY_SEPARATOR;
|
||||
$path = __DIR__.$sep."User".$sep;
|
||||
$classes = [];
|
||||
foreach(glob($path."Driver?*.php") as $file) {
|
||||
$name = basename($file, ".php");
|
||||
$name = NS_BASE."Db\\$name";
|
||||
if(class_exists($name)) {
|
||||
$classes[$name] = $name::driverName();
|
||||
}
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
protected $data;
|
||||
protected $u;
|
||||
protected $authz = true;
|
||||
protected $existSupported = 0;
|
||||
protected $authzSupported = 0;
|
||||
|
||||
static public function listDrivers(): array {
|
||||
$sep = \DIRECTORY_SEPARATOR;
|
||||
$path = __DIR__.$sep."User".$sep;
|
||||
$classes = [];
|
||||
foreach(glob($path."Driver?*.php") as $file) {
|
||||
$name = basename($file, ".php");
|
||||
$name = NS_BASE."Db\\$name";
|
||||
if(class_exists($name)) {
|
||||
$classes[$name] = $name::driverName();
|
||||
}
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
public function __construct(\JKingWeb\NewsSync\RuntimeData $data) {
|
||||
$this->data = $data;
|
||||
$driver = $data->conf->userDriver;
|
||||
$this->u = $driver::create($data);
|
||||
$this->existSupported = $this->u->driverFunctions("userExists");
|
||||
$this->authzSupported = $this->u->driverFunctions("authorize");
|
||||
}
|
||||
public function __construct(\JKingWeb\NewsSync\RuntimeData $data) {
|
||||
$this->data = $data;
|
||||
$driver = $data->conf->userDriver;
|
||||
$this->u = $driver::create($data);
|
||||
$this->existSupported = $this->u->driverFunctions("userExists");
|
||||
$this->authzSupported = $this->u->driverFunctions("authorize");
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
if($this->id===null) $this->credentials();
|
||||
return (string) $this->id;
|
||||
}
|
||||
public function __toString() {
|
||||
if($this->id===null) $this->credentials();
|
||||
return (string) $this->id;
|
||||
}
|
||||
|
||||
public function credentials(): array {
|
||||
if($this->data->conf->userAuthPreferHTTP) {
|
||||
return $this->credentialsHTTP();
|
||||
} else {
|
||||
return $this->credentialsForm();
|
||||
}
|
||||
}
|
||||
public function credentials(): array {
|
||||
if($this->data->conf->userAuthPreferHTTP) {
|
||||
return $this->credentialsHTTP();
|
||||
} else {
|
||||
return $this->credentialsForm();
|
||||
}
|
||||
}
|
||||
|
||||
public function credentialsForm(): array {
|
||||
// FIXME: stub
|
||||
$this->id = "john.doe@example.com";
|
||||
return ["user" => "john.doe@example.com", "password" => "secret"];
|
||||
}
|
||||
public function credentialsForm(): array {
|
||||
// FIXME: stub
|
||||
$this->id = "john.doe@example.com";
|
||||
return ["user" => "john.doe@example.com", "password" => "secret"];
|
||||
}
|
||||
|
||||
public function credentialsHTTP(): array {
|
||||
if($_SERVER['PHP_AUTH_USER']) {
|
||||
$out = ["user" => $_SERVER['PHP_AUTH_USER'], "password" => $_SERVER['PHP_AUTH_PW']];
|
||||
} else if($_SERVER['REMOTE_USER']) {
|
||||
$out = ["user" => $_SERVER['REMOTE_USER'], "password" => ""];
|
||||
} else {
|
||||
$out = ["user" => "", "password" => ""];
|
||||
}
|
||||
if($this->data->conf->userComposeNames && $out["user"] != "") {
|
||||
$out["user"] = $this->composeName($out["user"]);
|
||||
}
|
||||
$this->id = $out["user"];
|
||||
return $out;
|
||||
}
|
||||
public function credentialsHTTP(): array {
|
||||
if($_SERVER['PHP_AUTH_USER']) {
|
||||
$out = ["user" => $_SERVER['PHP_AUTH_USER'], "password" => $_SERVER['PHP_AUTH_PW']];
|
||||
} else if($_SERVER['REMOTE_USER']) {
|
||||
$out = ["user" => $_SERVER['REMOTE_USER'], "password" => ""];
|
||||
} else {
|
||||
$out = ["user" => "", "password" => ""];
|
||||
}
|
||||
if($this->data->conf->userComposeNames && $out["user"] != "") {
|
||||
$out["user"] = $this->composeName($out["user"]);
|
||||
}
|
||||
$this->id = $out["user"];
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function auth(string $user = null, string $password = null): bool {
|
||||
if($user===null) {
|
||||
if($this->data->conf->userAuthPreferHTTP) return $this->authHTTP();
|
||||
return $this->authForm();
|
||||
} else {
|
||||
if($this->u->auth($user, $password)) {
|
||||
$this->authPostProcess($user, $password);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public function auth(string $user = null, string $password = null): bool {
|
||||
if($user===null) {
|
||||
if($this->data->conf->userAuthPreferHTTP) return $this->authHTTP();
|
||||
return $this->authForm();
|
||||
} else {
|
||||
if($this->u->auth($user, $password)) {
|
||||
$this->authPostProcess($user, $password);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function authForm(): bool {
|
||||
$cred = $this->credentialsForm();
|
||||
if(!$cred["user"]) return $this->challengeForm();
|
||||
if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeForm();
|
||||
$this->authPostProcess($cred["user"], $cred["password"]);
|
||||
return true;
|
||||
}
|
||||
public function authForm(): bool {
|
||||
$cred = $this->credentialsForm();
|
||||
if(!$cred["user"]) return $this->challengeForm();
|
||||
if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeForm();
|
||||
$this->authPostProcess($cred["user"], $cred["password"]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function authHTTP(): bool {
|
||||
$cred = $this->credentialsHTTP();
|
||||
if(!$cred["user"]) return $this->challengeHTTP();
|
||||
if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeHTTP();
|
||||
$this->authPostProcess($cred["user"], $cred["password"]);
|
||||
return true;
|
||||
}
|
||||
public function authHTTP(): bool {
|
||||
$cred = $this->credentialsHTTP();
|
||||
if(!$cred["user"]) return $this->challengeHTTP();
|
||||
if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeHTTP();
|
||||
$this->authPostProcess($cred["user"], $cred["password"]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function driverFunctions(string $function = null) {
|
||||
return $this->u->driverFunctions($function);
|
||||
}
|
||||
|
||||
public function list(string $domain = null): array {
|
||||
if($this->u->driverFunctions("userList")==User\Driver::FUNC_EXTERNAL) {
|
||||
if($domain===null) {
|
||||
if(!$this->data->user->authorize("@".$domain, "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => $domain]);
|
||||
} else {
|
||||
if(!$this->data->user->authorize("", "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => "all users"]);
|
||||
}
|
||||
return $this->u->userList($domain);
|
||||
} else {
|
||||
return $this->data->db->userList($domain);
|
||||
}
|
||||
}
|
||||
public function driverFunctions(string $function = null) {
|
||||
return $this->u->driverFunctions($function);
|
||||
}
|
||||
|
||||
public function list(string $domain = null): array {
|
||||
if($this->u->driverFunctions("userList")==User\Driver::FUNC_EXTERNAL) {
|
||||
if($domain===null) {
|
||||
if(!$this->data->user->authorize("@".$domain, "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => $domain]);
|
||||
} else {
|
||||
if(!$this->data->user->authorize("", "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => "all users"]);
|
||||
}
|
||||
return $this->u->userList($domain);
|
||||
} else {
|
||||
return $this->data->db->userList($domain);
|
||||
}
|
||||
}
|
||||
|
||||
public function authorize(string $affectedUser, string $action, int $promoteLevel = 0): bool {
|
||||
if(!$this->authz) return true;
|
||||
if($this->id===null) $this->credentials();
|
||||
if($this->authzSupported) return $this->u->authorize($affectedUser, $action, $promoteLevel);
|
||||
// if the driver does not implement authorization, only allow operation for the current user (this means no new users can be added)
|
||||
if($affectedUser==$this->id && $action != "userRightsSet") return true;
|
||||
return false;
|
||||
}
|
||||
public function authorize(string $affectedUser, string $action, int $promoteLevel = 0): bool {
|
||||
if(!$this->authz) return true;
|
||||
if($this->id===null) $this->credentials();
|
||||
if($this->authzSupported) return $this->u->authorize($affectedUser, $action, $promoteLevel);
|
||||
// if the driver does not implement authorization, only allow operation for the current user (this means no new users can be added)
|
||||
if($affectedUser==$this->id && $action != "userRightsSet") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function authorizationEnabled(bool $setting = null): bool {
|
||||
if($setting===null) return $this->authz;
|
||||
$this->authz = $setting;
|
||||
return $setting;
|
||||
}
|
||||
|
||||
public function exists(string $user): bool {
|
||||
if($this->u->driverFunctions("userExists") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userExists")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userExists", "user" => $user]);
|
||||
}
|
||||
if(!$this->existSupported) return true;
|
||||
$out = $this->u->userExists($user);
|
||||
if($out && $this->existSupported==User\Driver::FUNC_EXTERNAL && !$this->data->db->userExist($user)) {
|
||||
try {$this->data->db->userAdd($user);} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
public function authorizationEnabled(bool $setting = null): bool {
|
||||
if($setting===null) return $this->authz;
|
||||
$this->authz = $setting;
|
||||
return $setting;
|
||||
}
|
||||
|
||||
public function exists(string $user): bool {
|
||||
if($this->u->driverFunctions("userExists") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userExists")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userExists", "user" => $user]);
|
||||
}
|
||||
if(!$this->existSupported) return true;
|
||||
$out = $this->u->userExists($user);
|
||||
if($out && $this->existSupported==User\Driver::FUNC_EXTERNAL && !$this->data->db->userExist($user)) {
|
||||
try {$this->data->db->userAdd($user);} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function add($user, $password = null): bool {
|
||||
if($this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userAdd")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userAdd", "user" => $user]);
|
||||
}
|
||||
if($this->exists($user)) return false;
|
||||
$out = $this->u->userAdd($user, $password);
|
||||
if($out && $this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) {
|
||||
try {
|
||||
if(!$this->data->db->userExists($user)) $this->data->db->userAdd($user, $password);
|
||||
} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
public function add($user, $password = null): bool {
|
||||
if($this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userAdd")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userAdd", "user" => $user]);
|
||||
}
|
||||
if($this->exists($user)) return false;
|
||||
$out = $this->u->userAdd($user, $password);
|
||||
if($out && $this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) {
|
||||
try {
|
||||
if(!$this->data->db->userExists($user)) $this->data->db->userAdd($user, $password);
|
||||
} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function remove(string $user): bool {
|
||||
if($this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRemove")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRemove", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
$out = $this->u->userRemove($user);
|
||||
if($out && $this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) {
|
||||
try {
|
||||
if($this->data->db->userExists($user)) $this->data->db->userRemove($user);
|
||||
} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
public function remove(string $user): bool {
|
||||
if($this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRemove")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRemove", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
$out = $this->u->userRemove($user);
|
||||
if($out && $this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) {
|
||||
try {
|
||||
if($this->data->db->userExists($user)) $this->data->db->userRemove($user);
|
||||
} catch(\Throwable $e) {}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function passwordSet(string $user, string $password): bool {
|
||||
if($this->u->driverFunctions("userPasswordSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPasswordSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPasswordSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
return $this->u->userPasswordSet($user, $password);
|
||||
}
|
||||
public function passwordSet(string $user, string $password): bool {
|
||||
if($this->u->driverFunctions("userPasswordSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPasswordSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPasswordSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
return $this->u->userPasswordSet($user, $password);
|
||||
}
|
||||
|
||||
public function propertiesGet(string $user): array {
|
||||
if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPropertiesGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesGet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
$domain = null;
|
||||
if($this->data->conf->userComposeNames) $domain = substr($user,strrpos($user,"@")+1);
|
||||
$init = [
|
||||
"id" => $user,
|
||||
"name" => $user,
|
||||
"rights" => User\Driver::RIGHTS_NONE,
|
||||
"domain" => $domain
|
||||
];
|
||||
if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_NOT_IMPLEMENTED) {
|
||||
return array_merge($init, $this->u->userPropertiesGet($user));
|
||||
}
|
||||
return $init;
|
||||
}
|
||||
public function propertiesGet(string $user): array {
|
||||
if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPropertiesGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesGet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
$domain = null;
|
||||
if($this->data->conf->userComposeNames) $domain = substr($user,strrpos($user,"@")+1);
|
||||
$init = [
|
||||
"id" => $user,
|
||||
"name" => $user,
|
||||
"rights" => User\Driver::RIGHTS_NONE,
|
||||
"domain" => $domain
|
||||
];
|
||||
if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_NOT_IMPLEMENTED) {
|
||||
return array_merge($init, $this->u->userPropertiesGet($user));
|
||||
}
|
||||
return $init;
|
||||
}
|
||||
|
||||
public function propertiesSet(string $user, array $properties): array {
|
||||
if($this->u->driverFunctions("userPropertiesSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPropertiesSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPropertiesSet"]);
|
||||
return $this->u->userPropertiesSet($user, $properties);
|
||||
}
|
||||
public function propertiesSet(string $user, array $properties): array {
|
||||
if($this->u->driverFunctions("userPropertiesSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userPropertiesSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPropertiesSet"]);
|
||||
return $this->u->userPropertiesSet($user, $properties);
|
||||
}
|
||||
|
||||
public function rightsGet(string $user): int {
|
||||
if($this->u->driverFunctions("userRightsGet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRightsGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsGet", "user" => $user]);
|
||||
}
|
||||
// we do not throw an exception here if the user does not exist, because it makes no material difference
|
||||
if(!$this->exists($user)) return User\Driver::RIGHTS_NONE;
|
||||
return $this->u->userRightsGet($user);
|
||||
}
|
||||
|
||||
public function rightsSet(string $user, int $level): bool {
|
||||
if($this->u->driverFunctions("userRightsSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRightsSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
return $this->u->userRightsSet($user, $level);
|
||||
}
|
||||
|
||||
// FIXME: stubs
|
||||
public function challenge(): bool {throw new User\Exception("authFailed");}
|
||||
public function challengeForm(): bool {throw new User\Exception("authFailed");}
|
||||
public function challengeHTTP(): bool {throw new User\Exception("authFailed");}
|
||||
public function rightsGet(string $user): int {
|
||||
if($this->u->driverFunctions("userRightsGet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRightsGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsGet", "user" => $user]);
|
||||
}
|
||||
// we do not throw an exception here if the user does not exist, because it makes no material difference
|
||||
if(!$this->exists($user)) return User\Driver::RIGHTS_NONE;
|
||||
return $this->u->userRightsGet($user);
|
||||
}
|
||||
|
||||
public function rightsSet(string $user, int $level): bool {
|
||||
if($this->u->driverFunctions("userRightsSet") != User\Driver::FUNC_INTERNAL) {
|
||||
if(!$this->data->user->authorize($user, "userRightsSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsSet", "user" => $user]);
|
||||
}
|
||||
if(!$this->exists($user)) return false;
|
||||
return $this->u->userRightsSet($user, $level);
|
||||
}
|
||||
|
||||
// FIXME: stubs
|
||||
public function challenge(): bool {throw new User\Exception("authFailed");}
|
||||
public function challengeForm(): bool {throw new User\Exception("authFailed");}
|
||||
public function challengeHTTP(): bool {throw new User\Exception("authFailed");}
|
||||
|
||||
protected function composeName(string $user): string {
|
||||
if(preg_match("/.+?@[^@]+$/",$user)) {
|
||||
return $user;
|
||||
} else {
|
||||
return $user."@".$_SERVER['HTTP_HOST'];
|
||||
}
|
||||
}
|
||||
protected function composeName(string $user): string {
|
||||
if(preg_match("/.+?@[^@]+$/",$user)) {
|
||||
return $user;
|
||||
} else {
|
||||
return $user."@".$_SERVER['HTTP_HOST'];
|
||||
}
|
||||
}
|
||||
|
||||
protected function authPostprocess(string $user, string $password): bool {
|
||||
if($this->u->driverFunctions("auth") != User\Driver::FUNC_INTERNAL && !$this->data->db->userExists($user)) {
|
||||
if($password=="") $password = null;
|
||||
try {$this->data->db->userAdd($user, $password);} catch(\Throwable $e) {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected function authPostprocess(string $user, string $password): bool {
|
||||
if($this->u->driverFunctions("auth") != User\Driver::FUNC_INTERNAL && !$this->data->db->userExists($user)) {
|
||||
if($password=="") $password = null;
|
||||
try {$this->data->db->userAdd($user, $password);} catch(\Throwable $e) {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,28 +3,28 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\User;
|
||||
|
||||
Interface Driver {
|
||||
const FUNC_NOT_IMPLEMENTED = 0;
|
||||
const FUNC_INTERNAL = 1;
|
||||
const FUNC_EXTERNAL = 2;
|
||||
const FUNC_NOT_IMPLEMENTED = 0;
|
||||
const FUNC_INTERNAL = 1;
|
||||
const FUNC_EXTERNAL = 2;
|
||||
|
||||
const RIGHTS_NONE = 0;
|
||||
const RIGHTS_DOMAIN_MANAGER = 25;
|
||||
const RIGHTS_DOMAIN_ADMIN = 50;
|
||||
const RIGHTS_GLOBAL_MANAGER = 75;
|
||||
const RIGHTS_GLOBAL_ADMIN = 100;
|
||||
const RIGHTS_NONE = 0;
|
||||
const RIGHTS_DOMAIN_MANAGER = 25;
|
||||
const RIGHTS_DOMAIN_ADMIN = 50;
|
||||
const RIGHTS_GLOBAL_MANAGER = 75;
|
||||
const RIGHTS_GLOBAL_ADMIN = 100;
|
||||
|
||||
static function create(\JKingWeb\NewsSync\RuntimeData $data): Driver;
|
||||
static function driverName(): string;
|
||||
function driverFunctions(string $function = null);
|
||||
function auth(string $user, string $password): bool;
|
||||
function authorize(string $affectedUser, string $action): bool;
|
||||
function userExists(string $user): bool;
|
||||
function userAdd(string $user, string $password = null): bool;
|
||||
function userRemove(string $user): bool;
|
||||
function userList(string $domain = null): array;
|
||||
function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool;
|
||||
function userPropertiesGet(string $user): array;
|
||||
function userPropertiesSet(string $user, array $properties): array;
|
||||
function userRightsGet(string $user): int;
|
||||
function userRightsSet(string $user, int $level): bool;
|
||||
static function create(\JKingWeb\NewsSync\RuntimeData $data): Driver;
|
||||
static function driverName(): string;
|
||||
function driverFunctions(string $function = null);
|
||||
function auth(string $user, string $password): bool;
|
||||
function authorize(string $affectedUser, string $action): bool;
|
||||
function userExists(string $user): bool;
|
||||
function userAdd(string $user, string $password = null): bool;
|
||||
function userRemove(string $user): bool;
|
||||
function userList(string $domain = null): array;
|
||||
function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool;
|
||||
function userPropertiesGet(string $user): array;
|
||||
function userPropertiesSet(string $user, array $properties): array;
|
||||
function userRightsGet(string $user): int;
|
||||
function userRightsSet(string $user, int $level): bool;
|
||||
}
|
|
@ -3,43 +3,43 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\NewsSync\User;
|
||||
|
||||
class DriverInternal implements Driver {
|
||||
use InternalFunctions;
|
||||
use InternalFunctions;
|
||||
|
||||
protected $data;
|
||||
protected $db;
|
||||
protected $functions = [
|
||||
"auth" => Driver::FUNC_INTERNAL,
|
||||
"authorize" => Driver::FUNC_INTERNAL,
|
||||
"userList" => Driver::FUNC_INTERNAL,
|
||||
"userExists" => Driver::FUNC_INTERNAL,
|
||||
"userAdd" => Driver::FUNC_INTERNAL,
|
||||
"userRemove" => Driver::FUNC_INTERNAL,
|
||||
"userPasswordSet" => Driver::FUNC_INTERNAL,
|
||||
"userPropertiesGet" => Driver::FUNC_INTERNAL,
|
||||
"userPropertiesSet" => Driver::FUNC_INTERNAL,
|
||||
"userRightsGet" => Driver::FUNC_INTERNAL,
|
||||
"userRightsSet" => Driver::FUNC_INTERNAL,
|
||||
];
|
||||
protected $data;
|
||||
protected $db;
|
||||
protected $functions = [
|
||||
"auth" => Driver::FUNC_INTERNAL,
|
||||
"authorize" => Driver::FUNC_INTERNAL,
|
||||
"userList" => Driver::FUNC_INTERNAL,
|
||||
"userExists" => Driver::FUNC_INTERNAL,
|
||||
"userAdd" => Driver::FUNC_INTERNAL,
|
||||
"userRemove" => Driver::FUNC_INTERNAL,
|
||||
"userPasswordSet" => Driver::FUNC_INTERNAL,
|
||||
"userPropertiesGet" => Driver::FUNC_INTERNAL,
|
||||
"userPropertiesSet" => Driver::FUNC_INTERNAL,
|
||||
"userRightsGet" => Driver::FUNC_INTERNAL,
|
||||
"userRightsSet" => Driver::FUNC_INTERNAL,
|
||||
];
|
||||
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver {
|
||||
return new static($data);
|
||||
}
|
||||
static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver {
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
public function __construct(\JKingWeb\NewsSync\RuntimeData $data) {
|
||||
$this->data = $data;
|
||||
$this->db = $this->data->db;
|
||||
}
|
||||
public function __construct(\JKingWeb\NewsSync\RuntimeData $data) {
|
||||
$this->data = $data;
|
||||
$this->db = $this->data->db;
|
||||
}
|
||||
|
||||
static public function driverName(): string {
|
||||
return "Internal";
|
||||
}
|
||||
static public function driverName(): string {
|
||||
return "Internal";
|
||||
}
|
||||
|
||||
public function driverFunctions(string $function = null) {
|
||||
if($function===null) return $this->functions;
|
||||
if(array_key_exists($function, $this->functions)) {
|
||||
return $this->functions[$function];
|
||||
} else {
|
||||
return Driver::FUNC_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
public function driverFunctions(string $function = null) {
|
||||
if($function===null) return $this->functions;
|
||||
if(array_key_exists($function, $this->functions)) {
|
||||
return $this->functions[$function];
|
||||
} else {
|
||||
return Driver::FUNC_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,78 +2,78 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\NewsSync\User;
|
||||
|
||||
trait InternalFunctions {
|
||||
protected $actor = [];
|
||||
|
||||
function auth(string $user, string $password): bool {
|
||||
if(!$this->data->user->exists($user)) return false;
|
||||
$hash = $this->db->userPasswordGet($user);
|
||||
if(!$hash) return false;
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
trait InternalFunctions {
|
||||
protected $actor = [];
|
||||
|
||||
function auth(string $user, string $password): bool {
|
||||
if(!$this->data->user->exists($user)) return false;
|
||||
$hash = $this->db->userPasswordGet($user);
|
||||
if(!$hash) return false;
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
function authorize(string $affectedUser, string $action, int $newRightsLevel = 0): bool {
|
||||
// if the affected user is the actor and the actor is not trying to grant themselves rights, accept the request
|
||||
if($affectedUser==$this->data->user->id && $action != "userRightsSet") return true;
|
||||
// get properties of actor if not already available
|
||||
if(!sizeof($this->actor)) $this->actor = $this->data->user->propertiesGet($this->data->user->id);
|
||||
$rights =& $this->actor["rights"];
|
||||
// if actor is a global admin, accept the request
|
||||
if($rights==self::RIGHTS_GLOBAL_ADMIN) return true;
|
||||
// if actor is a common user, deny the request
|
||||
if($rights==self::RIGHTS_NONE) return false;
|
||||
// if actor is not some other sort of admin, deny the request
|
||||
if(!in_array($rights,[self::RIGHTS_GLOBAL_MANAGER,self::RIGHTS_DOMAIN_MANAGER,self::RIGHTS_DOMAIN_ADMIN],true)) return false;
|
||||
// if actor is a domain admin/manager and domains don't match, deny the request
|
||||
if($this->data->conf->userComposeNames && $this->actor["domain"] && $rights != self::RIGHTS_GLOBAL_MANAGER) {
|
||||
$test = "@".$this->actor["domain"];
|
||||
if(substr($affectedUser,-1*strlen($test)) != $test) return false;
|
||||
}
|
||||
// certain actions shouldn't check affected user's rights
|
||||
if(in_array($action, ["userRightsGet","userExists","userList"], true)) return true;
|
||||
if($action=="userRightsSet") {
|
||||
// setting rights above your own (or equal to your own, for managers) is not allowed
|
||||
if($newRightsLevel > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $newRightsLevel==$rights)) return false;
|
||||
}
|
||||
$affectedRights = $this->data->user->rightsGet($affectedUser);
|
||||
// acting for users with rights greater than your own (or equal, for managers) is not allowed
|
||||
if($affectedRights > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $affectedRights==$rights)) return false;
|
||||
return true;
|
||||
}
|
||||
function authorize(string $affectedUser, string $action, int $newRightsLevel = 0): bool {
|
||||
// if the affected user is the actor and the actor is not trying to grant themselves rights, accept the request
|
||||
if($affectedUser==$this->data->user->id && $action != "userRightsSet") return true;
|
||||
// get properties of actor if not already available
|
||||
if(!sizeof($this->actor)) $this->actor = $this->data->user->propertiesGet($this->data->user->id);
|
||||
$rights =& $this->actor["rights"];
|
||||
// if actor is a global admin, accept the request
|
||||
if($rights==self::RIGHTS_GLOBAL_ADMIN) return true;
|
||||
// if actor is a common user, deny the request
|
||||
if($rights==self::RIGHTS_NONE) return false;
|
||||
// if actor is not some other sort of admin, deny the request
|
||||
if(!in_array($rights,[self::RIGHTS_GLOBAL_MANAGER,self::RIGHTS_DOMAIN_MANAGER,self::RIGHTS_DOMAIN_ADMIN],true)) return false;
|
||||
// if actor is a domain admin/manager and domains don't match, deny the request
|
||||
if($this->data->conf->userComposeNames && $this->actor["domain"] && $rights != self::RIGHTS_GLOBAL_MANAGER) {
|
||||
$test = "@".$this->actor["domain"];
|
||||
if(substr($affectedUser,-1*strlen($test)) != $test) return false;
|
||||
}
|
||||
// certain actions shouldn't check affected user's rights
|
||||
if(in_array($action, ["userRightsGet","userExists","userList"], true)) return true;
|
||||
if($action=="userRightsSet") {
|
||||
// setting rights above your own (or equal to your own, for managers) is not allowed
|
||||
if($newRightsLevel > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $newRightsLevel==$rights)) return false;
|
||||
}
|
||||
$affectedRights = $this->data->user->rightsGet($affectedUser);
|
||||
// acting for users with rights greater than your own (or equal, for managers) is not allowed
|
||||
if($affectedRights > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $affectedRights==$rights)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function userExists(string $user): bool {
|
||||
return $this->db->userExists($user);
|
||||
}
|
||||
function userExists(string $user): bool {
|
||||
return $this->db->userExists($user);
|
||||
}
|
||||
|
||||
function userAdd(string $user, string $password = null): bool {
|
||||
return $this->db->userAdd($user, $password);
|
||||
}
|
||||
function userAdd(string $user, string $password = null): bool {
|
||||
return $this->db->userAdd($user, $password);
|
||||
}
|
||||
|
||||
function userRemove(string $user): bool {
|
||||
return $this->db->userRemove($user);
|
||||
}
|
||||
function userRemove(string $user): bool {
|
||||
return $this->db->userRemove($user);
|
||||
}
|
||||
|
||||
function userList(string $domain = null): array {
|
||||
return $this->db->userList($domain);
|
||||
}
|
||||
|
||||
function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool {
|
||||
return $this->db->userPasswordSet($user, $newPassword);
|
||||
}
|
||||
function userList(string $domain = null): array {
|
||||
return $this->db->userList($domain);
|
||||
}
|
||||
|
||||
function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool {
|
||||
return $this->db->userPasswordSet($user, $newPassword);
|
||||
}
|
||||
|
||||
function userPropertiesGet(string $user): array {
|
||||
return $this->db->userPropertiesGet($user);
|
||||
}
|
||||
function userPropertiesGet(string $user): array {
|
||||
return $this->db->userPropertiesGet($user);
|
||||
}
|
||||
|
||||
function userPropertiesSet(string $user, array $properties): array {
|
||||
return $this->db->userPropertiesSet($user, $properties);
|
||||
}
|
||||
function userPropertiesSet(string $user, array $properties): array {
|
||||
return $this->db->userPropertiesSet($user, $properties);
|
||||
}
|
||||
|
||||
function userRightsGet(string $user): int {
|
||||
return $this->db->userRightsGet($user);
|
||||
}
|
||||
|
||||
function userRightsSet(string $user, int $level): bool {
|
||||
return $this->db->userRightsSet($user, $level);
|
||||
}
|
||||
function userRightsGet(string $user): int {
|
||||
return $this->db->userRightsGet($user);
|
||||
}
|
||||
|
||||
function userRightsSet(string $user, int $level): bool {
|
||||
return $this->db->userRightsSet($user, $level);
|
||||
}
|
||||
}
|
|
@ -1,52 +1,52 @@
|
|||
<?php
|
||||
return [
|
||||
'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php',
|
||||
//this should not usually be encountered
|
||||
'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred',
|
||||
'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php',
|
||||
//this should not usually be encountered
|
||||
'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable' => 'Insufficient permissions to read language file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt' => 'Language file "{0}" is corrupt or does not conform to expected format',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing' => 'Message string "{msgID}" missing from all loaded language files ({fileList})',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable' => 'Insufficient permissions to read language file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt' => 'Language file "{0}" is corrupt or does not conform to expected format',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing' => 'Message string "{msgID}" missing from all loaded language files ({fileList})',
|
||||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileMissing' => 'Configuration file "{0}" does not exist',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnreadable' => 'Insufficient permissions to read configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUncreatable' => 'Insufficient permissions to write new configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable' => 'Insufficient permissions to overwrite configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt' => 'Configuration file "{0}" is corrupt or does not conform to expected format',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manual' =>
|
||||
'{from_version, select,
|
||||
0 {{driver_name} database is configured for manual updates and is not initialized; please populate the database with the base schema}
|
||||
other {{driver_name} database is configured for manual updates; please update from schema version {current} to version {target}}
|
||||
}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly' =>
|
||||
'{from_version, select,
|
||||
0 {{driver_name} database must be updated manually and is not initialized; please populate the database with the base schema}
|
||||
other {{driver_name} database must be updated manually; please update from schema version {current} to version {target}}
|
||||
}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.tooNew' =>
|
||||
'{difference, select,
|
||||
0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}}
|
||||
other {Automatic updating of the {driver_name} database failed because its version, {current}, is newer than the requested version, {target}}
|
||||
}',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileMissing' => 'Configuration file "{0}" does not exist',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnreadable' => 'Insufficient permissions to read configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUncreatable' => 'Insufficient permissions to write new configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable' => 'Insufficient permissions to overwrite configuration file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt' => 'Configuration file "{0}" is corrupt or does not conform to expected format',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/User/Exception.alreadyExists' => 'Could not perform action "{action}" because the user {user} already exists',
|
||||
'Exception.JKingWeb/NewsSync/User/Exception.doesNotExist' => 'Could not perform action "{action}" because the user {user} does not exist',
|
||||
'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/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"',
|
||||
'Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manual' =>
|
||||
'{from_version, select,
|
||||
0 {{driver_name} database is configured for manual updates and is not initialized; please populate the database with the base schema}
|
||||
other {{driver_name} database is configured for manual updates; please update from schema version {current} to version {target}}
|
||||
}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly' =>
|
||||
'{from_version, select,
|
||||
0 {{driver_name} database must be updated manually and is not initialized; please populate the database with the base schema}
|
||||
other {{driver_name} database must be updated manually; please update from schema version {current} to version {target}}
|
||||
}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}',
|
||||
'Exception.JKingWeb/NewsSync/Db/Update/Exception.tooNew' =>
|
||||
'{difference, select,
|
||||
0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}}
|
||||
other {Automatic updating of the {driver_name} database failed because its version, {current}, is newer than the requested version, {target}}
|
||||
}',
|
||||
|
||||
'Exception.JKingWeb/NewsSync/User/Exception.alreadyExists' => 'Could not perform action "{action}" because the user {user} already exists',
|
||||
'Exception.JKingWeb/NewsSync/User/Exception.doesNotExist' => 'Could not perform action "{action}" because the user {user} does not exist',
|
||||
'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}',
|
||||
];
|
|
@ -1,110 +1,110 @@
|
|||
-- newsfeeds, deduplicated
|
||||
create table newssync_feeds(
|
||||
id integer primary key not null, -- sequence number
|
||||
url TEXT not null, -- URL of feed
|
||||
title TEXT, -- default title of feed
|
||||
favicon TEXT, -- URL of favicon
|
||||
source TEXT, -- URL of site to which the feed belongs
|
||||
updated datetime, -- time at which the feed was last fetched
|
||||
modified datetime, -- time at which the feed last actually changed
|
||||
etag TEXT, -- HTTP ETag hash used for cache validation, changes each time the content changes
|
||||
err_count integer not null default 0, -- count of successive times update resulted in error since last successful update
|
||||
err_msg TEXT, -- last error message
|
||||
username TEXT not null default '', -- HTTP authentication username
|
||||
password TEXT not null default '', -- HTTP authentication password (this is stored in plain text)
|
||||
unique(url,username,password) -- a URL with particular credentials should only appear once
|
||||
id integer primary key not null, -- sequence number
|
||||
url TEXT not null, -- URL of feed
|
||||
title TEXT, -- default title of feed
|
||||
favicon TEXT, -- URL of favicon
|
||||
source TEXT, -- URL of site to which the feed belongs
|
||||
updated datetime, -- time at which the feed was last fetched
|
||||
modified datetime, -- time at which the feed last actually changed
|
||||
etag TEXT, -- HTTP ETag hash used for cache validation, changes each time the content changes
|
||||
err_count integer not null default 0, -- count of successive times update resulted in error since last successful update
|
||||
err_msg TEXT, -- last error message
|
||||
username TEXT not null default '', -- HTTP authentication username
|
||||
password TEXT not null default '', -- HTTP authentication password (this is stored in plain text)
|
||||
unique(url,username,password) -- a URL with particular credentials should only appear once
|
||||
);
|
||||
|
||||
-- entries in newsfeeds
|
||||
create table newssync_articles(
|
||||
id integer primary key not null, -- sequence number
|
||||
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
|
||||
url TEXT not null, -- URL of article
|
||||
title TEXT, -- article title
|
||||
author TEXT, -- author's name
|
||||
published datetime, -- time of original publication
|
||||
edited datetime, -- time of last edit
|
||||
guid TEXT, -- GUID
|
||||
content TEXT, -- content, as (X)HTML
|
||||
modified datetime not null default CURRENT_TIMESTAMP, -- date when article properties were last modified
|
||||
hash varchar(64) not null, -- ownCloud hash
|
||||
fingerprint varchar(64) not null, -- ownCloud fingerprint
|
||||
enclosures_hash varchar(64), -- hash of enclosures, if any; since enclosures are not uniquely identified, we need to know when they change
|
||||
tags_hash varchar(64) -- hash of RSS/Atom categories included in article; since these categories are not uniquely identified, we need to know when they change
|
||||
id integer primary key not null, -- sequence number
|
||||
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
|
||||
url TEXT not null, -- URL of article
|
||||
title TEXT, -- article title
|
||||
author TEXT, -- author's name
|
||||
published datetime, -- time of original publication
|
||||
edited datetime, -- time of last edit
|
||||
guid TEXT, -- GUID
|
||||
content TEXT, -- content, as (X)HTML
|
||||
modified datetime not null default CURRENT_TIMESTAMP, -- date when article properties were last modified
|
||||
hash varchar(64) not null, -- ownCloud hash
|
||||
fingerprint varchar(64) not null, -- ownCloud fingerprint
|
||||
enclosures_hash varchar(64), -- hash of enclosures, if any; since enclosures are not uniquely identified, we need to know when they change
|
||||
tags_hash varchar(64) -- hash of RSS/Atom categories included in article; since these categories are not uniquely identified, we need to know when they change
|
||||
);
|
||||
|
||||
-- enclosures associated with articles
|
||||
create table newssync_enclosures(
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
url TEXT,
|
||||
type varchar(255)
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
url TEXT,
|
||||
type varchar(255)
|
||||
);
|
||||
|
||||
-- author labels ("categories" in RSS/Atom parlance) associated with newsfeed entries
|
||||
create table newssync_tags(
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
name TEXT
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
name TEXT
|
||||
);
|
||||
|
||||
-- settings
|
||||
create table newssync_settings(
|
||||
key varchar(255) primary key not null, --
|
||||
value varchar(255), --
|
||||
type varchar(255) not null check(
|
||||
type in('int','numeric','text','timestamp','date','time','bool','null','json')
|
||||
) default 'text' --
|
||||
key varchar(255) primary key not null, --
|
||||
value varchar(255), --
|
||||
type varchar(255) not null check(
|
||||
type in('int','numeric','text','timestamp','date','time','bool','null','json')
|
||||
) default 'text' --
|
||||
);
|
||||
|
||||
-- users
|
||||
create table newssync_users(
|
||||
id TEXT primary key not null, -- user id
|
||||
password TEXT, -- password, salted and hashed; if using external authentication this would be blank
|
||||
name TEXT, -- display name
|
||||
avatar_url TEXT, -- external URL to avatar
|
||||
avatar_type TEXT, -- internal avatar image's MIME content type
|
||||
avatar_data BLOB, -- internal avatar image's binary data
|
||||
rights integer not null default 0 -- any administrative rights the user may have
|
||||
id TEXT primary key not null, -- user id
|
||||
password TEXT, -- password, salted and hashed; if using external authentication this would be blank
|
||||
name TEXT, -- display name
|
||||
avatar_url TEXT, -- external URL to avatar
|
||||
avatar_type TEXT, -- internal avatar image's MIME content type
|
||||
avatar_data BLOB, -- internal avatar image's binary data
|
||||
rights integer not null default 0 -- any administrative rights the user may have
|
||||
);
|
||||
|
||||
-- TT-RSS categories and ownCloud folders
|
||||
create table newssync_categories(
|
||||
id integer primary key not null, -- sequence number
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of category
|
||||
parent integer, -- parent category id
|
||||
folder integer not null, -- first-level category (ownCloud folder)
|
||||
name TEXT not null, -- category name
|
||||
modified datetime not null default CURRENT_TIMESTAMP, --
|
||||
unique(owner,name,parent) -- cannot have multiple categories with the same name under the same parent for the same owner
|
||||
id integer primary key not null, -- sequence number
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of category
|
||||
parent integer, -- parent category id
|
||||
folder integer not null, -- first-level category (ownCloud folder)
|
||||
name TEXT not null, -- category name
|
||||
modified datetime not null default CURRENT_TIMESTAMP, --
|
||||
unique(owner,name,parent) -- cannot have multiple categories with the same name under the same parent for the same owner
|
||||
);
|
||||
|
||||
-- users' subscriptions to newsfeeds, with settings
|
||||
create table newssync_subscriptions(
|
||||
id integer primary key not null, -- sequence number
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription
|
||||
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
|
||||
added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added
|
||||
modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified
|
||||
title TEXT, -- user-supplied title
|
||||
order_type int not null default 0, -- ownCloud sort order
|
||||
pinned boolean not null default 0, -- whether feed is pinned (always sorts at top)
|
||||
category integer references newssync_categories(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed
|
||||
unique(owner,feed) -- a given feed should only appear once for a given owner
|
||||
id integer primary key not null, -- sequence number
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription
|
||||
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
|
||||
added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added
|
||||
modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified
|
||||
title TEXT, -- user-supplied title
|
||||
order_type int not null default 0, -- ownCloud sort order
|
||||
pinned boolean not null default 0, -- whether feed is pinned (always sorts at top)
|
||||
category integer references newssync_categories(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed
|
||||
unique(owner,feed) -- a given feed should only appear once for a given owner
|
||||
);
|
||||
|
||||
-- users' actions on newsfeed entries
|
||||
create table newssync_subscription_articles(
|
||||
id integer primary key not null,
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
read boolean not null default 0,
|
||||
starred boolean not null default 0,
|
||||
modified datetime not null default CURRENT_TIMESTAMP
|
||||
id integer primary key not null,
|
||||
article integer not null references newssync_articles(id) on delete cascade,
|
||||
read boolean not null default 0,
|
||||
starred boolean not null default 0,
|
||||
modified datetime not null default CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- user labels associated with newsfeed entries
|
||||
create table newssync_labels(
|
||||
sub_article integer not null references newssync_subscription_articles(id) on delete cascade, --
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade,
|
||||
name TEXT
|
||||
sub_article integer not null references newssync_subscription_articles(id) on delete cascade, --
|
||||
owner TEXT not null references newssync_users(id) on delete cascade on update cascade,
|
||||
name TEXT
|
||||
);
|
||||
create index newssync_label_names on newssync_labels(name);
|
||||
|
||||
|
|
|
@ -5,101 +5,101 @@ use \org\bovigo\vfs\vfsStream;
|
|||
|
||||
|
||||
class TestConf extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools;
|
||||
|
||||
static $vfs;
|
||||
static $path;
|
||||
|
||||
static function setUpBeforeClass() {
|
||||
self::$vfs = vfsStream::setup("root", null, [
|
||||
'confGood' => '<?php return Array("lang" => "xx");',
|
||||
'confNotArray' => '<?php return 0;',
|
||||
'confCorrupt' => '<?php return 0',
|
||||
'confNotPHP' => 'DEAD BEEF',
|
||||
'confEmpty' => '',
|
||||
'confUnreadable' => '',
|
||||
]);
|
||||
self::$path = self::$vfs->url()."/";
|
||||
// set up a file without read access
|
||||
chmod(self::$path."confUnreadable", 0000);
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
}
|
||||
|
||||
function testLoadDefaultValues() {
|
||||
$this->assertInstanceOf(Conf::class, new Conf());
|
||||
}
|
||||
use Test\Tools;
|
||||
|
||||
/**
|
||||
static $vfs;
|
||||
static $path;
|
||||
|
||||
static function setUpBeforeClass() {
|
||||
self::$vfs = vfsStream::setup("root", null, [
|
||||
'confGood' => '<?php return Array("lang" => "xx");',
|
||||
'confNotArray' => '<?php return 0;',
|
||||
'confCorrupt' => '<?php return 0',
|
||||
'confNotPHP' => 'DEAD BEEF',
|
||||
'confEmpty' => '',
|
||||
'confUnreadable' => '',
|
||||
]);
|
||||
self::$path = self::$vfs->url()."/";
|
||||
// set up a file without read access
|
||||
chmod(self::$path."confUnreadable", 0000);
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
}
|
||||
|
||||
function testLoadDefaultValues() {
|
||||
$this->assertInstanceOf(Conf::class, new Conf());
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testLoadDefaultValues
|
||||
*/
|
||||
function testImportFromArray() {
|
||||
$arr = ['lang' => "xx"];
|
||||
$conf = new Conf();
|
||||
$conf->import($arr);
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
}
|
||||
function testImportFromArray() {
|
||||
$arr = ['lang' => "xx"];
|
||||
$conf = new Conf();
|
||||
$conf->import($arr);
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromArray
|
||||
*/
|
||||
function testImportFromFile() {
|
||||
$conf = new Conf();
|
||||
$conf->importFile(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
$conf = new Conf(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
}
|
||||
function testImportFromFile() {
|
||||
$conf = new Conf();
|
||||
$conf->importFile(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
$conf = new Conf(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromMissingFile() {
|
||||
$this->assertException("fileMissing", "Conf");
|
||||
$conf = new Conf(self::$path."confMissing");
|
||||
}
|
||||
function testImportFromMissingFile() {
|
||||
$this->assertException("fileMissing", "Conf");
|
||||
$conf = new Conf(self::$path."confMissing");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromEmptyFile() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confEmpty");
|
||||
}
|
||||
function testImportFromEmptyFile() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confEmpty");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromFileWithoutReadPermission() {
|
||||
$this->assertException("fileUnreadable", "Conf");
|
||||
$conf = new Conf(self::$path."confUnreadable");
|
||||
}
|
||||
function testImportFromFileWithoutReadPermission() {
|
||||
$this->assertException("fileUnreadable", "Conf");
|
||||
$conf = new Conf(self::$path."confUnreadable");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromFileWhichIsNotAnArray() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confNotArray");
|
||||
}
|
||||
function testImportFromFileWhichIsNotAnArray() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confNotArray");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromFileWhichIsNotPhp() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
// this should not print the output of the non-PHP file
|
||||
$conf = new Conf(self::$path."confNotPHP");
|
||||
}
|
||||
function testImportFromFileWhichIsNotPhp() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
// this should not print the output of the non-PHP file
|
||||
$conf = new Conf(self::$path."confNotPHP");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testImportFromFile
|
||||
*/
|
||||
function testImportFromCorruptFile() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confCorrupt");
|
||||
}
|
||||
function testImportFromCorruptFile() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."confCorrupt");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,67 +4,67 @@ namespace JKingWeb\NewsSync;
|
|||
|
||||
|
||||
class TestException extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools;
|
||||
use Test\Tools;
|
||||
|
||||
static function setUpBeforeClass() {
|
||||
Lang::set("");
|
||||
}
|
||||
static function setUpBeforeClass() {
|
||||
Lang::set("");
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
Lang::set(Lang::DEFAULT);
|
||||
}
|
||||
|
||||
function testBaseClass() {
|
||||
$this->assertException("unknown");
|
||||
throw new Exception("unknown");
|
||||
}
|
||||
static function tearDownAfterClass() {
|
||||
Lang::set(Lang::DEFAULT);
|
||||
}
|
||||
|
||||
function testBaseClass() {
|
||||
$this->assertException("unknown");
|
||||
throw new Exception("unknown");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testBaseClass
|
||||
*/
|
||||
function testBaseClassWithoutMessage() {
|
||||
$this->assertException("unknown");
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/**
|
||||
function testBaseClassWithoutMessage() {
|
||||
$this->assertException("unknown");
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testBaseClass
|
||||
*/
|
||||
function testDerivedClass() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
throw new Lang\Exception("fileMissing");
|
||||
}
|
||||
function testDerivedClass() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
throw new Lang\Exception("fileMissing");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testDerivedClass
|
||||
*/
|
||||
function testDerivedClassWithMessageParameters() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
throw new Lang\Exception("fileMissing", "en");
|
||||
}
|
||||
function testDerivedClassWithMessageParameters() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
throw new Lang\Exception("fileMissing", "en");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testBaseClass
|
||||
*/
|
||||
function testBaseClassWithUnknownCode() {
|
||||
$this->assertException("uncoded");
|
||||
throw new Exception("testThisExceptionMessageDoesNotExist");
|
||||
}
|
||||
function testBaseClassWithUnknownCode() {
|
||||
$this->assertException("uncoded");
|
||||
throw new Exception("testThisExceptionMessageDoesNotExist");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testBaseClass
|
||||
*/
|
||||
function testBaseClassWithMissingMessage() {
|
||||
$this->assertException("stringMissing", "Lang");
|
||||
throw new Exception("invalid");
|
||||
}
|
||||
function testBaseClassWithMissingMessage() {
|
||||
$this->assertException("stringMissing", "Lang");
|
||||
throw new Exception("invalid");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testBaseClassWithUnknownCode
|
||||
*/
|
||||
function testDerivedClassWithMissingMessage() {
|
||||
$this->assertException("uncoded");
|
||||
throw new Lang\Exception("testThisExceptionMessageDoesNotExist");
|
||||
}
|
||||
|
||||
function testDerivedClassWithMissingMessage() {
|
||||
$this->assertException("uncoded");
|
||||
throw new Lang\Exception("testThisExceptionMessageDoesNotExist");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,58 +5,58 @@ use \org\bovigo\vfs\vfsStream;
|
|||
|
||||
|
||||
class TestLang extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
|
||||
function testListLanguages() {
|
||||
$this->assertCount(sizeof(self::$files), Lang::list("en"));
|
||||
}
|
||||
function testListLanguages() {
|
||||
$this->assertCount(sizeof(self::$files), Lang::list("en"));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testListLanguages
|
||||
*/
|
||||
function testSetLanguage() {
|
||||
$this->assertEquals("en", Lang::set("en"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca"));
|
||||
$this->assertEquals("de", Lang::set("de_ch"));
|
||||
$this->assertEquals("en", Lang::set("en_gb_hixie"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca_jking"));
|
||||
$this->assertEquals("en", Lang::set("es"));
|
||||
$this->assertEquals("", Lang::set(""));
|
||||
}
|
||||
function testSetLanguage() {
|
||||
$this->assertEquals("en", Lang::set("en"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca"));
|
||||
$this->assertEquals("de", Lang::set("de_ch"));
|
||||
$this->assertEquals("en", Lang::set("en_gb_hixie"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca_jking"));
|
||||
$this->assertEquals("en", Lang::set("es"));
|
||||
$this->assertEquals("", Lang::set(""));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testSetLanguage
|
||||
*/
|
||||
function testLoadInternalStrings() {
|
||||
$this->assertEquals("", Lang::set("", true));
|
||||
$this->assertCount(sizeof(Lang::REQUIRED), Lang::dump());
|
||||
}
|
||||
function testLoadInternalStrings() {
|
||||
$this->assertEquals("", Lang::set("", true));
|
||||
$this->assertCount(sizeof(Lang::REQUIRED), Lang::dump());
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testLoadInternalStrings
|
||||
*/
|
||||
function testLoadDefaultLanguage() {
|
||||
$this->assertEquals(Lang::DEFAULT, Lang::set(Lang::DEFAULT, true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
}
|
||||
function testLoadDefaultLanguage() {
|
||||
$this->assertEquals(Lang::DEFAULT, Lang::set(Lang::DEFAULT, true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testLoadDefaultLanguage
|
||||
*/
|
||||
function testLoadSupplementaryLanguage() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
$this->assertEquals("ja", Lang::set("ja", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
$this->assertArrayHasKey('Test.absentText', $str);
|
||||
}
|
||||
function testLoadSupplementaryLanguage() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
$this->assertEquals("ja", Lang::set("ja", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
$this->assertArrayHasKey('Test.absentText', $str);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,51 +5,51 @@ use \org\bovigo\vfs\vfsStream;
|
|||
|
||||
|
||||
class TestLangErrors extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
|
||||
function setUp() {
|
||||
Lang::set("", true);
|
||||
}
|
||||
function setUp() {
|
||||
Lang::set("", true);
|
||||
}
|
||||
|
||||
function testLoadEmptyFile() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("fr_ca", true);
|
||||
}
|
||||
function testLoadEmptyFile() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("fr_ca", true);
|
||||
}
|
||||
|
||||
function testLoadFileWhichDoesNotReturnAnArray() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("it", true);
|
||||
}
|
||||
function testLoadFileWhichDoesNotReturnAnArray() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("it", true);
|
||||
}
|
||||
|
||||
function testLoadFileWhichIsNotPhp() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("ko", true);
|
||||
}
|
||||
function testLoadFileWhichIsNotPhp() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("ko", true);
|
||||
}
|
||||
|
||||
function testLoadFileWhichIsCorrupt() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("zh", true);
|
||||
}
|
||||
function testLoadFileWhichIsCorrupt() {
|
||||
$this->assertException("fileCorrupt", "Lang");
|
||||
Lang::set("zh", true);
|
||||
}
|
||||
|
||||
function testLoadFileWithooutReadPermission() {
|
||||
$this->assertException("fileUnreadable", "Lang");
|
||||
Lang::set("ru", true);
|
||||
}
|
||||
function testLoadFileWithooutReadPermission() {
|
||||
$this->assertException("fileUnreadable", "Lang");
|
||||
Lang::set("ru", true);
|
||||
}
|
||||
|
||||
function testLoadSubtagOfMissingLanguage() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
Lang::set("pt_br", true);
|
||||
}
|
||||
function testLoadSubtagOfMissingLanguage() {
|
||||
$this->assertException("fileMissing", "Lang");
|
||||
Lang::set("pt_br", true);
|
||||
}
|
||||
|
||||
function testLoadMissingDefaultLanguage() {
|
||||
// this should be the last test of the series
|
||||
unlink(self::$path.Lang::DEFAULT.".php");
|
||||
$this->assertException("defaultFileMissing", "Lang");
|
||||
Lang::set("fr", true);
|
||||
}
|
||||
function testLoadMissingDefaultLanguage() {
|
||||
// this should be the last test of the series
|
||||
unlink(self::$path.Lang::DEFAULT.".php");
|
||||
$this->assertException("defaultFileMissing", "Lang");
|
||||
Lang::set("fr", true);
|
||||
}
|
||||
}
|
|
@ -6,43 +6,43 @@ use \org\bovigo\vfs\vfsStream, \JKingWeb\NewsSync\Lang;
|
|||
|
||||
|
||||
trait Setup {
|
||||
static function setUpBeforeClass() {
|
||||
// this is required to keep from having exceptions in Lang::msg() in turn calling Lang::msg() and looping
|
||||
\JKingWeb\NewsSync\Lang\Exception::$test = true;
|
||||
// test files
|
||||
self::$files = [
|
||||
'en.php' => '<?php return ["Test.presentText" => "and the Philosopher\'s Stone"];',
|
||||
'en_ca.php' => '<?php return ["Test.presentText" => "{0} and {1}"];',
|
||||
'en_us.php' => '<?php return ["Test.presentText" => "and the Sorcerer\'s Stone"];',
|
||||
'fr.php' => '<?php return ["Test.presentText" => "à l\'école des sorciers"];',
|
||||
'ja.php' => '<?php return ["Test.absentText" => "賢者の石"];',
|
||||
'de.php' => '<?php return ["Test.presentText" => "und der Stein der Weisen"];',
|
||||
'pt_br.php' => '<?php return ["Test.presentText" => "e a Pedra Filosofal"];',
|
||||
'vi.php' => '<?php return [];',
|
||||
// corrupt files
|
||||
'it.php' => '<?php return 0;',
|
||||
'zh.php' => '<?php return 0',
|
||||
'ko.php' => 'DEAD BEEF',
|
||||
'fr_ca.php' => '',
|
||||
// unreadable file
|
||||
'ru.php' => '',
|
||||
];
|
||||
self::$vfs = vfsStream::setup("langtest", 0777, self::$files);
|
||||
self::$path = self::$vfs->url()."/";
|
||||
// set up a file without read access
|
||||
chmod(self::$path."ru.php", 0000);
|
||||
// make the Lang class use the vfs files
|
||||
self::$defaultPath = Lang::$path;
|
||||
Lang::$path = self::$path;
|
||||
}
|
||||
static function setUpBeforeClass() {
|
||||
// this is required to keep from having exceptions in Lang::msg() in turn calling Lang::msg() and looping
|
||||
\JKingWeb\NewsSync\Lang\Exception::$test = true;
|
||||
// test files
|
||||
self::$files = [
|
||||
'en.php' => '<?php return ["Test.presentText" => "and the Philosopher\'s Stone"];',
|
||||
'en_ca.php' => '<?php return ["Test.presentText" => "{0} and {1}"];',
|
||||
'en_us.php' => '<?php return ["Test.presentText" => "and the Sorcerer\'s Stone"];',
|
||||
'fr.php' => '<?php return ["Test.presentText" => "à l\'école des sorciers"];',
|
||||
'ja.php' => '<?php return ["Test.absentText" => "賢者の石"];',
|
||||
'de.php' => '<?php return ["Test.presentText" => "und der Stein der Weisen"];',
|
||||
'pt_br.php' => '<?php return ["Test.presentText" => "e a Pedra Filosofal"];',
|
||||
'vi.php' => '<?php return [];',
|
||||
// corrupt files
|
||||
'it.php' => '<?php return 0;',
|
||||
'zh.php' => '<?php return 0',
|
||||
'ko.php' => 'DEAD BEEF',
|
||||
'fr_ca.php' => '',
|
||||
// unreadable file
|
||||
'ru.php' => '',
|
||||
];
|
||||
self::$vfs = vfsStream::setup("langtest", 0777, self::$files);
|
||||
self::$path = self::$vfs->url()."/";
|
||||
// set up a file without read access
|
||||
chmod(self::$path."ru.php", 0000);
|
||||
// make the Lang class use the vfs files
|
||||
self::$defaultPath = Lang::$path;
|
||||
Lang::$path = self::$path;
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
\JKingWeb\NewsSync\Lang\Exception::$test = false;
|
||||
Lang::$path = self::$defaultPath;
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
self::$files = null;
|
||||
Lang::set("", true);
|
||||
Lang::set(Lang::DEFAULT);
|
||||
}
|
||||
static function tearDownAfterClass() {
|
||||
\JKingWeb\NewsSync\Lang\Exception::$test = false;
|
||||
Lang::$path = self::$defaultPath;
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
self::$files = null;
|
||||
Lang::set("", true);
|
||||
Lang::set(Lang::DEFAULT);
|
||||
}
|
||||
}
|
|
@ -4,15 +4,15 @@ namespace JKingWeb\NewsSync\Test;
|
|||
use \JKingWeb\NewsSync\Exception;
|
||||
|
||||
trait Tools {
|
||||
function assertException(string $msg, string $prefix = "", string $type = "Exception") {
|
||||
$class = \JKingWeb\NewsSync\NS_BASE . ($prefix !== "" ? str_replace("/", "\\", $prefix) . "\\" : "") . $type;
|
||||
$msgID = ($prefix !== "" ? $prefix . "/" : "") . $type. ".$msg";
|
||||
if(array_key_exists($msgID, Exception::CODES)) {
|
||||
$code = Exception::CODES[$msgID];
|
||||
} else {
|
||||
$code = 0;
|
||||
}
|
||||
$this->expectException($class);
|
||||
$this->expectExceptionCode($code);
|
||||
}
|
||||
function assertException(string $msg, string $prefix = "", string $type = "Exception") {
|
||||
$class = \JKingWeb\NewsSync\NS_BASE . ($prefix !== "" ? str_replace("/", "\\", $prefix) . "\\" : "") . $type;
|
||||
$msgID = ($prefix !== "" ? $prefix . "/" : "") . $type. ".$msg";
|
||||
if(array_key_exists($msgID, Exception::CODES)) {
|
||||
$code = Exception::CODES[$msgID];
|
||||
} else {
|
||||
$code = 0;
|
||||
}
|
||||
$this->expectException($class);
|
||||
$this->expectExceptionCode($code);
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
bootstrap="../bootstrap.php"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestSize="true"
|
||||
stopOnError="true">
|
||||
colors="true"
|
||||
bootstrap="../bootstrap.php"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestSize="true"
|
||||
stopOnError="true">
|
||||
|
||||
<testsuite name="Localization and exceptions">
|
||||
<file>TestLang.php</file>
|
||||
<file>TestLangComplex.php</file>
|
||||
<file>TestException.php</file>
|
||||
<file>TestLangErrors.php</file>
|
||||
<file>TestLang.php</file>
|
||||
<file>TestLangComplex.php</file>
|
||||
<file>TestException.php</file>
|
||||
<file>TestLangErrors.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Configuration loading and saving">
|
||||
<file>TestConf.php</file>
|
||||
<file>TestConf.php</file>
|
||||
</testsuite>
|
||||
</phpunit>
|
|
@ -5,82 +5,82 @@ use \org\bovigo\vfs\vfsStream;
|
|||
|
||||
|
||||
class TestLangComplex extends \PHPUnit\Framework\TestCase {
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
use Test\Tools, Test\Lang\Setup;
|
||||
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
|
||||
function setUp() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
}
|
||||
function setUp() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
}
|
||||
|
||||
function testLazyLoad() {
|
||||
Lang::set("ja");
|
||||
$this->assertArrayNotHasKey('Test.absentText', Lang::dump());
|
||||
}
|
||||
|
||||
function testLoadCascadeOfFiles() {
|
||||
Lang::set("ja", true);
|
||||
$this->assertEquals("de", Lang::set("de", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayNotHasKey('Test.absentText', $str);
|
||||
$this->assertEquals('und der Stein der Weisen', $str['Test.presentText']);
|
||||
}
|
||||
function testLazyLoad() {
|
||||
Lang::set("ja");
|
||||
$this->assertArrayNotHasKey('Test.absentText', Lang::dump());
|
||||
}
|
||||
|
||||
function testLoadCascadeOfFiles() {
|
||||
Lang::set("ja", true);
|
||||
$this->assertEquals("de", Lang::set("de", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayNotHasKey('Test.absentText', $str);
|
||||
$this->assertEquals('und der Stein der Weisen', $str['Test.presentText']);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testLoadCascadeOfFiles
|
||||
*/
|
||||
function testLoadSubtag() {
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca", true));
|
||||
}
|
||||
|
||||
function testFetchAMessage() {
|
||||
Lang::set("de", true);
|
||||
$this->assertEquals('und der Stein der Weisen', Lang::msg('Test.presentText'));
|
||||
}
|
||||
function testLoadSubtag() {
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca", true));
|
||||
}
|
||||
|
||||
function testFetchAMessage() {
|
||||
Lang::set("de", true);
|
||||
$this->assertEquals('und der Stein der Weisen', Lang::msg('Test.presentText'));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testFetchAMessage
|
||||
*/
|
||||
function testFetchAMessageWithSingleNumericParameter() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Default language file "en" missing', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing', Lang::DEFAULT));
|
||||
}
|
||||
function testFetchAMessageWithSingleNumericParameter() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Default language file "en" missing', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing', Lang::DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testFetchAMessage
|
||||
*/
|
||||
function testFetchAMessageWithMultipleNumericParameters() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Happy Rotter and the Philosopher\'s Stone', Lang::msg('Test.presentText', ['Happy Rotter', 'the Philosopher\'s Stone']));
|
||||
}
|
||||
function testFetchAMessageWithMultipleNumericParameters() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Happy Rotter and the Philosopher\'s Stone', Lang::msg('Test.presentText', ['Happy Rotter', 'the Philosopher\'s Stone']));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testFetchAMessage
|
||||
*/
|
||||
function testFetchAMessageWithNamedParameters() {
|
||||
$this->assertEquals('Message string "Test.absentText" missing from all loaded language files (en)', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing', ['msgID' => 'Test.absentText', 'fileList' => 'en']));
|
||||
}
|
||||
function testFetchAMessageWithNamedParameters() {
|
||||
$this->assertEquals('Message string "Test.absentText" missing from all loaded language files (en)', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing', ['msgID' => 'Test.absentText', 'fileList' => 'en']));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testFetchAMessage
|
||||
*/
|
||||
function testReloadDefaultStrings() {
|
||||
Lang::set("de", true);
|
||||
Lang::set("en", true);
|
||||
$this->assertEquals('and the Philosopher\'s Stone', Lang::msg('Test.presentText'));
|
||||
}
|
||||
function testReloadDefaultStrings() {
|
||||
Lang::set("de", true);
|
||||
Lang::set("en", true);
|
||||
$this->assertEquals('and the Philosopher\'s Stone', Lang::msg('Test.presentText'));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @depends testFetchAMessage
|
||||
*/
|
||||
function testReloadGeneralTagAfterSubtag() {
|
||||
Lang::set("en", true);
|
||||
Lang::set("en_us", true);
|
||||
$this->assertEquals('and the Sorcerer\'s Stone', Lang::msg('Test.presentText'));
|
||||
Lang::set("en", true);
|
||||
$this->assertEquals('and the Philosopher\'s Stone', Lang::msg('Test.presentText'));
|
||||
}
|
||||
function testReloadGeneralTagAfterSubtag() {
|
||||
Lang::set("en", true);
|
||||
Lang::set("en_us", true);
|
||||
$this->assertEquals('and the Sorcerer\'s Stone', Lang::msg('Test.presentText'));
|
||||
Lang::set("en", true);
|
||||
$this->assertEquals('and the Philosopher\'s Stone', Lang::msg('Test.presentText'));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue