2020-01-26 13:19:35 +00:00
|
|
|
<?php declare(strict_types=1);
|
2009-07-19 07:38:13 +00:00
|
|
|
/**
|
2014-04-29 05:33:03 +00:00
|
|
|
* Class Image
|
|
|
|
*
|
2014-04-27 19:33:57 +00:00
|
|
|
* An object representing an entry in the images table.
|
|
|
|
*
|
|
|
|
* As of 2.2, this no longer necessarily represents an
|
|
|
|
* image per se, but could be a video, sound file, or any
|
|
|
|
* other supported upload type.
|
2007-12-06 11:01:18 +00:00
|
|
|
*/
|
2019-05-28 16:59:38 +00:00
|
|
|
class Image
|
|
|
|
{
|
2019-06-15 16:18:52 +00:00
|
|
|
public const IMAGE_DIR = "images";
|
|
|
|
public const THUMBNAIL_DIR = "thumbs";
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var null|int */
|
|
|
|
public $id = null;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var int */
|
|
|
|
public $height;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var int */
|
|
|
|
public $width;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
|
|
|
public $hash;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2020-01-26 16:38:26 +00:00
|
|
|
/** @var int */
|
2019-05-28 16:59:38 +00:00
|
|
|
public $filesize;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
|
|
|
public $filename;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
2020-06-14 16:05:55 +00:00
|
|
|
private $ext;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
private $mime;
|
2014-04-27 19:33:57 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string[]|null */
|
|
|
|
public $tag_array;
|
2014-04-28 06:23:45 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var int */
|
|
|
|
public $owner_id;
|
2019-10-02 09:49:32 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
|
|
|
public $owner_ip;
|
2019-10-02 09:49:32 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
|
|
|
public $posted;
|
2019-10-02 09:49:32 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var string */
|
|
|
|
public $source;
|
2019-10-02 09:49:32 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/** @var boolean */
|
|
|
|
public $locked = false;
|
|
|
|
|
2019-06-25 20:17:13 +00:00
|
|
|
/** @var boolean */
|
|
|
|
public $lossless = null;
|
|
|
|
|
|
|
|
/** @var boolean */
|
|
|
|
public $video = null;
|
|
|
|
|
2020-08-28 14:11:14 +00:00
|
|
|
/** @var string */
|
|
|
|
public $video_codec = null;
|
|
|
|
|
2020-01-29 20:22:50 +00:00
|
|
|
/** @var boolean */
|
|
|
|
public $image = null;
|
|
|
|
|
2019-06-25 20:17:13 +00:00
|
|
|
/** @var boolean */
|
|
|
|
public $audio = null;
|
|
|
|
|
|
|
|
/** @var int */
|
|
|
|
public $length = null;
|
|
|
|
|
2020-01-29 20:22:50 +00:00
|
|
|
public static $bool_props = ["locked", "lossless", "video", "audio"];
|
|
|
|
public static $int_props = ["id", "owner_id", "height", "width", "filesize", "length"];
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* One will very rarely construct an image directly, more common
|
|
|
|
* would be to use Image::by_id, Image::by_hash, etc.
|
|
|
|
*/
|
|
|
|
public function __construct(?array $row=null)
|
|
|
|
{
|
|
|
|
if (!is_null($row)) {
|
|
|
|
foreach ($row as $name => $value) {
|
2020-10-24 21:16:18 +00:00
|
|
|
if (is_numeric($name)) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-10-24 18:13:46 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
// some databases use table.name rather than name
|
|
|
|
$name = str_replace("images.", "", $name);
|
|
|
|
|
2020-01-26 13:19:35 +00:00
|
|
|
// hax, this is likely the cause of much scrutinizer-ci complaints.
|
2020-01-29 20:22:50 +00:00
|
|
|
if (is_null($value)) {
|
|
|
|
$this->$name = null;
|
|
|
|
} elseif (in_array($name, self::$bool_props)) {
|
2020-01-26 18:10:58 +00:00
|
|
|
$this->$name = bool_escape((string)$value);
|
2020-01-29 20:22:50 +00:00
|
|
|
} elseif (in_array($name, self::$int_props)) {
|
2020-01-26 18:10:58 +00:00
|
|
|
$this->$name = int_escape((string)$value);
|
2020-01-26 16:38:26 +00:00
|
|
|
} else {
|
2020-01-26 13:19:35 +00:00
|
|
|
$this->$name = $value;
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-29 17:23:29 +00:00
|
|
|
public static function by_id(int $id): ?Image
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", ["id"=>$id]);
|
|
|
|
return ($row ? new Image($row) : null);
|
|
|
|
}
|
|
|
|
|
2019-05-29 17:23:29 +00:00
|
|
|
public static function by_hash(string $hash): ?Image
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
2020-02-10 20:44:36 +00:00
|
|
|
$hash = strtolower($hash);
|
2019-05-28 16:59:38 +00:00
|
|
|
$row = $database->get_row("SELECT images.* FROM images WHERE hash=:hash", ["hash"=>$hash]);
|
|
|
|
return ($row ? new Image($row) : null);
|
|
|
|
}
|
|
|
|
|
2019-10-04 19:48:21 +00:00
|
|
|
public static function by_id_or_hash(string $id): ?Image
|
|
|
|
{
|
|
|
|
return (is_numeric($id) && strlen($id) != 32) ? Image::by_id((int)$id) : Image::by_hash($id);
|
|
|
|
}
|
|
|
|
|
2019-07-25 10:07:05 +00:00
|
|
|
public static function by_random(array $tags=[], int $limit_range=0): ?Image
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
$max = Image::count_images($tags);
|
|
|
|
if ($max < 1) {
|
|
|
|
return null;
|
|
|
|
} // From Issue #22 - opened by HungryFeline on May 30, 2011.
|
2019-10-15 13:22:23 +00:00
|
|
|
if ($limit_range > 0 && $max > $limit_range) {
|
2019-07-25 10:07:05 +00:00
|
|
|
$max = $limit_range;
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
$rand = mt_rand(0, $max-1);
|
|
|
|
$set = Image::find_images($rand, 1, $tags);
|
|
|
|
if (count($set) > 0) {
|
|
|
|
return $set[0];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-05 15:24:46 +00:00
|
|
|
private static function find_images_internal(int $start = 0, ?int $limit = null, array $tags=[]): iterable
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database, $user, $config;
|
|
|
|
|
|
|
|
if ($start < 0) {
|
|
|
|
$start = 0;
|
|
|
|
}
|
2019-07-05 15:24:46 +00:00
|
|
|
if ($limit!=null && $limit < 1) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$limit = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPEED_HAX) {
|
2019-07-09 14:10:21 +00:00
|
|
|
if (!$user->can(Permissions::BIG_SEARCH) and count($tags) > 3) {
|
2019-05-28 16:59:38 +00:00
|
|
|
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 12:55:13 +00:00
|
|
|
$querylet = Image::build_search_querylet($tags, $limit, $start);
|
2020-01-27 19:49:50 +00:00
|
|
|
$result = $database->get_all_iterable($querylet->sql, $querylet->variables);
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2019-07-05 15:24:46 +00:00
|
|
|
return $result;
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 22:44:25 +00:00
|
|
|
/**
|
2019-07-05 15:24:46 +00:00
|
|
|
* Search for an array of images
|
2019-06-12 22:44:25 +00:00
|
|
|
*
|
|
|
|
* #param string[] $tags
|
2019-07-05 15:24:46 +00:00
|
|
|
* #return Image[]
|
2019-06-12 22:44:25 +00:00
|
|
|
*/
|
2019-10-04 19:47:48 +00:00
|
|
|
public static function find_images(int $start, ?int $limit = null, array $tags=[]): array
|
2019-06-12 22:44:25 +00:00
|
|
|
{
|
2019-07-05 15:24:46 +00:00
|
|
|
$result = self::find_images_internal($start, $limit, $tags);
|
2019-06-12 22:44:25 +00:00
|
|
|
|
|
|
|
$images = [];
|
2019-07-05 15:24:46 +00:00
|
|
|
foreach ($result as $row) {
|
|
|
|
$images[] = new Image($row);
|
2019-06-12 22:44:25 +00:00
|
|
|
}
|
2019-07-05 15:24:46 +00:00
|
|
|
return $images;
|
|
|
|
}
|
2019-06-12 22:44:25 +00:00
|
|
|
|
2019-07-05 15:24:46 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
2019-06-12 22:44:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/*
|
|
|
|
* Image-related utility functions
|
|
|
|
*/
|
|
|
|
|
2020-02-04 23:05:07 +00:00
|
|
|
public static function count_total_images(): int
|
|
|
|
{
|
|
|
|
global $cache, $database;
|
|
|
|
$total = $cache->get("image-count");
|
|
|
|
if (!$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]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Count the number of image results for a given search
|
|
|
|
*
|
|
|
|
* #param string[] $tags
|
|
|
|
*/
|
|
|
|
public static function count_images(array $tags=[]): int
|
|
|
|
{
|
2019-10-02 09:49:32 +00:00
|
|
|
global $cache, $database;
|
2019-05-28 16:59:38 +00:00
|
|
|
$tag_count = count($tags);
|
|
|
|
|
2020-07-15 22:20:29 +00:00
|
|
|
if (SPEED_HAX && $tag_count === 0) {
|
2020-02-04 22:44:27 +00:00
|
|
|
// total number of images in the DB
|
2020-02-04 23:05:07 +00:00
|
|
|
$total = self::count_total_images();
|
2020-07-15 22:20:29 +00:00
|
|
|
} elseif (SPEED_HAX && $tag_count === 1 && !preg_match("/[:=><\*\?]/", $tags[0])) {
|
2020-10-25 19:15:13 +00:00
|
|
|
if (!str_starts_with($tags[0], "-")) {
|
2020-02-04 23:05:07 +00:00
|
|
|
// 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));
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
} else {
|
2020-02-04 22:44:27 +00:00
|
|
|
// complex query
|
2020-02-13 02:09:31 +00:00
|
|
|
// implode(tags) can be too long for memcache...
|
|
|
|
$cache_key = "image-count:" . md5(Tag::implode($tags));
|
|
|
|
$total = $cache->get($cache_key);
|
2020-02-04 23:05:07 +00:00
|
|
|
if (!$total) {
|
2020-02-04 22:44:27 +00:00
|
|
|
if (Extension::is_enabled(RatingsInfo::KEY)) {
|
|
|
|
$tags[] = "rating:*";
|
|
|
|
}
|
2020-02-04 23:27:01 +00:00
|
|
|
$querylet = Image::build_search_querylet($tags);
|
2020-02-04 22:44:27 +00:00
|
|
|
$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
|
2020-02-13 02:09:31 +00:00
|
|
|
$cache->set($cache_key, $total, 3600);
|
2020-02-04 22:44:27 +00:00
|
|
|
}
|
2019-06-27 04:11:59 +00:00
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
if (is_null($total)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return $total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the number of pages for a given search
|
|
|
|
*
|
|
|
|
* #param string[] $tags
|
|
|
|
*/
|
2020-01-26 13:19:35 +00:00
|
|
|
public static function count_pages(array $tags=[]): int
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $config;
|
2020-01-26 13:19:35 +00:00
|
|
|
return (int)ceil(Image::count_images($tags) / $config->get_int(IndexConfig::IMAGES));
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2019-06-16 18:11:16 +00:00
|
|
|
private static function terms_to_conditions(array $terms): array
|
2019-06-16 18:07:55 +00:00
|
|
|
{
|
2019-06-16 18:11:16 +00:00
|
|
|
$tag_conditions = [];
|
|
|
|
$img_conditions = [];
|
2020-10-25 12:32:10 +00:00
|
|
|
$stpen = 0; // search term parse event number
|
2020-10-25 12:55:13 +00:00
|
|
|
$order = null;
|
2019-06-16 18:07:55 +00:00
|
|
|
|
|
|
|
/*
|
2019-06-16 18:11:16 +00:00
|
|
|
* Turn a bunch of strings into a bunch of TagCondition
|
|
|
|
* and ImgCondition objects
|
2019-06-16 18:07:55 +00:00
|
|
|
*/
|
2020-10-25 12:55:13 +00:00
|
|
|
/** @var $stpe SearchTermParseEvent */
|
2020-10-25 12:32:10 +00:00
|
|
|
$stpe = send_event(new SearchTermParseEvent($stpen++, null, $terms));
|
2020-10-25 12:55:13 +00:00
|
|
|
if ($stpe->order) {
|
|
|
|
$order = $stpe->order;
|
2020-10-25 13:09:51 +00:00
|
|
|
} elseif (!empty($stpe->querylets)) {
|
2020-10-25 12:55:13 +00:00
|
|
|
foreach ($stpe->querylets as $querylet) {
|
2019-06-16 18:11:16 +00:00
|
|
|
$img_conditions[] = new ImgCondition($querylet, true);
|
2019-06-16 18:07:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($terms as $term) {
|
|
|
|
$positive = true;
|
|
|
|
if (is_string($term) && !empty($term) && ($term[0] == '-')) {
|
|
|
|
$positive = false;
|
|
|
|
$term = substr($term, 1);
|
|
|
|
}
|
|
|
|
if (strlen($term) === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-25 12:55:13 +00:00
|
|
|
/** @var $stpe SearchTermParseEvent */
|
2020-10-25 12:32:10 +00:00
|
|
|
$stpe = send_event(new SearchTermParseEvent($stpen++, $term, $terms));
|
2020-10-25 12:55:13 +00:00
|
|
|
if ($stpe->order) {
|
|
|
|
$order = $stpe->order;
|
2020-10-25 13:09:51 +00:00
|
|
|
} elseif (!empty($stpe->querylets)) {
|
2020-10-25 12:55:13 +00:00
|
|
|
foreach ($stpe->querylets as $querylet) {
|
2019-06-16 18:11:16 +00:00
|
|
|
$img_conditions[] = new ImgCondition($querylet, $positive);
|
2019-06-16 18:07:55 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-06-16 18:25:40 +00:00
|
|
|
// if the whole match is wild, skip this
|
2019-06-16 18:07:55 +00:00
|
|
|
if (str_replace("*", "", $term) != "") {
|
2019-06-16 18:11:16 +00:00
|
|
|
$tag_conditions[] = new TagCondition($term, $positive);
|
2019-06-16 18:07:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-25 12:55:13 +00:00
|
|
|
return [$tag_conditions, $img_conditions, $order];
|
2019-06-16 18:07:55 +00:00
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/*
|
|
|
|
* Accessors & mutators
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the next image in the sequence.
|
|
|
|
*
|
|
|
|
* Rather than simply $this_id + 1, one must take into account
|
|
|
|
* deleted images and search queries
|
|
|
|
*
|
|
|
|
* #param string[] $tags
|
|
|
|
*/
|
|
|
|
public function get_next(array $tags=[], bool $next=true): ?Image
|
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
|
|
|
|
if ($next) {
|
|
|
|
$gtlt = "<";
|
|
|
|
$dir = "DESC";
|
|
|
|
} else {
|
|
|
|
$gtlt = ">";
|
|
|
|
$dir = "ASC";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($tags) === 0) {
|
|
|
|
$row = $database->get_row('
|
2016-06-07 01:05:19 +00:00
|
|
|
SELECT images.*
|
|
|
|
FROM images
|
|
|
|
WHERE images.id '.$gtlt.' '.$this->id.'
|
|
|
|
ORDER BY images.id '.$dir.'
|
|
|
|
LIMIT 1
|
|
|
|
');
|
2019-05-28 16:59:38 +00:00
|
|
|
} else {
|
|
|
|
$tags[] = 'id'. $gtlt . $this->id;
|
2020-02-04 23:27:01 +00:00
|
|
|
$querylet = Image::build_search_querylet($tags);
|
2019-05-28 16:59:38 +00:00
|
|
|
$querylet->append_sql(' ORDER BY images.id '.$dir.' LIMIT 1');
|
|
|
|
$row = $database->get_row($querylet->sql, $querylet->variables);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ($row ? new Image($row) : null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The reverse of get_next
|
|
|
|
*
|
|
|
|
* #param string[] $tags
|
|
|
|
*/
|
|
|
|
public function get_prev(array $tags=[]): ?Image
|
|
|
|
{
|
|
|
|
return $this->get_next($tags, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the User who owns this Image
|
|
|
|
*/
|
|
|
|
public function get_owner(): User
|
|
|
|
{
|
|
|
|
return User::by_id($this->owner_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the image's owner.
|
|
|
|
*/
|
2019-05-29 17:23:29 +00:00
|
|
|
public function set_owner(User $owner): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
if ($owner->id != $this->owner_id) {
|
|
|
|
$database->execute("
|
2016-06-07 01:05:19 +00:00
|
|
|
UPDATE images
|
|
|
|
SET owner_id=:owner_id
|
|
|
|
WHERE id=:id
|
2019-05-28 16:59:38 +00:00
|
|
|
", ["owner_id"=>$owner->id, "id"=>$this->id]);
|
2020-02-01 21:37:07 +00:00
|
|
|
log_info("core_image", "Owner for Image #{$this->id} set to {$owner->name}");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-29 20:22:50 +00:00
|
|
|
public function save_to_db()
|
|
|
|
{
|
|
|
|
global $database, $user;
|
|
|
|
$cut_name = substr($this->filename, 0, 255);
|
|
|
|
|
|
|
|
if (is_null($this->id)) {
|
|
|
|
$database->execute(
|
|
|
|
"INSERT INTO images(
|
|
|
|
owner_id, owner_ip,
|
|
|
|
filename, filesize,
|
2020-06-14 16:05:55 +00:00
|
|
|
hash, mime, ext,
|
2020-01-29 20:22:50 +00:00
|
|
|
width, height,
|
|
|
|
posted, source
|
|
|
|
)
|
|
|
|
VALUES (
|
|
|
|
:owner_id, :owner_ip,
|
|
|
|
:filename, :filesize,
|
2020-06-14 16:05:55 +00:00
|
|
|
:hash, :mime, :ext,
|
2020-01-29 20:22:50 +00:00
|
|
|
0, 0,
|
|
|
|
now(), :source
|
|
|
|
)",
|
|
|
|
[
|
|
|
|
"owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'],
|
|
|
|
"filename" => $cut_name, "filesize" => $this->filesize,
|
2020-06-14 16:05:55 +00:00
|
|
|
"hash" => $this->hash, "mime" => strtolower($this->mime),
|
|
|
|
"ext" => strtolower($this->ext), "source" => $this->source
|
2020-01-29 20:22:50 +00:00
|
|
|
]
|
|
|
|
);
|
|
|
|
$this->id = $database->get_last_insert_id('images_id_seq');
|
|
|
|
} else {
|
|
|
|
$database->execute(
|
|
|
|
"UPDATE images SET ".
|
|
|
|
"filename = :filename, filesize = :filesize, hash = :hash, ".
|
2020-06-14 16:05:55 +00:00
|
|
|
"mime = :mime, ext = :ext, width = 0, height = 0, source = :source ".
|
2020-01-29 20:22:50 +00:00
|
|
|
"WHERE id = :id",
|
|
|
|
[
|
|
|
|
"filename" => $cut_name,
|
|
|
|
"filesize" => $this->filesize,
|
|
|
|
"hash" => $this->hash,
|
2020-06-14 16:05:55 +00:00
|
|
|
"mime" => strtolower($this->mime),
|
2020-01-29 20:22:50 +00:00
|
|
|
"ext" => strtolower($this->ext),
|
|
|
|
"source" => $this->source,
|
|
|
|
"id" => $this->id,
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$database->execute(
|
|
|
|
"UPDATE images SET ".
|
|
|
|
"lossless = :lossless, ".
|
2020-08-17 22:05:51 +00:00
|
|
|
"video = :video, video_codec = :video_codec, audio = :audio,image = :image, ".
|
2020-01-29 20:22:50 +00:00
|
|
|
"height = :height, width = :width, ".
|
|
|
|
"length = :length WHERE id = :id",
|
|
|
|
[
|
|
|
|
"id" => $this->id,
|
|
|
|
"width" => $this->width ?? 0,
|
|
|
|
"height" => $this->height ?? 0,
|
2020-10-27 00:49:50 +00:00
|
|
|
"lossless" => $this->lossless,
|
|
|
|
"video" => $this->video,
|
2020-10-26 23:18:14 +00:00
|
|
|
"video_codec" => $this->video_codec,
|
2020-10-27 00:34:28 +00:00
|
|
|
"image" => $this->image,
|
2020-10-27 00:49:50 +00:00
|
|
|
"audio" => $this->audio,
|
2020-01-29 20:22:50 +00:00
|
|
|
"length" => $this->length
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2020-01-29 20:34:02 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Get this image's tags as an array.
|
|
|
|
*
|
|
|
|
* #return string[]
|
|
|
|
*/
|
|
|
|
public function get_tag_array(): array
|
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
if (!isset($this->tag_array)) {
|
|
|
|
$this->tag_array = $database->get_col("
|
2016-06-07 01:05:19 +00:00
|
|
|
SELECT tag
|
|
|
|
FROM image_tags
|
|
|
|
JOIN tags ON image_tags.tag_id = tags.id
|
|
|
|
WHERE image_id=:id
|
|
|
|
ORDER BY tag
|
2019-05-28 16:59:38 +00:00
|
|
|
", ["id"=>$this->id]);
|
|
|
|
}
|
|
|
|
return $this->tag_array;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get this image's tags as a string.
|
|
|
|
*/
|
|
|
|
public function get_tag_list(): string
|
|
|
|
{
|
|
|
|
return Tag::implode($this->get_tag_array());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the URL for the full size image
|
|
|
|
*/
|
|
|
|
public function get_image_link(): string
|
|
|
|
{
|
2019-06-18 18:45:59 +00:00
|
|
|
return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext');
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 18:22:48 +00:00
|
|
|
/**
|
|
|
|
* Get the nicely formatted version of the file name
|
|
|
|
*/
|
|
|
|
public function get_nice_image_name(): string
|
|
|
|
{
|
2020-02-25 12:26:56 +00:00
|
|
|
$plte = new ParseLinkTemplateEvent('$id - $tags.$ext', $this);
|
|
|
|
send_event($plte);
|
|
|
|
return $plte->text;
|
2019-06-09 18:22:48 +00:00
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Get the URL for the thumbnail
|
|
|
|
*/
|
|
|
|
public function get_thumb_link(): string
|
|
|
|
{
|
2019-06-09 18:22:48 +00:00
|
|
|
global $config;
|
2020-06-14 16:05:55 +00:00
|
|
|
$mime = $config->get_string(ImageConfig::THUMB_MIME);
|
|
|
|
$ext = FileExtension::get_for_mime($mime);
|
2019-06-18 18:45:59 +00:00
|
|
|
return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check configured template for a link, then try nice URL, then plain URL
|
|
|
|
*/
|
|
|
|
private function get_link(string $template, string $nice, string $plain): string
|
|
|
|
{
|
|
|
|
global $config;
|
|
|
|
|
|
|
|
$image_link = $config->get_string($template);
|
|
|
|
|
|
|
|
if (!empty($image_link)) {
|
2020-10-25 19:31:58 +00:00
|
|
|
if (!str_contains($image_link, "://") && !str_starts_with($image_link, "/")) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$image_link = make_link($image_link);
|
|
|
|
}
|
2020-02-25 12:18:18 +00:00
|
|
|
$chosen = $image_link;
|
2019-05-28 16:59:38 +00:00
|
|
|
} elseif ($config->get_bool('nice_urls', false)) {
|
2020-02-25 12:18:18 +00:00
|
|
|
$chosen = make_link($nice);
|
2019-05-28 16:59:38 +00:00
|
|
|
} else {
|
2020-02-25 12:18:18 +00:00
|
|
|
$chosen = make_link($plain);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2020-02-25 12:18:18 +00:00
|
|
|
return $this->parse_link_template($chosen);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the tooltip for this image, formatted according to the
|
|
|
|
* configured template.
|
|
|
|
*/
|
|
|
|
public function get_tooltip(): string
|
|
|
|
{
|
|
|
|
global $config;
|
2020-02-25 12:26:56 +00:00
|
|
|
$plte = new ParseLinkTemplateEvent($config->get_string(ImageConfig::TIP), $this);
|
|
|
|
send_event($plte);
|
|
|
|
return $plte->text;
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 14:41:25 +00:00
|
|
|
/**
|
|
|
|
* Get the info for this image, formatted according to the
|
|
|
|
* configured template.
|
|
|
|
*/
|
|
|
|
public function get_info(): string
|
|
|
|
{
|
|
|
|
global $config;
|
|
|
|
$plte = new ParseLinkTemplateEvent($config->get_string(ImageConfig::INFO), $this);
|
|
|
|
send_event($plte);
|
|
|
|
return $plte->text;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Figure out where the full size image is on disk.
|
|
|
|
*/
|
|
|
|
public function get_image_filename(): string
|
|
|
|
{
|
2019-06-15 16:18:52 +00:00
|
|
|
return warehouse_path(self::IMAGE_DIR, $this->hash);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Figure out where the thumbnail is on disk.
|
|
|
|
*/
|
|
|
|
public function get_thumb_filename(): string
|
|
|
|
{
|
2019-06-15 16:18:52 +00:00
|
|
|
return warehouse_path(self::THUMBNAIL_DIR, $this->hash);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the original filename.
|
|
|
|
*/
|
|
|
|
public function get_filename(): string
|
|
|
|
{
|
|
|
|
return $this->filename;
|
|
|
|
}
|
|
|
|
|
2020-06-14 16:05:55 +00:00
|
|
|
/**
|
|
|
|
* Get the image's extension.
|
|
|
|
*/
|
|
|
|
public function get_ext(): string
|
|
|
|
{
|
|
|
|
return $this->ext;
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Get the image's mime type.
|
|
|
|
*/
|
2020-10-08 22:40:34 +00:00
|
|
|
public function get_mime(): ?string
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2020-06-14 16:05:55 +00:00
|
|
|
if ($this->mime===MimeType::WEBP&&$this->lossless) {
|
|
|
|
return MimeType::WEBP_LOSSLESS;
|
|
|
|
}
|
2020-07-07 16:07:19 +00:00
|
|
|
$m = $this->mime;
|
|
|
|
if (is_null($m)) {
|
|
|
|
$m = MimeMap::get_for_extension($this->ext)[0];
|
|
|
|
}
|
|
|
|
return $m;
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-14 16:05:55 +00:00
|
|
|
* Set the image's mime type.
|
2019-05-28 16:59:38 +00:00
|
|
|
*/
|
2020-06-14 16:05:55 +00:00
|
|
|
public function set_mime($mime): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2020-06-14 16:05:55 +00:00
|
|
|
$this->mime = $mime;
|
|
|
|
$this->ext = FileExtension::get_for_mime($this->get_mime());
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 16:05:55 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* Get the image's source URL
|
|
|
|
*/
|
2019-05-28 18:00:23 +00:00
|
|
|
public function get_source(): ?string
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
return $this->source;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the image's source URL
|
|
|
|
*/
|
|
|
|
public function set_source(string $new_source): void
|
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
$old_source = $this->source;
|
|
|
|
if (empty($new_source)) {
|
|
|
|
$new_source = null;
|
|
|
|
}
|
|
|
|
if ($new_source != $old_source) {
|
|
|
|
$database->execute("UPDATE images SET source=:source WHERE id=:id", ["source"=>$new_source, "id"=>$this->id]);
|
2020-02-01 21:37:07 +00:00
|
|
|
log_info("core_image", "Source for Image #{$this->id} set to: $new_source (was $old_source)");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the image is locked.
|
|
|
|
*/
|
|
|
|
public function is_locked(): bool
|
|
|
|
{
|
|
|
|
return $this->locked;
|
|
|
|
}
|
|
|
|
|
2020-10-27 01:05:12 +00:00
|
|
|
public function set_locked(bool $locked): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
2020-10-27 01:05:12 +00:00
|
|
|
if ($locked !== $this->locked) {
|
|
|
|
$database->execute("UPDATE images SET locked=:yn WHERE id=:id", ["yn"=>$locked, "id"=>$this->id]);
|
|
|
|
log_info("core_image", "Setting Image #{$this->id} lock to: $locked");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete all tags from this image.
|
|
|
|
*
|
|
|
|
* Normally in preparation to set them to a new set.
|
|
|
|
*/
|
|
|
|
public function delete_tags_from_image(): void
|
|
|
|
{
|
|
|
|
global $database;
|
2019-06-20 15:42:32 +00:00
|
|
|
if ($database->get_driver_name() == DatabaseDriver::MYSQL) {
|
2019-05-28 16:59:38 +00:00
|
|
|
//mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this
|
|
|
|
$database->execute(
|
|
|
|
"
|
2015-12-04 11:38:44 +00:00
|
|
|
UPDATE tags t
|
|
|
|
INNER JOIN image_tags it ON t.id = it.tag_id
|
|
|
|
SET count = count - 1
|
|
|
|
WHERE it.image_id = :id",
|
2019-05-28 16:59:38 +00:00
|
|
|
["id"=>$this->id]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$database->execute("
|
2015-12-04 11:38:44 +00:00
|
|
|
UPDATE tags
|
|
|
|
SET count = count - 1
|
2016-06-07 01:05:19 +00:00
|
|
|
WHERE id IN (
|
|
|
|
SELECT tag_id
|
|
|
|
FROM image_tags
|
|
|
|
WHERE image_id = :id
|
|
|
|
)
|
2019-05-28 16:59:38 +00:00
|
|
|
", ["id"=>$this->id]);
|
|
|
|
}
|
|
|
|
$database->execute("
|
2016-06-07 01:05:19 +00:00
|
|
|
DELETE
|
|
|
|
FROM image_tags
|
|
|
|
WHERE image_id=:id
|
2019-05-28 16:59:38 +00:00
|
|
|
", ["id"=>$this->id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the tags for this image.
|
|
|
|
*/
|
2019-05-29 17:23:29 +00:00
|
|
|
public function set_tags(array $unfiltered_tags): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2019-12-15 19:47:18 +00:00
|
|
|
global $cache, $database, $page;
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2019-06-01 16:39:03 +00:00
|
|
|
$unfiltered_tags = array_unique($unfiltered_tags);
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$tags = [];
|
|
|
|
foreach ($unfiltered_tags as $tag) {
|
|
|
|
if (mb_strlen($tag, 'UTF-8') > 255) {
|
2019-12-15 19:47:18 +00:00
|
|
|
$page->flash("Can't set a tag longer than 255 characters");
|
2019-05-28 16:59:38 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-10-25 19:15:13 +00:00
|
|
|
if (str_starts_with($tag, "-")) {
|
2019-12-15 19:47:18 +00:00
|
|
|
$page->flash("Can't set a tag which starts with a minus");
|
2019-05-28 16:59:38 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$tags[] = $tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($tags) <= 0) {
|
|
|
|
throw new SCoreException('Tried to set zero tags');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Tag::implode($tags) != $this->get_tag_list()) {
|
|
|
|
// delete old
|
|
|
|
$this->delete_tags_from_image();
|
2019-06-09 19:18:25 +00:00
|
|
|
|
|
|
|
$written_tags = [];
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
// insert each new tags
|
|
|
|
foreach ($tags as $tag) {
|
|
|
|
$id = $database->get_one(
|
2020-02-01 22:44:50 +00:00
|
|
|
"
|
2016-06-07 01:05:19 +00:00
|
|
|
SELECT id
|
|
|
|
FROM tags
|
2019-12-15 16:07:46 +00:00
|
|
|
WHERE LOWER(tag) = LOWER(:tag)
|
2020-02-01 22:44:50 +00:00
|
|
|
",
|
2019-05-28 16:59:38 +00:00
|
|
|
["tag"=>$tag]
|
|
|
|
);
|
|
|
|
if (empty($id)) {
|
|
|
|
// a new tag
|
|
|
|
$database->execute(
|
|
|
|
"INSERT INTO tags(tag) VALUES (:tag)",
|
|
|
|
["tag"=>$tag]
|
|
|
|
);
|
2019-11-02 19:57:34 +00:00
|
|
|
$database->execute(
|
2020-02-01 22:44:50 +00:00
|
|
|
"INSERT INTO image_tags(image_id, tag_id)
|
|
|
|
VALUES(:id, (SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)))",
|
2019-05-28 16:59:38 +00:00
|
|
|
["id"=>$this->id, "tag"=>$tag]
|
|
|
|
);
|
|
|
|
} else {
|
2019-06-09 19:18:25 +00:00
|
|
|
// check if tag has already been written
|
2019-06-14 12:16:58 +00:00
|
|
|
if (in_array($id, $written_tags)) {
|
2019-06-09 19:18:25 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$database->execute("
|
2019-06-09 19:18:25 +00:00
|
|
|
INSERT INTO image_tags(image_id, tag_id)
|
|
|
|
VALUES(:iid, :tid)
|
|
|
|
", ["iid"=>$this->id, "tid"=>$id]);
|
|
|
|
|
|
|
|
array_push($written_tags, $id);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
$database->execute(
|
2020-02-01 22:44:50 +00:00
|
|
|
"
|
2016-06-07 01:05:19 +00:00
|
|
|
UPDATE tags
|
|
|
|
SET count = count + 1
|
2019-12-15 16:07:46 +00:00
|
|
|
WHERE LOWER(tag) = LOWER(:tag)
|
2020-02-01 22:44:50 +00:00
|
|
|
",
|
2019-05-28 16:59:38 +00:00
|
|
|
["tag"=>$tag]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-01 21:37:07 +00:00
|
|
|
log_info("core_image", "Tags for Image #{$this->id} set to: ".Tag::implode($tags));
|
2019-10-02 09:49:32 +00:00
|
|
|
$cache->delete("image-{$this->id}-tags");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete this image from the database and disk
|
|
|
|
*/
|
|
|
|
public function delete(): void
|
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
$this->delete_tags_from_image();
|
|
|
|
$database->execute("DELETE FROM images WHERE id=:id", ["id"=>$this->id]);
|
2020-02-01 21:37:07 +00:00
|
|
|
log_info("core_image", 'Deleted Image #'.$this->id.' ('.$this->hash.')');
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
unlink($this->get_image_filename());
|
|
|
|
unlink($this->get_thumb_filename());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function removes an image (and thumbnail) from the DISK ONLY.
|
|
|
|
* It DOES NOT remove anything from the database.
|
|
|
|
*/
|
|
|
|
public function remove_image_only(): void
|
|
|
|
{
|
2020-02-01 21:37:07 +00:00
|
|
|
log_info("core_image", 'Removed Image File ('.$this->hash.')');
|
2019-05-28 16:59:38 +00:00
|
|
|
@unlink($this->get_image_filename());
|
|
|
|
@unlink($this->get_thumb_filename());
|
|
|
|
}
|
|
|
|
|
2020-02-09 16:02:16 +00:00
|
|
|
public function parse_link_template(string $tmpl, int $n=0): string
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2020-02-09 19:22:25 +00:00
|
|
|
$plte = send_event(new ParseLinkTemplateEvent($tmpl, $this));
|
|
|
|
$tmpl = $plte->link;
|
2020-02-09 16:02:16 +00:00
|
|
|
return load_balance_url($tmpl, $this->hash, $n);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2020-02-05 01:26:18 +00:00
|
|
|
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_name() === DatabaseDriver::SQLITE) {
|
|
|
|
$sq .= "ESCAPE '\\'";
|
|
|
|
}
|
|
|
|
return $database->get_col($sq, ["tag" => Tag::sqlify($tag)]);
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
|
|
|
* #param string[] $terms
|
|
|
|
*/
|
2020-02-04 23:27:01 +00:00
|
|
|
private static function build_search_querylet(
|
2020-10-25 12:55:13 +00:00
|
|
|
array $terms,
|
2020-02-04 23:27:01 +00:00
|
|
|
?int $limit=null,
|
|
|
|
?int $offset=null
|
2020-02-05 00:16:47 +00:00
|
|
|
): Querylet {
|
2020-10-25 12:55:13 +00:00
|
|
|
global $config;
|
|
|
|
|
|
|
|
list($tag_conditions, $img_conditions, $order) = self::terms_to_conditions($terms);
|
|
|
|
$order = ($order ?: "images.".$config->get_string(IndexConfig::ORDER));
|
2020-02-04 23:27:01 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$positive_tag_count = 0;
|
|
|
|
$negative_tag_count = 0;
|
2019-06-16 18:11:16 +00:00
|
|
|
foreach ($tag_conditions as $tq) {
|
2019-06-16 18:07:55 +00:00
|
|
|
if ($tq->positive) {
|
|
|
|
$positive_tag_count++;
|
2019-05-28 16:59:38 +00:00
|
|
|
} else {
|
2019-06-16 18:07:55 +00:00
|
|
|
$negative_tag_count++;
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 ($positive_tag_count === 0 && $negative_tag_count === 0) {
|
2020-02-05 01:38:32 +00:00
|
|
|
$query = new Querylet("SELECT images.* FROM images WHERE 1=1");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2008-10-09 03:28:58 +00:00
|
|
|
|
2020-02-04 23:49:54 +00:00
|
|
|
// 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.
|
2020-02-04 23:43:24 +00:00
|
|
|
elseif (
|
2020-02-04 23:49:54 +00:00
|
|
|
(
|
|
|
|
($positive_tag_count === 1 && $negative_tag_count === 0)
|
|
|
|
|| ($positive_tag_count === 0 && $negative_tag_count === 1)
|
|
|
|
)
|
2020-02-04 23:43:24 +00:00
|
|
|
&& empty($img_conditions)
|
2020-02-05 00:27:37 +00:00
|
|
|
&& ($order == "id DESC" || $order == "images.id DESC")
|
2020-02-04 23:43:24 +00:00
|
|
|
&& !is_null($offset)
|
|
|
|
&& !is_null($limit)
|
|
|
|
) {
|
2020-02-04 23:49:54 +00:00
|
|
|
$in = $positive_tag_count === 1 ? "IN" : "NOT IN";
|
2020-02-09 16:02:16 +00:00
|
|
|
// IN (SELECT id FROM tags) is 100x slower than doing a separate
|
|
|
|
// query and then a second query for IN(first_query_results)??
|
2020-02-05 01:26:18 +00:00
|
|
|
$tag_array = self::tag_or_wildcard_to_ids($tag_conditions[0]->tag);
|
|
|
|
if (count($tag_array) == 0) {
|
|
|
|
if ($positive_tag_count == 1) {
|
|
|
|
$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
|
2020-02-05 09:01:22 +00:00
|
|
|
ORDER BY images.id DESC
|
2020-02-05 01:26:18 +00:00
|
|
|
", ["limit"=>$limit, "offset"=>$offset]);
|
2020-02-05 01:38:32 +00:00
|
|
|
// don't offset at the image level because
|
|
|
|
// we already offset at the image_tags level
|
2020-02-05 09:01:22 +00:00
|
|
|
$order = null;
|
|
|
|
$limit = null;
|
|
|
|
$offset = null;
|
2020-02-05 01:26:18 +00:00
|
|
|
}
|
2020-02-04 23:43:24 +00:00
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
// more than one positive tag, or more than zero negative tags
|
|
|
|
else {
|
2020-02-05 00:16:47 +00:00
|
|
|
$positive_tag_id_array = [];
|
|
|
|
$positive_wildcard_id_array = [];
|
|
|
|
$negative_tag_id_array = [];
|
|
|
|
|
|
|
|
foreach ($tag_conditions as $tq) {
|
2020-02-05 01:26:18 +00:00
|
|
|
$tag_ids = self::tag_or_wildcard_to_ids($tq->tag);
|
2020-02-05 00:16:47 +00:00
|
|
|
$tag_count = count($tag_ids);
|
|
|
|
|
|
|
|
if ($tq->positive) {
|
|
|
|
if ($tag_count== 0) {
|
|
|
|
# one of the positive tags had zero results, therefor there
|
|
|
|
# can be no results; "where 1=0" should shortcut things
|
2020-02-05 01:38:32 +00:00
|
|
|
return new Querylet("SELECT images.* FROM images WHERE 1=0");
|
2020-02-05 00:16:47 +00:00
|
|
|
} 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 {
|
|
|
|
// 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, @$_GET['q']);
|
|
|
|
if (!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);
|
2020-02-05 01:38:32 +00:00
|
|
|
$sub_query = "SELECT it.image_id FROM image_tags it ";
|
2020-02-05 00:16:47 +00:00
|
|
|
$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");
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge all the image metadata searches into one generic querylet
|
|
|
|
* and append to the base querylet with "AND blah"
|
|
|
|
*/
|
2019-06-16 18:11:16 +00:00
|
|
|
if (!empty($img_conditions)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$n = 0;
|
|
|
|
$img_sql = "";
|
|
|
|
$img_vars = [];
|
2019-06-16 18:11:16 +00:00
|
|
|
foreach ($img_conditions as $iq) {
|
2019-05-28 16:59:38 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-02-04 23:27:01 +00:00
|
|
|
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]));
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
return $query;
|
|
|
|
}
|
2009-01-04 13:53:14 +00:00
|
|
|
}
|