Merge branch 'main'
This commit is contained in:
commit
2dc79e9569
50 changed files with 1455 additions and 1107 deletions
43
.devcontainer/devcontainer.json
Normal file
43
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
|
||||||
|
{
|
||||||
|
"name": "Shimmie Dockerfile",
|
||||||
|
"build": {
|
||||||
|
"context": "..",
|
||||||
|
"dockerfile": "../Dockerfile",
|
||||||
|
"target": "devcontainer"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Uncomment the next line to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "apt update && apt install -y composer php8.2-xdebug git",
|
||||||
|
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"recca0120.vscode-phpunit",
|
||||||
|
"ryanluker.vscode-coverage-gutters",
|
||||||
|
"xdebug.php-debug",
|
||||||
|
"DEVSENSE.phptools-vscode",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"phpunit.args": [
|
||||||
|
"--configuration", "${workspaceFolder}/tests/phpunit.xml",
|
||||||
|
"--coverage-clover", "data/coverage.clover"
|
||||||
|
],
|
||||||
|
"coverage-gutters.coverageFileNames": [
|
||||||
|
"data/coverage.clover"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "devcontainer"
|
||||||
|
}
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -57,7 +57,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: ['8.1', '8.2']
|
php: ['8.1', '8.2', '8.3']
|
||||||
database: ['pgsql', 'mysql', 'sqlite']
|
database: ['pgsql', 'mysql', 'sqlite']
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,7 +5,6 @@ thumbs
|
||||||
*.phar
|
*.phar
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.cache
|
*.cache
|
||||||
.devcontainer
|
|
||||||
trace.json
|
trace.json
|
||||||
|
|
||||||
#Composer
|
#Composer
|
||||||
|
|
|
@ -11,6 +11,7 @@ RUN apt update && apt upgrade -y && apt install -y \
|
||||||
# Composer has 100MB of dependencies, and we only need that during build and test
|
# Composer has 100MB of dependencies, and we only need that during build and test
|
||||||
FROM base AS composer
|
FROM base AS composer
|
||||||
RUN apt update && apt upgrade -y && apt install -y composer php${PHP_VERSION}-xdebug && rm -rf /var/lib/apt/lists/*
|
RUN apt update && apt upgrade -y && apt install -y composer php${PHP_VERSION}-xdebug && rm -rf /var/lib/apt/lists/*
|
||||||
|
ENV XDEBUG_MODE=coverage
|
||||||
|
|
||||||
# "Build" shimmie (composer install - done in its own stage so that we don't
|
# "Build" shimmie (composer install - done in its own stage so that we don't
|
||||||
# need to include all the composer fluff in the final image)
|
# need to include all the composer fluff in the final image)
|
||||||
|
@ -36,6 +37,10 @@ RUN [ $RUN_TESTS = false ] || (\
|
||||||
echo '=== Coverage ===' && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
|
echo '=== Coverage ===' && XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
|
||||||
echo '=== Cleaning ===' && rm -rf data)
|
echo '=== Cleaning ===' && rm -rf data)
|
||||||
|
|
||||||
|
# Devcontainer target
|
||||||
|
FROM composer AS devcontainer
|
||||||
|
RUN apt update && apt upgrade -y && apt install -y git && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Actually run shimmie
|
# Actually run shimmie
|
||||||
FROM base
|
FROM base
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
|
@ -60,10 +60,10 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"require-dev" : {
|
"require-dev" : {
|
||||||
"phpunit/phpunit" : "^9.0",
|
"phpunit/phpunit" : "10.5.3",
|
||||||
"friendsofphp/php-cs-fixer" : "^3.12",
|
"friendsofphp/php-cs-fixer" : "3.41.1",
|
||||||
"scrutinizer/ocular": "dev-master",
|
"scrutinizer/ocular": "1.9",
|
||||||
"phpstan/phpstan": "1.10.x-dev"
|
"phpstan/phpstan": "1.10.50"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-memcache": "memcache caching",
|
"ext-memcache": "memcache caching",
|
||||||
|
|
801
composer.lock
generated
801
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -278,27 +278,27 @@ class DatabaseConfig extends BaseConfig
|
||||||
$this->table_name = $table_name;
|
$this->table_name = $table_name;
|
||||||
$this->sub_value = $sub_value;
|
$this->sub_value = $sub_value;
|
||||||
$this->sub_column = $sub_column;
|
$this->sub_column = $sub_column;
|
||||||
$this->cache_name = empty($sub_value) ? "config" : "config_{$sub_value}";
|
$this->cache_name = empty($sub_value) ? "config" : "config_{$sub_column}_{$sub_value}";
|
||||||
|
$this->values = cache_get_or_set($this->cache_name, fn () => $this->get_values());
|
||||||
|
}
|
||||||
|
|
||||||
$cached = $cache->get($this->cache_name);
|
private function get_values(): mixed
|
||||||
if (!is_null($cached)) {
|
{
|
||||||
$this->values = $cached;
|
$values = [];
|
||||||
} else {
|
|
||||||
$this->values = [];
|
|
||||||
|
|
||||||
$query = "SELECT name, value FROM {$this->table_name}";
|
$query = "SELECT name, value FROM {$this->table_name}";
|
||||||
$args = [];
|
$args = [];
|
||||||
|
|
||||||
if (!empty($sub_column) && !empty($sub_value)) {
|
if (!empty($this->sub_column) && !empty($this->sub_value)) {
|
||||||
$query .= " WHERE $sub_column = :sub_value";
|
$query .= " WHERE {$this->sub_column} = :sub_value";
|
||||||
$args["sub_value"] = $sub_value;
|
$args["sub_value"] = $this->sub_value;
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->database->get_all($query, $args) as $row) {
|
|
||||||
$this->values[$row["name"]] = $row["value"];
|
|
||||||
}
|
|
||||||
$cache->set($this->cache_name, $this->values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->database->get_all($query, $args) as $row) {
|
||||||
|
$values[$row["name"]] = $row["value"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(string $name = null): void
|
public function save(string $name = null): void
|
||||||
|
|
|
@ -110,6 +110,10 @@ abstract class Extension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExtensionNotFound extends SCoreException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
enum ExtensionVisibility
|
enum ExtensionVisibility
|
||||||
{
|
{
|
||||||
case DEFAULT;
|
case DEFAULT;
|
||||||
|
@ -235,7 +239,7 @@ abstract class ExtensionInfo
|
||||||
return self::$all_info_by_class[$normal];
|
return self::$all_info_by_class[$normal];
|
||||||
} else {
|
} else {
|
||||||
$infos = print_r(array_keys(self::$all_info_by_class), true);
|
$infos = print_r(array_keys(self::$all_info_by_class), true);
|
||||||
throw new SCoreException("$normal not found in {$infos}");
|
throw new ExtensionNotFound("$normal not found in {$infos}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ class Image
|
||||||
|
|
||||||
public static function by_random(array $tags = [], int $limit_range = 0): ?Image
|
public static function by_random(array $tags = [], int $limit_range = 0): ?Image
|
||||||
{
|
{
|
||||||
$max = Image::count_images($tags);
|
$max = Search::count_images($tags);
|
||||||
if ($max < 1) {
|
if ($max < 1) {
|
||||||
return null;
|
return null;
|
||||||
} // From Issue #22 - opened by HungryFeline on May 30, 2011.
|
} // From Issue #22 - opened by HungryFeline on May 30, 2011.
|
||||||
|
@ -134,7 +134,7 @@ class Image
|
||||||
$max = $limit_range;
|
$max = $limit_range;
|
||||||
}
|
}
|
||||||
$rand = mt_rand(0, $max - 1);
|
$rand = mt_rand(0, $max - 1);
|
||||||
$set = Image::find_images($rand, 1, $tags);
|
$set = Search::find_images($rand, 1, $tags);
|
||||||
if (count($set) > 0) {
|
if (count($set) > 0) {
|
||||||
return $set[0];
|
return $set[0];
|
||||||
} else {
|
} else {
|
||||||
|
@ -142,136 +142,6 @@ class Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags = []): iterable
|
|
||||||
{
|
|
||||||
global $database, $user;
|
|
||||||
|
|
||||||
if ($start < 0) {
|
|
||||||
$start = 0;
|
|
||||||
}
|
|
||||||
if ($limit !== null && $limit < 1) {
|
|
||||||
$limit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SPEED_HAX) {
|
|
||||||
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
|
||||||
throw new PermissionDeniedException("Anonymous users may only search for up to 3 tags at a time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$querylet = Image::build_search_querylet($tags, $limit, $start);
|
|
||||||
return $database->get_all_iterable($querylet->sql, $querylet->variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for an array of images
|
|
||||||
*
|
|
||||||
* @param string[] $tags
|
|
||||||
* @return Image[]
|
|
||||||
*/
|
|
||||||
#[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])]
|
|
||||||
public static function find_images(int $offset = 0, ?int $limit = null, array $tags = []): array
|
|
||||||
{
|
|
||||||
$result = self::find_images_internal($offset, $limit, $tags);
|
|
||||||
|
|
||||||
$images = [];
|
|
||||||
foreach ($result as $row) {
|
|
||||||
$images[] = new Image($row);
|
|
||||||
}
|
|
||||||
return $images;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for an array of images, returning a iterable object of Image
|
|
||||||
*/
|
|
||||||
public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags = []): \Generator
|
|
||||||
{
|
|
||||||
$result = self::find_images_internal($start, $limit, $tags);
|
|
||||||
foreach ($result as $row) {
|
|
||||||
yield new Image($row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Image-related utility functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static function count_total_images(): int
|
|
||||||
{
|
|
||||||
global $cache, $database;
|
|
||||||
$total = $cache->get("image-count");
|
|
||||||
if (is_null($total)) {
|
|
||||||
$total = (int)$database->get_one("SELECT COUNT(*) FROM images");
|
|
||||||
$cache->set("image-count", $total, 600);
|
|
||||||
}
|
|
||||||
return $total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function count_tag(string $tag): int
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
return (int)$database->get_one(
|
|
||||||
"SELECT count FROM tags WHERE LOWER(tag) = LOWER(:tag)",
|
|
||||||
["tag" => $tag]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of image results for a given search
|
|
||||||
*
|
|
||||||
* @param string[] $tags
|
|
||||||
*/
|
|
||||||
public static function count_images(array $tags = []): int
|
|
||||||
{
|
|
||||||
global $cache, $database;
|
|
||||||
$tag_count = count($tags);
|
|
||||||
|
|
||||||
if (SPEED_HAX && $tag_count === 0) {
|
|
||||||
// total number of images in the DB
|
|
||||||
$total = self::count_total_images();
|
|
||||||
} elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) {
|
|
||||||
if (!str_starts_with($tags[0], "-")) {
|
|
||||||
// one tag - we can look that up directly
|
|
||||||
$total = self::count_tag($tags[0]);
|
|
||||||
} else {
|
|
||||||
// one negative tag - subtract from the total
|
|
||||||
$total = self::count_total_images() - self::count_tag(substr($tags[0], 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// complex query
|
|
||||||
// implode(tags) can be too long for memcache...
|
|
||||||
$cache_key = "image-count:" . md5(Tag::implode($tags));
|
|
||||||
$total = $cache->get($cache_key);
|
|
||||||
if (is_null($total)) {
|
|
||||||
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
|
||||||
$tags[] = "rating:*";
|
|
||||||
}
|
|
||||||
$querylet = Image::build_search_querylet($tags);
|
|
||||||
$total = (int)$database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables);
|
|
||||||
if (SPEED_HAX && $total > 5000) {
|
|
||||||
// when we have a ton of images, the count
|
|
||||||
// won't change dramatically very often
|
|
||||||
$cache->set($cache_key, $total, 3600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_null($total)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return $total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of pages for a given search
|
|
||||||
*
|
|
||||||
* @param string[] $tags
|
|
||||||
*/
|
|
||||||
public static function count_pages(array $tags = []): int
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
return (int)ceil(Image::count_images($tags) / $config->get_int(IndexConfig::IMAGES));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Accessors & mutators
|
* Accessors & mutators
|
||||||
*/
|
*/
|
||||||
|
@ -304,15 +174,13 @@ class Image
|
||||||
ORDER BY images.id '.$dir.'
|
ORDER BY images.id '.$dir.'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
');
|
');
|
||||||
|
return ($row ? new Image($row) : null);
|
||||||
} else {
|
} else {
|
||||||
$tags[] = 'id'. $gtlt . $this->id;
|
$tags[] = 'id'. $gtlt . $this->id;
|
||||||
$tags[] = 'order:id_'. strtolower($dir);
|
$tags[] = 'order:id_'. strtolower($dir);
|
||||||
$querylet = Image::build_search_querylet($tags);
|
$images = Search::find_images(0, 1, $tags);
|
||||||
$querylet->append_sql(' LIMIT 1');
|
return (count($images) > 0) ? $images[0] : null;
|
||||||
$row = $database->get_row($querylet->sql, $querylet->variables);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($row ? new Image($row) : null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -737,223 +605,4 @@ class Image
|
||||||
$tmpl = $plte->link;
|
$tmpl = $plte->link;
|
||||||
return load_balance_url($tmpl, $this->hash, $n);
|
return load_balance_url($tmpl, $this->hash, $n);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function tag_or_wildcard_to_ids(string $tag): array
|
|
||||||
{
|
|
||||||
global $database;
|
|
||||||
$sq = "SELECT id FROM tags WHERE LOWER(tag) LIKE LOWER(:tag)";
|
|
||||||
if ($database->get_driver_id() === DatabaseDriverID::SQLITE) {
|
|
||||||
$sq .= "ESCAPE '\\'";
|
|
||||||
}
|
|
||||||
return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $terms
|
|
||||||
*/
|
|
||||||
private static function build_search_querylet(
|
|
||||||
array $terms,
|
|
||||||
?int $limit = null,
|
|
||||||
?int $offset = null
|
|
||||||
): Querylet {
|
|
||||||
global $config;
|
|
||||||
|
|
||||||
$tag_conditions = [];
|
|
||||||
$img_conditions = [];
|
|
||||||
$order = null;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turn a bunch of strings into a bunch of TagCondition
|
|
||||||
* and ImgCondition objects
|
|
||||||
*/
|
|
||||||
$stpen = 0; // search term parse event number
|
|
||||||
foreach (array_merge([null], $terms) as $term) {
|
|
||||||
$stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms));
|
|
||||||
$order ??= $stpe->order;
|
|
||||||
$img_conditions = array_merge($img_conditions, $stpe->img_conditions);
|
|
||||||
$tag_conditions = array_merge($tag_conditions, $stpe->tag_conditions);
|
|
||||||
}
|
|
||||||
|
|
||||||
$order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turn a bunch of Querylet objects into a base query
|
|
||||||
*
|
|
||||||
* Must follow the format
|
|
||||||
*
|
|
||||||
* SELECT images.*
|
|
||||||
* FROM (...) AS images
|
|
||||||
* WHERE (...)
|
|
||||||
*
|
|
||||||
* ie, return a set of images.* columns, and end with a WHERE
|
|
||||||
*/
|
|
||||||
|
|
||||||
// no tags, do a simple search
|
|
||||||
if (count($tag_conditions) === 0) {
|
|
||||||
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
|
||||||
}
|
|
||||||
|
|
||||||
// one tag sorted by ID - we can fetch this from the image_tags table,
|
|
||||||
// and do the offset / limit there, which is 10x faster than fetching
|
|
||||||
// all the image_tags and doing the offset / limit on the result.
|
|
||||||
elseif (
|
|
||||||
count($tag_conditions) === 1
|
|
||||||
&& empty($img_conditions)
|
|
||||||
&& ($order == "id DESC" || $order == "images.id DESC")
|
|
||||||
&& !is_null($offset)
|
|
||||||
&& !is_null($limit)
|
|
||||||
) {
|
|
||||||
$tc = $tag_conditions[0];
|
|
||||||
$in = $tc->positive ? "IN" : "NOT IN";
|
|
||||||
// IN (SELECT id FROM tags) is 100x slower than doing a separate
|
|
||||||
// query and then a second query for IN(first_query_results)??
|
|
||||||
$tag_array = self::tag_or_wildcard_to_ids($tc->tag);
|
|
||||||
if (count($tag_array) == 0) {
|
|
||||||
// if wildcard expanded to nothing, take a shortcut
|
|
||||||
if ($tc->positive) {
|
|
||||||
$query = new Querylet("SELECT images.* FROM images WHERE 1=0");
|
|
||||||
} else {
|
|
||||||
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$set = implode(', ', $tag_array);
|
|
||||||
$query = new Querylet("
|
|
||||||
SELECT images.*
|
|
||||||
FROM images INNER JOIN (
|
|
||||||
SELECT it.image_id
|
|
||||||
FROM image_tags it
|
|
||||||
WHERE it.tag_id $in ($set)
|
|
||||||
ORDER BY it.image_id DESC
|
|
||||||
LIMIT :limit OFFSET :offset
|
|
||||||
) a on a.image_id = images.id
|
|
||||||
ORDER BY images.id DESC
|
|
||||||
", ["limit" => $limit, "offset" => $offset]);
|
|
||||||
// don't offset at the image level because
|
|
||||||
// we already offset at the image_tags level
|
|
||||||
$order = null;
|
|
||||||
$limit = null;
|
|
||||||
$offset = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// more than one tag, or more than zero other conditions, or a non-default sort order
|
|
||||||
else {
|
|
||||||
$positive_tag_id_array = [];
|
|
||||||
$positive_wildcard_id_array = [];
|
|
||||||
$negative_tag_id_array = [];
|
|
||||||
$all_nonexistent_negatives = true;
|
|
||||||
|
|
||||||
foreach ($tag_conditions as $tq) {
|
|
||||||
$tag_ids = self::tag_or_wildcard_to_ids($tq->tag);
|
|
||||||
$tag_count = count($tag_ids);
|
|
||||||
|
|
||||||
if ($tq->positive) {
|
|
||||||
$all_nonexistent_negatives = false;
|
|
||||||
if ($tag_count == 0) {
|
|
||||||
# one of the positive tags had zero results, therefor there
|
|
||||||
# can be no results; "where 1=0" should shortcut things
|
|
||||||
return new Querylet("SELECT images.* FROM images WHERE 1=0");
|
|
||||||
} elseif ($tag_count == 1) {
|
|
||||||
// All wildcard terms that qualify for a single tag can be treated the same as non-wildcards
|
|
||||||
$positive_tag_id_array[] = $tag_ids[0];
|
|
||||||
} else {
|
|
||||||
// Terms that resolve to multiple tags act as an OR within themselves
|
|
||||||
// and as an AND in relation to all other terms,
|
|
||||||
$positive_wildcard_id_array[] = $tag_ids;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($tag_count > 0) {
|
|
||||||
$all_nonexistent_negatives = false;
|
|
||||||
// Unlike positive criteria, negative criteria are all handled in an OR fashion,
|
|
||||||
// so we can just compile them all into a single sub-query.
|
|
||||||
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array || $all_nonexistent_negatives, @$_GET['q']);
|
|
||||||
|
|
||||||
if ($all_nonexistent_negatives) {
|
|
||||||
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
|
||||||
} elseif (!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) {
|
|
||||||
$inner_joins = [];
|
|
||||||
if (!empty($positive_tag_id_array)) {
|
|
||||||
foreach ($positive_tag_id_array as $tag) {
|
|
||||||
$inner_joins[] = "= $tag";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!empty($positive_wildcard_id_array)) {
|
|
||||||
foreach ($positive_wildcard_id_array as $tags) {
|
|
||||||
$positive_tag_id_list = join(', ', $tags);
|
|
||||||
$inner_joins[] = "IN ($positive_tag_id_list)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$first = array_shift($inner_joins);
|
|
||||||
$sub_query = "SELECT it.image_id FROM image_tags it ";
|
|
||||||
$i = 0;
|
|
||||||
foreach ($inner_joins as $inner_join) {
|
|
||||||
$i++;
|
|
||||||
$sub_query .= " INNER JOIN image_tags it$i ON it$i.image_id = it.image_id AND it$i.tag_id $inner_join ";
|
|
||||||
}
|
|
||||||
if (!empty($negative_tag_id_array)) {
|
|
||||||
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
|
||||||
$sub_query .= " LEFT JOIN image_tags negative ON negative.image_id = it.image_id AND negative.tag_id IN ($negative_tag_id_list) ";
|
|
||||||
}
|
|
||||||
$sub_query .= "WHERE it.tag_id $first ";
|
|
||||||
if (!empty($negative_tag_id_array)) {
|
|
||||||
$sub_query .= " AND negative.image_id IS NULL";
|
|
||||||
}
|
|
||||||
$sub_query .= " GROUP BY it.image_id ";
|
|
||||||
|
|
||||||
$query = new Querylet("
|
|
||||||
SELECT images.*
|
|
||||||
FROM images
|
|
||||||
INNER JOIN ($sub_query) a on a.image_id = images.id
|
|
||||||
");
|
|
||||||
} elseif (!empty($negative_tag_id_array)) {
|
|
||||||
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
|
||||||
$query = new Querylet("
|
|
||||||
SELECT images.*
|
|
||||||
FROM images
|
|
||||||
LEFT JOIN image_tags negative ON negative.image_id = images.id AND negative.tag_id in ($negative_tag_id_list)
|
|
||||||
WHERE negative.image_id IS NULL
|
|
||||||
");
|
|
||||||
} else {
|
|
||||||
throw new SCoreException("No criteria specified");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Merge all the image metadata searches into one generic querylet
|
|
||||||
* and append to the base querylet with "AND blah"
|
|
||||||
*/
|
|
||||||
if (!empty($img_conditions)) {
|
|
||||||
$n = 0;
|
|
||||||
$img_sql = "";
|
|
||||||
$img_vars = [];
|
|
||||||
foreach ($img_conditions as $iq) {
|
|
||||||
if ($n++ > 0) {
|
|
||||||
$img_sql .= " AND";
|
|
||||||
}
|
|
||||||
if (!$iq->positive) {
|
|
||||||
$img_sql .= " NOT";
|
|
||||||
}
|
|
||||||
$img_sql .= " (" . $iq->qlet->sql . ")";
|
|
||||||
$img_vars = array_merge($img_vars, $iq->qlet->variables);
|
|
||||||
}
|
|
||||||
$query->append_sql(" AND ");
|
|
||||||
$query->append(new Querylet($img_sql, $img_vars));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_null($order)) {
|
|
||||||
$query->append(new Querylet(" ORDER BY ".$order));
|
|
||||||
}
|
|
||||||
if (!is_null($limit)) {
|
|
||||||
$query->append(new Querylet(" LIMIT :limit ", ["limit" => $limit]));
|
|
||||||
$query->append(new Querylet(" OFFSET :offset ", ["offset" => $offset]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shimmie2;
|
namespace Shimmie2;
|
||||||
|
|
||||||
|
use GQLA\Query;
|
||||||
|
|
||||||
class Querylet
|
class Querylet
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -33,7 +35,7 @@ class TagCondition
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $tag,
|
public string $tag,
|
||||||
public bool $positive,
|
public bool $positive = true,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +44,365 @@ class ImgCondition
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Querylet $qlet,
|
public Querylet $qlet,
|
||||||
public bool $positive,
|
public bool $positive = true,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Search
|
||||||
|
{
|
||||||
|
public static array $_search_path = [];
|
||||||
|
|
||||||
|
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags = []): iterable
|
||||||
|
{
|
||||||
|
global $database, $user;
|
||||||
|
|
||||||
|
if ($start < 0) {
|
||||||
|
$start = 0;
|
||||||
|
}
|
||||||
|
if ($limit !== null && $limit < 1) {
|
||||||
|
$limit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SPEED_HAX) {
|
||||||
|
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
||||||
|
throw new PermissionDeniedException("Anonymous users may only search for up to 3 tags at a time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags);
|
||||||
|
$querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order, $limit, $start);
|
||||||
|
return $database->get_all_iterable($querylet->sql, $querylet->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for an array of images
|
||||||
|
*
|
||||||
|
* @param string[] $tags
|
||||||
|
* @return Image[]
|
||||||
|
*/
|
||||||
|
#[Query(name: "posts", type: "[Post!]!", args: ["tags" => "[string!]"])]
|
||||||
|
public static function find_images(int $offset = 0, ?int $limit = null, array $tags = []): array
|
||||||
|
{
|
||||||
|
$result = self::find_images_internal($offset, $limit, $tags);
|
||||||
|
|
||||||
|
$images = [];
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$images[] = new Image($row);
|
||||||
|
}
|
||||||
|
return $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for an array of images, returning a iterable object of Image
|
||||||
|
*/
|
||||||
|
public static function find_images_iterable(int $start = 0, ?int $limit = null, array $tags = []): \Generator
|
||||||
|
{
|
||||||
|
$result = self::find_images_internal($start, $limit, $tags);
|
||||||
|
foreach ($result as $row) {
|
||||||
|
yield new Image($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Image-related utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static function count_tag(string $tag): int
|
||||||
|
{
|
||||||
|
global $database;
|
||||||
|
return (int)$database->get_one(
|
||||||
|
"SELECT count FROM tags WHERE LOWER(tag) = LOWER(:tag)",
|
||||||
|
["tag" => $tag]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function count_total_images(): int
|
||||||
|
{
|
||||||
|
global $database;
|
||||||
|
return cache_get_or_set("image-count", fn () => (int)$database->get_one("SELECT COUNT(*) FROM images"), 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of image results for a given search
|
||||||
|
*
|
||||||
|
* @param string[] $tags
|
||||||
|
*/
|
||||||
|
public static function count_images(array $tags = []): int
|
||||||
|
{
|
||||||
|
global $cache, $database;
|
||||||
|
$tag_count = count($tags);
|
||||||
|
|
||||||
|
// SPEED_HAX ignores the fact that extensions can add img_conditions
|
||||||
|
// even when there are no tags being searched for
|
||||||
|
if (SPEED_HAX && $tag_count === 0) {
|
||||||
|
// total number of images in the DB
|
||||||
|
$total = self::count_total_images();
|
||||||
|
} elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) {
|
||||||
|
if (!str_starts_with($tags[0], "-")) {
|
||||||
|
// one positive tag - we can look that up directly
|
||||||
|
$total = self::count_tag($tags[0]);
|
||||||
|
} else {
|
||||||
|
// one negative tag - subtract from the total
|
||||||
|
$total = self::count_total_images() - self::count_tag(substr($tags[0], 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// complex query
|
||||||
|
// implode(tags) can be too long for memcache, so use the hash of tags as the key
|
||||||
|
$cache_key = "image-count:" . md5(Tag::implode($tags));
|
||||||
|
$total = $cache->get($cache_key);
|
||||||
|
if (is_null($total)) {
|
||||||
|
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
||||||
|
$tags[] = "rating:*";
|
||||||
|
}
|
||||||
|
[$tag_conditions, $img_conditions, $order] = self::terms_to_conditions($tags);
|
||||||
|
$querylet = self::build_search_querylet($tag_conditions, $img_conditions, $order);
|
||||||
|
$total = (int)$database->get_one("SELECT COUNT(*) AS cnt FROM ($querylet->sql) AS tbl", $querylet->variables);
|
||||||
|
if (SPEED_HAX && $total > 5000) {
|
||||||
|
// when we have a ton of images, the count
|
||||||
|
// won't change dramatically very often
|
||||||
|
$cache->set($cache_key, $total, 3600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_null($total)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function tag_or_wildcard_to_ids(string $tag): array
|
||||||
|
{
|
||||||
|
global $database;
|
||||||
|
$sq = "SELECT id FROM tags WHERE LOWER(tag) LIKE LOWER(:tag)";
|
||||||
|
if ($database->get_driver_id() === DatabaseDriverID::SQLITE) {
|
||||||
|
$sq .= "ESCAPE '\\'";
|
||||||
|
}
|
||||||
|
return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a human input string into a an abstract search query
|
||||||
|
*
|
||||||
|
* @param string[] $terms
|
||||||
|
* @return array{0: TagCondition[], 1: ImgCondition[], 2: string}
|
||||||
|
*/
|
||||||
|
private static function terms_to_conditions(array $terms): array
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
$tag_conditions = [];
|
||||||
|
$img_conditions = [];
|
||||||
|
$order = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn a bunch of strings into a bunch of TagCondition
|
||||||
|
* and ImgCondition objects
|
||||||
|
*/
|
||||||
|
$stpen = 0; // search term parse event number
|
||||||
|
foreach (array_merge([null], $terms) as $term) {
|
||||||
|
$stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms));
|
||||||
|
$order ??= $stpe->order;
|
||||||
|
$img_conditions = array_merge($img_conditions, $stpe->img_conditions);
|
||||||
|
$tag_conditions = array_merge($tag_conditions, $stpe->tag_conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER));
|
||||||
|
|
||||||
|
return [$tag_conditions, $img_conditions, $order];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn an abstract search query into an SQL Querylet
|
||||||
|
*
|
||||||
|
* @param TagCondition[] $tag_conditions
|
||||||
|
* @param ImgCondition[] $img_conditions
|
||||||
|
*/
|
||||||
|
private static function build_search_querylet(
|
||||||
|
array $tag_conditions,
|
||||||
|
array $img_conditions,
|
||||||
|
string $order,
|
||||||
|
?int $limit = null,
|
||||||
|
?int $offset = null
|
||||||
|
): Querylet {
|
||||||
|
// no tags, do a simple search
|
||||||
|
if (count($tag_conditions) === 0) {
|
||||||
|
static::$_search_path[] = "no_tags";
|
||||||
|
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// one tag sorted by ID - we can fetch this from the image_tags table,
|
||||||
|
// and do the offset / limit there, which is 10x faster than fetching
|
||||||
|
// all the image_tags and doing the offset / limit on the result.
|
||||||
|
elseif (
|
||||||
|
count($tag_conditions) === 1
|
||||||
|
&& $tag_conditions[0]->positive
|
||||||
|
// We can only do this if img_conditions is empty, because
|
||||||
|
// we're going to apply the offset / limit to the image_tags
|
||||||
|
// subquery, and applying extra conditions to the top-level
|
||||||
|
// query might reduce the total results below the target limit
|
||||||
|
&& empty($img_conditions)
|
||||||
|
// We can only do this if we're sorting by ID, because
|
||||||
|
// we're going to be using the image_tags table, which
|
||||||
|
// only has image_id and tag_id, not any other columns
|
||||||
|
&& ($order == "id DESC" || $order == "images.id DESC")
|
||||||
|
// This is only an optimisation if we are applying limit
|
||||||
|
// and offset
|
||||||
|
&& !is_null($limit)
|
||||||
|
&& !is_null($offset)
|
||||||
|
) {
|
||||||
|
static::$_search_path[] = "fast";
|
||||||
|
$tc = $tag_conditions[0];
|
||||||
|
// IN (SELECT id FROM tags) is 100x slower than doing a separate
|
||||||
|
// query and then a second query for IN(first_query_results)??
|
||||||
|
$tag_array = self::tag_or_wildcard_to_ids($tc->tag);
|
||||||
|
if (count($tag_array) == 0) {
|
||||||
|
// if wildcard expanded to nothing, take a shortcut
|
||||||
|
static::$_search_path[] = "invalid_tag";
|
||||||
|
$query = new Querylet("SELECT images.* FROM images WHERE 1=0");
|
||||||
|
} else {
|
||||||
|
$set = implode(', ', $tag_array);
|
||||||
|
$query = new Querylet("
|
||||||
|
SELECT images.*
|
||||||
|
FROM images INNER JOIN (
|
||||||
|
SELECT DISTINCT it.image_id
|
||||||
|
FROM image_tags it
|
||||||
|
WHERE it.tag_id IN ($set)
|
||||||
|
ORDER BY it.image_id DESC
|
||||||
|
LIMIT :limit OFFSET :offset
|
||||||
|
) a on a.image_id = images.id
|
||||||
|
WHERE 1=1
|
||||||
|
", ["limit" => $limit, "offset" => $offset]);
|
||||||
|
// don't offset at the image level because
|
||||||
|
// we already offset at the image_tags level
|
||||||
|
$limit = null;
|
||||||
|
$offset = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than one tag, or more than zero other conditions, or a non-default sort order
|
||||||
|
else {
|
||||||
|
static::$_search_path[] = "general";
|
||||||
|
$positive_tag_id_array = [];
|
||||||
|
$positive_wildcard_id_array = [];
|
||||||
|
$negative_tag_id_array = [];
|
||||||
|
$all_nonexistent_negatives = true;
|
||||||
|
|
||||||
|
foreach ($tag_conditions as $tq) {
|
||||||
|
$tag_ids = self::tag_or_wildcard_to_ids($tq->tag);
|
||||||
|
$tag_count = count($tag_ids);
|
||||||
|
|
||||||
|
if ($tq->positive) {
|
||||||
|
$all_nonexistent_negatives = false;
|
||||||
|
if ($tag_count == 0) {
|
||||||
|
# one of the positive tags had zero results, therefor there
|
||||||
|
# can be no results; "where 1=0" should shortcut things
|
||||||
|
static::$_search_path[] = "invalid_tag";
|
||||||
|
return new Querylet("SELECT images.* FROM images WHERE 1=0");
|
||||||
|
} elseif ($tag_count == 1) {
|
||||||
|
// All wildcard terms that qualify for a single tag can be treated the same as non-wildcards
|
||||||
|
$positive_tag_id_array[] = $tag_ids[0];
|
||||||
|
} else {
|
||||||
|
// Terms that resolve to multiple tags act as an OR within themselves
|
||||||
|
// and as an AND in relation to all other terms,
|
||||||
|
$positive_wildcard_id_array[] = $tag_ids;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($tag_count > 0) {
|
||||||
|
$all_nonexistent_negatives = false;
|
||||||
|
// Unlike positive criteria, negative criteria are all handled in an OR fashion,
|
||||||
|
// so we can just compile them all into a single sub-query.
|
||||||
|
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert($positive_tag_id_array || $positive_wildcard_id_array || $negative_tag_id_array || $all_nonexistent_negatives, @$_GET['q']);
|
||||||
|
|
||||||
|
if ($all_nonexistent_negatives) {
|
||||||
|
static::$_search_path[] = "all_nonexistent_negatives";
|
||||||
|
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
||||||
|
} elseif (!empty($positive_tag_id_array) || !empty($positive_wildcard_id_array)) {
|
||||||
|
static::$_search_path[] = "some_positives";
|
||||||
|
$inner_joins = [];
|
||||||
|
if (!empty($positive_tag_id_array)) {
|
||||||
|
foreach ($positive_tag_id_array as $tag) {
|
||||||
|
$inner_joins[] = "= $tag";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($positive_wildcard_id_array)) {
|
||||||
|
foreach ($positive_wildcard_id_array as $tags) {
|
||||||
|
$positive_tag_id_list = join(', ', $tags);
|
||||||
|
$inner_joins[] = "IN ($positive_tag_id_list)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = array_shift($inner_joins);
|
||||||
|
$sub_query = "SELECT DISTINCT it.image_id FROM image_tags it ";
|
||||||
|
$i = 0;
|
||||||
|
foreach ($inner_joins as $inner_join) {
|
||||||
|
$i++;
|
||||||
|
$sub_query .= " INNER JOIN image_tags it$i ON it$i.image_id = it.image_id AND it$i.tag_id $inner_join ";
|
||||||
|
}
|
||||||
|
if (!empty($negative_tag_id_array)) {
|
||||||
|
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||||
|
$sub_query .= " LEFT JOIN image_tags negative ON negative.image_id = it.image_id AND negative.tag_id IN ($negative_tag_id_list) ";
|
||||||
|
}
|
||||||
|
$sub_query .= "WHERE it.tag_id $first ";
|
||||||
|
if (!empty($negative_tag_id_array)) {
|
||||||
|
$sub_query .= " AND negative.image_id IS NULL";
|
||||||
|
}
|
||||||
|
$sub_query .= " GROUP BY it.image_id ";
|
||||||
|
|
||||||
|
$query = new Querylet("
|
||||||
|
SELECT images.*
|
||||||
|
FROM images
|
||||||
|
INNER JOIN ($sub_query) a on a.image_id = images.id
|
||||||
|
");
|
||||||
|
} elseif (!empty($negative_tag_id_array)) {
|
||||||
|
static::$_search_path[] = "only_negative_tags";
|
||||||
|
$negative_tag_id_list = join(', ', $negative_tag_id_array);
|
||||||
|
$query = new Querylet("
|
||||||
|
SELECT images.*
|
||||||
|
FROM images
|
||||||
|
LEFT JOIN image_tags negative ON negative.image_id = images.id AND negative.tag_id in ($negative_tag_id_list)
|
||||||
|
WHERE negative.image_id IS NULL
|
||||||
|
");
|
||||||
|
} else {
|
||||||
|
throw new SCoreException("No criteria specified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Merge all the image metadata searches into one generic querylet
|
||||||
|
* and append to the base querylet with "AND blah"
|
||||||
|
*/
|
||||||
|
if (!empty($img_conditions)) {
|
||||||
|
$n = 0;
|
||||||
|
$img_sql = "";
|
||||||
|
$img_vars = [];
|
||||||
|
foreach ($img_conditions as $iq) {
|
||||||
|
if ($n++ > 0) {
|
||||||
|
$img_sql .= " AND";
|
||||||
|
}
|
||||||
|
if (!$iq->positive) {
|
||||||
|
$img_sql .= " NOT";
|
||||||
|
}
|
||||||
|
$img_sql .= " (" . $iq->qlet->sql . ")";
|
||||||
|
$img_vars = array_merge($img_vars, $iq->qlet->variables);
|
||||||
|
}
|
||||||
|
$query->append_sql(" AND ");
|
||||||
|
$query->append(new Querylet($img_sql, $img_vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->append(new Querylet(" ORDER BY ".$order));
|
||||||
|
|
||||||
|
if (!is_null($limit)) {
|
||||||
|
$query->append(new Querylet(" LIMIT :limit ", ["limit" => $limit]));
|
||||||
|
$query->append(new Querylet(" OFFSET :offset ", ["offset" => $offset]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -55,9 +55,9 @@ class TagUsage
|
||||||
$cache_key .= "-" . $limit;
|
$cache_key .= "-" . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $cache->get($cache_key);
|
$res = cache_get_or_set(
|
||||||
if (is_null($res)) {
|
$cache_key,
|
||||||
$res = $database->get_pairs(
|
fn () => $database->get_pairs(
|
||||||
"
|
"
|
||||||
SELECT tag, count
|
SELECT tag, count
|
||||||
FROM tags
|
FROM tags
|
||||||
|
@ -68,9 +68,9 @@ class TagUsage
|
||||||
$limitSQL
|
$limitSQL
|
||||||
",
|
",
|
||||||
$SQLarr
|
$SQLarr
|
||||||
);
|
),
|
||||||
$cache->set($cache_key, $res, 600);
|
600
|
||||||
}
|
);
|
||||||
|
|
||||||
$counts = [];
|
$counts = [];
|
||||||
foreach ($res as $k => $v) {
|
foreach ($res as $k => $v) {
|
||||||
|
|
|
@ -814,3 +814,18 @@ function stringer($s): string
|
||||||
}
|
}
|
||||||
return "<Unstringable>";
|
return "<Unstringable>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a value is in the cache, return it; otherwise, call the callback
|
||||||
|
* to generate it and store it in the cache.
|
||||||
|
*/
|
||||||
|
function cache_get_or_set(string $key, callable $callback, int $ttl = 0)
|
||||||
|
{
|
||||||
|
global $cache;
|
||||||
|
$value = $cache->get($key);
|
||||||
|
if ($value === null) {
|
||||||
|
$value = $callback();
|
||||||
|
$cache->set($key, $value, $ttl);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Shimmie2;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class TestInit extends TestCase
|
class InitTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testInitExt()
|
public function testInitExt()
|
||||||
{
|
{
|
490
core/tests/SearchTest.php
Normal file
490
core/tests/SearchTest.php
Normal file
|
@ -0,0 +1,490 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shimmie2;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\Depends;
|
||||||
|
use PHPUnit\Framework\Constraint\IsEqual;
|
||||||
|
|
||||||
|
require_once "core/imageboard/search.php";
|
||||||
|
|
||||||
|
class SearchTest extends ShimmiePHPUnitTestCase
|
||||||
|
{
|
||||||
|
public function testWeirdTags()
|
||||||
|
{
|
||||||
|
$this->log_in_as_user();
|
||||||
|
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "question? colon:thing exclamation!");
|
||||||
|
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "question. colon_thing exclamation%");
|
||||||
|
|
||||||
|
$this->assert_search_results(["question?"], [$image_id_1]);
|
||||||
|
$this->assert_search_results(["question."], [$image_id_2]);
|
||||||
|
$this->assert_search_results(["colon:thing"], [$image_id_1]);
|
||||||
|
$this->assert_search_results(["colon_thing"], [$image_id_2]);
|
||||||
|
$this->assert_search_results(["exclamation!"], [$image_id_1]);
|
||||||
|
$this->assert_search_results(["exclamation%"], [$image_id_2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// base case
|
||||||
|
public function testUpload(): array
|
||||||
|
{
|
||||||
|
$this->log_in_as_user();
|
||||||
|
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone");
|
||||||
|
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop");
|
||||||
|
$this->log_out();
|
||||||
|
|
||||||
|
# make sure both uploads were ok
|
||||||
|
$this->assertTrue($image_id_1 > 0);
|
||||||
|
$this->assertTrue($image_id_2 > 0);
|
||||||
|
|
||||||
|
return [$image_id_1, $image_id_2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************
|
||||||
|
* Test turning a string into an abstract query
|
||||||
|
*/
|
||||||
|
private function assert_TTC(string $tags, array $expected_tag_conditions, array $expected_img_conditions, string $expected_order)
|
||||||
|
{
|
||||||
|
$class = new \ReflectionClass('\Shimmie2\Search');
|
||||||
|
$terms_to_conditions = $class->getMethod("terms_to_conditions");
|
||||||
|
$terms_to_conditions->setAccessible(true); // Use this if you are running PHP older than 8.1.0
|
||||||
|
|
||||||
|
$obj = new Search();
|
||||||
|
[$tag_conditions, $img_conditions, $order] = $terms_to_conditions->invokeArgs($obj, [Tag::explode($tags, false)]);
|
||||||
|
|
||||||
|
static::assertThat(
|
||||||
|
[
|
||||||
|
"tags" => $expected_tag_conditions,
|
||||||
|
"imgs" => $expected_img_conditions,
|
||||||
|
"order" => $expected_order,
|
||||||
|
],
|
||||||
|
new IsEqual([
|
||||||
|
"tags" => $tag_conditions,
|
||||||
|
"imgs" => $img_conditions,
|
||||||
|
"order" => $order,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTTC_Empty()
|
||||||
|
{
|
||||||
|
$this->assert_TTC(
|
||||||
|
"",
|
||||||
|
[
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ImgCondition(new Querylet("trash != :true", ["true" => true])),
|
||||||
|
new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [
|
||||||
|
"private_owner_id" => 1,
|
||||||
|
"true" => true])),
|
||||||
|
new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])),
|
||||||
|
],
|
||||||
|
"images.id DESC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTTC_Hash()
|
||||||
|
{
|
||||||
|
$this->assert_TTC(
|
||||||
|
"hash=1234567890",
|
||||||
|
[
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ImgCondition(new Querylet("trash != :true", ["true" => true])),
|
||||||
|
new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [
|
||||||
|
"private_owner_id" => 1,
|
||||||
|
"true" => true])),
|
||||||
|
new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])),
|
||||||
|
new ImgCondition(new Querylet("images.hash = :hash", ["hash" => "1234567890"])),
|
||||||
|
],
|
||||||
|
"images.id DESC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTTC_Ratio()
|
||||||
|
{
|
||||||
|
$this->assert_TTC(
|
||||||
|
"ratio=42:12345",
|
||||||
|
[
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ImgCondition(new Querylet("trash != :true", ["true" => true])),
|
||||||
|
new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [
|
||||||
|
"private_owner_id" => 1,
|
||||||
|
"true" => true])),
|
||||||
|
new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])),
|
||||||
|
new ImgCondition(new Querylet("width / :width1 = height / :height1", ['width1' => 42,
|
||||||
|
'height1' => 12345])),
|
||||||
|
],
|
||||||
|
"images.id DESC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTTC_Order()
|
||||||
|
{
|
||||||
|
$this->assert_TTC(
|
||||||
|
"order=score",
|
||||||
|
[
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ImgCondition(new Querylet("trash != :true", ["true" => true])),
|
||||||
|
new ImgCondition(new Querylet("private != :true OR owner_id = :private_owner_id", [
|
||||||
|
"private_owner_id" => 1,
|
||||||
|
"true" => true])),
|
||||||
|
new ImgCondition(new Querylet("rating IN ('?', 's', 'q', 'e')", [])),
|
||||||
|
],
|
||||||
|
"images.numeric_score DESC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************************
|
||||||
|
* Test turning an abstract query into SQL + fetching the results
|
||||||
|
*/
|
||||||
|
private function assert_BSQ(
|
||||||
|
array $tcs = [],
|
||||||
|
array $ics = [],
|
||||||
|
string $order = "id DESC",
|
||||||
|
int $limit = 9999,
|
||||||
|
int $start = 0,
|
||||||
|
array $res = [],
|
||||||
|
array $path = null,
|
||||||
|
) {
|
||||||
|
global $database;
|
||||||
|
|
||||||
|
$tcs = array_map(
|
||||||
|
fn ($tag) => ($tag[0] == "-") ?
|
||||||
|
new TagCondition(substr($tag, 1), false) :
|
||||||
|
new TagCondition($tag),
|
||||||
|
$tcs
|
||||||
|
);
|
||||||
|
|
||||||
|
$ics = array_map(
|
||||||
|
fn ($ic) => send_event(new SearchTermParseEvent(0, $ic, []))->img_conditions,
|
||||||
|
$ics
|
||||||
|
);
|
||||||
|
$ics = array_merge(...$ics);
|
||||||
|
|
||||||
|
Search::$_search_path = [];
|
||||||
|
|
||||||
|
$class = new \ReflectionClass('\Shimmie2\Search');
|
||||||
|
$build_search_querylet = $class->getMethod("build_search_querylet");
|
||||||
|
$build_search_querylet->setAccessible(true); // Use this if you are running PHP older than 8.1.0
|
||||||
|
|
||||||
|
$obj = new Search();
|
||||||
|
$querylet = $build_search_querylet->invokeArgs($obj, [$tcs, $ics, $order, $limit, $start]);
|
||||||
|
|
||||||
|
$results = $database->get_all($querylet->sql, $querylet->variables);
|
||||||
|
|
||||||
|
static::assertThat(
|
||||||
|
[
|
||||||
|
"res" => array_map(fn ($row) => $row['id'], $results),
|
||||||
|
"path" => Search::$_search_path,
|
||||||
|
],
|
||||||
|
new IsEqual([
|
||||||
|
"res" => $res,
|
||||||
|
"path" => $path ?? Search::$_search_path,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * * * * * * * * * *
|
||||||
|
* No-tag search *
|
||||||
|
* * * * * * * * * * */
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_NoTags($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: [],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * * * * * * * * * *
|
||||||
|
* Fast-path search *
|
||||||
|
* * * * * * * * * * */
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_NoResults($image_ids)
|
||||||
|
{
|
||||||
|
$this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["maumaumau"],
|
||||||
|
res: [],
|
||||||
|
path: ["fast", "invalid_tag"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_OneResult($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["pbx"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["fast"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_ManyResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["fast"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_WildNoResults($image_ids)
|
||||||
|
{
|
||||||
|
$this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["asdfasdf*"],
|
||||||
|
res: [],
|
||||||
|
path: ["fast", "invalid_tag"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only the first image matches both the wildcard and the tag.
|
||||||
|
* This checks for a bug where searching for "a* b" would return
|
||||||
|
* an image tagged "a1 a2" because the number of matched tags
|
||||||
|
* was equal to the number of searched tags.
|
||||||
|
*
|
||||||
|
* https://github.com/shish/shimmie2/issues/547
|
||||||
|
*/
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_WildOneResult($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["screen*"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["fast"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the fast path doesn't return duplicate results
|
||||||
|
* when a wildcard matches one image multiple times.
|
||||||
|
*/
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_FastPath_WildManyResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
// two images match comp* - one matches it once, one matches it twice
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["comp*"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["fast"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * * * * * * * * * *
|
||||||
|
* General search *
|
||||||
|
* * * * * * * * * * */
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_NoResults($image_ids)
|
||||||
|
{
|
||||||
|
$this->testUpload();
|
||||||
|
# multiple tags, one of which doesn't exist
|
||||||
|
# (test the "one tag doesn't exist = no hits" path)
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer", "not_a_tag"],
|
||||||
|
res: [],
|
||||||
|
path: ["general", "invalid_tag"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_OneResult($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer", "screenshot"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only the first image matches both the wildcard and the tag.
|
||||||
|
* This checks for a bug where searching for "a* b" would return
|
||||||
|
* an image tagged "a1 a2" because the number of matched tags
|
||||||
|
* was equal to the number of searched tags.
|
||||||
|
*
|
||||||
|
* https://github.com/shish/shimmie2/issues/547
|
||||||
|
*/
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_WildOneResult($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["comp*", "screenshot"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_ManyResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer", "thing"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_WildManyResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["comp*", "-asdf"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_SubtractValidFromResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer", "-pbx"],
|
||||||
|
res: [$image_ids[1]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_SubtractNotValidFromResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer", "-not_a_tag"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_SubtractValidFromDefault($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
// negative tag alone, should remove the image with that tag
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["-pbx"],
|
||||||
|
res: [$image_ids[1]],
|
||||||
|
path: ["general", "only_negative_tags"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_SubtractNotValidFromDefault($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
// negative that doesn't exist, should return all results
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["-not_a_tag"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "all_nonexistent_negatives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_GeneralPath_SubtractMultipleNotValidFromDefault($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
// multiple negative tags that don't exist, should return all results
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["-not_a_tag", "-also_not_a_tag"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "all_nonexistent_negatives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * * * * * * * * * *
|
||||||
|
* Meta Search *
|
||||||
|
* * * * * * * * * * */
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_ImgCond_NoResults($image_ids)
|
||||||
|
{
|
||||||
|
$this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["hash=1234567890"],
|
||||||
|
res: [],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["ratio=42:12345"],
|
||||||
|
res: [],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_ImgCond_OneResult($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["hash=feb01bab5698a11dd87416724c7a89e3"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["id={$image_ids[1]}"],
|
||||||
|
res: [$image_ids[1]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["filename=screenshot"],
|
||||||
|
res: [$image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_ImgCond_ManyResults($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["size=640x480"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["tags=5"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
$this->assert_BSQ(
|
||||||
|
ics: ["ext=jpg"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["no_tags"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * * * * * * * * * *
|
||||||
|
* Mixed *
|
||||||
|
* * * * * * * * * * */
|
||||||
|
#[Depends('testUpload')]
|
||||||
|
public function testBSQ_TagCondWithImgCond($image_ids)
|
||||||
|
{
|
||||||
|
$image_ids = $this->testUpload();
|
||||||
|
// multiple tags, many results
|
||||||
|
$this->assert_BSQ(
|
||||||
|
tcs: ["computer"],
|
||||||
|
ics: ["size=640x480"],
|
||||||
|
res: [$image_ids[1], $image_ids[0]],
|
||||||
|
path: ["general", "some_positives"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -209,7 +209,7 @@ class Artists extends Extension
|
||||||
$userIsLogged = !$user->is_anonymous();
|
$userIsLogged = !$user->is_anonymous();
|
||||||
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
$userIsAdmin = $user->can(Permissions::ARTISTS_ADMIN);
|
||||||
|
|
||||||
$images = Image::find_images(limit: 4, tags: Tag::explode($artist['name']));
|
$images = Search::find_images(limit: 4, tags: Tag::explode($artist['name']));
|
||||||
|
|
||||||
$this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin);
|
$this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin);
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -62,10 +62,8 @@ class AutoComplete extends Extension
|
||||||
$cache_key .= "-" . $limit;
|
$cache_key .= "-" . $limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $cache->get($cache_key);
|
return cache_get_or_set($cache_key, fn () => $database->get_pairs(
|
||||||
if (is_null($res)) {
|
"
|
||||||
$res = $database->get_pairs(
|
|
||||||
"
|
|
||||||
SELECT tag, count
|
SELECT tag, count
|
||||||
FROM tags
|
FROM tags
|
||||||
WHERE LOWER(tag) LIKE LOWER(:search)
|
WHERE LOWER(tag) LIKE LOWER(:search)
|
||||||
|
@ -74,11 +72,7 @@ class AutoComplete extends Extension
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
$limitSQL
|
$limitSQL
|
||||||
",
|
",
|
||||||
$SQLarr
|
$SQLarr
|
||||||
);
|
), 600);
|
||||||
$cache->set($cache_key, $res, 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,7 @@ class Blocks extends Extension
|
||||||
{
|
{
|
||||||
global $cache, $database, $page, $user;
|
global $cache, $database, $page, $user;
|
||||||
|
|
||||||
$blocks = $cache->get("blocks");
|
$blocks = cache_get_or_set("blocks", fn () => $database->get_all("SELECT * FROM blocks"), 600);
|
||||||
if (is_null($blocks)) {
|
|
||||||
$blocks = $database->get_all("SELECT * FROM blocks");
|
|
||||||
$cache->set("blocks", $blocks, 600);
|
|
||||||
}
|
|
||||||
foreach ($blocks as $block) {
|
foreach ($blocks as $block) {
|
||||||
$path = implode("/", $event->args);
|
$path = implode("/", $event->args);
|
||||||
if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) {
|
if (strlen($path) < 4000 && fnmatch($block['pages'], $path)) {
|
||||||
|
|
|
@ -214,7 +214,7 @@ class BulkActions extends Extension
|
||||||
private function yield_search_results(string $query): \Generator
|
private function yield_search_results(string $query): \Generator
|
||||||
{
|
{
|
||||||
$tags = Tag::explode($query);
|
$tags = Tag::explode($query);
|
||||||
return Image::find_images_iterable(0, null, $tags);
|
return Search::find_images_iterable(0, null, $tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sort_blocks($a, $b)
|
private function sort_blocks($a, $b)
|
||||||
|
|
|
@ -283,20 +283,17 @@ class CommentList extends Extension
|
||||||
{
|
{
|
||||||
global $cache, $config, $database, $user;
|
global $cache, $config, $database, $user;
|
||||||
|
|
||||||
|
$threads_per_page = 10;
|
||||||
|
|
||||||
$where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
|
$where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
|
||||||
|
|
||||||
$total_pages = $cache->get("comment_pages");
|
$total_pages = cache_get_or_set("comment_pages", fn () => (int)ceil($database->get_one("
|
||||||
if (is_null($total_pages)) {
|
SELECT COUNT(c1)
|
||||||
$total_pages = (int)ceil($database->get_one("
|
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
||||||
SELECT COUNT(c1)
|
") / $threads_per_page), 600);
|
||||||
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
|
||||||
") / 10);
|
|
||||||
$cache->set("comment_pages", $total_pages, 600);
|
|
||||||
}
|
|
||||||
$total_pages = max($total_pages, 1);
|
$total_pages = max($total_pages, 1);
|
||||||
|
|
||||||
$current_page = $event->try_page_num(1, $total_pages);
|
$current_page = $event->try_page_num(1, $total_pages);
|
||||||
$threads_per_page = 10;
|
|
||||||
$start = $threads_per_page * $current_page;
|
$start = $threads_per_page * $current_page;
|
||||||
|
|
||||||
$result = $database->execute("
|
$result = $database->execute("
|
||||||
|
@ -357,11 +354,7 @@ class CommentList extends Extension
|
||||||
global $cache, $config;
|
global $cache, $config;
|
||||||
$cc = $config->get_int("comment_count");
|
$cc = $config->get_int("comment_count");
|
||||||
if ($cc > 0) {
|
if ($cc > 0) {
|
||||||
$recent = $cache->get("recent_comments");
|
$recent = cache_get_or_set("recent_comments", fn () => $this->get_recent_comments($cc), 60);
|
||||||
if (is_null($recent)) {
|
|
||||||
$recent = $this->get_recent_comments($cc);
|
|
||||||
$cache->set("recent_comments", $recent, 60);
|
|
||||||
}
|
|
||||||
if (count($recent) > 0) {
|
if (count($recent) > 0) {
|
||||||
$this->theme->display_recent_comments($recent);
|
$this->theme->display_recent_comments($recent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,8 +199,8 @@ class DanbooruApi extends Extension
|
||||||
$tags = array_filter($tags, static function ($element) {
|
$tags = array_filter($tags, static function ($element) {
|
||||||
return $element !== "*";
|
return $element !== "*";
|
||||||
});
|
});
|
||||||
$count = Image::count_images($tags);
|
$count = Search::count_images($tags);
|
||||||
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
|
$results = Search::find_images(max($start, 0), min($limit, 100), $tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we have the array $results filled with Image objects
|
// Now we have the array $results filled with Image objects
|
||||||
|
|
|
@ -72,7 +72,7 @@ class Favorites extends Extension
|
||||||
|
|
||||||
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$i_favorites_count = Image::count_images(["favorited_by={$event->display_user->name}"]);
|
$i_favorites_count = Search::count_images(["favorited_by={$event->display_user->name}"]);
|
||||||
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
||||||
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
|
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
|
||||||
$favorites_link = search_link(["favorited_by={$event->display_user->name}"]);
|
$favorites_link = search_link(["favorited_by={$event->display_user->name}"]);
|
||||||
|
|
|
@ -52,14 +52,17 @@ class Featured extends Extension
|
||||||
global $cache, $config, $page, $user;
|
global $cache, $config, $page, $user;
|
||||||
$fid = $config->get_int("featured_id");
|
$fid = $config->get_int("featured_id");
|
||||||
if ($fid > 0) {
|
if ($fid > 0) {
|
||||||
$image = $cache->get("featured_image_object:$fid");
|
$image = cache_get_or_set(
|
||||||
if (is_null($image)) {
|
"featured_image_object:$fid",
|
||||||
$image = Image::by_id($fid);
|
function () use ($fid) {
|
||||||
if ($image) { // make sure the object is fully populated before saving
|
$image = Image::by_id($fid);
|
||||||
$image->get_tag_array();
|
if ($image) { // make sure the object is fully populated before saving
|
||||||
}
|
$image->get_tag_array();
|
||||||
$cache->set("featured_image_object:$fid", $image, 600);
|
}
|
||||||
}
|
return $image;
|
||||||
|
},
|
||||||
|
600
|
||||||
|
);
|
||||||
if (!is_null($image)) {
|
if (!is_null($image)) {
|
||||||
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
||||||
if (!in_array($image->rating, Ratings::get_user_class_privs($user))) {
|
if (!in_array($image->rating, Ratings::get_user_class_privs($user))) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ class Home extends Extension
|
||||||
$num_comma = "";
|
$num_comma = "";
|
||||||
$counter_text = "";
|
$counter_text = "";
|
||||||
if ($counter_dir != 'none') {
|
if ($counter_dir != 'none') {
|
||||||
$total = Image::count_images();
|
$total = Search::count_images();
|
||||||
$num_comma = number_format($total);
|
$num_comma = number_format($total);
|
||||||
|
|
||||||
if ($counter_dir != 'text-only') {
|
if ($counter_dir != 'text-only') {
|
||||||
|
|
|
@ -263,7 +263,7 @@ class ImageIO extends Extension
|
||||||
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
public function onUserPageBuilding(UserPageBuildingEvent $event)
|
||||||
{
|
{
|
||||||
$u_name = url_escape($event->display_user->name);
|
$u_name = url_escape($event->display_user->name);
|
||||||
$i_image_count = Image::count_images(["user={$event->display_user->name}"]);
|
$i_image_count = Search::count_images(["user={$event->display_user->name}"]);
|
||||||
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
|
||||||
$h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old));
|
$h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old));
|
||||||
$images_link = search_link(["user=$u_name"]);
|
$images_link = search_link(["user=$u_name"]);
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ImageBanTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assertEquals(200, $page->code);
|
$this->assertEquals(200, $page->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onNotSuccessfulTest(\Throwable $t): void
|
public function onNotSuccessfulTest(\Throwable $t): never
|
||||||
{
|
{
|
||||||
send_event(new RemoveImageHashBanEvent($this->hash));
|
send_event(new RemoveImageHashBanEvent($this->hash));
|
||||||
parent::onNotSuccessfulTest($t); // TODO: Change the autogenerated stub
|
parent::onNotSuccessfulTest($t); // TODO: Change the autogenerated stub
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Index extends Extension
|
||||||
|
|
||||||
public function onPageRequest(PageRequestEvent $event)
|
public function onPageRequest(PageRequestEvent $event)
|
||||||
{
|
{
|
||||||
global $cache, $page, $user;
|
global $cache, $config, $page, $user;
|
||||||
if ($event->page_matches("post/list")) {
|
if ($event->page_matches("post/list")) {
|
||||||
if (isset($_GET['search'])) {
|
if (isset($_GET['search'])) {
|
||||||
$page->set_mode(PageMode::REDIRECT);
|
$page->set_mode(PageMode::REDIRECT);
|
||||||
|
@ -67,7 +67,7 @@ class Index extends Extension
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$total_pages = Image::count_pages($search_terms);
|
$total_pages = (int)ceil(Search::count_images($search_terms) / $config->get_int(IndexConfig::IMAGES));
|
||||||
$images = [];
|
$images = [];
|
||||||
|
|
||||||
if (SPEED_HAX && $total_pages > $fast_page_limit && !$user->can("big_search")) {
|
if (SPEED_HAX && $total_pages > $fast_page_limit && !$user->can("big_search")) {
|
||||||
|
@ -77,16 +77,16 @@ class Index extends Extension
|
||||||
if (SPEED_HAX) {
|
if (SPEED_HAX) {
|
||||||
if ($count_search_terms === 0 && ($page_number < 10)) {
|
if ($count_search_terms === 0 && ($page_number < 10)) {
|
||||||
// extra caching for the first few post/list pages
|
// extra caching for the first few post/list pages
|
||||||
$images = $cache->get("post-list:$page_number");
|
$images = cache_get_or_set(
|
||||||
if (is_null($images)) {
|
"post-list:$page_number",
|
||||||
$images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
fn () => Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms),
|
||||||
$cache->set("post-list:$page_number", $images, 60);
|
60
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$images) {
|
if (!$images) {
|
||||||
$images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
$images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
||||||
}
|
}
|
||||||
} catch (PermissionDeniedException $pde) {
|
} catch (PermissionDeniedException $pde) {
|
||||||
$this->theme->display_error(403, "Permission denied", $pde->error);
|
$this->theme->display_error(403, "Permission denied", $pde->error);
|
||||||
|
@ -156,7 +156,7 @@ class Index extends Extension
|
||||||
}
|
}
|
||||||
if ($event->cmd == "search") {
|
if ($event->cmd == "search") {
|
||||||
$query = count($event->args) > 0 ? Tag::explode($event->args[0]) : [];
|
$query = count($event->args) > 0 ? Tag::explode($event->args[0]) : [];
|
||||||
$items = Image::find_images(limit: 1000, tags: $query);
|
$items = Search::find_images(limit: 1000, tags: $query);
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
print("{$item->hash}\n");
|
print("{$item->hash}\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shimmie2;
|
namespace Shimmie2;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\Depends;
|
||||||
|
|
||||||
class IndexTest extends ShimmiePHPUnitTestCase
|
class IndexTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function testIndexPage()
|
public function testIndexPage()
|
||||||
|
@ -47,177 +49,6 @@ class IndexTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_response(200);
|
$this->assert_response(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWeirdTags()
|
|
||||||
{
|
|
||||||
$this->log_in_as_user();
|
|
||||||
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "question? colon:thing exclamation!");
|
|
||||||
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "question. colon_thing exclamation%");
|
|
||||||
|
|
||||||
$this->assert_search_results(["question?"], [$image_id_1]);
|
|
||||||
$this->assert_search_results(["question."], [$image_id_2]);
|
|
||||||
$this->assert_search_results(["colon:thing"], [$image_id_1]);
|
|
||||||
$this->assert_search_results(["colon_thing"], [$image_id_2]);
|
|
||||||
$this->assert_search_results(["exclamation!"], [$image_id_1]);
|
|
||||||
$this->assert_search_results(["exclamation%"], [$image_id_2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// base case
|
|
||||||
public function testUpload(): array
|
|
||||||
{
|
|
||||||
$this->log_in_as_user();
|
|
||||||
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone");
|
|
||||||
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop");
|
|
||||||
$this->log_out();
|
|
||||||
|
|
||||||
# make sure both uploads were ok
|
|
||||||
$this->assertTrue($image_id_1 > 0);
|
|
||||||
$this->assertTrue($image_id_2 > 0);
|
|
||||||
|
|
||||||
return [$image_id_1, $image_id_2];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Tag Search *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testTagSearchNoResults($image_ids)
|
|
||||||
{
|
|
||||||
$this->testUpload();
|
|
||||||
$this->assert_search_results(["maumaumau"], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testTagSearchOneResult($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["pbx"], [$image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testTagSearchManyResults($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["computer"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Multi-Tag Search *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMultiTagSearchNoResults($image_ids)
|
|
||||||
{
|
|
||||||
$this->testUpload();
|
|
||||||
# multiple tags, one of which doesn't exist
|
|
||||||
# (test the "one tag doesn't exist = no hits" path)
|
|
||||||
$this->assert_search_results(["computer", "asdfasdfwaffle"], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMultiTagSearchOneResult($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["computer", "screenshot"], [$image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMultiTagSearchManyResults($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["computer", "thing"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Meta Search *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMetaSearchNoResults($image_ids)
|
|
||||||
{
|
|
||||||
$this->testUpload();
|
|
||||||
$this->assert_search_results(["hash=1234567890"], []);
|
|
||||||
$this->assert_search_results(["ratio=42:12345"], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMetaSearchOneResult($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["hash=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]);
|
|
||||||
$this->assert_search_results(["md5=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]);
|
|
||||||
$this->assert_search_results(["id={$image_ids[1]}"], [$image_ids[1]]);
|
|
||||||
$this->assert_search_results(["filename=screenshot"], [$image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMetaSearchManyResults($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
$this->assert_search_results(["size=640x480"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
$this->assert_search_results(["tags=5"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
$this->assert_search_results(["ext=jpg"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Wildcards *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testWildSearchNoResults($image_ids)
|
|
||||||
{
|
|
||||||
$this->testUpload();
|
|
||||||
$this->assert_search_results(["asdfasdf*"], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testWildSearchOneResult($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
// Only the first image matches both the wildcard and the tag.
|
|
||||||
// This checks for https://github.com/shish/shimmie2/issues/547
|
|
||||||
$this->assert_search_results(["comp*", "screenshot"], [$image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testWildSearchManyResults($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
// two images match comp* - one matches it once,
|
|
||||||
// one matches it twice
|
|
||||||
$this->assert_search_results(["comp*"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Mixed *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testMixedSearchTagMeta($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
// multiple tags, many results
|
|
||||||
$this->assert_search_results(["computer", "size=640x480"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
// tag + negative
|
|
||||||
// wildcards + ???
|
|
||||||
|
|
||||||
/* * * * * * * * * * *
|
|
||||||
* Negative *
|
|
||||||
* * * * * * * * * * */
|
|
||||||
/** @depends testUpload */
|
|
||||||
public function testNegative($image_ids)
|
|
||||||
{
|
|
||||||
$image_ids = $this->testUpload();
|
|
||||||
|
|
||||||
// negative tag, should have one result
|
|
||||||
$this->assert_search_results(["computer", "-pbx"], [$image_ids[1]]);
|
|
||||||
|
|
||||||
// negative tag alone, should work
|
|
||||||
$this->assert_search_results(["-pbx"], [$image_ids[1]]);
|
|
||||||
|
|
||||||
// negative that doesn't exist
|
|
||||||
$this->assert_search_results(["-not_a_tag"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
|
|
||||||
// multiple negative tags that don't exist
|
|
||||||
$this->assert_search_results(["-not_a_tag", "-also_not_a_tag"], [$image_ids[1], $image_ids[0]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This isn't really an index thing, we just want to test this from
|
// This isn't really an index thing, we just want to test this from
|
||||||
// SOMEWHERE because the default theme doesn't use them.
|
// SOMEWHERE because the default theme doesn't use them.
|
||||||
public function test_nav()
|
public function test_nav()
|
||||||
|
|
|
@ -119,9 +119,9 @@ class NumericScore extends Extension
|
||||||
$this->theme->get_nuller($event->display_user);
|
$this->theme->get_nuller($event->display_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
$n_up = Image::count_images(["upvoted_by={$event->display_user->name}"]);
|
$n_up = Search::count_images(["upvoted_by={$event->display_user->name}"]);
|
||||||
$link_up = search_link(["upvoted_by={$event->display_user->name}"]);
|
$link_up = search_link(["upvoted_by={$event->display_user->name}"]);
|
||||||
$n_down = Image::count_images(["downvoted_by={$event->display_user->name}"]);
|
$n_down = Search::count_images(["downvoted_by={$event->display_user->name}"]);
|
||||||
$link_down = search_link(["downvoted_by={$event->display_user->name}]"]);
|
$link_down = search_link(["downvoted_by={$event->display_user->name}]"]);
|
||||||
$event->add_stats("<a href='$link_up'>$n_up Upvotes</a> / <a href='$link_down'>$n_down Downvotes</a>");
|
$event->add_stats("<a href='$link_up'>$n_up Upvotes</a> / <a href='$link_down'>$n_down Downvotes</a>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,7 +420,7 @@ class OuroborosAPI extends Extension
|
||||||
protected function postIndex(int $limit, int $page, array $tags)
|
protected function postIndex(int $limit, int $page, array $tags)
|
||||||
{
|
{
|
||||||
$start = ($page - 1) * $limit;
|
$start = ($page - 1) * $limit;
|
||||||
$results = Image::find_images(max($start, 0), min($limit, 100), $tags);
|
$results = Search::find_images(max($start, 0), min($limit, 100), $tags);
|
||||||
$posts = [];
|
$posts = [];
|
||||||
foreach ($results as $img) {
|
foreach ($results as $img) {
|
||||||
if (!is_object($img)) {
|
if (!is_object($img)) {
|
||||||
|
|
|
@ -297,18 +297,17 @@ class PrivMsg extends Extension
|
||||||
|
|
||||||
private function count_pms(User $user)
|
private function count_pms(User $user)
|
||||||
{
|
{
|
||||||
global $cache, $database;
|
global $database;
|
||||||
|
|
||||||
$count = $cache->get("pm-count:{$user->id}");
|
return cache_get_or_set(
|
||||||
if (is_null($count)) {
|
"pm-count:{$user->id}",
|
||||||
$count = $database->get_one("
|
fn () => $database->get_one("
|
||||||
SELECT count(*)
|
SELECT count(*)
|
||||||
FROM private_message
|
FROM private_message
|
||||||
WHERE to_id = :to_id
|
WHERE to_id = :to_id
|
||||||
AND is_read = :is_read
|
AND is_read = :is_read
|
||||||
", ["to_id" => $user->id, "is_read" => false]);
|
", ["to_id" => $user->id, "is_read" => false]),
|
||||||
$cache->set("pm-count:{$user->id}", $count, 600);
|
600
|
||||||
}
|
);
|
||||||
return $count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,7 +372,7 @@ class Pools extends Extension
|
||||||
break;
|
break;
|
||||||
case "import":
|
case "import":
|
||||||
if ($this->have_permission($user, $pool)) {
|
if ($this->have_permission($user, $pool)) {
|
||||||
$images = Image::find_images(
|
$images = Search::find_images(
|
||||||
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
|
||||||
tags: Tag::explode($_POST["pool_tag"])
|
tags: Tag::explode($_POST["pool_tag"])
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shimmie2;
|
namespace Shimmie2;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\Depends;
|
||||||
|
|
||||||
class PoolsTest extends ShimmiePHPUnitTestCase
|
class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
|
@ -50,7 +52,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$pool_id, [$image_id_1, $image_id_2]];
|
return [$pool_id, [$image_id_1, $image_id_2]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testOnViewImage($args)
|
public function testOnViewImage($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -64,7 +66,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testSearch($args)
|
public function testSearch($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -76,7 +78,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testList($args)
|
public function testList($args)
|
||||||
{
|
{
|
||||||
$this->testCreate();
|
$this->testCreate();
|
||||||
|
@ -84,7 +86,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testView($args)
|
public function testView($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -93,7 +95,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testHistory($args)
|
public function testHistory($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -102,7 +104,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testImport($args)
|
public function testImport($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -114,7 +116,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_text("Pool");
|
$this->assert_text("Pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testRemovePosts($args): array
|
public function testRemovePosts($args): array
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
@ -128,7 +130,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$pool_id, $image_ids];
|
return [$pool_id, $image_ids];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testRemovePosts */
|
#[Depends('testRemovePosts')]
|
||||||
public function testAddPosts($args)
|
public function testAddPosts($args)
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testRemovePosts(null);
|
[$pool_id, $image_ids] = $this->testRemovePosts(null);
|
||||||
|
@ -140,7 +142,7 @@ class PoolsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assertEquals(PageMode::REDIRECT, $page->mode);
|
$this->assertEquals(PageMode::REDIRECT, $page->mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @depends testCreate */
|
#[Depends('testCreate')]
|
||||||
public function testEditDescription($args): array
|
public function testEditDescription($args): array
|
||||||
{
|
{
|
||||||
[$pool_id, $image_ids] = $this->testCreate();
|
[$pool_id, $image_ids] = $this->testCreate();
|
||||||
|
|
|
@ -376,7 +376,7 @@ class Ratings extends Extension
|
||||||
} else {
|
} else {
|
||||||
$n = 0;
|
$n = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
$images = Image::find_images($n, 100, Tag::explode($_POST["query"]));
|
$images = Search::find_images($n, 100, Tag::explode($_POST["query"]));
|
||||||
if (count($images) == 0) {
|
if (count($images) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class RegenThumb extends Extension
|
||||||
}
|
}
|
||||||
if ($event->page_matches("regen_thumb/mass") && $user->can(Permissions::DELETE_IMAGE) && isset($_POST['tags'])) {
|
if ($event->page_matches("regen_thumb/mass") && $user->can(Permissions::DELETE_IMAGE) && isset($_POST['tags'])) {
|
||||||
$tags = Tag::explode(strtolower($_POST['tags']), false);
|
$tags = Tag::explode(strtolower($_POST['tags']), false);
|
||||||
$images = Image::find_images(limit: 10000, tags: $tags);
|
$images = Search::find_images(limit: 10000, tags: $tags);
|
||||||
|
|
||||||
foreach ($images as $image) {
|
foreach ($images as $image) {
|
||||||
$this->regenerate_thumbnail($image);
|
$this->regenerate_thumbnail($image);
|
||||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shimmie2;
|
namespace Shimmie2;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\Depends;
|
||||||
|
|
||||||
class RelationshipsTest extends ShimmiePHPUnitTestCase
|
class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
{
|
{
|
||||||
//=================================================================
|
//=================================================================
|
||||||
|
@ -32,9 +34,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testNoParent')]
|
||||||
* @depends testNoParent
|
|
||||||
*/
|
|
||||||
public function testSetParent($imgs): array
|
public function testSetParent($imgs): array
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testNoParent();
|
[$image_1, $image_2, $image_3] = $this->testNoParent();
|
||||||
|
@ -56,9 +56,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testSetParent')]
|
||||||
* @depends testSetParent
|
|
||||||
*/
|
|
||||||
public function testChangeParent($imgs): array
|
public function testChangeParent($imgs): array
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
|
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
|
||||||
|
@ -79,9 +77,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testSetParent')]
|
||||||
* @depends testSetParent
|
|
||||||
*/
|
|
||||||
public function testSearch($imgs)
|
public function testSearch($imgs)
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
|
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
|
||||||
|
@ -94,9 +90,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
$this->assert_search_results(["child:none"], [$image_3->id, $image_2->id]);
|
$this->assert_search_results(["child:none"], [$image_3->id, $image_2->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testChangeParent')]
|
||||||
* @depends testChangeParent
|
|
||||||
*/
|
|
||||||
public function testRemoveParent($imgs)
|
public function testRemoveParent($imgs)
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testChangeParent(null);
|
[$image_1, $image_2, $image_3] = $this->testChangeParent(null);
|
||||||
|
@ -146,9 +140,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testSetParentByTagBase')]
|
||||||
* @depends testSetParentByTagBase
|
|
||||||
*/
|
|
||||||
public function testSetParentByTag($imgs): array
|
public function testSetParentByTag($imgs): array
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testSetParentByTagBase();
|
[$image_1, $image_2, $image_3] = $this->testSetParentByTagBase();
|
||||||
|
@ -171,9 +163,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testSetParentByTag')]
|
||||||
* @depends testSetParentByTag
|
|
||||||
*/
|
|
||||||
public function testSetChildByTag($imgs): array
|
public function testSetChildByTag($imgs): array
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testSetParentByTag(null);
|
[$image_1, $image_2, $image_3] = $this->testSetParentByTag(null);
|
||||||
|
@ -196,9 +186,7 @@ class RelationshipsTest extends ShimmiePHPUnitTestCase
|
||||||
return [$image_1, $image_2, $image_3];
|
return [$image_1, $image_2, $image_3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Depends('testSetChildByTag')]
|
||||||
* @depends testSetChildByTag
|
|
||||||
*/
|
|
||||||
public function testRemoveParentByTag($imgs)
|
public function testRemoveParentByTag($imgs)
|
||||||
{
|
{
|
||||||
[$image_1, $image_2, $image_3] = $this->testSetChildByTag(null);
|
[$image_1, $image_2, $image_3] = $this->testSetChildByTag(null);
|
||||||
|
|
|
@ -240,14 +240,12 @@ class ReportImage extends Extension
|
||||||
|
|
||||||
public function count_reported_images(): int
|
public function count_reported_images(): int
|
||||||
{
|
{
|
||||||
global $cache, $database;
|
global $database;
|
||||||
|
|
||||||
$count = $cache->get("image-report-count");
|
return (int)cache_get_or_set(
|
||||||
if (is_null($count)) {
|
"image-report-count",
|
||||||
$count = $database->get_one("SELECT count(*) FROM image_reports");
|
fn () => $database->get_one("SELECT count(*) FROM image_reports"),
|
||||||
$cache->set("image-report-count", $count, 600);
|
600
|
||||||
}
|
);
|
||||||
|
|
||||||
return (int)$count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class RSSImages extends Extension
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
$images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
||||||
$this->do_rss($images, $search_terms, $page_number);
|
$this->do_rss($images, $search_terms, $page_number);
|
||||||
} catch (SearchTermParseException $stpe) {
|
} catch (SearchTermParseException $stpe) {
|
||||||
$this->theme->display_error(400, "Search parse error", $stpe->error);
|
$this->theme->display_error(400, "Search parse error", $stpe->error);
|
||||||
|
|
|
@ -84,7 +84,7 @@ class Rule34 extends Extension
|
||||||
{
|
{
|
||||||
global $cache;
|
global $cache;
|
||||||
if ($event->cmd == "wipe-thumb-cache") {
|
if ($event->cmd == "wipe-thumb-cache") {
|
||||||
foreach (Image::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) {
|
foreach (Search::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) {
|
||||||
print($image->id . "\n");
|
print($image->id . "\n");
|
||||||
$cache->delete("thumb-block:{$image->id}");
|
$cache->delete("thumb-block:{$image->id}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class ShimmieApi extends Extension
|
||||||
$search_terms = $event->get_search_terms();
|
$search_terms = $event->get_search_terms();
|
||||||
$page_number = $event->get_page_number();
|
$page_number = $event->get_page_number();
|
||||||
$page_size = $event->get_page_size();
|
$page_size = $event->get_page_size();
|
||||||
$images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
$images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
|
||||||
$safe_images = [];
|
$safe_images = [];
|
||||||
foreach ($images as $image) {
|
foreach ($images as $image) {
|
||||||
$image->get_tag_array();
|
$image->get_tag_array();
|
||||||
|
@ -131,7 +131,7 @@ class ShimmieApi extends Extension
|
||||||
for ($i = 0; $i < 4; $i++) {
|
for ($i = 0; $i < 4; $i++) {
|
||||||
unset($all[$i]);
|
unset($all[$i]);
|
||||||
}
|
}
|
||||||
$all['uploadcount'] = Image::count_images(["user_id=" . $all['id']]);
|
$all['uploadcount'] = Search::count_images(["user_id=" . $all['id']]);
|
||||||
$all['commentcount'] = $database->get_one(
|
$all['commentcount'] = $database->get_one(
|
||||||
"SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id",
|
"SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id",
|
||||||
["owner_id" => $all['id']]
|
["owner_id" => $all['id']]
|
||||||
|
|
|
@ -42,7 +42,7 @@ class XMLSitemap extends Extension
|
||||||
private function handle_smaller_sitemap()
|
private function handle_smaller_sitemap()
|
||||||
{
|
{
|
||||||
/* --- Add latest images to sitemap with higher priority --- */
|
/* --- Add latest images to sitemap with higher priority --- */
|
||||||
$latestimages = Image::find_images(limit: 50);
|
$latestimages = Search::find_images(limit: 50);
|
||||||
if (empty($latestimages)) {
|
if (empty($latestimages)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ class XMLSitemap extends Extension
|
||||||
$this->add_sitemap_queue($popular_tags, "monthly", "0.9" /* not sure how to deal with date here */);
|
$this->add_sitemap_queue($popular_tags, "monthly", "0.9" /* not sure how to deal with date here */);
|
||||||
|
|
||||||
/* --- Add latest images to sitemap with higher priority --- */
|
/* --- Add latest images to sitemap with higher priority --- */
|
||||||
$latestimages = Image::find_images(limit: 50);
|
$latestimages = Search::find_images(limit: 50);
|
||||||
$latestimages_urllist = [];
|
$latestimages_urllist = [];
|
||||||
$latest_image = null;
|
$latest_image = null;
|
||||||
foreach ($latestimages as $arrayid => $image) {
|
foreach ($latestimages as $arrayid => $image) {
|
||||||
|
@ -107,7 +107,7 @@ class XMLSitemap extends Extension
|
||||||
$this->add_sitemap_queue($other_tags, "monthly", "0.7" /* not sure how to deal with date here */);
|
$this->add_sitemap_queue($other_tags, "monthly", "0.7" /* not sure how to deal with date here */);
|
||||||
|
|
||||||
/* --- Add all other images to sitemap with lower priority --- */
|
/* --- Add all other images to sitemap with lower priority --- */
|
||||||
$otherimages = Image::find_images(offset: 51, limit: 10000000);
|
$otherimages = Search::find_images(offset: 51, limit: 10000000);
|
||||||
$image = null;
|
$image = null;
|
||||||
foreach ($otherimages as $arrayid => $image) {
|
foreach ($otherimages as $arrayid => $image) {
|
||||||
// create url from image id's
|
// create url from image id's
|
||||||
|
|
|
@ -300,7 +300,7 @@ class TagEdit extends Extension
|
||||||
log_info("tag_edit", "Mass editing tags: '$search' -> '$replace'");
|
log_info("tag_edit", "Mass editing tags: '$search' -> '$replace'");
|
||||||
|
|
||||||
if (count($search_set) == 1 && count($replace_set) == 1) {
|
if (count($search_set) == 1 && count($replace_set) == 1) {
|
||||||
$images = Image::find_images(limit: 10, tags: $replace_set);
|
$images = Search::find_images(limit: 10, tags: $replace_set);
|
||||||
if (count($images) == 0) {
|
if (count($images) == 0) {
|
||||||
log_info("tag_edit", "No images found with target tag, doing in-place rename");
|
log_info("tag_edit", "No images found with target tag, doing in-place rename");
|
||||||
$database->execute(
|
$database->execute(
|
||||||
|
@ -329,7 +329,7 @@ class TagEdit extends Extension
|
||||||
$search_forward[] = "id<$last_id";
|
$search_forward[] = "id<$last_id";
|
||||||
}
|
}
|
||||||
|
|
||||||
$images = Image::find_images(limit: 100, tags: $search_forward);
|
$images = Search::find_images(limit: 100, tags: $search_forward);
|
||||||
if (count($images) == 0) {
|
if (count($images) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ class TagEdit extends Extension
|
||||||
$search_forward[] = "id<$last_id";
|
$search_forward[] = "id<$last_id";
|
||||||
}
|
}
|
||||||
|
|
||||||
$images = Image::find_images(limit: 100, tags: $search_forward);
|
$images = Search::find_images(limit: 100, tags: $search_forward);
|
||||||
if (count($images) == 0) {
|
if (count($images) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -553,7 +553,6 @@ class TagList extends Extension
|
||||||
{
|
{
|
||||||
global $cache, $database;
|
global $cache, $database;
|
||||||
|
|
||||||
|
|
||||||
$wild_tags = $search;
|
$wild_tags = $search;
|
||||||
$cache_key = "related_tags:" . md5(Tag::implode($search));
|
$cache_key = "related_tags:" . md5(Tag::implode($search));
|
||||||
$related_tags = $cache->get($cache_key);
|
$related_tags = $cache->get($cache_key);
|
||||||
|
|
|
@ -64,11 +64,15 @@ abstract class ShimmiePHPUnitTestCase extends TestCase
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
global $database, $_tracer;
|
global $database, $_tracer;
|
||||||
$_tracer->begin($this->getName());
|
$_tracer->begin($this->name());
|
||||||
$_tracer->begin("setUp");
|
$_tracer->begin("setUp");
|
||||||
$class = str_replace("Test", "", get_class($this));
|
$class = str_replace("Test", "", get_class($this));
|
||||||
if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) {
|
try {
|
||||||
$this->markTestSkipped("$class not supported with this database");
|
if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) {
|
||||||
|
$this->markTestSkipped("$class not supported with this database");
|
||||||
|
}
|
||||||
|
} catch (ExtensionNotFound $e) {
|
||||||
|
// ignore - this is a core test rather than an extension test
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up a clean environment for each test
|
// Set up a clean environment for each test
|
||||||
|
@ -220,7 +224,7 @@ abstract class ShimmiePHPUnitTestCase extends TestCase
|
||||||
|
|
||||||
protected function assert_search_results($tags, $results): void
|
protected function assert_search_results($tags, $results): void
|
||||||
{
|
{
|
||||||
$images = Image::find_images(0, null, $tags);
|
$images = Search::find_images(0, null, $tags);
|
||||||
$ids = [];
|
$ids = [];
|
||||||
foreach ($images as $image) {
|
foreach ($images as $image) {
|
||||||
$ids[] = $image->id;
|
$ids[] = $image->id;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="../tests/bootstrap.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="../tests/bootstrap.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd">
|
||||||
<coverage>
|
<coverage/>
|
||||||
<include>
|
|
||||||
<directory suffix=".php">../core</directory>
|
|
||||||
<directory suffix=".php">../ext</directory>
|
|
||||||
<directory suffix=".php">../themes/default</directory>
|
|
||||||
</include>
|
|
||||||
</coverage>
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="core">
|
<testsuite name="core">
|
||||||
<directory suffix="test.php">../core/</directory>
|
<directory suffix="Test.php">../core/</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="ext">
|
<testsuite name="ext">
|
||||||
<directory suffix="test.php">../ext/</directory>
|
<directory suffix="test.php">../ext/</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">../core</directory>
|
||||||
|
<directory suffix=".php">../ext</directory>
|
||||||
|
<directory suffix=".php">../themes/default</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
Reference in a new issue