mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Restrict options in not-context and hopefully make it easier to use
This commit is contained in:
parent
14c02d56ac
commit
b950ac066f
11 changed files with 170 additions and 112 deletions
101
lib/Context/Context.php
Normal file
101
lib/Context/Context.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
/** @license MIT
|
||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||
* See LICENSE and AUTHORS files for details */
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Context;
|
||||
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
|
||||
class Context extends ExclusionContext {
|
||||
public $not;
|
||||
public $reverse = false;
|
||||
public $limit = 0;
|
||||
public $offset = 0;
|
||||
public $unread;
|
||||
public $starred;
|
||||
public $labelled;
|
||||
public $annotated;
|
||||
public $oldestArticle;
|
||||
public $latestArticle;
|
||||
public $oldestEdition;
|
||||
public $latestEdition;
|
||||
public $modifiedSince;
|
||||
public $notModifiedSince;
|
||||
public $markedSince;
|
||||
public $notMarkedSince;
|
||||
|
||||
public function __construct() {
|
||||
$this->not = new ExclusionContext;
|
||||
}
|
||||
|
||||
public function __clone() {
|
||||
// clone the exclusion context as well
|
||||
$this->not = clone $this->not;
|
||||
}
|
||||
|
||||
public function reverse(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function limit(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function offset(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function unread(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function starred(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function labelled(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function annotated(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function latestArticle(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function oldestArticle(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function latestEdition(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function oldestEdition(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function modifiedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function notModifiedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function markedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function notMarkedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
}
|
|
@ -4,49 +4,27 @@
|
|||
* See LICENSE and AUTHORS files for details */
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Misc;
|
||||
namespace JKingWeb\Arsse\Context;
|
||||
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||
|
||||
class Context {
|
||||
public $not = null;
|
||||
public $reverse = false;
|
||||
public $limit = 0;
|
||||
public $offset = 0;
|
||||
class ExclusionContext {
|
||||
public $folder;
|
||||
public $folderShallow;
|
||||
public $subscription;
|
||||
public $oldestArticle;
|
||||
public $latestArticle;
|
||||
public $oldestEdition;
|
||||
public $latestEdition;
|
||||
public $unread = null;
|
||||
public $starred = null;
|
||||
public $modifiedSince;
|
||||
public $notModifiedSince;
|
||||
public $markedSince;
|
||||
public $notMarkedSince;
|
||||
public $edition;
|
||||
public $article;
|
||||
public $editions;
|
||||
public $articles;
|
||||
public $label;
|
||||
public $labelName;
|
||||
public $labelled = null;
|
||||
public $annotated = null;
|
||||
public $annotationTerms = null;
|
||||
public $searchTerms = null;
|
||||
public $titleTerms = null;
|
||||
public $authorTerms = null;
|
||||
public $annotationTerms;
|
||||
public $searchTerms;
|
||||
public $titleTerms;
|
||||
public $authorTerms;
|
||||
|
||||
protected $props = [];
|
||||
|
||||
public function __clone() {
|
||||
// clone the negation context, if any
|
||||
$this->not = $this->not ? clone $this->not : null;
|
||||
}
|
||||
|
||||
protected function act(string $prop, int $set, $value) {
|
||||
if ($set) {
|
||||
if (is_null($value)) {
|
||||
|
@ -87,18 +65,6 @@ class Context {
|
|||
return array_values($spec);
|
||||
}
|
||||
|
||||
public function reverse(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function limit(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function offset(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function folder(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
@ -111,50 +77,6 @@ class Context {
|
|||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function latestArticle(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function oldestArticle(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function latestEdition(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function oldestEdition(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function unread(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function starred(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function modifiedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function notModifiedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function markedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function notMarkedSince($spec = null) {
|
||||
$spec = Date::normalize($spec);
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function edition(int $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
@ -185,14 +107,6 @@ class Context {
|
|||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function labelled(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function annotated(bool $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function annotationTerms(array $spec = null) {
|
||||
if (isset($spec)) {
|
||||
$spec = $this->cleanStringArray($spec);
|
||||
|
@ -220,8 +134,4 @@ class Context {
|
|||
}
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
|
||||
public function not(self $spec = null) {
|
||||
return $this->act(__FUNCTION__, func_num_args(), $spec);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,8 @@ namespace JKingWeb\Arsse;
|
|||
use JKingWeb\DrUUID\UUID;
|
||||
use JKingWeb\Arsse\Db\Statement;
|
||||
use JKingWeb\Arsse\Misc\Query;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Context\ExclusionContext;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||
|
||||
|
@ -1178,8 +1179,9 @@ class Database {
|
|||
// if there are no output columns requested we're getting a count and should not group, but otherwise we should
|
||||
$q->setGroup("arsse_articles.id", "arsse_marks.note", "arsse_enclosures.url", "arsse_enclosures.type", "arsse_subscriptions.title", "arsse_feeds.title", "arsse_subscriptions.id", "arsse_marks.modified", "arsse_label_members.modified", "arsse_marks.read", "arsse_marks.starred", "latest_editions.edition");
|
||||
}
|
||||
$excContext = new ExclusionContext;
|
||||
// handle the simple context options
|
||||
foreach ([
|
||||
$options = [
|
||||
// each context array consists of a column identifier (see $colDefs above), a comparison operator, a data type, and an upper bound if the value is an array
|
||||
"edition" => ["edition", "=", "int", 1],
|
||||
"editions" => ["edition", "in", "int", self::LIMIT_ARTICLES],
|
||||
|
@ -1197,7 +1199,8 @@ class Database {
|
|||
"subscription" => ["subscription", "=", "int", 1],
|
||||
"unread" => ["unread", "=", "bool", 1],
|
||||
"starred" => ["starred", "=", "bool", 1],
|
||||
] as $m => list($col, $op, $type, $max)) {
|
||||
];
|
||||
foreach ($options as $m => list($col, $op, $type, $max)) {
|
||||
if (!$context->$m()) {
|
||||
// context is not being used
|
||||
continue;
|
||||
|
@ -1213,6 +1216,25 @@ class Database {
|
|||
$q->setWhere("{$colDefs[$col]} $op ?", $type, $context->$m);
|
||||
}
|
||||
}
|
||||
if ($context->not != $excContext) {
|
||||
// further handle exclusionary options if specified
|
||||
foreach ($options as $m => list($col, $op, $type, $max)) {
|
||||
if (!method_exists($context->not, $m) || !$context->not->$m()) {
|
||||
// context option is not being used
|
||||
continue;
|
||||
} elseif (is_array($context->not->$m)) {
|
||||
if (!$context->not->$m) {
|
||||
// for exclusions we don't care if the array is empty
|
||||
} elseif (sizeof($context->not->$m) > $max) {
|
||||
throw new Db\ExceptionInput("tooLong", ['field' => $m, 'action' => $this->caller(), 'max' => $max]); // @codeCoverageIgnore
|
||||
}
|
||||
list($clause, $types, $values) = $this->generateIn($context->$m, $type);
|
||||
$q->setWhereNot("{$colDefs[$col]} $op ($clause)", $types, $values);
|
||||
} else {
|
||||
$q->setWhereNot("{$colDefs[$col]} $op ?", $type, $context->$m);
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle complex context options
|
||||
if ($context->labelled()) {
|
||||
// any label (true) or no label (false)
|
||||
|
|
|
@ -20,6 +20,9 @@ class Query {
|
|||
protected $qWhere = []; // WHERE clause components
|
||||
protected $tWhere = []; // WHERE clause type bindings
|
||||
protected $vWhere = []; // WHERE clause binding values
|
||||
protected $qWhereNot = []; // WHERE NOT clause components
|
||||
protected $tWhereNot = []; // WHERE NOT clause type bindings
|
||||
protected $vWhereNot = []; // WHERE NOT clause binding values
|
||||
protected $group = []; // GROUP BY clause components
|
||||
protected $order = []; // ORDER BY clause components
|
||||
protected $limit = 0;
|
||||
|
@ -69,6 +72,15 @@ class Query {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function setWhereNot(string $where, $types = null, $values = null): bool {
|
||||
$this->qWhereNot[] = $where;
|
||||
if (!is_null($types)) {
|
||||
$this->tWhereNot[] = $types;
|
||||
$this->vWhereNot[] = $values;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setGroup(string ...$column): bool {
|
||||
foreach ($column as $col) {
|
||||
$this->group[] = $col;
|
||||
|
@ -94,7 +106,7 @@ class Query {
|
|||
public function pushCTE(string $tableSpec, string $join = ''): bool {
|
||||
// this function takes the query body and converts it to a common table expression, putting it at the bottom of the existing CTE stack
|
||||
// all WHERE, ORDER BY, and LIMIT parts belong to the new CTE and are removed from the main query
|
||||
$this->setCTE($tableSpec, $this->buildQueryBody(), [$this->tBody, $this->tWhere], [$this->vBody, $this->vWhere]);
|
||||
$this->setCTE($tableSpec, $this->buildQueryBody(), [$this->tBody, $this->tWhere, $this->tWhereNot], [$this->vBody, $this->vWhere, $this->vWhereNot]);
|
||||
$this->jCTE = [];
|
||||
$this->tBody = [];
|
||||
$this->vBody = [];
|
||||
|
@ -129,11 +141,11 @@ class Query {
|
|||
}
|
||||
|
||||
public function getTypes(): array {
|
||||
return [$this->tCTE, $this->tBody, $this->tJoin, $this->tWhere];
|
||||
return [$this->tCTE, $this->tBody, $this->tJoin, $this->tWhere, $this->tWhereNot];
|
||||
}
|
||||
|
||||
public function getValues(): array {
|
||||
return [$this->vCTE, $this->vBody, $this->vJoin, $this->vWhere];
|
||||
return [$this->vCTE, $this->vBody, $this->vJoin, $this->vWhere, $this->vWhereNot];
|
||||
}
|
||||
|
||||
public function getJoinTypes(): array {
|
||||
|
@ -173,8 +185,12 @@ class Query {
|
|||
$out .= " ".implode(" ", $this->qJoin);
|
||||
}
|
||||
// add any WHERE terms
|
||||
if (sizeof($this->qWhere)) {
|
||||
$out .= " WHERE ".implode(" AND ", $this->qWhere);
|
||||
if (sizeof($this->qWhere) || sizeof($this->qWhereNot)) {
|
||||
$where = implode(" AND ", $this->qWhere);
|
||||
$whereNot = implode(" OR ", $this->qWhereNot);
|
||||
$whereNot = strlen($whereNot) ? "NOT ($whereNot)" : "";
|
||||
$where = implode(" AND ", array_filter([$where, $whereNot]));
|
||||
$out .= " WHERE $where";
|
||||
}
|
||||
// add any GROUP BY terms
|
||||
if (sizeof($this->group)) {
|
||||
|
|
|
@ -10,7 +10,7 @@ use JKingWeb\Arsse\Arsse;
|
|||
use JKingWeb\Arsse\Database;
|
||||
use JKingWeb\Arsse\User;
|
||||
use JKingWeb\Arsse\Service;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||
use JKingWeb\Arsse\AbstractException;
|
||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
|
|
|
@ -12,7 +12,7 @@ use JKingWeb\Arsse\Database;
|
|||
use JKingWeb\Arsse\User;
|
||||
use JKingWeb\Arsse\Service;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||
use JKingWeb\Arsse\AbstractException;
|
||||
use JKingWeb\Arsse\ExceptionType;
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace JKingWeb\Arsse\TestCase\Database;
|
|||
|
||||
use JKingWeb\Arsse\Database;
|
||||
use JKingWeb\Arsse\Arsse;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use Phake;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\Arsse\TestCase\Database;
|
||||
|
||||
use JKingWeb\Arsse\Arsse;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use Phake;
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\TestCase\Misc;
|
||||
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Misc\ValueInfo;
|
||||
|
||||
/** @covers \JKingWeb\Arsse\Misc\Context */
|
||||
/** @covers \JKingWeb\Arsse\Context\Context<extended> */
|
||||
class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||
public function testVerifyInitialState() {
|
||||
$c = new Context;
|
||||
|
@ -96,4 +96,13 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
$this->assertSame($out, $c->$method($in)->$method, "Context method $method did not return the expected results");
|
||||
}
|
||||
}
|
||||
|
||||
public function testCloneAContext() {
|
||||
$c1 = new Context;
|
||||
$c2 = clone $c1;
|
||||
$this->assertEquals($c1, $c2);
|
||||
$this->assertEquals($c1->not, $c2->not);
|
||||
$this->assertNotSame($c1, $c2);
|
||||
$this->assertNotSame($c1->not, $c2->not);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use JKingWeb\Arsse\Database;
|
|||
use JKingWeb\Arsse\Service;
|
||||
use JKingWeb\Arsse\Test\Result;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
use JKingWeb\Arsse\Db\Transaction;
|
||||
use JKingWeb\Arsse\REST\NextCloudNews\V1_2;
|
||||
|
|
|
@ -14,7 +14,7 @@ use JKingWeb\Arsse\Service;
|
|||
use JKingWeb\Arsse\REST\Request;
|
||||
use JKingWeb\Arsse\Test\Result;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Context\Context;
|
||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
use JKingWeb\Arsse\Db\Transaction;
|
||||
use JKingWeb\Arsse\REST\TinyTinyRSS\API;
|
||||
|
|
Loading…
Reference in a new issue