This repository has been archived on 2024-09-05. You can view files and clone it, but cannot push or open issues or pull requests.
shimmie2/core/imageboard.pack.php

1011 lines
27 KiB
PHP
Raw Normal View History

<?php
2009-07-19 07:38:13 +00:00
/**
* All the imageboard-specific bits of code should be in this file, everything
* else in /core should be standard SCore bits.
*/
2009-07-21 03:18:40 +00:00
/**
* \page search Shimmie2: Searching
*
* The current search system is built of several search item -> image ID list
* translators, eg:
*
* \li the item "fred" will search the image_tags table to find image IDs with the fred tag
* \li the item "size=640x480" will search the images table to find image IDs of 640x480 images
*
* So the search "fred size=640x480" will calculate two lists and take the
* intersection. (There are some optimisations in there making it more
* complicated behind the scenes, but as long as you can turn a single word
* into a list of image IDs, making a search plugin should be simple)
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Classes *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$tag_n = 0; // temp hack
2009-07-19 07:38:13 +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.
*/
class Image {
var $id = null;
var $height, $width;
var $hash, $filesize;
var $filename, $ext;
var $owner_ip;
var $posted;
var $source;
2010-02-17 14:16:20 +00:00
var $locked;
2009-07-19 07:38:13 +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 Image($row=null) {
if(!is_null($row)) {
foreach($row as $name => $value) {
// some databases use table.name rather than name
$name = str_replace("images.", "", $name);
$this->$name = $value; // hax
}
$this->posted_timestamp = strtotime($this->posted); // pray
2010-02-02 00:29:38 +00:00
assert(is_numeric($this->id));
assert(is_numeric($this->height));
assert(is_numeric($this->width));
}
}
2009-07-19 07:38:13 +00:00
/**
* Find an image by ID
*
* @retval Image
2009-07-19 07:38:13 +00:00
*/
public static function by_id($id) {
assert(is_numeric($id));
global $database;
$image = null;
2011-01-01 15:28:30 +00:00
$row = $database->get_row("SELECT * FROM images WHERE images.id=:id", array("id"=>$id));
return ($row ? new Image($row) : null);
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Find an image by hash
*
* @retval Image
2009-07-19 07:38:13 +00:00
*/
public static function by_hash($hash) {
assert(is_string($hash));
global $database;
$image = null;
2011-01-01 16:28:04 +00:00
$row = $database->get_row("SELECT images.* FROM images WHERE hash=:hash", array("hash"=>$hash));
return ($row ? new Image($row) : null);
}
2009-07-19 07:38:13 +00:00
/**
* Pick a random image out of a set
*
* @retval Image
2009-07-19 07:38:13 +00:00
*/
public static function by_random($tags=array()) {
assert(is_array($tags));
$max = Image::count_images($tags);
2011-09-12 01:41:46 +00:00
if ($max < 1) return null; // From Issue #22 - opened by HungryFeline on May 30, 2011.
$rand = mt_rand(0, $max-1);
$set = Image::find_images($rand, 1, $tags);
if(count($set) > 0) return $set[0];
else return null;
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Search for an array of images
*/
public static function find_images($start, $limit, $tags=array()) {
assert(is_numeric($start));
assert(is_numeric($limit));
assert(is_array($tags));
global $database;
$images = array();
if($start < 0) $start = 0;
if($limit < 1) $limit = 1;
2009-01-04 19:18:37 +00:00
$querylet = Image::build_search_querylet($tags);
2011-01-01 15:28:30 +00:00
$querylet->append(new Querylet("ORDER BY images.id DESC LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start)));
2011-03-14 14:20:30 +00:00
#var_dump($querylet->sql); var_dump($querylet->variables);
$result = $database->execute($querylet->sql, $querylet->variables);
2009-01-04 19:18:37 +00:00
2011-01-01 15:28:30 +00:00
while($row = $result->fetch()) {
$images[] = new Image($row);
}
return $images;
}
/*
* Image-related utility functions
*/
2009-07-19 07:38:13 +00:00
/**
* Count the number of image results for a given search
*/
public static function count_images($tags=array()) {
assert(is_array($tags));
global $database;
if(count($tags) == 0) {
2011-01-01 16:28:04 +00:00
#return $database->get_one("SELECT COUNT(*) FROM images");
2010-02-02 00:29:38 +00:00
$total = $database->cache->get("image-count");
if(!$total) {
2011-01-01 15:28:30 +00:00
$total = $database->get_one("SELECT COUNT(*) FROM images");
2010-02-02 00:29:38 +00:00
$database->cache->set("image-count", $total, 600);
}
return $total;
}
else if(count($tags) == 1 && !preg_match("/[:=><]/", $tags[0])) {
2011-01-01 15:28:30 +00:00
return $database->get_one(
$database->engine->scoreql_to_sql("SELECT count FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)"),
array("tag"=>$tags[0]));
}
else {
$querylet = Image::build_search_querylet($tags);
$result = $database->execute($querylet->sql, $querylet->variables);
2011-01-01 15:58:09 +00:00
return $result->rowCount();
}
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Count the number of pages for a given search
*/
public static function count_pages($tags=array()) {
assert(is_array($tags));
global $config, $database;
$images_per_page = $config->get_int('index_width') * $config->get_int('index_height');
return ceil(Image::count_images($tags) / $images_per_page);
}
/*
* Accessors & mutators
*/
2009-07-19 07:38:13 +00:00
/**
* Find the next image in the sequence.
*
* Rather than simply $this_id + 1, one must take into account
* deleted images and search queries
*
* @retval Image
2009-07-19 07:38:13 +00:00
*/
public function get_next($tags=array(), $next=true) {
assert(is_array($tags));
assert(is_bool($next));
global $database;
2009-01-04 19:18:37 +00:00
if($next) {
$gtlt = "<";
$dir = "DESC";
}
else {
$gtlt = ">";
$dir = "ASC";
}
if(count($tags) == 0) {
2011-01-01 16:28:04 +00:00
$row = $database->get_row("SELECT images.* FROM images WHERE images.id $gtlt {$this->id} ORDER BY images.id $dir LIMIT 1");
}
else {
$tags[] = "id$gtlt{$this->id}";
$querylet = Image::build_search_querylet($tags);
$querylet->append_sql(" ORDER BY images.id $dir LIMIT 1");
2011-01-01 16:28:04 +00:00
$row = $database->get_row($querylet->sql, $querylet->variables);
}
2009-01-04 19:18:37 +00:00
return ($row ? new Image($row) : null);
}
2009-07-19 07:38:13 +00:00
/**
* The reverse of get_next
*
* @retval Image
2009-07-19 07:38:13 +00:00
*/
public function get_prev($tags=array()) {
return $this->get_next($tags, false);
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Find the User who owns this Image
*
* @retval User
2009-07-19 07:38:13 +00:00
*/
public function get_owner() {
return User::by_id($this->owner_id);
}
2009-07-19 07:38:13 +00:00
/**
* Get this image's tags as an array
*/
public function get_tag_array() {
global $database;
$cached = $database->cache->get("image-{$this->id}-tags");
2009-01-19 18:27:53 +00:00
if($cached) return $cached;
if(!isset($this->tag_array)) {
2011-01-01 15:28:30 +00:00
$this->tag_array = $database->get_col("SELECT tag FROM image_tags JOIN tags ON image_tags.tag_id = tags.id WHERE image_id=:id ORDER BY tag", array("id"=>$this->id));
}
2009-01-19 18:27:53 +00:00
$database->cache->set("image-{$this->id}-tags", $this->tag_array);
return $this->tag_array;
}
2009-07-19 07:38:13 +00:00
/**
* Get this image's tags as a string
*/
public function get_tag_list() {
2009-08-24 02:33:51 +00:00
return Tag::implode($this->get_tag_array());
}
2009-07-19 07:38:13 +00:00
/**
* Get the URL for the full size image
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_image_link() {
global $config;
if(strlen($config->get_string('image_ilink')) > 0) {
return $this->parse_link_template($config->get_string('image_ilink'));
2008-12-27 10:17:53 +00:00
}
else if($config->get_bool('nice_urls', false)) {
2009-01-22 10:49:22 +00:00
return $this->parse_link_template(make_link('_images/$hash/$id - $tags.$ext'));
2008-12-27 10:17:53 +00:00
}
else {
2009-01-22 13:42:44 +00:00
return $this->parse_link_template(make_link('image/$id.$ext'));
2008-12-27 10:17:53 +00:00
}
}
2009-07-19 07:38:13 +00:00
/**
* Get a short link to the full size image
*
* @deprecated
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_short_link() {
global $config;
return $this->parse_link_template($config->get_string('image_slink'));
}
2009-07-19 07:38:13 +00:00
/**
* Get the URL for the thumbnail
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_thumb_link() {
global $config;
if(strlen($config->get_string('image_tlink')) > 0) {
return $this->parse_link_template($config->get_string('image_tlink'));
2008-12-27 10:17:53 +00:00
}
else if($config->get_bool('nice_urls', false)) {
2009-01-22 10:49:22 +00:00
return $this->parse_link_template(make_link('_thumbs/$hash/thumb.jpg'));
2008-12-27 10:17:53 +00:00
}
else {
2009-12-24 07:34:29 +00:00
return $this->parse_link_template(make_link('thumb/$id.jpg'));
2008-12-27 10:17:53 +00:00
}
}
2009-07-19 07:38:13 +00:00
/**
* Get the tooltip for this image, formatted according to the
* configured template
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_tooltip() {
global $config;
2010-03-24 18:24:47 +00:00
return $this->parse_link_template($config->get_string('image_tip'), "no_escape");
}
2009-07-19 07:38:13 +00:00
/**
* Figure out where the full size image is on disk
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_image_filename() {
2010-02-02 00:29:38 +00:00
return warehouse_path("images", $this->hash);
}
2009-07-19 07:38:13 +00:00
/**
* Figure out where the thumbnail is on disk
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_thumb_filename() {
2010-02-02 00:29:38 +00:00
return warehouse_path("thumbs", $this->hash);
}
2009-07-19 07:38:13 +00:00
/**
* Get the original filename
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_filename() {
return $this->filename;
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Get the image's mime type
*
* FIXME: now we handle more than just images
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_mime_type() {
2010-07-19 12:35:10 +00:00
$type = strtolower($this->ext);
if($type == "jpg") $type = "jpeg";
return "image/$type";
}
2009-07-19 07:38:13 +00:00
/**
* Get the image's filename extension
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_ext() {
return $this->ext;
}
2009-07-19 07:38:13 +00:00
/**
* Get the image's source URL
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function get_source() {
return $this->source;
}
2009-01-04 19:18:37 +00:00
2009-07-19 07:38:13 +00:00
/**
* Set the image's source URL
*/
public function set_source($source) {
global $database;
if(empty($source)) $source = null;
2011-01-01 15:58:09 +00:00
$database->execute("UPDATE images SET source=:source WHERE id=:id", array("source"=>$source, "id"=>$this->id));
}
2010-02-17 14:16:20 +00:00
public function is_locked() {
2010-02-17 15:07:05 +00:00
return ($this->locked === true || $this->locked == "Y" || $this->locked == "t");
2010-02-17 14:16:20 +00:00
}
public function set_locked($tf) {
global $database;
$ln = $tf ? "Y" : "N";
$sln = $database->engine->scoreql_to_sql("SCORE_BOOL_$ln");
$sln = str_replace("'", "", $sln);
$sln = str_replace('"', "", $sln);
2011-01-01 15:58:09 +00:00
$database->execute("UPDATE images SET locked=:yn WHERE id=:id", array("yn"=>$sln, "id"=>$this->id));
2010-02-17 14:16:20 +00:00
}
2009-07-19 07:38:13 +00:00
/**
* Delete all tags from this image.
*
* Normally in preparation to set them to a new set.
*/
public function delete_tags_from_image() {
global $database;
$database->execute(
"UPDATE tags SET count = count - 1 WHERE id IN ".
2011-01-01 15:58:09 +00:00
"(SELECT tag_id FROM image_tags WHERE image_id = :id)", array("id"=>$this->id));
$database->execute("DELETE FROM image_tags WHERE image_id=:id", array("id"=>$this->id));
}
2009-07-19 07:38:13 +00:00
/**
* Set the tags for this image
*/
public function set_tags($tags) {
global $database;
2009-01-24 11:32:48 +00:00
$tags = Tag::resolve_list($tags);
2009-01-18 01:07:06 +00:00
assert(is_array($tags));
assert(count($tags) > 0);
// delete old
$this->delete_tags_from_image();
2009-01-04 19:18:37 +00:00
2009-01-18 01:07:06 +00:00
// insert each new tags
foreach($tags as $tag) {
2011-01-01 16:28:04 +00:00
$id = $database->get_one(
$database->engine->scoreql_to_sql(
2011-01-01 15:58:09 +00:00
"SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)"
),
2011-01-01 15:58:09 +00:00
array("tag"=>$tag));
2009-01-22 17:08:33 +00:00
if(empty($id)) {
// a new tag
$database->execute(
2011-01-01 15:58:09 +00:00
"INSERT INTO tags(tag) VALUES (:tag)",
array("tag"=>$tag));
$database->execute(
2009-01-22 17:08:33 +00:00
"INSERT INTO image_tags(image_id, tag_id)
2011-01-01 15:58:09 +00:00
VALUES(:id, (SELECT id FROM tags WHERE tag = :tag))",
array("id"=>$this->id, "tag"=>$tag));
2009-01-22 17:08:33 +00:00
}
else {
// user of an existing tag
$database->execute(
2011-01-01 15:58:09 +00:00
"INSERT INTO image_tags(image_id, tag_id) VALUES(:iid, :tid)",
array("iid"=>$this->id, "tid"=>$id));
2009-01-22 17:08:33 +00:00
}
$database->execute(
$database->engine->scoreql_to_sql(
2011-01-01 15:58:09 +00:00
"UPDATE tags SET count = count + 1 WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)"
),
2011-01-01 15:58:09 +00:00
array("tag"=>$tag));
}
2009-01-19 18:27:53 +00:00
2009-05-08 10:52:29 +00:00
log_info("core-image", "Tags for Image #{$this->id} set to: ".implode(" ", $tags));
$database->cache->delete("image-{$this->id}-tags");
}
2009-07-19 07:38:13 +00:00
/**
* Delete this image from the database and disk
*/
public function delete() {
global $database;
2009-05-11 14:48:18 +00:00
$this->delete_tags_from_image();
2011-01-01 15:58:09 +00:00
$database->execute("DELETE FROM images WHERE id=:id", array("id"=>$this->id));
2009-07-14 19:52:55 +00:00
log_info("core-image", "Deleted Image #{$this->id} ({$this->hash})");
2009-01-04 19:18:37 +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() {
log_info("core-image", "Removed Image File ({$this->hash})");
@unlink($this->get_image_filename());
@unlink($this->get_thumb_filename());
}
2009-07-19 07:38:13 +00:00
/**
* Someone please explain this
2009-07-19 07:38:13 +00:00
*
* @retval string
2009-07-19 07:38:13 +00:00
*/
public function parse_link_template($tmpl, $_escape="url_escape") {
global $config;
// don't bother hitting the database if it won't be used...
$tags = "";
if(strpos($tmpl, '$tags') !== false) { // * stabs dynamically typed languages with a rusty spoon *
$tags = $this->get_tag_list();
$tags = str_replace("/", "", $tags);
$tags = preg_replace("/^\.+/", "", $tags);
}
$base_href = $config->get_string('base_href');
$fname = $this->get_filename();
$base_fname = strpos($fname, '.') ? substr($fname, 0, strrpos($fname, '.')) : $fname;
$tmpl = str_replace('$id', $this->id, $tmpl);
2009-11-12 09:27:09 +00:00
$tmpl = str_replace('$hash_ab', substr($this->hash, 0, 2), $tmpl);
$tmpl = str_replace('$hash_cd', substr($this->hash, 2, 2), $tmpl);
2009-11-12 09:30:41 +00:00
$tmpl = str_replace('$hash', $this->hash, $tmpl);
$tmpl = str_replace('$tags', $_escape($tags), $tmpl);
$tmpl = str_replace('$base', $base_href, $tmpl);
$tmpl = str_replace('$ext', $this->ext, $tmpl);
$tmpl = str_replace('$size', "{$this->width}x{$this->height}", $tmpl);
$tmpl = str_replace('$filesize', to_shorthand_int($this->filesize), $tmpl);
$tmpl = str_replace('$filename', $_escape($base_fname), $tmpl);
$tmpl = str_replace('$title', $_escape($config->get_string("title")), $tmpl);
$plte = new ParseLinkTemplateEvent($tmpl, $this);
send_event($plte);
$tmpl = $plte->link;
return $tmpl;
}
private static function build_search_querylet($terms) {
assert(is_array($terms));
global $database;
2009-05-11 10:52:48 +00:00
if($database->engine->name == "mysql")
return Image::build_ugly_search_querylet($terms);
2009-05-11 10:52:48 +00:00
else
return Image::build_accurate_search_querylet($terms);
2009-05-11 10:52:48 +00:00
}
/**
* "foo bar -baz user=foo" becomes
*
* SELECT * FROM images WHERE
* images.id IN (SELECT image_id FROM image_tags WHERE tag='foo')
* AND images.id IN (SELECT image_id FROM image_tags WHERE tag='bar')
* AND NOT images.id IN (SELECT image_id FROM image_tags WHERE tag='baz')
* AND images.id IN (SELECT id FROM images WHERE owner_name='foo')
*
* This is:
* A) Incredibly simple:
* Each search term maps to a list of image IDs
* B) Runs really fast on a good database:
* These lists are calucalted once, and the set intersection taken
* C) Runs really slow on bad databases:
* All the subqueries are executed every time for every row in the
* images table. Yes, MySQL does suck this much.
*/
private static function build_accurate_search_querylet($terms) {
global $config, $database;
$tag_querylets = array();
$img_querylets = array();
$positive_tag_count = 0;
$stpe = new SearchTermParseEvent(null, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, true);
}
}
// parse the words that are searched for into
// various types of querylet
foreach($terms as $term) {
$positive = true;
if((strlen($term) > 0) && ($term[0] == '-')) {
$positive = false;
$term = substr($term, 1);
}
2009-01-04 19:18:37 +00:00
2009-01-04 18:39:11 +00:00
$term = Tag::resolve_alias($term);
$stpe = new SearchTermParseEvent($term, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, $positive);
}
}
else {
$term = str_replace("*", "%", $term);
$term = str_replace("?", "_", $term);
if(!preg_match("/^[%_]+$/", $term)) {
2009-01-04 18:39:11 +00:00
$expansions = Tag::resolve_wildcard($term);
if($positive) $positive_tag_count++;
foreach($expansions as $term) {
$tag_querylets[] = new TagQuerylet($term, $positive);
}
}
}
}
// merge all the image metadata searches into one generic querylet
$n = 0;
$sql = "";
$terms = array();
foreach($img_querylets as $iq) {
if($n++ > 0) $sql .= " AND";
if(!$iq->positive) $sql .= " NOT";
$sql .= " (" . $iq->qlet->sql . ")";
$terms = array_merge($terms, $iq->qlet->variables);
}
$img_search = new Querylet($sql, $terms);
// no tags, do a simple search (+image metadata if we have any)
if(count($tag_querylets) == 0) {
$query = new Querylet("SELECT images.* FROM images ");
if(strlen($img_search->sql) > 0) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
}
// one positive tag (a common case), do an optimised search
else if(count($tag_querylets) == 1 && $tag_querylets[0]->positive) {
2010-02-02 11:11:03 +00:00
$query = new Querylet($database->engine->scoreql_to_sql("
SELECT images.* FROM images
2011-01-22 15:51:55 +00:00
WHERE images.id IN (
SELECT image_id FROM image_tags WHERE tag_id = (
SELECT tags.id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)
)
)
2011-01-01 15:58:09 +00:00
"), array("tag"=>$tag_querylets[0]->tag));
if(strlen($img_search->sql) > 0) {
$query->append_sql(" AND ");
$query->append($img_search);
}
}
// more than one positive tag, or more than zero negative tags
else {
$positive_tag_id_array = array();
$negative_tag_id_array = array();
$tags_ok = true;
foreach($tag_querylets as $tq) {
2011-01-03 15:18:24 +00:00
$tag_ids = $database->get_col(
2010-02-02 11:11:03 +00:00
$database->engine->scoreql_to_sql(
2011-01-01 15:58:09 +00:00
"SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)"
2010-02-02 11:11:03 +00:00
),
2011-01-01 15:58:09 +00:00
array("tag"=>$tq->tag));
if($tq->positive) {
$positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids);
$tags_ok = count($tag_ids) > 0;
if(!$tags_ok) break;
}
else {
$negative_tag_id_array = array_merge($negative_tag_id_array, $tag_ids);
}
}
if($tags_ok) {
$have_pos = count($positive_tag_id_array) > 0;
$have_neg = count($negative_tag_id_array) > 0;
$sql = "SELECT images.* FROM images WHERE ";
if($have_pos) {
$positive_tag_id_list = join(', ', $positive_tag_id_array);
$sql .= "
images.id IN (
SELECT image_id
FROM image_tags
WHERE tag_id IN ($positive_tag_id_list)
GROUP BY image_id
HAVING COUNT(image_id)>=$positive_tag_count
)
";
}
if($have_pos && $have_neg) {
$sql .= " AND ";
}
if($have_neg) {
$negative_tag_id_list = join(', ', $negative_tag_id_array);
$sql .= "
images.id NOT IN (
SELECT image_id
FROM image_tags
WHERE tag_id IN ($negative_tag_id_list)
)
";
}
$query = new Querylet($sql);
if(strlen($img_search->sql) > 0) {
$query->append_sql(" AND ");
$query->append($img_search);
}
}
else {
# one of the positive tags had zero results, therefor there
# can be no results; "where 1=0" should shortcut things
$query = new Querylet("
SELECT images.*
FROM images
WHERE 1=0
");
}
}
return $query;
}
2009-05-11 10:52:48 +00:00
/**
* this function exists because mysql is a turd, see the docs for
* build_accurate_search_querylet() for a full explanation
*/
private static function build_ugly_search_querylet($terms) {
global $config, $database;
2009-05-11 10:52:48 +00:00
$tag_querylets = array();
$img_querylets = array();
$positive_tag_count = 0;
$negative_tag_count = 0;
$stpe = new SearchTermParseEvent(null, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, true);
}
}
// turn each term into a specific type of querylet
foreach($terms as $term) {
$negative = false;
if((strlen($term) > 0) && ($term[0] == '-')) {
$negative = true;
$term = substr($term, 1);
}
$term = Tag::resolve_alias($term);
$stpe = new SearchTermParseEvent($term, $terms);
send_event($stpe);
if($stpe->is_querylet_set()) {
foreach($stpe->get_querylets() as $querylet) {
$img_querylets[] = new ImgQuerylet($querylet, !$negative);
}
}
else {
$term = str_replace("*", "%", $term);
$term = str_replace("?", "_", $term);
if(!preg_match("/^[%_]+$/", $term)) {
$tag_querylets[] = new TagQuerylet($term, !$negative);
}
}
}
// merge all the tag querylets into one generic one
$sql = "0";
$terms = array();
foreach($tag_querylets as $tq) {
global $tag_n;
2009-05-11 10:52:48 +00:00
$sign = $tq->positive ? "+" : "-";
$sql .= " $sign (tag LIKE :tag$tag_n)";
$terms["tag$tag_n"] = $tq->tag;
$tag_n++;
2009-05-11 10:52:48 +00:00
if($sign == "+") $positive_tag_count++;
else $negative_tag_count++;
}
$tag_search = new Querylet($sql, $terms);
// merge all the image metadata searches into one generic querylet
$n = 0;
$sql = "";
$terms = array();
foreach($img_querylets as $iq) {
if($n++ > 0) $sql .= " AND";
if(!$iq->positive) $sql .= " NOT";
$sql .= " (" . $iq->qlet->sql . ")";
$terms = array_merge($terms, $iq->qlet->variables);
}
$img_search = new Querylet($sql, $terms);
// no tags, do a simple search (+image metadata if we have any)
if($positive_tag_count + $negative_tag_count == 0) {
$query = new Querylet("SELECT images.*,UNIX_TIMESTAMP(posted) AS posted_timestamp FROM images ");
if(strlen($img_search->sql) > 0) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
}
// one positive tag (a common case), do an optimised search
else if($positive_tag_count == 1 && $negative_tag_count == 0) {
$query = new Querylet(
// MySQL is braindead, and does a full table scan on images, running the subquery once for each row -_-
// "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ",
"
SELECT images.*, UNIX_TIMESTAMP(posted) AS posted_timestamp
FROM tags, image_tags, images
WHERE
2011-03-14 14:20:30 +00:00
tag LIKE :tag0
2009-05-11 10:52:48 +00:00
AND tags.id = image_tags.tag_id
AND image_tags.image_id = images.id
",
$tag_search->variables);
if(strlen($img_search->sql) > 0) {
$query->append_sql(" AND ");
$query->append($img_search);
}
}
// more than one positive tag, or more than zero negative tags
else {
$s_tag_array = array_map("sql_escape", $tag_search->variables);
$s_tag_list = join(', ', $s_tag_array);
$tag_id_array = array();
$tags_ok = true;
foreach($tag_search->variables as $tag) {
2011-01-01 15:58:09 +00:00
$tag_ids = $database->get_col("SELECT id FROM tags WHERE tag LIKE :tag", array("tag"=>$tag));
2009-05-11 10:52:48 +00:00
$tag_id_array = array_merge($tag_id_array, $tag_ids);
$tags_ok = count($tag_ids) > 0;
if(!$tags_ok) break;
}
if($tags_ok) {
$tag_id_list = join(', ', $tag_id_array);
$subquery = new Querylet("
SELECT images.*, SUM({$tag_search->sql}) AS score
FROM images
LEFT JOIN image_tags ON image_tags.image_id = images.id
JOIN tags ON image_tags.tag_id = tags.id
WHERE tags.id IN ({$tag_id_list})
GROUP BY images.id
2011-01-01 15:58:09 +00:00
HAVING score = :score",
2009-05-11 10:52:48 +00:00
array_merge(
$tag_search->variables,
2011-01-01 15:58:09 +00:00
array("score"=>$positive_tag_count)
2009-05-11 10:52:48 +00:00
)
);
$query = new Querylet("
SELECT *, UNIX_TIMESTAMP(posted) AS posted_timestamp
FROM ({$subquery->sql}) AS images ", $subquery->variables);
if(strlen($img_search->sql) > 0) {
$query->append_sql(" WHERE ");
$query->append($img_search);
}
}
else {
# there are no results, "where 1=0" should shortcut things
$query = new Querylet("
SELECT images.*
FROM images
WHERE 1=0
");
}
}
$tag_n = 0;
2009-05-11 10:52:48 +00:00
return $query;
}
}
2009-07-19 07:38:13 +00:00
/**
* A class for organising the tag related functions.
*
* All the methods are static, one should never actually use a tag object.
*/
2009-01-04 18:39:11 +00:00
class Tag {
2009-07-19 07:38:13 +00:00
/**
* Remove any excess fluff from a user-input tag
*/
2009-01-04 18:39:11 +00:00
public static function sanitise($tag) {
assert(is_string($tag));
$tag = preg_replace("/[\s?*]/", "", $tag);
$tag = preg_replace("/\.+/", ".", $tag);
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag);
return $tag;
}
2009-07-19 07:38:13 +00:00
/**
* Turn any string or array into a valid tag array
*/
2009-01-24 11:32:48 +00:00
public static function explode($tags) {
2009-07-19 07:38:13 +00:00
assert(is_string($tags) || is_array($tags));
2009-01-24 11:32:48 +00:00
if(is_string($tags)) {
$tags = explode(' ', $tags);
}
else if(is_array($tags)) {
// do nothing
}
$tags = array_map("trim", $tags);
$tag_array = array();
foreach($tags as $tag) {
if(is_string($tag) && strlen($tag) > 0) {
$tag_array[] = $tag;
}
}
if(count($tag_array) == 0) {
$tag_array = array("tagme");
}
2011-01-01 18:40:21 +00:00
sort($tag_array);
2009-01-24 11:32:48 +00:00
return $tag_array;
}
2009-08-24 02:33:51 +00:00
public static function implode($tags) {
assert(is_string($tags) || is_array($tags));
if(is_string($tags)) {
// do nothing
}
else if(is_array($tags)) {
2011-01-01 18:40:21 +00:00
sort($tags);
2009-08-24 02:33:51 +00:00
$tags = implode(' ', $tags);
}
return $tags;
}
2009-01-04 18:39:11 +00:00
public static function resolve_alias($tag) {
assert(is_string($tag));
global $database;
2011-01-22 15:51:55 +00:00
$newtag = $database->get_one(
$database->engine->scoreql_to_sql("SELECT newtag FROM aliases WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)"),
array("tag"=>$tag));
2009-01-04 18:39:11 +00:00
if(!empty($newtag)) {
return $newtag;
} else {
return $tag;
}
}
public static function resolve_wildcard($tag) {
if(strpos($tag, "%") === false && strpos($tag, "_") === false) {
return array($tag);
}
else {
global $database;
2011-01-03 15:18:24 +00:00
$newtags = $database->get_col("SELECT tag FROM tags WHERE tag LIKE ?", array($tag));
2009-01-04 18:39:11 +00:00
if(count($newtags) > 0) {
$resolved = $newtags;
} else {
$resolved = array($tag);
}
return $resolved;
}
}
2009-01-24 11:32:48 +00:00
public static function resolve_list($tags) {
$tags = Tag::explode($tags);
$new = array();
foreach($tags as $tag) {
$new_set = explode(' ', Tag::resolve_alias($tag));
foreach($new_set as $new_one) {
$new[] = $new_one;
}
}
$new = array_map(array('Tag', 'sanitise'), $new);
$new = array_iunique($new); // remove any duplicate tags
return $new;
}
2009-01-04 18:39:11 +00:00
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Misc functions *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-07-19 07:38:13 +00:00
/**
* Move a file from PHP's temporary area into shimmie's image storage
* heirachy, or throw an exception trying
*/
function move_upload_to_archive($event) {
2010-02-02 00:29:38 +00:00
$target = warehouse_path("images", $event->hash);
2010-03-25 10:37:52 +00:00
if(!file_exists(dirname($target))) mkdir(dirname($target), 0755, true);
2010-02-02 00:29:38 +00:00
if(!@copy($event->tmpname, $target)) {
2011-11-08 11:02:04 +00:00
$errors = error_get_last(); // note: requires php 5.2
throw new UploadException("Failed to copy file from uploads ({$event->tmpname}) to archive ($target): {$errors['type']} / {$errors['message']}");
return false;
}
return true;
}
2009-07-19 07:38:13 +00:00
/**
* Given a full size pair of dimentions, return a pair scaled down to fit
* into the configured thumbnail square, with ratio intact
*/
function get_thumbnail_size($orig_width, $orig_height) {
global $config;
if($orig_width == 0) $orig_width = 192;
if($orig_height == 0) $orig_height = 192;
if($orig_width > $orig_height * 5) $orig_width = $orig_height * 5;
if($orig_height > $orig_width * 5) $orig_height = $orig_width * 5;
$max_width = $config->get_int('thumb_width');
$max_height = $config->get_int('thumb_height');
$xscale = ($max_height / $orig_height);
$yscale = ($max_width / $orig_width);
$scale = ($xscale < $yscale) ? $xscale : $yscale;
if($scale > 1 && $config->get_bool('thumb_upscale')) {
return array((int)$orig_width, (int)$orig_height);
}
else {
return array((int)($orig_width*$scale), (int)($orig_height*$scale));
}
}
?>