Browse Source

Add text search exclusions

microsub
J. King 5 years ago
parent
commit
677e33e518
  1. 33
      lib/Database.php
  2. 1
      tests/cases/Database/SeriesArticle.php

33
lib/Database.php

@ -159,8 +159,9 @@ class Database {
*
* @param string[] $terms The terms to search for
* @param string[] $cols The columns to match against; these are -not- sanitized, so much -not- come directly from user input
* @param boolean $matchAny Whether the search is successful when it matches any (true) or all (false) terms
*/
protected function generateSearch(array $terms, array $cols): array {
protected function generateSearch(array $terms, array $cols, bool $matchAny = false): array {
$clause = [];
$types = [];
$values = [];
@ -176,7 +177,8 @@ class Database {
}
$clause[] = "(".implode(" or ", $spec).")";
}
$clause = "(".implode(" and ", $clause).")";
$glue = $matchAny ? "or" : "and";
$clause = "(".implode(" $glue ", $clause).")";
return [$clause, $types, $values];
}
@ -382,7 +384,7 @@ class Database {
*
* @param string $uer The user whose folders are to be listed
* @param integer|null $parent Restricts the list to the descendents of the specified folder identifier
* @param boolean $recursive Whether to list all descendents, or only direct children
* @param boolean $recursive Whether to list all descendents (true) or only direct children (false)
*/
public function folderList(string $user, $parent = null, bool $recursive = true): Db\Result {
// if the user isn't authorized to perform this action then throw an exception.
@ -500,7 +502,7 @@ class Database {
*
* @param string $user The user who owns the folder to be validated
* @param integer|null $id The identifier of the folder to validate; null or zero represent the implied root folder
* @param boolean $subject Whether the folder is the subject rather than the object of the operation being performed; this only affects the semantics of the error message if validation fails
* @param boolean $subject Whether the folder is the subject (true) rather than the object (false) of the operation being performed; this only affects the semantics of the error message if validation fails
*/
protected function folderValidateId(string $user, $id = null, bool $subject = false): array {
// if the specified ID is not a non-negative integer (or null), this will always fail
@ -839,7 +841,7 @@ class Database {
*
* @param string $user The user who owns the subscription to be validated
* @param integer|null $id The identifier of the subscription to validate
* @param boolean $subject Whether the subscription is the subject rather than the object of the operation being performed; this only affects the semantics of the error message if validation fails
* @param boolean $subject Whether the subscription is the subject (true) rather than the object (false) of the operation being performed; this only affects the semantics of the error message if validation fails
*/
protected function subscriptionValidateId(string $user, $id, bool $subject = false): array {
if (!ValueInfo::id($id)) {
@ -1199,7 +1201,6 @@ class Database {
"unread" => ["unread", "=", "bool", "", 1],
"starred" => ["starred", "=", "bool", "", 1],
];
$optionsSeen = [];
foreach ($options as $m => list($col, $op, $type, $pair, $max)) {
if (!$context->$m()) {
@ -1273,12 +1274,13 @@ class Database {
$q->setWhere("arsse_subscriptions.folder in (select folder from folders)");
}
// handle text-matching context options
foreach ([
$options = [
"titleTerms" => [10, ["arsse_articles.title"]],
"searchTerms" => [20, ["arsse_articles.title", "arsse_articles.content"]],
"authorTerms" => [10, ["arsse_articles.author"]],
"annotationTerms" => [20, ["arsse_marks.note"]],
] as $m => list($max, $cols)) {
];
foreach ($options as $m => list($max, $cols)) {
if (!$context->$m()) {
continue;
} elseif (!$context->$m) {
@ -1288,6 +1290,17 @@ class Database {
}
$q->setWhere(...$this->generateSearch($context->$m, $cols));
}
// further handle exclusionary text-matching context options
foreach ($options as $m => list($max, $cols)) {
if (!$context->not->$m()) {
continue;
} elseif (!$context->not->$m) {
continue;
} elseif (sizeof($context->not->$m) > $max) {
throw new Db\ExceptionInput("tooLong", ['field' => "$m (not)", 'action' => $this->caller(), 'max' => $max]);
}
$q->setWhereNot(...$this->generateSearch($context->not->$m, $cols, true));
}
// return the query
return $q;
}
@ -1503,7 +1516,7 @@ class Database {
*
* @param string $user The user whose labels are to be listed
* @param integer $id The numeric identifier of the article whose labels are to be listed
* @param boolean $byName Whether to return the label names instead of the numeric label identifiers
* @param boolean $byName Whether to return the label names (true) instead of the numeric label identifiers (false)
*/
public function articleLabelsGet(string $user, $id, bool $byName = false): array {
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
@ -1903,7 +1916,7 @@ class Database {
* @param integer|string $id The numeric identifier or name of the label to validate
* @param boolean $byName Whether to interpret the $id parameter as the label's name (true) or identifier (false)
* @param boolean $checkDb Whether to check whether the label exists (true) or only if the identifier or name is syntactically valid (false)
* @param boolean $subject Whether the label is the subject rather than the object of the operation being performed; this only affects the semantics of the error message if validation fails
* @param boolean $subject Whether the label is the subject (true) rather than the object (false) of the operation being performed; this only affects the semantics of the error message if validation fails
*/
protected function labelValidateId(string $user, $id, bool $byName, bool $checkDb = true, bool $subject = false): array {
if (!$byName && !ValueInfo::id($id)) {

1
tests/cases/Database/SeriesArticle.php

@ -452,6 +452,7 @@ trait SeriesArticle {
"Folder tree 1 excluding articles 7 and 8" => [(new Context)->folder(1)->not->articles([7,8]), [5,6]],
"Folder tree 1 excluding no articles" => [(new Context)->folder(1)->not->articles([]), [5,6,7,8]],
"Marked or labelled between 2000 and 2015 excluding in 2010" => [(new Context)->markedSince("2000-01-01T00:00:00Z")->notMarkedSince("2015-12-31T23:59:59")->not->markedSince("2010-01-01T00:00:00Z")->not->notMarkedSince("2010-12-31T23:59:59Z"), [1,3,5,7,8]],
"Search with exclusion" => [(new Context)->searchTerms(["Article"])->not->searchTerms(["one", "two"]), [3]],
];
}

Loading…
Cancel
Save