From 6a4031dfd59bbd04ca01dd4fc2c57138afa6ca41 Mon Sep 17 00:00:00 2001 From: Diftraku <diftraku@derpy.me> Date: Tue, 3 Dec 2013 00:45:07 +0200 Subject: [PATCH 01/30] Bumping Ouroros API to v0.2: now with XML support and post creation! --- ext/ouroboros_api/main.php | 546 +++++++++++++++++++++++++++++++------ 1 file changed, 470 insertions(+), 76 deletions(-) diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index a2413e42..4ff9553c 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -3,6 +3,7 @@ * Name: Ouroboros API * Author: Diftraku <diftraku[at]derpy.me> * Description: Ouroboros-like API for Shimmie + * Version: 0.2 * Documentation: * Currently working features * <ul> @@ -210,7 +211,10 @@ class _SafeOuroborosImage $this->parent_id = null; if (defined('ENABLED_EXTS')) { if (strstr(ENABLED_EXTS, 'rating') !== false) { - //$this->rating = $img->rating; + // 'u' is not a "valid" rating + if($img->rating == 's' || $img->rating == 'q' || $img->rating == 'e') { + $this->rating = $img->rating; + } } if (strstr(ENABLED_EXTS, 'numeric_score') !== false) { $this->score = $img->numeric_score; @@ -234,6 +238,70 @@ class _SafeOuroborosImage $this->sample_url = make_http($img->get_image_link()); } } +class OuroborosPost extends _SafeOuroborosImage { + /** + * Multipart File + * @var array + */ + public $file = array(); + + /** + * Create with rating locked + * @var bool + */ + public $is_rating_locked = false; + + /** + * Create with notes locked + * @var bool + */ + public $is_note_locked = false; + + + /** + * Initialize an OuroborosPost for creation + * Mainly just acts as a wrapper and validation layer + * @TODO implement more validation from OuroborosAPI + * @param array $post + */ + public function __construct(array $post) { + if (array_key_exists('tags', $post)) { + $this->tags = $post['tags']; + } + if (array_key_exists('file', $post)) { + assert(is_array($post['file'])); + assert(array_key_exists('tmp_name', $post['file'])); + assert(array_key_exists('name', $post['file'])); + $this->file = $post['file']; + } + if (array_key_exists('rating', $post)) { + assert( + $post['rating'] == 's' || + $post['rating'] == 'q' || + $post['rating'] == 'e' + ); + $this->rating = $post['rating']; + } + if (array_key_exists('source', $post)) { + $this->file_url = $post['source']; + } + if (array_key_exists('sourceurl', $post)) { + $this->source = $post['sourceurl']; + } + if (array_key_exists('description', $post)) { + $this->description = $post['description']; + } + if (array_key_exists('is_rating_locked', $post)) { + $this->is_rating_locked = $post['is_rating_locked']; + } + if (array_key_exists('is_note_locked', $post)) { + $this->is_note_locked = $post['is_note_locked']; + } + if (array_key_exists('parent_id', $post)) { + $this->parent_id = $post['parent_id']; + } + } +} class _SafeOuroborosTag { public $ambiguous = false; @@ -252,16 +320,39 @@ class _SafeOuroborosTag class OuroborosAPI extends Extension { private $event; - const ERROR_HTTP_200 = 'Request was successful'; - const ERROR_HTTP_403 = 'Access denied'; - const ERROR_HTTP_404 = 'Not found'; - const ERROR_HTTP_420 = 'Record could not be saved'; - const ERROR_HTTP_421 = 'User is throttled, try again later'; - const ERROR_HTTP_422 = 'The resource is locked and cannot be modified'; - const ERROR_HTTP_423 = 'Resource already exists'; - const ERROR_HTTP_424 = 'The given parameters were invalid'; - const ERROR_HTTP_500 = 'Some unknown error occurred on the server'; - const ERROR_HTTP_503 = 'Server cannot currently handle the request, try again later'; + private $type; + const HEADER_HTTP_200 = 'OK'; + const MSG_HTTP_200 = 'Request was successful'; + + const HEADER_HTTP_403 = 'Forbidden'; + const MSG_HTTP_403 = 'Access denied'; + + const HEADER_HTTP_404 = 'Not found'; + const MSG_HTTP_404 = 'Not found'; + + const HEADER_HTTP_418 = 'I\'m a teapot'; + const MSG_HTTP_418 = 'Short and stout'; + + const HEADER_HTTP_420 = 'Invalid Record'; + const MSG_HTTP_420 = 'Record could not be saved'; + + const HEADER_HTTP_421 = 'User Throttled'; + const MSG_HTTP_421 = 'User is throttled, try again later'; + + const HEADER_HTTP_422 = 'Locked'; + const MSG_HTTP_422 = 'The resource is locked and cannot be modified'; + + const HEADER_HTTP_423 = 'Already Exists'; + const MSG_HTTP_423 = 'Resource already exists'; + + const HEADER_HTTP_424 = 'Invalid Parameters'; + const MSG_HTTP_424 = 'The given parameters were invalid'; + + const HEADER_HTTP_500 = 'Internal Server Error'; + const MSG_HTTP_500 = 'Some unknown error occurred on the server'; + + const HEADER_HTTP_503 = 'Service Unavailable'; + const MSG_HTTP_503 = 'Server cannot currently handle the request, try again later'; const ERROR_POST_CREATE_MD5 = 'MD5 mismatch'; const ERROR_POST_CREATE_DUPE = 'Duplicate'; @@ -272,46 +363,42 @@ class OuroborosAPI extends Extension if (preg_match("%\.(xml|json)$%", implode('/', $event->args), $matches) === 1) { $this->event = $event; - $type = $matches[1]; - if ($type == 'json') { + $this->type = $matches[1]; + if ($this->type == 'json') { $page->set_type('application/json; charset=utf-8'); } - elseif ($type == 'xml') { - $page->set_type('text/xml'); + elseif ($this->type == 'xml') { + $page->set_type('text/xml; charset=utf-8'); } $page->set_mode('data'); + $this->tryAuth(); if ($event->page_matches('post')) { if ($this->match('create')) { // Create + // @TODO Should move the validation logic into OuroborosPost instead? $post = array( 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var($_REQUEST['post']['tags'], FILTER_SANITIZE_STRING) : 'tagme', 'file' => !empty($_REQUEST['post']['file']) ? filter_var($_REQUEST['post']['file'], FILTER_UNSAFE_RAW) : null, - 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : null, - 'source' => !empty($_REQUEST['post']['source']) ? filter_var($_REQUEST['post']['source'], FILTER_SANITIZE_URL) : null, - 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var($_REQUEST['post']['sourceurl'], FILTER_SANITIZE_URL) : '', + 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : 'q', + 'source' => !empty($_REQUEST['post']['source']) ? filter_var(urldecode($_REQUEST['post']['source']), FILTER_SANITIZE_URL) : null, + 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var(urldecode($_REQUEST['post']['sourceurl']), FILTER_SANITIZE_URL) : '', 'description' => !empty($_REQUEST['post']['description']) ? filter_var($_REQUEST['post']['description'], FILTER_SANITIZE_STRING) : '', 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var($_REQUEST['post']['is_rating_locked'], FILTER_SANITIZE_NUMBER_INT) : false, 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var($_REQUEST['post']['is_note_locked'], FILTER_SANITIZE_NUMBER_INT) : false, 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var($_REQUEST['post']['parent_id'], FILTER_SANITIZE_NUMBER_INT) : null, ); $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null; - + $this->postCreate(new OuroborosPost($post), $md5); } elseif ($this->match('update')) { // Update + //@todo add post update } elseif ($this->match('show')) { // Show - if (isset($_REQUEST['id'])) { - $id = $_REQUEST['id']; - $posts = array(); - $posts[] = new _SafeOuroborosImage(Image::by_id($id)); - $page->set_data(json_encode($posts)); - } - else { - $page->set_data(json_encode(array())); - } + $id = !empty($_REQUEST['id']) ? filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT) : null; + $this->postShow($id); } elseif ($this->match('index') || $this->match('list')) { // List @@ -321,17 +408,7 @@ class OuroborosAPI extends Extension if (!empty($tags)) { $tags = Tag::explode($tags); } - $start = ( $p - 1 ) * $limit; - //var_dump($limit, $p, $tags, $start);die(); - $results = Image::find_images(max($start, 0), min($limit, 100), $tags); - $posts = array(); - foreach ($results as $img) { - if (!is_object($img)) { - continue; - } - $posts[] = new _SafeOuroborosImage($img); - } - $page->set_data(json_encode($posts)); + $this->postIndex($limit, $p, $tags); } } elseif ($event->page_matches('tag')) { @@ -343,48 +420,365 @@ class OuroborosAPI extends Extension $after_id = !empty($_REQUEST['after_id']) ? intval(filter_var($_REQUEST['after_id'], FILTER_SANITIZE_NUMBER_INT)) : null; $name = !empty($_REQUEST['name']) ? filter_var($_REQUEST['name'], FILTER_SANITIZE_STRING) : ''; $name_pattern = !empty($_REQUEST['name_pattern']) ? filter_var($_REQUEST['name_pattern'], FILTER_SANITIZE_STRING) : ''; - $start = ( $p - 1 ) * $limit; - $tag_data = array(); - switch ($order) { - case 'name': - $tag_data = $database->get_col($database->scoreql_to_sql(" - SELECT DISTINCT - id, SCORE_STRNORM(substr(tag, 1, 1)), count - FROM tags - WHERE count >= :tags_min - ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items - "), array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); - break; - case 'count': - $tag_data = $database->get_all(" - SELECT id, tag, count - FROM tags - WHERE count >= :tags_min - ORDER BY count DESC, tag ASC LIMIT :start, :max_items - ", array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); - break; - case 'date': - $tag_data = $database->get_all(" - SELECT id, tag, count - FROM tags - WHERE count >= :tags_min - ORDER BY count DESC, tag ASC LIMIT :start, :max_items - ", array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); - break; - } - $tags = array(); - foreach ($tag_data as $tag) { - if (!is_array($tag)) { - continue; - } - $tags[] = new _SafeOuroborosTag($tag); - } - $page->set_data(json_encode($tags)); + $this->tagIndex($limit, $p, $order, $id, $after_id, $name, $name_pattern); } } } } + /** + * Post + */ + + /** + * Wrapper for post creation + * @param OuroborosPost $post + * @param string $md5 + */ + protected function postCreate(OuroborosPost $post, $md5 = '') { + global $page, $config, $user; + if (!empty($md5)) { + $img = Image::by_hash($md5); + if (!is_null($img)) { + $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); + } + } + $meta = array(); + $meta['tags'] = $post->tags; + $meta['source'] = $post->source; + if (defined('ENABLED_EXTS')) { + if (strstr(ENABLED_EXTS, 'rating') !== false) { + $meta['rating'] = $post->rating; + } + } + // Check where we should try for the file + if (empty($post->file) && !empty($post->file_url) && filter_var($post->file_url, FILTER_VALIDATE_URL) !== false) { + // Transload from source + $meta['file'] = tempnam('/tmp', 'shimmie_transload_'.$config->get_string('transload_engine')); + $meta['filename'] = basename($post->file_url); + if ($config->get_string('transload_engine') == 'fopen') { + $fp = fopen($post->file_url, 'r'); + if (!$fp) { + $this->sendResponse(500, 'fopen failed'); + } + + $data = ""; + $length = 0; + while (!feof($fp) && $length <= $config->get_int('upload_size')) { + $data .= fread($fp, 8192); + $length = strlen($data); + } + fclose($fp); + + $fp = fopen($meta['file'], 'w'); + fwrite($fp, $data); + fclose($fp); + } + elseif ($config->get_string('transload_engine') == 'curl') { + $ch = curl_init($post->file_url); + $fp = fopen($meta['file'], 'w'); + + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_HEADER, 0); + + curl_exec($ch); + curl_close($ch); + fclose($fp); + } + $meta['hash'] = md5_file($meta['file']); + } + else { + // Use file + $meta['file'] = $post->file['tmp_name']; + $meta['filename'] = $post->file['name']; + $meta['hash'] = md5_file($meta['file']); + } + if (!empty($md5) && $md5 !== $meta['hash']) { + $this->sendResponse(420, self::ERROR_POST_CREATE_MD5); + } + if (!empty($meta['hash'])) { + $img = Image::by_hash($meta['hash']); + if (!is_null($img)) { + $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); + } + } + $meta['extension'] = pathinfo($meta['filename'], PATHINFO_EXTENSION); + try { + $upload = new DataUploadEvent($meta['file'], $meta); + send_event($upload); + $image = Image::by_hash($meta['hash']); + if (!is_null($image)) { + $this->sendResponse(200, make_link('post/view/'.$image->id), true); + } + else { + // Fail, unsupported file? + $this->sendResponse(500, 'Unknown error'); + } + } catch (UploadException $e) { + // Cleanup in case shit hit the fan + $this->sendResponse(500, $e->getMessage()); + } + } + + /** + * Wrapper for getting a single post + * @param int $id + */ + protected function postShow($id = null) { + if (!is_null($id)) { + $post = new _SafeOuroborosImage(Image::by_id($id)); + $this->sendData('post', $post); + } + else { + $this->sendResponse(424, 'ID is mandatory'); + } + } + + /** + * Wrapper for getting a list of posts + * @param $limit + * @param $page + * @param $tags + */ + protected function postIndex($limit, $page, $tags) { + $start = ( $page - 1 ) * $limit; + $results = Image::find_images(max($start, 0), min($limit, 100), $tags); + $posts = array(); + foreach ($results as $img) { + if (!is_object($img)) { + continue; + } + $posts[] = new _SafeOuroborosImage($img); + } + $this->sendData('post', $posts, max($start, 0)); + } + + /** + * Tag + */ + + /** + * Wrapper for getting a list of tags + * @param $limit + * @param $page + * @param $order + * @param $id + * @param $after_id + * @param $name + * @param $name_pattern + */ + protected function tagIndex($limit, $page, $order, $id, $after_id, $name, $name_pattern) { + global $database, $config; + $start = ( $page - 1 ) * $limit; + $tag_data = array(); + switch ($order) { + case 'name': + $tag_data = $database->get_col($database->scoreql_to_sql(" + SELECT DISTINCT + id, SCORE_STRNORM(substr(tag, 1, 1)), count + FROM tags + WHERE count >= :tags_min + ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items + "), array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + case 'count': + $tag_data = $database->get_all(" + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + case 'date': + $tag_data = $database->get_all(" + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + } + $tags = array(); + foreach ($tag_data as $tag) { + if (!is_array($tag)) { + continue; + } + $tags[] = new _SafeOuroborosTag($tag); + } + $this->sendData('tag', $tags, $start); + } + + /** + * Utility methods + */ + + /** + * Sends a simple {success,reason} message to browser + * + * @param int $code HTTP equivalent code for the message + * @param string $reason Reason for the code + * @param bool $location Is $reason a location? (used mainly for post/create) + */ + private function sendResponse($code = 200, $reason = '', $location = false) { + global $page; + if ($code == 200) { + $success = true; + } + else { + $success = false; + } + if (empty($reason)) { + if (defined("self::MSG_HTTP_{$code}")) { + $reason = constant("self::MSG_HTTP_{$code}"); + } + else { + $reason = self::MSG_HTTP_418; + } + } + if ($code != 200) { + $proto = $_SERVER['SERVER_PROTOCOL']; + if (defined("self::HEADER_HTTP_{$code}")) { + $header = constant("self::HEADER_HTTP_{$code}"); + } + else { + // I'm a teapot! + $code = 418; + $header = self::HEADER_HTTP_418; + } + header("{$proto} {$code} {$header}", true); + } + $response = array('success' => $success, 'reason' => $reason); + if ($this->type == 'json') { + if ($location !== false) { + $response['location'] = $response['reason']; + unset($response['reason']); + } + $response = json_encode($response); + } + elseif ($this->type == 'xml') { + // Seriously, XML sucks... + $xml = new XMLWriter(); + $xml->openMemory(); + $xml->startDocument('1.0', 'utf-8'); + $xml->startElement('response'); + $xml->writeAttribute('success', var_export($success, true)); + if ($location !== false) { + $xml->writeAttribute('location', $reason); + } + else { + $xml->writeAttribute('reason', $reason); + } + $xml->endElement(); + $xml->endDocument(); + $response = $xml->outputMemory(true); + unset($xml); + } + $page->set_data($response); + $page->display(); + } + + /** + * Send data to the browser + * @param string $type + * @param mixed $data + * @param int $offset + */ + private function sendData($type = '', $data = array(), $offset = 0) { + global $page; + $response = ''; + if ($this->type == 'json') { + $response = json_encode($data); + } + elseif ($this->type == 'xml') { + $xml = new XMLWriter(); + $xml->openMemory(); + $xml->startDocument('1.0', 'utf-8'); + if (array_key_exists(0, $data)) { + $xml->startElement($type.'s'); + if ($type == 'post') { + $xml->writeAttribute('count', count($data)); + $xml->writeAttribute('offset', $offset); + } + if ($type == 'tag') { + $xml->writeAttribute('type', 'array'); + } + foreach ($data as $item) { + $this->createItemXML($xml, $type, $item); + } + $xml->endElement(); + } + else { + $this->createItemXML($xml, $type, $data); + } + $xml->endDocument(); + $response = $xml->outputMemory(true); + unset($xml); + } + $page->set_data($response); + $page->display(); + exit; + } + + private function createItemXML(XMLWriter &$xml, $type, $item) { + $xml->startElement($type); + foreach ($item as $key => $val) { + if ($key == 'created_at' && $type == 'post') { + $xml->writeAttribute($key, $val['s']); + } + else { + if (is_bool($val)) { + $val = $val ? 'true' : 'false'; + } + $xml->writeAttribute($key, $val); + } + } + $xml->endElement(); + } + + /** + * Try to figure who is uploading + * + * Currently checks for either user & session in request or cookies + * and initializes a global User + * @param void + * @return void + */ + private function tryAuth() { + global $config, $user; + + if (isset($_REQUEST['user']) && isset($_REQUEST['session'])) { + //Auth by session data from query + $name = $_REQUEST['user']; + $session = $_REQUEST['session']; + $duser = User::by_session($name, $session); + if (!is_null($duser)) { + $user = $duser; + } + else { + $user = User::by_id($config->get_int("anon_id", 0)); + } + } + elseif (isset($_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'session']) && + isset($_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'user']) + ) { + //Auth by session data from cookies + $session = $_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'session']; + $user = $_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'user']; + $duser = User::by_session($user, $session); + if (!is_null($duser)) { + $user = $duser; + } + else { + $user = User::by_id($config->get_int("anon_id", 0)); + } + } + } + + /** + * Helper for matching API methods from event + * @param $page + * @return bool + */ private function match($page) { return (preg_match("%{$page}\.(xml|json)$%", implode('/', $this->event->args), $matches) === 1); } From 095f743d57f004612f044ef60d6a005967dc7ca8 Mon Sep 17 00:00:00 2001 From: Diftraku <diftraku@derpy.me> Date: Tue, 3 Dec 2013 01:07:27 +0200 Subject: [PATCH 02/30] Checking if the user can actually create new posts, seems the base DataHandlerExtension doesn't do this. Also forgot to update documentation! --- ext/ouroboros_api/main.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 4ff9553c..1304b9a7 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -377,19 +377,25 @@ class OuroborosAPI extends Extension if ($this->match('create')) { // Create // @TODO Should move the validation logic into OuroborosPost instead? - $post = array( - 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var($_REQUEST['post']['tags'], FILTER_SANITIZE_STRING) : 'tagme', - 'file' => !empty($_REQUEST['post']['file']) ? filter_var($_REQUEST['post']['file'], FILTER_UNSAFE_RAW) : null, - 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : 'q', - 'source' => !empty($_REQUEST['post']['source']) ? filter_var(urldecode($_REQUEST['post']['source']), FILTER_SANITIZE_URL) : null, - 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var(urldecode($_REQUEST['post']['sourceurl']), FILTER_SANITIZE_URL) : '', - 'description' => !empty($_REQUEST['post']['description']) ? filter_var($_REQUEST['post']['description'], FILTER_SANITIZE_STRING) : '', - 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var($_REQUEST['post']['is_rating_locked'], FILTER_SANITIZE_NUMBER_INT) : false, - 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var($_REQUEST['post']['is_note_locked'], FILTER_SANITIZE_NUMBER_INT) : false, - 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var($_REQUEST['post']['parent_id'], FILTER_SANITIZE_NUMBER_INT) : null, - ); - $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null; - $this->postCreate(new OuroborosPost($post), $md5); + if($user->can("create_image")) { + $post = array( + 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var($_REQUEST['post']['tags'], FILTER_SANITIZE_STRING) : 'tagme', + 'file' => !empty($_REQUEST['post']['file']) ? filter_var($_REQUEST['post']['file'], FILTER_UNSAFE_RAW) : null, + 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : 'q', + 'source' => !empty($_REQUEST['post']['source']) ? filter_var(urldecode($_REQUEST['post']['source']), FILTER_SANITIZE_URL) : null, + 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var(urldecode($_REQUEST['post']['sourceurl']), FILTER_SANITIZE_URL) : '', + 'description' => !empty($_REQUEST['post']['description']) ? filter_var($_REQUEST['post']['description'], FILTER_SANITIZE_STRING) : '', + 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var($_REQUEST['post']['is_rating_locked'], FILTER_SANITIZE_NUMBER_INT) : false, + 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var($_REQUEST['post']['is_note_locked'], FILTER_SANITIZE_NUMBER_INT) : false, + 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var($_REQUEST['post']['parent_id'], FILTER_SANITIZE_NUMBER_INT) : null, + ); + $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null; + $this->postCreate(new OuroborosPost($post), $md5); + } + else { + $this->sendResponse(403, 'You cannot create new posts'); + } + } elseif ($this->match('update')) { // Update From 1a25014564ed81c5ddaf2ac308567c9377922a8f Mon Sep 17 00:00:00 2001 From: Diftraku <diftraku@derpy.me> Date: Tue, 3 Dec 2013 05:51:55 +0200 Subject: [PATCH 03/30] Derp, forgot I was actually giving the post[file] to OuroborosPost, making assert fail for null --- ext/ouroboros_api/main.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 1304b9a7..b16abbc7 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -11,6 +11,7 @@ * <ul> * <li>Index/List</li> * <li>Show</li> + * <li>Create</li> * </ul> * </li> * <li>Tag: @@ -269,10 +270,12 @@ class OuroborosPost extends _SafeOuroborosImage { $this->tags = $post['tags']; } if (array_key_exists('file', $post)) { - assert(is_array($post['file'])); - assert(array_key_exists('tmp_name', $post['file'])); - assert(array_key_exists('name', $post['file'])); - $this->file = $post['file']; + if (!is_null($post['file'])) { + assert(is_array($post['file'])); + assert(array_key_exists('tmp_name', $post['file'])); + assert(array_key_exists('name', $post['file'])); + $this->file = $post['file']; + } } if (array_key_exists('rating', $post)) { assert( From 453d9a453bdda03ae2529e1be74781dfb6d8b1b3 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Sun, 8 Dec 2013 15:05:27 +0000 Subject: [PATCH 04/30] bookmarklet now supports danbooru2 + fixed issues with other sites --- ext/upload/bookmarklet.js | 147 +++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 82 deletions(-) diff --git a/ext/upload/bookmarklet.js b/ext/upload/bookmarklet.js index e902d135..0c4dda31 100644 --- a/ext/upload/bookmarklet.js +++ b/ext/upload/bookmarklet.js @@ -1,6 +1,5 @@ /* Imageboard to Shimmie */ // This should work with "most" sites running Danbooru/Gelbooru/Shimmie -// TODO: Make this use jQuery! (if we can be sure that jquery is loaded) // maxsize, supext, CA are set inside the bookmarklet (see theme.php) var maxsize = (maxsize.match("(?:\.*[0-9])")) * 1024; // This assumes we are only working with MB. @@ -25,67 +24,84 @@ else if(CA === 2) { // New Tags } + /* - * Danbooru (oreno.imouto | konachan | sankakucomplex) + * Danbooru2 + * jQuery should always active here, meaning we can use jQuery in this part of the bookmarklet. */ -if(document.getElementById("post_tags") !== null) { + +if(document.getElementById("post_tag_string") !== null) { if (typeof tag !== "ftp://ftp." && chk !==1) { - var tag = document.getElementById("post_tags").value; + var tag = $('#post_tag_string').text().replace(/\n/g, ""); } - tag = tag.replace(/\+/g, "%2B"); // This should stop + not showing in tags :x + tag = tag.replace(/\+/g, "%2B"); - var source = "http://" + document.location.hostname + document.location.href.match("\/post\/show\/[0-9]+"); - if(source.search("oreno\\.imouto") >= 0 || source.search("konachan\\.com") >= 0) { - var rating = document.getElementById("stats").innerHTML.match("<li>Rating: (.*) <span")[1]; - } - else { - var rating = document.getElementById("stats").innerHTML.match("<li>Rating: (.*)<\/li>")[1]; + var source = "http://" + document.location.hostname + document.location.href.match("\/posts\/[0-9]+"); + + var rlist = $('[name="post[rating]"]'); + for(x=0;x<3;x++){ + var rating = (rlist[x].checked == true ? rlist[x].value : rating); } - if(tag.search(/\bflash\b/)===-1){ - var highres_url = document.getElementById("highres").href; - if(source.search("oreno\\.imouto") >= 0 || source.search("konachan\\.com") >= 0){ // oreno's theme seems to have moved the filesize - var filesize = document.getElementById("highres").innerHTML.match("[a-zA-Z0-9]+ \\(+([0-9]+\\.[0-9]+) ([a-zA-Z]+)"); + var fileinfo = $('#sidebar > section:eq(3) > ul > :contains("Size") > a'); + var furl = "http://" + document.location.hostname + fileinfo.attr('href'); + var fs = fileinfo.text().split(" "); + var filesize = (fs[1] == "MB" ? fs[0] * 1024 : fs[0]); + + if(supext.search(furl.match("[a-zA-Z0-9]+$")[0]) !== -1){ + if(filesize <= maxsize){ + location.href = ste+furl+"&tags="+tag+"&rating="+rating+"&source="+source; + } + else{ + alert(toobig); + } + } + else{ + alert(notsup); + } +} + +/* + * konachan | sankakucomplex | gelbooru + */ +else if(document.getElementById('tag-sidebar') !== null) { + if (typeof tag !== "ftp://ftp." && chk !==1) { + if(document.location.href.search("sankakucomplex\\.com") >= 0 || document.location.href.search("gelbooru\\.com")){ + var tag = document.getElementById('tag-sidebar').innerText.replace(/ /g, "_").replace(/[\?_]*(.*?)_(\(\?\)_)?[0-9]+\n/g, "$1 "); }else{ - var filesize = document.getElementById("stats").innerHTML.match("[0-9] \\(((?:\.*[0-9])) ([a-zA-Z]+)"); - } - if(filesize[2] == "MB") { - var filesize = filesize[1] * 1024; - } - else { - var filesize = filesize[2].match("[0-9]+"); - } - - if(supext.search(highres_url.match("http\:\/\/.*\\.([a-z0-9]+)")[1]) !== -1) { - if(filesize <= maxsize) { - if(source.search("oreno\\.imouto") >= 0) { - // this regex tends to be a bit picky with tags -_-;; - var highres_url = highres_url.match("(http\:\/\/[a-z0-9]+\.[a-z]+\.[a-z]\/[a-z0-9]+\/[a-z0-9]+)\/[a-z0-9A-Z%_-]+(\.[a-zA-Z0-9]+)"); - var highres_url = highres_url[1]+highres_url[2]; // this should bypass hotlink protection - } - else if(source.search("konachan\\.com") >= 0) { - // konachan affixs konachan.com to the start of the tags, this requires different regex - var highres_url = highres_url.match("(http\:\/\/[a-z0-9]+\.[a-z]+\.[a-z]\/[a-z0-9]+\/[a-z0-9]+)\/[a-z0-9A-Z%_]+\.[a-zA-Z0-9%_-]+(\.[a-z0-9A-Z]+)") - var highres_url = highres_url[1]+highres_url[2]; - } - location.href = ste+highres_url+"&tags="+tag+"&rating="+rating+"&source="+source; - } - else{ - alert(toobig); - } - } - else{ - alert(notsup); + var tag = document.getElementById("post_tags").value; } } - else { - if(supext.search("swf") !== -1) { - location.href = ste+document.getElementsByName("movie")[0].value+"&tags="+tag+"&rating="+rating+"&source="+source; + tag = tag.replace(/\+/g, "%2B"); + + var source = "http://" + document.location.hostname + (document.location.href.match("\/post\/show\/[0-9]+") || encodeURIComponent(document.location.href.match(/\/index\.php\?page=post&s=view&id=[0-9]+/))); + + var rating = document.getElementById("stats").innerHTML.match("Rating: ([a-zA-Z]+)")[1]; + + if(source.search("sankakucomplex\\.com") >= 0 || source.search("konachan\\.com") >= 0){ + var fileinfo = document.getElementById("highres"); + //NOTE: If highres doesn't exist, post must be flash (only sankakucomplex has flash) + }else if(source.search("gelbooru\\.com") >= 0){ + var fileinfo = document.getElementById('pfd').parentNode.parentNode.getElementsByTagName('a')[0]; + //gelbooru has no easy way to select the original image link, so we need to double check it is the correct link. + fileinfo = (fileinfo.getAttribute('href') == "#" ? document.getElementById('pfd').parentNode.parentNode.getElementsByTagName('a')[1] : fileinfo); + } + fileinfo = fileinfo || document.getElementsByTagName('embed')[0]; //If fileinfo is null then image is most likely flash. + var furl = fileinfo.href || fileinfo.src; + var fs = (fileinfo.innerText.match(/[0-9]+ (KB|MB)/) || ["0 KB"])[0].split(" "); + var filesize = (fs[1] == "MB" ? fs[0] * 1024 : fs[0]); + + if(supext.search(furl.match("[a-zA-Z0-9]+$")[0]) !== -1){ + if(filesize <= maxsize){ + location.href = ste+furl+"&tags="+tag+"&rating="+rating+"&source="+source; } else{ - alert(notsup); + alert(toobig); } } + else{ + alert(notsup); + } } /* @@ -128,37 +144,4 @@ else if(document.getElementsByTagName("title")[0].innerHTML.search("Image [0-9.- alert(notsup); } } -} - -/* - * Gelbooru - */ -else if(document.getElementById("tags") !== null) { - if (typeof tag !=="ftp://ftp." && chk !==1) { - var tag = document.getElementById("tags").value; - } - - var rating = document.getElementById("stats").innerHTML.match("<li>Rating: (.*)<\/li>")[1]; - - // Can't seem to grab source due to url containing a & - // var source="http://" + document.location.hostname + document.location.href.match("\/index\.php?page=post&s=view\\&id=.*"); - - // Updated Nov. 24, 2013 by jgen. - var gmi; - try { - gmi = document.getElementById("image").src.match(".*img[0-9]*\.gelbooru\.com[\/]+images[\/]+[0-9]+[\/]+[a-z0-9]+\.[a-z0-9]+")[0]; - - // Since Gelbooru does not allow flash, no need to search for flash tag. - // Gelbooru doesn't show file size in statistics either... - if(supext.search(gmi.match("http\:\/\/.*\\.([a-z0-9]+)")[1]) !== -1){ - location.href = ste+gmi+"&tags="+tag+"&rating="+rating;//+"&source="+source; - } - else{ - alert(notsup); - } - } - catch (err) - { - alert("Unable to locate the image on the page!\n(Gelbooru may have changed the structure of their page, please file a bug.)"); - } -} +} \ No newline at end of file From 59eb6d7ec280ab8105b6bf0579809da12e4a7f2d Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Sun, 29 Dec 2013 20:46:37 +0000 Subject: [PATCH 05/30] pool title should be unique --- ext/pools/main.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/pools/main.php b/ext/pools/main.php index b161e8bb..42bbe2c9 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -71,6 +71,11 @@ class Pools extends Extension { log_info("pools", "extension installed"); } + + if ($config->get_int("ext_pools_version") < 2){ + $database->Execute("ALTER TABLE `pools` ADD UNIQUE INDEX (`title`);"); + $config->set_int("ext_pools_version", 2); + } } // Add a block to the Board Config / Setup From 3e240fa78d8e24daf0088d5d723c7d8e6ed131fa Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Sun, 29 Dec 2013 22:24:34 +0000 Subject: [PATCH 06/30] return error when pool title exists + fix pool error reporting --- ext/pools/main.php | 34 +++++++++++++++++++++++----------- ext/pools/theme.php | 15 --------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/ext/pools/main.php b/ext/pools/main.php index 42bbe2c9..e449ab1f 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -116,7 +116,7 @@ class Pools extends Extension { $this->theme->new_pool_composer($page); } else { $errMessage = "You must be registered and logged in to create a new pool."; - $this->theme->display_error($errMessage); + $this->theme->display_error(401, "Error", $errMessage); } break; @@ -127,7 +127,7 @@ class Pools extends Extension { $page->set_redirect(make_link("pool/view/".$newPoolID)); } catch(PoolCreationException $e) { - $this->theme->display_error($e->error); + $this->theme->display_error(400, "Error", $e->error); } break; @@ -173,7 +173,7 @@ class Pools extends Extension { $page->set_mode("redirect"); $page->set_redirect(make_link("pool/view/".$pool_id)); } else { - $this->theme->display_error("Permssion denied."); + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); } } break; @@ -182,7 +182,7 @@ class Pools extends Extension { if ($this->have_permission($user, $pool)) { $this->import_posts($pool_id); } else { - $this->theme->display_error("Permssion denied."); + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); } break; @@ -192,7 +192,7 @@ class Pools extends Extension { $page->set_mode("redirect"); $page->set_redirect(make_link("pool/view/".$pool_id)); } else { - $this->theme->display_error("Permssion denied."); + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); } break; @@ -202,7 +202,7 @@ class Pools extends Extension { $page->set_mode("redirect"); $page->set_redirect(make_link("pool/view/".$pool_id)); } else { - $this->theme->display_error("Permssion denied."); + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); } break; @@ -215,7 +215,7 @@ class Pools extends Extension { $page->set_mode("redirect"); $page->set_redirect(make_link("pool/list")); } else { - $this->theme->display_error("Permssion denied."); + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); } break; @@ -336,14 +336,16 @@ class Pools extends Extension { */ private function add_pool() { global $user, $database; - + #throw new PoolCreationException("Pool needs a title"); if($user->is_anonymous()) { throw new PoolCreationException("You must be registered and logged in to add a image."); } if(empty($_POST["title"])) { - throw new PoolCreationException("Pool needs a title"); + throw new PoolCreationException("Pool title is empty."); + } + if($this->get_single_pool_from_title($_POST["title"])) { + throw new PoolCreationException("A pool using this title already exists."); } - $public = $_POST["public"] == "Y" ? "Y" : "N"; $database->execute(" INSERT INTO pools (user_id, public, title, description, date) @@ -366,7 +368,7 @@ class Pools extends Extension { global $database; return $database->get_all("SELECT * FROM pools WHERE id=:id", array("id"=>$poolID)); } - + /** * Retrieve information about a pool given a pool ID. * @param $poolID Integer @@ -377,6 +379,16 @@ class Pools extends Extension { return $database->get_row("SELECT * FROM pools WHERE id=:id", array("id"=>$poolID)); } + /** + * Retrieve information about a pool given a pool title. + * @param $poolTitle Integer + * @retval 2D array (with only 1 element in the one dimension) + */ + private function get_single_pool_from_title(/*string*/ $poolTitle) { + global $database; + return $database->get_row("SELECT * FROM pools WHERE title=:title", array("title"=>$poolTitle)); + } + /** * Get all of the pool IDs that an image is in, given an image ID. * @param $imageID Integer diff --git a/ext/pools/theme.php b/ext/pools/theme.php index d9c397c7..39c4318e 100644 --- a/ext/pools/theme.php +++ b/ext/pools/theme.php @@ -389,20 +389,5 @@ class PoolsTheme extends Themelet { $this->display_paginator($page, "pool/updated", null, $pageNumber, $totalPages); } - - - /** - * Display an error message to the user. - */ - public function display_error(/*int*/ $code, /*string*/ $title, /*string*/ $message) { - global $page; - - // Quick n' Dirty fix - $message = $code; - - $page->set_title("Error"); - $page->set_heading("Error"); - $page->add_block(new Block("Error", $errMessage, "main", 10)); - } } ?> From b79a042bdcb68524a872038d440c15bfda7c5856 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 01:48:07 +0000 Subject: [PATCH 07/30] added option to order pool list by created date/last updated/title/count --- ext/pools/main.php | 17 ++++++++++++++++- ext/pools/script.js | 10 ++++++++++ ext/pools/theme.php | 10 ++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 ext/pools/script.js diff --git a/ext/pools/main.php b/ext/pools/main.php index e449ab1f..7cdc0374 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -74,6 +74,8 @@ class Pools extends Extension { if ($config->get_int("ext_pools_version") < 2){ $database->Execute("ALTER TABLE `pools` ADD UNIQUE INDEX (`title`);"); + $database->Execute("ALTER TABLE `pools` ADD `lastupdated` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;"); + $config->set_int("ext_pools_version", 2); } } @@ -314,13 +316,26 @@ class Pools extends Extension { $poolsPerPage = $config->get_int("poolsListsPerPage"); + + $order_by = ""; + $order = get_prefixed_cookie("ui-order-pool"); + if($order == "created" || is_null($order)){ + $order_by = "ORDER BY p.date DESC"; + }elseif($order == "updated"){ + $order_by = "ORDER BY p.lastupdated DESC"; + }elseif($order == "name"){ + $order_by = "ORDER BY p.title ASC"; + }elseif($order == "count"){ + $order_by = "ORDER BY p.posts DESC"; + } + $pools = $database->get_all(" SELECT p.id, p.user_id, p.public, p.title, p.description, p.posts, u.name as user_name FROM pools AS p INNER JOIN users AS u ON p.user_id = u.id - ORDER BY p.date DESC + $order_by LIMIT :l OFFSET :o ", array("l"=>$poolsPerPage, "o"=>$pageNumber * $poolsPerPage) ); diff --git a/ext/pools/script.js b/ext/pools/script.js new file mode 100644 index 00000000..72ea80e6 --- /dev/null +++ b/ext/pools/script.js @@ -0,0 +1,10 @@ +$(function() { + var order_pool = $.cookie("shm_ui-order-pool"); + $("#order_pool option[value="+order_pool+"]").attr("selected", true); + + $('#order_pool').change(function(){ + var val = $("#order_pool option:selected").val(); + $.cookie("shm_ui-order-pool", val, {path: '/', expires: 365}); //FIXME: This won't play nice if COOKIE_PREFIX is not "shm_". + window.location.href = ''; + }); +}); diff --git a/ext/pools/theme.php b/ext/pools/theme.php index 39c4318e..cc924045 100644 --- a/ext/pools/theme.php +++ b/ext/pools/theme.php @@ -67,11 +67,21 @@ class PoolsTheme extends Themelet { <br><a href="'.make_link("pool/updated").'">Pool Changes</a> '; + $order_html = ' + <select id="order_pool"> + <option value="created">Recently created</option> + <option value="updated">Last updated</option> + <option value="name">Name</option> + <option value="count">Post count</option> + </select> + '; + $blockTitle = "Pools"; $page->set_title(html_escape($blockTitle)); $page->set_heading(html_escape($blockTitle)); $page->add_block(new Block($blockTitle, $html, "main", 10)); $page->add_block(new Block("Navigation", $nav_html, "left", 10)); + $page->add_block(new Block("Order By", $order_html, "left", 15)); $this->display_paginator($page, "pool/list", null, $pageNumber, $totalPages); } From 9511569ed425270f3624e0bed2be8205ed40409e Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 06:51:47 +0000 Subject: [PATCH 08/30] added option to edit pool description to pool edit page --- ext/pools/main.php | 22 ++++++++++++++++++++++ ext/pools/theme.php | 14 +++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ext/pools/main.php b/ext/pools/main.php index 7cdc0374..6bb9a24d 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -209,6 +209,17 @@ class Pools extends Extension { break; + case "edit_description": + if ($this->have_permission($user, $pool)) { + $this->edit_description(); + $page->set_mode("redirect"); + $page->set_redirect(make_link("pool/view/".$pool_id)); + } else { + $this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); + } + + break; + case "nuke": // Completely remove the given pool. // -> Only admins and owners may do this @@ -508,6 +519,17 @@ class Pools extends Extension { return $poolID; } + /* + * Allows editing of pool description. + */ + private function edit_description() { + global $database; + + $poolID = int_escape($_POST['pool_id']); + $database->execute("UPDATE pools SET description=:dsc WHERE id=:pid", array("dsc"=>$_POST['description'], "pid"=>$poolID)); + + return $poolID; + } /** * This function checks if a given image is contained within a given pool. diff --git a/ext/pools/theme.php b/ext/pools/theme.php index cc924045..269b9a1c 100644 --- a/ext/pools/theme.php +++ b/ext/pools/theme.php @@ -322,8 +322,17 @@ class PoolsTheme extends Themelet { public function edit_pool(Page $page, /*array*/ $pools, /*array*/ $images) { global $user; - $this->display_top($pools, "Editing Pool", true); + /* EDIT POOL DESCRIPTION */ + $desc_html = " + ".make_form(make_link("pool/edit_description"))." + <textarea name='description'>".$pools[0]['description']."</textarea><br /> + <input type='hidden' name='pool_id' value='".$pools[0]['id']."'> + <input type='submit' value='Change Description' /> + </form> + "; + + /* REMOVE POOLS */ $pool_images = "\n<form action='".make_link("pool/remove_posts")."' method='POST' name='checks'>"; foreach($images as $pair) { @@ -341,6 +350,9 @@ class PoolsTheme extends Themelet { "<input type='hidden' name='pool_id' value='".$pools[0]['id']."'>". "</form>"; + $pools[0]['description'] = ""; //This is a rogue fix to avoid showing the description twice. + $this->display_top($pools, "Editing Pool", true); + $page->add_block(new Block("Editing Description", $desc_html, "main", 28)); $page->add_block(new Block("Editing Posts", $pool_images, "main", 30)); } From 9eaebfd1c28d36fd605195c7ad68e62c7ce30466 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 06:52:17 +0000 Subject: [PATCH 09/30] if cookie doesn't exist, default to "created" --- ext/pools/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/pools/script.js b/ext/pools/script.js index 72ea80e6..505718a9 100644 --- a/ext/pools/script.js +++ b/ext/pools/script.js @@ -1,5 +1,5 @@ $(function() { - var order_pool = $.cookie("shm_ui-order-pool"); + var order_pool = $.cookie("shm_ui-order-pool") || "created"; $("#order_pool option[value="+order_pool+"]").attr("selected", true); $('#order_pool').change(function(){ From 3dd31019959625efe5e22c155bc06d9544e4a5f4 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 08:15:10 +0000 Subject: [PATCH 10/30] added option to set pool & source via tag --- core/imageboard.pack.php | 12 ++++++++++++ ext/pools/main.php | 20 +++++++++++++++++++- ext/pools/theme.php | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index e36c40ee..869e7de6 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -476,6 +476,18 @@ class Image { $this->delete_tags_from_image(); // insert each new tags foreach($tags as $tag) { + if(preg_match("/^source=(.*)$/i", $tag, $matches)) { + $this->set_source($matches[1]); + continue; + } + if(preg_match("/^pool=(.*)$/i", $tag, $matches)) { + if(class_exists("Pools")) { + $pls = new Pools(); + $pls->add_post_from_tag($matches[1], $this->id); + } + continue; + } + $id = $database->get_one( $database->scoreql_to_sql( "SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)" diff --git a/ext/pools/main.php b/ext/pools/main.php index 6bb9a24d..22ac55d8 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -293,6 +293,23 @@ class Pools extends Extension { } } + public function add_post_from_tag(/*str*/ $poolTag, /*int*/ $imageID){ + $poolTag = str_replace("_", " ", $poolTag); + //First check if pool tag is a title + if(ctype_digit($poolTag)){ + //If string only contains numeric characters, assume it is $poolID + if($this->get_single_pool($poolTag)){ //Make sure pool exists + $this->add_post($poolTag, $imageID); + } + }else{ + //If string doesn't contain only numeric characters, check to see if tag is title. + $pool = $this->get_single_pool_from_title($poolTag); + if($pool){ + $this->add_post($pool['id'], $imageID); + } + } + } + /* ------------------------------------------------- */ /* -------------- Private Functions -------------- */ /* ------------------------------------------------- */ @@ -362,7 +379,7 @@ class Pools extends Extension { */ private function add_pool() { global $user, $database; - #throw new PoolCreationException("Pool needs a title"); + if($user->is_anonymous()) { throw new PoolCreationException("You must be registered and logged in to add a image."); } @@ -372,6 +389,7 @@ class Pools extends Extension { if($this->get_single_pool_from_title($_POST["title"])) { throw new PoolCreationException("A pool using this title already exists."); } + $public = $_POST["public"] == "Y" ? "Y" : "N"; $database->execute(" INSERT INTO pools (user_id, public, title, description, date) diff --git a/ext/pools/theme.php b/ext/pools/theme.php index 269b9a1c..3b2e315c 100644 --- a/ext/pools/theme.php +++ b/ext/pools/theme.php @@ -350,7 +350,7 @@ class PoolsTheme extends Themelet { "<input type='hidden' name='pool_id' value='".$pools[0]['id']."'>". "</form>"; - $pools[0]['description'] = ""; //This is a rogue fix to avoid showing the description twice. + $pools[0]['description'] = ""; //This is a rough fix to avoid showing the description twice. $this->display_top($pools, "Editing Pool", true); $page->add_block(new Block("Editing Description", $desc_html, "main", 28)); $page->add_block(new Block("Editing Posts", $pool_images, "main", 30)); From fd6f2ddb431e45d33b0ea3617e3a32dc461bd94d Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 08:42:29 +0000 Subject: [PATCH 11/30] show pool navigation box on pool/view pages --- ext/pools/theme.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/pools/theme.php b/ext/pools/theme.php index 3b2e315c..444e76b5 100644 --- a/ext/pools/theme.php +++ b/ext/pools/theme.php @@ -166,6 +166,13 @@ class PoolsTheme extends Themelet { $pool_images .= "\n".$thumb_html."\n"; } + $nav_html = ' + <a href="'.make_link().'">Index</a> + <br><a href="'.make_link("pool/new").'">Create Pool</a> + <br><a href="'.make_link("pool/updated").'">Pool Changes</a> + '; + + $page->add_block(new Block("Navigation", $nav_html, "left", 10)); $page->add_block(new Block("Viewing Posts", $pool_images, "main", 30)); $this->display_paginator($page, "pool/view/".$pools[0]['id'], null, $pageNumber, $totalPages); } From c892386bcb64a48ed06a1a30cd06aa00c332d33e Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 30 Dec 2013 10:36:32 +0000 Subject: [PATCH 12/30] remove backticks --- ext/pools/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/pools/main.php b/ext/pools/main.php index 22ac55d8..008838d3 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -73,8 +73,8 @@ class Pools extends Extension { } if ($config->get_int("ext_pools_version") < 2){ - $database->Execute("ALTER TABLE `pools` ADD UNIQUE INDEX (`title`);"); - $database->Execute("ALTER TABLE `pools` ADD `lastupdated` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;"); + $database->Execute("ALTER TABLE pools ADD UNIQUE INDEX (title);"); + $database->Execute("ALTER TABLE pools ADD lastupdated TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;"); $config->set_int("ext_pools_version", 2); } From 85880804d24ded84feda563a18bf3118b2c03d40 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Tue, 31 Dec 2013 08:22:58 +0000 Subject: [PATCH 13/30] have theme.php manage block creation so themes can move/remove if they wish --- ext/numeric_score/main.php | 6 ++---- ext/numeric_score/theme.php | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index ed7597ab..9cf21ab3 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -31,16 +31,14 @@ class NumericScore extends Extension { public function onDisplayingImage(DisplayingImageEvent $event) { global $user, $page; if(!$user->is_anonymous()) { - $html = $this->theme->get_voter_html($event->image); - $page->add_block(new Block("Image Score", $html, "left", 20)); + $this->theme->get_voter($event->image); } } public function onUserPageBuilding(UserPageBuildingEvent $event) { global $page, $user; if($user->can("edit_other_vote")) { - $html = $this->theme->get_nuller_html($event->display_user); - $page->add_block(new Block("Votes", $html, "main", 60)); + $this->theme->get_nuller($event->display_user); } } diff --git a/ext/numeric_score/theme.php b/ext/numeric_score/theme.php index baefad39..e60af00c 100644 --- a/ext/numeric_score/theme.php +++ b/ext/numeric_score/theme.php @@ -1,8 +1,8 @@ <?php class NumericScoreTheme extends Themelet { - public function get_voter_html(Image $image) { - global $user; + public function get_voter(Image $image) { + global $user, $page; $i_image_id = int_escape($image->id); $i_score = int_escape($image->numeric_score); @@ -46,11 +46,11 @@ class NumericScoreTheme extends Themelet { </div> "; } - return $html; + $page->add_block(new Block("Image Score", $html, "left", 20)); } - public function get_nuller_html(User $duser) { - global $user; + public function get_nuller(User $duser) { + global $user, $page; $html = " <form action='".make_link("numeric_score/remove_votes_by")."' method='POST'> ".$user->get_auth_html()." @@ -58,7 +58,7 @@ class NumericScoreTheme extends Themelet { <input type='submit' value='Delete all votes by this user'> </form> "; - return $html; + $page->add_block(new Block("Votes", $html, "main", 60)); } public function view_popular($images, $dte) { From fde6558a6faeafd2e19d0870f7b2c1202b8aece4 Mon Sep 17 00:00:00 2001 From: HungryFeline <HungryFeline@users.noreply.github.com> Date: Wed, 1 Jan 2014 01:41:11 +0100 Subject: [PATCH 14/30] Don't silently ignore invalid URLs Scenario: Providing an invalid url via $_GET (wrong/missing extension or file isn't an image (also happens on download errors)) Behavior before: Silently redirect to index Behavior after: Display error message --- core/extension.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/extension.class.php b/core/extension.class.php index a1175341..0b4c767b 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -197,6 +197,9 @@ abstract class DataHandlerExtension extends Extension { } } } + else{ + throw new UploadException("Unsupported extension or file isn't an image"); + } } public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { From 7cf79171a8a3b232044e75f0bc3955c29676b358 Mon Sep 17 00:00:00 2001 From: HungryFeline <HungryFeline@users.noreply.github.com> Date: Wed, 1 Jan 2014 18:25:28 +0100 Subject: [PATCH 15/30] Update extension.class.php Fix my previous commit. Also put the results of the tests into variables so we don't need to check them again. --- core/extension.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/extension.class.php b/core/extension.class.php index 0b4c767b..3407ba10 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -142,7 +142,7 @@ abstract class DataHandlerExtension extends Extension { public function onDataUpload(DataUploadEvent $event) { global $user; - if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) { + if(($supported_ext = $this->supported_ext($event->type)) && ($check_contents = $this->check_contents($event->tmpname))) { if(!move_upload_to_archive($event)) return; send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); @@ -197,8 +197,8 @@ abstract class DataHandlerExtension extends Extension { } } } - else{ - throw new UploadException("Unsupported extension or file isn't an image"); + elseif($supported_ext && !$check_contents){ + throw new UploadException("Invalid or corrupted file"); } } From 25c286b71faf23231fea2346e6c5dcef1a39df15 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Thu, 2 Jan 2014 14:00:24 +0000 Subject: [PATCH 16/30] add support for using : as a metatag seperator + updated docs --- core/imageboard.pack.php | 4 +-- ext/danbooru_api/main.php | 8 ----- ext/favorites/main.php | 8 ++--- ext/index/main.php | 70 +++++++++++++++++++++++++-------------- ext/notes/main.php | 10 +++--- ext/rating/main.php | 4 +-- ext/user/main.php | 6 ++-- 7 files changed, 61 insertions(+), 49 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 869e7de6..c9a2ce4a 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -476,11 +476,11 @@ class Image { $this->delete_tags_from_image(); // insert each new tags foreach($tags as $tag) { - if(preg_match("/^source=(.*)$/i", $tag, $matches)) { + if(preg_match("/^source[=|:](.*)$/i", $tag, $matches)) { $this->set_source($matches[1]); continue; } - if(preg_match("/^pool=(.*)$/i", $tag, $matches)) { + if(preg_match("/^pool[=|:](.*)$/i", $tag, $matches)) { if(class_exists("Pools")) { $pls = new Pools(); $pls->add_post_from_tag($matches[1], $this->id); diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index f5d8773e..2ba1b82d 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -54,14 +54,6 @@ class DanbooruApi extends Extension { } } - public function onSearchTermParse(SearchTermParseEvent $event) { - $matches = array(); - if(preg_match("/^md5:([0-9a-fA-F]*)$/i", $event->term, $matches)) { - $hash = strtolower($matches[1]); - $event->add_querylet(new Querylet("images.hash = '$hash'")); // :-O - } - } - // Danbooru API private function api_danbooru(PageRequestEvent $event) { diff --git a/ext/favorites/main.php b/ext/favorites/main.php index aee35d6a..6517377e 100644 --- a/ext/favorites/main.php +++ b/ext/favorites/main.php @@ -117,12 +117,12 @@ class Favorites extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/favorites(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) { - $cmp = $matches[1]; + if(preg_match("/^favorites([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $favorites = $matches[2]; $event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)")); } - else if(preg_match("/favorited_by=(.*)/i", $event->term, $matches)) { + else if(preg_match("/^favorited_by[=|:](.*)$/i", $event->term, $matches)) { global $database; $user = User::by_name($matches[1]); if(!is_null($user)) { @@ -134,7 +134,7 @@ class Favorites extends Extension { $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)")); } - else if(preg_match("/favorited_by_userno=([0-9]+)/i", $event->term, $matches)) { + else if(preg_match("/^favorited_by_userno[=|:](\d+)$/i", $event->term, $matches)) { $user_id = int_escape($matches[1]); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)")); } diff --git a/ext/index/main.php b/ext/index/main.php index f851bde7..4acc663c 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -38,36 +38,53 @@ * <li>id<20 -- search only the first few images * <li>id>=500 -- search later images * </ul> - * <li>user=Username, eg + * <li>user=Username & poster=Username, eg * <ul> * <li>user=Shish -- find all of Shish's posts + * <li>poster=Shish -- same as above * </ul> - * <li>hash=md5sum, eg + * <li>user_id=userID & poster_id=userID, eg + * <ul> + * <li>user_id=2 -- find all posts by user id 2 + * <li>poster_id=2 -- same as above + * </ul> + * <li>hash=md5sum & md5=md5sum, eg * <ul> * <li>hash=bf5b59173f16b6937a4021713dbfaa72 -- find the "Taiga want up!" image + * <li>md5=bf5b59173f16b6937a4021713dbfaa72 -- same as above * </ul> - * <li>filetype=type, eg + * <li>filetype=type & ext=type, eg * <ul> * <li>filetype=png -- find all PNG images + * <li>ext=png -- same as above * </ul> - * <li>filename=blah, eg + * <li>filename=blah & name=blah, eg * <ul> * <li>filename=kitten -- find all images with "kitten" in the original filename + * <li>name=kitten -- same as above * </ul> * <li>posted (=, <, >, <=, >=) date, eg * <ul> * <li>posted>=2009-12-25 posted<=2010-01-01 -- find images posted between christmas and new year * </ul> + * <li>tags (=, <, >, <=, >=) count, eg + * <ul> + * <li>tags=1 -- search for images with only 1 tag + * <li>tags>=10 -- search for images with 10 or more tags + * <li>tags<25 -- search for images with less than 25 tags + * </ul> + * <li>source=url, eg + * <ul> + * <li>source=http://example.com -- find all images with "http://example.com" in the source + * </ul> * </ul> * <p>Search items can be combined to search for images which match both, * or you can stick "-" in front of an item to search for things that don't * match it. + * <p>Metatags can be followed by ":" rather than "=" if you prefer. + * <br />I.E: "posted:2014-01-01", "id:>=500" etc. * <p>Some search methods provided by extensions: * <ul> - * <li>Danbooru API - * <ul> - * <li>md5:[hash] -- same as "hash=", but the API calls it by a different name - * </ul> * <li>Numeric Score * <ul> * <li>score (=, <, >, <=, >=) number -- seach by score @@ -81,11 +98,14 @@ * <li>Favorites * <ul> * <li>favorites (=, <, >, <=, >=) number -- search for images favourited a certain number of times - * <li>favourited_by=Username -- search for a user's choices + * <li>favourited_by=Username -- search for a user's choices by username + * <li>favorited_by_userno=UserID -- search for a user's choice by userID * </ul> * <li>Notes * <ul> * <li>notes (=, <, >, <=, >=) number -- search by the number of notes an image has + * <li>notes_by=Username -- search for a notes created by username + * <li>notes_by_userno=UserID -- search for a notes created by userID * </ul> * </ul> */ @@ -240,45 +260,45 @@ class Index extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); // check for tags first as tag based searches are more common. - if(preg_match("/tags(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) { - $cmp = $matches[1]; + if(preg_match("/^tags([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $tags = $matches[2]; $event->add_querylet(new Querylet('images.id IN (SELECT DISTINCT image_id FROM image_tags GROUP BY image_id HAVING count(image_id) '.$cmp.' '.$tags.')')); } - else if(preg_match("/^ratio(<|>|<=|>=|=)(\d+):(\d+)$/", $event->term, $matches)) { - $cmp = $matches[1]; + else if(preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/", $event->term, $matches)) { + $cmp = preg_replace('/^:/', '=', $matches[1]); $args = array("width{$this->stpen}"=>int_escape($matches[2]), "height{$this->stpen}"=>int_escape($matches[3])); $event->add_querylet(new Querylet("width / height $cmp :width{$this->stpen} / :height{$this->stpen}", $args)); } - else if(preg_match("/^(filesize|id)(<|>|<=|>=|=)(\d+[kmg]?b?)$/i", $event->term, $matches)) { + else if(preg_match("/^(filesize|id)([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+[kmg]?b?)$/i", $event->term, $matches)) { $col = $matches[1]; - $cmp = $matches[2]; + $cmp = ltrim($matches[2], ":") ?: "="; $val = parse_shorthand_int($matches[3]); $event->add_querylet(new Querylet("images.$col $cmp :val{$this->stpen}", array("val{$this->stpen}"=>$val))); } - else if(preg_match("/^(hash|md5)=([0-9a-fA-F]*)$/i", $event->term, $matches)) { + else if(preg_match("/^(hash|md5)[=|:]([0-9a-fA-F]*)$/i", $event->term, $matches)) { $hash = strtolower($matches[2]); $event->add_querylet(new Querylet('images.hash = :hash', array("hash" => $hash))); } - else if(preg_match("/^(filetype|ext)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) { + else if(preg_match("/^(filetype|ext)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { $ext = strtolower($matches[2]); $event->add_querylet(new Querylet('images.ext = :ext', array("ext" => $ext))); } - else if(preg_match("/^(filename|name)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) { + else if(preg_match("/^(filename|name)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { $filename = strtolower($matches[2]); $event->add_querylet(new Querylet("images.filename LIKE :filename{$this->stpen}", array("filename{$this->stpen}"=>"%$filename%"))); } - else if(preg_match("/^(source)=([a-zA-Z0-9]*)$/i", $event->term, $matches)) { - $filename = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.source LIKE :src', array("src"=>"%$filename%"))); + else if(preg_match("/^(source)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { + $source = strtolower($matches[2]); + $event->add_querylet(new Querylet('images.source LIKE :src', array("src"=>"%$source%"))); } - else if(preg_match("/^posted(<|>|<=|>=|=)([0-9-]*)$/", $event->term, $matches)) { - $cmp = $matches[1]; + else if(preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $val = $matches[2]; $event->add_querylet(new Querylet("images.posted $cmp :posted{$this->stpen}", array("posted{$this->stpen}"=>$val))); } - else if(preg_match("/^size(<|>|<=|>=|=)(\d+)x(\d+)$/", $event->term, $matches)) { - $cmp = $matches[1]; + else if(preg_match("/^size([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)x(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $args = array("width{$this->stpen}"=>int_escape($matches[2]), "height{$this->stpen}"=>int_escape($matches[3])); $event->add_querylet(new Querylet("width $cmp :width{$this->stpen} AND height $cmp :height{$this->stpen}", $args)); } diff --git a/ext/notes/main.php b/ext/notes/main.php index 5bc09ef6..f29a22e9 100644 --- a/ext/notes/main.php +++ b/ext/notes/main.php @@ -210,16 +210,16 @@ class Notes extends Extension { */ public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/note=(.*)/i", $event->term, $matches)) { + if(preg_match("/^note[=|:](.*)$/i", $event->term, $matches)) { $notes = int_escape($matches[1]); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE note = $notes)")); } - else if(preg_match("/notes(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) { - $cmp = $matches[1]; + else if(preg_match("/^notes([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)%/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $notes = $matches[2]; $event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE notes $cmp $notes)")); } - else if(preg_match("/notes_by=(.*)/i", $event->term, $matches)) { + else if(preg_match("/^notes_by[=|:](.*)$/i", $event->term, $matches)) { global $database; $user = User::by_name($matches[1]); if(!is_null($user)) { @@ -231,7 +231,7 @@ class Notes extends Extension { $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)")); } - else if(preg_match("/notes_by_userno=([0-9]+)/i", $event->term, $matches)) { + else if(preg_match("/^notes_by_userno[=|:](\d+)$/i", $event->term, $matches)) { $user_id = int_escape($matches[1]); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM notes WHERE user_id = $user_id)")); } diff --git a/ext/rating/main.php b/ext/rating/main.php index be54cdb4..1b41b89f 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -116,7 +116,7 @@ class Ratings extends Extension { $set = Ratings::privs_to_sql(Ratings::get_user_privs($user)); $event->add_querylet(new Querylet("rating IN ($set)")); } - if(preg_match("/^rating=(?:([sqeu]+)|(safe|questionable|explicit|unknown))$/D", strtolower($event->term), $matches)) { + if(preg_match("/^rating[=|:](?:([sqeu]+)|(safe|questionable|explicit|unknown))$/D", strtolower($event->term), $matches)) { $ratings = $matches[1] ? $matches[1] : array($matches[2][0]); $ratings = array_intersect(str_split($ratings), str_split(Ratings::get_user_privs($user))); $set = "'" . join("', '", $ratings) . "'"; @@ -199,7 +199,7 @@ class Ratings extends Extension { private function no_rating_query($context) { foreach($context as $term) { - if(preg_match("/^rating=/", $term)) { + if(preg_match("/^rating[=|:]/", $term)) { return false; } } diff --git a/ext/user/main.php b/ext/user/main.php index 5a4058ca..67755004 100644 --- a/ext/user/main.php +++ b/ext/user/main.php @@ -309,7 +309,7 @@ class UserPage extends Extension { global $user; $matches = array(); - if(preg_match("/^(poster|user)=(.*)$/i", $event->term, $matches)) { + if(preg_match("/^(poster|user)[=|:](.*)$/i", $event->term, $matches)) { $duser = User::by_name($matches[2]); if(!is_null($duser)) { $user_id = $duser->id; @@ -319,11 +319,11 @@ class UserPage extends Extension { } $event->add_querylet(new Querylet("images.owner_id = $user_id")); } - else if(preg_match("/^(poster|user)_id=([0-9]+)$/i", $event->term, $matches)) { + else if(preg_match("/^(poster|user)_id[=|:]([0-9]+)$/i", $event->term, $matches)) { $user_id = int_escape($matches[2]); $event->add_querylet(new Querylet("images.owner_id = $user_id")); } - else if($user->can("view_ip") && preg_match("/^(poster|user)_ip=([0-9\.]+)$/i", $event->term, $matches)) { + else if($user->can("view_ip") && preg_match("/^(poster|user)_ip[=|:]([0-9\.]+)$/i", $event->term, $matches)) { $user_ip = $matches[2]; // FIXME: ip_escape? $event->add_querylet(new Querylet("images.owner_ip = '$user_ip'")); } From 14899e79ad1ec00acb556607ca0928b517045911 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Thu, 2 Jan 2014 14:10:08 +0000 Subject: [PATCH 17/30] added height & width metatags --- ext/index/main.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ext/index/main.php b/ext/index/main.php index 4acc663c..20820fe2 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -21,6 +21,16 @@ * <li>size>=500x500 -- no small images * <li>size<1000x1000 -- no large images * </ul> + * <li>width (=, <, >, <=, >=) width, eg + * <ul> + * <li>width=1024 -- find images with 1024 width + * <li>width>2000 -- find images bigger than 2000 width + * </ul> + * <li>height (=, <, >, <=, >=) height, eg + * <ul> + * <li>height=768 -- find images with 768 height + * <li>height>1000 -- find images bigger than 1000 height + * </ul> * <li>ratio (=, <, >, <=, >=) width : height, eg * <ul> * <li>ratio=4:3, ratio=16:9 -- standard wallpaper @@ -302,6 +312,14 @@ class Index extends Extension { $args = array("width{$this->stpen}"=>int_escape($matches[2]), "height{$this->stpen}"=>int_escape($matches[3])); $event->add_querylet(new Querylet("width $cmp :width{$this->stpen} AND height $cmp :height{$this->stpen}", $args)); } + else if(preg_match("/^width([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; + $event->add_querylet(new Querylet("width $cmp :width{$this->stpen}", array("width{$this->stpen}"=>int_escape($matches[2])))); + } + else if(preg_match("/^height([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; + $event->add_querylet(new Querylet("height $cmp :height{$this->stpen}",array("height{$this->stpen}"=>int_escape($matches[2])))); + } $this->stpen++; } From 9f06a5c5656049c923d5c6361c0217d010729b86 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Fri, 3 Jan 2014 00:41:28 +0000 Subject: [PATCH 18/30] fix search not working properly for aliases to multiple tags fix issue 359 --- core/imageboard.pack.php | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index c9a2ce4a..eb34035e 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -663,6 +663,8 @@ class Image { } } + $terms = Tag::resolve_aliases($terms); + // parse the words that are searched for into // various types of querylet foreach($terms as $term) { @@ -675,8 +677,6 @@ class Image { continue; } - $term = Tag::resolve_alias($term); - $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); if($stpe->is_querylet_set()) { @@ -824,6 +824,8 @@ class Image { } } + $terms = Tag::resolve_aliases($terms); + reset($terms); // rewind to first element in array. // turn each term into a specific type of querylet @@ -833,8 +835,6 @@ class Image { $negative = true; $term = substr($term, 1); } - - $term = Tag::resolve_alias($term); $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); @@ -1082,11 +1082,22 @@ class Tag { assert(is_array($tags)); $new = array(); - foreach($tags as $tag) { - $new_set = explode(' ', Tag::resolve_alias($tag)); - foreach($new_set as $new_one) { - $new[] = $new_one; + + $i = 0; + $tag_count = count($tags); + while($i<$tag_count) { + $aliases = explode(' ', Tag::resolve_alias($tags[$i])); + foreach($aliases as $alias){ + if(!in_array($alias, $new)){ + if($tags[$i] == $alias){ + $new[] = $alias; + }elseif(!in_array($alias, $tags)){ + $tags[] = $alias; + $tag_count++; + } + } } + $i++; } $new = array_iunique($new); // remove any duplicate tags From 9cae856df7a408ec874a7b270eb0e03e6678a774 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Fri, 3 Jan 2014 00:58:53 +0000 Subject: [PATCH 19/30] use the Content-Disposition header for filename & Content-Type for extension if either doesn't exist, it will fallback to using pathinfo --- core/util.inc.php | 64 +++++++++++++++++++++++++++++++++++++++++---- ext/upload/main.php | 14 +++++----- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/core/util.inc.php b/core/util.inc.php index b1331a4a..b63c0d43 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -801,17 +801,24 @@ function transload($url, $mfile) { $ch = curl_init($url); $fp = fopen($mfile, "w"); - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_VERBOSE, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_REFERER, $url); curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - curl_exec($ch); + $response = curl_exec($ch); + + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size))))); + $body = substr($response, $header_size); + curl_close($ch); + fwrite($fp, $body); fclose($fp); - return true; + return $headers; } if($config->get_string("transload_engine") === "wget") { @@ -839,12 +846,59 @@ function transload($url, $mfile) { fwrite($fp, $data); fclose($fp); - return true; + $headers = http_parse_headers(implode("\n", $http_response_header)); + + return $headers; } return false; } +if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/function.http-parse-headers.php#112917 + function http_parse_headers ($raw_headers){ + $headers = array(); // $headers = []; + + foreach (explode("\n", $raw_headers) as $i => $h) { + $h = explode(':', $h, 2); + + if (isset($h[1])){ + if(!isset($headers[$h[0]])){ + $headers[$h[0]] = trim($h[1]); + }else if(is_array($headers[$h[0]])){ + $tmp = array_merge($headers[$h[0]],array(trim($h[1]))); + $headers[$h[0]] = $tmp; + }else{ + $tmp = array_merge(array($headers[$h[0]]),array(trim($h[1]))); + $headers[$h[0]] = $tmp; + } + } + } + return $headers; + } +} + +function getExtension ($mime_type){ + if(empty($mime_type)){ + return false; + } + + $extensions = array( + 'image/jpeg' => 'jpg', + 'image/gif' => 'gif', + 'image/png' => 'png', + 'application/x-shockwave-flash' => 'swf', + 'image/x-icon' => 'ico', + 'image/svg+xml' => 'svg', + 'audio/mpeg' => 'mp3', + 'video/x-flv' => 'flv', + 'audio/mp4' => 'mp4', + 'video/mp4' => 'mp4', + 'audio/webm' => 'webm', + 'video/webm' => 'webm' + ); + + return $extensions[$mime_type]; +} $_included = array(); /** diff --git a/ext/upload/main.php b/ext/upload/main.php index 2e7a073e..b92fc479 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -329,9 +329,12 @@ class Upload extends Extension { } $tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload"); - $filename = basename($url); - if(!transload($url, $tmp_filename)) { + $headers = transload($url, $tmp_filename); + $h_filename = (isset($headers['Content-Disposition']) ? preg_replace('/^.*filename="([^ ]+)"/i', '$1', $headers['Content-Disposition']) : null); + $filename = $h_filename ?: basename($url); + + if(!$headers) { $this->theme->display_upload_error($page, "Error with ".html_escape($filename), "Error reading from ".html_escape($url)); return false; @@ -341,12 +344,11 @@ class Upload extends Extension { $this->theme->display_upload_error($page, "Error with ".html_escape($filename), "No data found -- perhaps the site has hotlink protection?"); $ok = false; - } - else { + }else{ global $user; $pathinfo = pathinfo($url); - $metadata['filename'] = $pathinfo['basename']; - $metadata['extension'] = $pathinfo['extension']; + $metadata['filename'] = $filename; + $metadata['extension'] = getExtension($headers['Content-Type']) ?: $pathinfo['extension']; $metadata['tags'] = $tags; $metadata['source'] = $source; From 2c2f27ca640985d49e3eb5134b53516687e91d80 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Fri, 3 Jan 2014 01:24:55 +0000 Subject: [PATCH 20/30] add order metatag not too happy with how this works...but it does work --- core/imageboard.pack.php | 48 ++++++++++++++++++++++------------------ ext/index/main.php | 13 +++++++++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index eb34035e..ac4d0ea5 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -26,6 +26,7 @@ $tag_n = 0; // temp hack $_flexihash = null; $_fh_last_opts = null; +$order_sql = null; // this feels ugly require_once "lib/flexihash.php"; @@ -114,7 +115,7 @@ class Image { assert(is_numeric($start)); assert(is_numeric($limit)); assert(is_array($tags)); - global $database, $user; + global $database, $user, $order_sql; $images = array(); @@ -128,13 +129,15 @@ class Image { } $querylet = Image::build_search_querylet($tags); - $querylet->append(new Querylet("ORDER BY images.id DESC LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start))); + $querylet->append(new Querylet($order_sql ?: " ORDER BY images.id DESC")); + $querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start))); #var_dump($querylet->sql); var_dump($querylet->variables); $result = $database->execute($querylet->sql, $querylet->variables); while($row = $result->fetch()) { $images[] = new Image($row); } + $order_sql = null; return $images; } @@ -663,8 +666,6 @@ class Image { } } - $terms = Tag::resolve_aliases($terms); - // parse the words that are searched for into // various types of querylet foreach($terms as $term) { @@ -677,6 +678,15 @@ class Image { continue; } + $aliases = explode(" ", Tag::resolve_alias($term)); + $found = array_search($term, $aliases); + if($found !== false){ + unset($aliases[$found]); + }else{ + $term = array_shift($aliases); + } + foreach($aliases as $alias) array_push($terms, $alias); + $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); if($stpe->is_querylet_set()) { @@ -824,8 +834,6 @@ class Image { } } - $terms = Tag::resolve_aliases($terms); - reset($terms); // rewind to first element in array. // turn each term into a specific type of querylet @@ -835,6 +843,15 @@ class Image { $negative = true; $term = substr($term, 1); } + + $aliases = explode(" ", Tag::resolve_alias($term)); + $found = array_search($term, $aliases); + if($found !== false){ + unset($aliases[$found]); + }else{ + $term = array_shift($aliases); + } + foreach($aliases as $alias) array_push($terms, $alias); $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); @@ -1082,22 +1099,11 @@ class Tag { assert(is_array($tags)); $new = array(); - - $i = 0; - $tag_count = count($tags); - while($i<$tag_count) { - $aliases = explode(' ', Tag::resolve_alias($tags[$i])); - foreach($aliases as $alias){ - if(!in_array($alias, $new)){ - if($tags[$i] == $alias){ - $new[] = $alias; - }elseif(!in_array($alias, $tags)){ - $tags[] = $alias; - $tag_count++; - } - } + foreach($tags as $tag) { + $new_set = explode(' ', Tag::resolve_alias($tag)); + foreach($new_set as $new_one) { + $new[] = $new_one; } - $i++; } $new = array_iunique($new); // remove any duplicate tags diff --git a/ext/index/main.php b/ext/index/main.php index 20820fe2..b2df34f0 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -87,6 +87,11 @@ * <ul> * <li>source=http://example.com -- find all images with "http://example.com" in the source * </ul> + * <li>order (id, width, height, filesize, filename)_(ASC, DESC), eg + * <ul> + * <li>order=width -- find all images sorted from highest > lowest width + * <li>order=filesize_asc -- find all images sorted from lowest > highest filesize + * </ul> * </ul> * <p>Search items can be combined to search for images which match both, * or you can stick "-" in front of an item to search for things that don't @@ -320,6 +325,14 @@ class Index extends Extension { $cmp = ltrim($matches[1], ":") ?: "="; $event->add_querylet(new Querylet("height $cmp :height{$this->stpen}",array("height{$this->stpen}"=>int_escape($matches[2])))); } + else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){ + global $order_sql; + $order = strtolower($matches[1]); + $sort = isset($matches[2]) ? strtoupper($matches[2]) : (preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC"); + // $event->add_querylet(new Querylet("ORDER BY images.:order :sort", array("order" => $order, "sort" => $sort))); + $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag + $order_sql = " ORDER BY images.$order $sort"; + } $this->stpen++; } From 152f5fbf26791c142eedc9bedb5d7ac0505b378d Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Fri, 3 Jan 2014 08:24:47 +0000 Subject: [PATCH 21/30] add config option for default order --- core/imageboard.pack.php | 4 ++-- ext/index/main.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index ac4d0ea5..0bf145e9 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -115,7 +115,7 @@ class Image { assert(is_numeric($start)); assert(is_numeric($limit)); assert(is_array($tags)); - global $database, $user, $order_sql; + global $database, $user, $config, $order_sql; $images = array(); @@ -129,7 +129,7 @@ class Image { } $querylet = Image::build_search_querylet($tags); - $querylet->append(new Querylet($order_sql ?: " ORDER BY images.id DESC")); + $querylet->append(new Querylet(" ORDER BY images.".($order_sql ?: $config->get_string("index_order")))); $querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", array("limit"=>$limit, "offset"=>$start))); #var_dump($querylet->sql); var_dump($querylet->variables); $result = $database->execute($querylet->sql, $querylet->variables); diff --git a/ext/index/main.php b/ext/index/main.php index b2df34f0..1831ec68 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -176,6 +176,7 @@ class Index extends Extension { global $config; $config->set_default_int("index_images", 24); $config->set_default_bool("index_tips", true); + $config->set_default_string("index_order", "id DESC"); } public function onPageRequest(PageRequestEvent $event) { @@ -327,11 +328,10 @@ class Index extends Extension { } else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){ global $order_sql; - $order = strtolower($matches[1]); + $ord = strtolower($matches[1]); $sort = isset($matches[2]) ? strtoupper($matches[2]) : (preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC"); - // $event->add_querylet(new Querylet("ORDER BY images.:order :sort", array("order" => $order, "sort" => $sort))); + $order_sql = "$ord $sort"; $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag - $order_sql = " ORDER BY images.$order $sort"; } $this->stpen++; From 7d49e21792297a79566241aa0f5e76a1b5734711 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Sun, 5 Jan 2014 11:52:09 +0000 Subject: [PATCH 22/30] readability + moved stuff --- core/util.inc.php | 47 +++++++++++++++++++++++----------------------- ext/index/main.php | 5 +++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/core/util.inc.php b/core/util.inc.php index b63c0d43..09dbe7fe 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -556,6 +556,30 @@ function getMimeType($file, $ext="") { return 'application/octet-stream'; } + +function getExtension ($mime_type){ + if(empty($mime_type)){ + return false; + } + + $extensions = array( + 'image/jpeg' => 'jpg', + 'image/gif' => 'gif', + 'image/png' => 'png', + 'application/x-shockwave-flash' => 'swf', + 'image/x-icon' => 'ico', + 'image/svg+xml' => 'svg', + 'audio/mpeg' => 'mp3', + 'video/x-flv' => 'flv', + 'audio/mp4' => 'mp4', + 'video/mp4' => 'mp4', + 'audio/webm' => 'webm', + 'video/webm' => 'webm' + ); + + return $extensions[$mime_type]; +} + /** * @private */ @@ -877,29 +901,6 @@ if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/func } } -function getExtension ($mime_type){ - if(empty($mime_type)){ - return false; - } - - $extensions = array( - 'image/jpeg' => 'jpg', - 'image/gif' => 'gif', - 'image/png' => 'png', - 'application/x-shockwave-flash' => 'swf', - 'image/x-icon' => 'ico', - 'image/svg+xml' => 'svg', - 'audio/mpeg' => 'mp3', - 'video/x-flv' => 'flv', - 'audio/mp4' => 'mp4', - 'video/mp4' => 'mp4', - 'audio/webm' => 'webm', - 'video/webm' => 'webm' - ); - - return $extensions[$mime_type]; -} - $_included = array(); /** * Get the active contents of a .php file diff --git a/ext/index/main.php b/ext/index/main.php index 1831ec68..45f71f02 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -87,7 +87,7 @@ * <ul> * <li>source=http://example.com -- find all images with "http://example.com" in the source * </ul> - * <li>order (id, width, height, filesize, filename)_(ASC, DESC), eg + * <li>order=(id, width, height, filesize, filename)_(ASC, DESC), eg * <ul> * <li>order=width -- find all images sorted from highest > lowest width * <li>order=filesize_asc -- find all images sorted from lowest > highest filesize @@ -329,7 +329,8 @@ class Index extends Extension { else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){ global $order_sql; $ord = strtolower($matches[1]); - $sort = isset($matches[2]) ? strtoupper($matches[2]) : (preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC"); + $default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC"; + $sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column; $order_sql = "$ord $sort"; $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag } From 325da1111913ccbbe5824c2d1e5cd7b6a5937b8f Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 13 Jan 2014 09:13:56 +0000 Subject: [PATCH 23/30] artist/comment/numeric_score metatags now work using : also updated docs --- ext/artists/main.php | 2 +- ext/comment/main.php | 8 ++++---- ext/index/main.php | 12 ++++++++++++ ext/numeric_score/main.php | 12 ++++++------ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ext/artists/main.php b/ext/artists/main.php index d426d512..984b12af 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -45,7 +45,7 @@ class Artists extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/^author=(.*)$/", $event->term, $matches)) { + if(preg_match("/^author[=|:](.*)$/", $event->term, $matches)) { $char = $matches[1]; $event->add_querylet(new Querylet("Author = :author_char", array("author_char"=>$char))); } diff --git a/ext/comment/main.php b/ext/comment/main.php index 48d4f013..1c472626 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -262,12 +262,12 @@ class CommentList extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/comments(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) { - $cmp = $matches[1]; + if(preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $comments = $matches[2]; $event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM comments GROUP BY image_id HAVING count(image_id) $cmp $comments)")); } - else if(preg_match("/commented_by=(.*)/i", $event->term, $matches)) { + else if(preg_match("/^commented_by[=|:](.*)$/i", $event->term, $matches)) { global $database; $user = User::by_name($matches[1]); if(!is_null($user)) { @@ -279,7 +279,7 @@ class CommentList extends Extension { $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)")); } - else if(preg_match("/commented_by_userid=([0-9]+)/i", $event->term, $matches)) { + else if(preg_match("/^commented_by_userno[=|:]([0-9]+)$/i", $event->term, $matches)) { $user_id = int_escape($matches[1]); $event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)")); } diff --git a/ext/index/main.php b/ext/index/main.php index 45f71f02..bd4f3e30 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -105,6 +105,8 @@ * <li>score (=, <, >, <=, >=) number -- seach by score * <li>upvoted_by=Username -- search for a user's likes * <li>downvoted_by=Username -- search for a user's dislikes + * <li>upvoted_by_id=UserID -- search for a user's likes by user ID + * <li>downvoted_by_id=UserID -- search for a user's dislikes by user ID * </ul> * <li>Image Rating * <ul> @@ -122,6 +124,16 @@ * <li>notes_by=Username -- search for a notes created by username * <li>notes_by_userno=UserID -- search for a notes created by userID * </ul> + * <li>Artists + * <ul> + * <li>author=ArtistName -- search for images by artist + * </ul> + * <li>Image Comments + * <ul> + * <li>comments (=, <, >, <=, >=) number -- search for images by number of comments + * <li>commented_by=Username -- search for a user's comments by username + * <li>commented_by_userno=UserID -- search for a user's comments by userID + * </ul> * </ul> */ diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index 9cf21ab3..2da74577 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -217,12 +217,12 @@ class NumericScore extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/^score(<|<=|=|>=|>)(-?\d+)$/", $event->term, $matches)) { - $cmp = $matches[1]; + if(preg_match("/^score([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(-?\d+)$/", $event->term, $matches)) { + $cmp = ltrim($matches[1], ":") ?: "="; $score = $matches[2]; $event->add_querylet(new Querylet("numeric_score $cmp $score")); } - if(preg_match("/^upvoted_by=(.*)$/", $event->term, $matches)) { + if(preg_match("/^upvoted_by[=|:](.*)$/", $event->term, $matches)) { $duser = User::by_name($matches[1]); if(is_null($duser)) { throw new SearchTermParseException( @@ -232,7 +232,7 @@ class NumericScore extends Extension { "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)", array("ns_user_id"=>$duser->id))); } - if(preg_match("/^downvoted_by=(.*)$/", $event->term, $matches)) { + if(preg_match("/^downvoted_by[=|:](.*)$/", $event->term, $matches)) { $duser = User::by_name($matches[1]); if(is_null($duser)) { throw new SearchTermParseException( @@ -242,13 +242,13 @@ class NumericScore extends Extension { "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)", array("ns_user_id"=>$duser->id))); } - if(preg_match("/^upvoted_by_id=(\d+)$/", $event->term, $matches)) { + if(preg_match("/^upvoted_by_id[=|:](\d+)$/", $event->term, $matches)) { $iid = int_escape($matches[1]); $event->add_querylet(new Querylet( "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)", array("ns_user_id"=>$iid))); } - if(preg_match("/^downvoted_by_id=(\d+)$/", $event->term, $matches)) { + if(preg_match("/^downvoted_by_id[=|:](\d+)$/", $event->term, $matches)) { $iid = int_escape($matches[1]); $event->add_querylet(new Querylet( "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)", From ae4da2b410234f425224369f98d06283551ef90a Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Mon, 13 Jan 2014 10:03:38 +0000 Subject: [PATCH 24/30] add option for getMimeType to return list of extensions --- core/util.inc.php | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/core/util.inc.php b/core/util.inc.php index 09dbe7fe..a4c5b244 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -509,14 +509,15 @@ function captcha_check() { * @param string &$file File path * @return string */ -function getMimeType($file, $ext="") { +function getMimeType($file, $ext="", $list=false) { // Static extension lookup $ext = strtolower($ext); static $exts = array( 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', - 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf', + 'swf' => 'application/x-shockwave-flash', 'video/x-flv' => 'flv', + 'svg' => 'image/svg+xml', 'pdf' => 'application/pdf', 'zip' => 'application/zip', 'gz' => 'application/x-gzip', 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', @@ -529,6 +530,8 @@ function getMimeType($file, $ext="") { 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm' ); + if ($list == true){ return $exts; } + if (isset($exts[$ext])) { return $exts[$ext]; } $type = false; @@ -562,22 +565,9 @@ function getExtension ($mime_type){ return false; } - $extensions = array( - 'image/jpeg' => 'jpg', - 'image/gif' => 'gif', - 'image/png' => 'png', - 'application/x-shockwave-flash' => 'swf', - 'image/x-icon' => 'ico', - 'image/svg+xml' => 'svg', - 'audio/mpeg' => 'mp3', - 'video/x-flv' => 'flv', - 'audio/mp4' => 'mp4', - 'video/mp4' => 'mp4', - 'audio/webm' => 'webm', - 'video/webm' => 'webm' - ); - - return $extensions[$mime_type]; + $extensions = getMimeType(null, null, true); + $ext = array_search($mime_type, $extensions); + return ($ext ?: false); } /** From ce256f5bf4a7a883243d063a89c628ddc92c8e76 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Tue, 14 Jan 2014 06:12:34 +0000 Subject: [PATCH 25/30] added pool & pool_by_name search metatags --- ext/index/main.php | 13 +++++++++---- ext/pools/main.php | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ext/index/main.php b/ext/index/main.php index bd4f3e30..0aadaf2b 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -121,8 +121,8 @@ * <li>Notes * <ul> * <li>notes (=, <, >, <=, >=) number -- search by the number of notes an image has - * <li>notes_by=Username -- search for a notes created by username - * <li>notes_by_userno=UserID -- search for a notes created by userID + * <li>notes_by=Username -- search for images contains notes created by username + * <li>notes_by_userno=UserID -- search for images contains notes created by userID * </ul> * <li>Artists * <ul> @@ -131,8 +131,13 @@ * <li>Image Comments * <ul> * <li>comments (=, <, >, <=, >=) number -- search for images by number of comments - * <li>commented_by=Username -- search for a user's comments by username - * <li>commented_by_userno=UserID -- search for a user's comments by userID + * <li>commented_by=Username -- search for images contains user's comments by username + * <li>commented_by_userno=UserID -- search for images contains user's comments by userID + * </ul> + * <li>Pools + * <ul> + * <li>pool=PoolID -- search for images in a pool by PoolID + * <li>pool_by_name=PoolName -- search for images in a pool by PoolName. underscores are replaced with spaces * </ul> * </ul> */ diff --git a/ext/pools/main.php b/ext/pools/main.php index 008838d3..8431a653 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -293,6 +293,22 @@ class Pools extends Extension { } } + public function onSearchTermParse(SearchTermParseEvent $event) { + $matches = array(); + if(preg_match("/^pool[=|:]([0-9]+)$/", $event->term, $matches)) { + $poolID = $matches[1]; + $event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM pool_images WHERE pool_id = $poolID)")); + } + else if(preg_match("/^pool_by_name[=|:](.*)$/", $event->term, $matches)) { + $poolTitle = str_replace("_", " ", $matches[1]); + + $pool = $this->get_single_pool_from_title($poolTitle); + $poolID = 0; + if ($pool){ $poolID = $pool['id']; } + $event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM pool_images WHERE pool_id = $poolID)")); + } + } + public function add_post_from_tag(/*str*/ $poolTag, /*int*/ $imageID){ $poolTag = str_replace("_", " ", $poolTag); //First check if pool tag is a title From 55ff224ac0bd6354ef21a37cf9175a836f92a389 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Tue, 14 Jan 2014 06:27:12 +0000 Subject: [PATCH 26/30] added any/none options to the source/pool metatags --- ext/index/main.php | 14 +++++++++++--- ext/pools/main.php | 10 ++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ext/index/main.php b/ext/index/main.php index 0aadaf2b..4d41ed20 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -83,9 +83,11 @@ * <li>tags>=10 -- search for images with 10 or more tags * <li>tags<25 -- search for images with less than 25 tags * </ul> - * <li>source=url, eg + * <li>source=(URL, any, none) eg * <ul> * <li>source=http://example.com -- find all images with "http://example.com" in the source + * <li>source=any -- find all images with a source + * <li>source=none -- find all images without a source * </ul> * <li>order=(id, width, height, filesize, filename)_(ASC, DESC), eg * <ul> @@ -136,7 +138,7 @@ * </ul> * <li>Pools * <ul> - * <li>pool=PoolID -- search for images in a pool by PoolID + * <li>pool=(PoolID, any, none) -- search for images in a pool by PoolID. * <li>pool_by_name=PoolName -- search for images in a pool by PoolName. underscores are replaced with spaces * </ul> * </ul> @@ -323,7 +325,13 @@ class Index extends Extension { } else if(preg_match("/^(source)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { $source = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.source LIKE :src', array("src"=>"%$source%"))); + + if(preg_match("/^(any|none)$/", $source)){ + $not = ($source == "any" ? "NOT" : ""); + $event->add_querylet(new Querylet("images.source IS $not NULL")); + }else{ + $event->add_querylet(new Querylet('images.source LIKE :src', array("src"=>"%$source%"))); + } } else if(preg_match("/^posted([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])([0-9-]*)$/", $event->term, $matches)) { $cmp = ltrim($matches[1], ":") ?: "="; diff --git a/ext/pools/main.php b/ext/pools/main.php index 8431a653..6bcd8760 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -295,9 +295,15 @@ class Pools extends Extension { public function onSearchTermParse(SearchTermParseEvent $event) { $matches = array(); - if(preg_match("/^pool[=|:]([0-9]+)$/", $event->term, $matches)) { + if(preg_match("/^pool[=|:]([0-9]+|any|none)$/", $event->term, $matches)) { $poolID = $matches[1]; - $event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM pool_images WHERE pool_id = $poolID)")); + + if(preg_match("/^(any|none)$/", $poolID)){ + $not = ($poolID == "none" ? "NOT" : ""); + $event->add_querylet(new Querylet("images.id $not IN (SELECT DISTINCT image_id FROM pool_images)")); + }else{ + $event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM pool_images WHERE pool_id = $poolID)")); + } } else if(preg_match("/^pool_by_name[=|:](.*)$/", $event->term, $matches)) { $poolTitle = str_replace("_", " ", $matches[1]); From b5f70de49638241686ba64e7e9f6b02e4c6cfe28 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Tue, 14 Jan 2014 07:52:45 +0000 Subject: [PATCH 27/30] change source metatag regex to allow searching for URLs --- ext/index/main.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/index/main.php b/ext/index/main.php index 4d41ed20..9cf5abfc 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -123,8 +123,8 @@ * <li>Notes * <ul> * <li>notes (=, <, >, <=, >=) number -- search by the number of notes an image has - * <li>notes_by=Username -- search for images contains notes created by username - * <li>notes_by_userno=UserID -- search for images contains notes created by userID + * <li>notes_by=Username -- search for images containing notes created by username + * <li>notes_by_userno=UserID -- search for images containing notes created by userID * </ul> * <li>Artists * <ul> @@ -133,8 +133,8 @@ * <li>Image Comments * <ul> * <li>comments (=, <, >, <=, >=) number -- search for images by number of comments - * <li>commented_by=Username -- search for images contains user's comments by username - * <li>commented_by_userno=UserID -- search for images contains user's comments by userID + * <li>commented_by=Username -- search for images containing user's comments by username + * <li>commented_by_userno=UserID -- search for images containing user's comments by userID * </ul> * <li>Pools * <ul> @@ -323,7 +323,7 @@ class Index extends Extension { $filename = strtolower($matches[2]); $event->add_querylet(new Querylet("images.filename LIKE :filename{$this->stpen}", array("filename{$this->stpen}"=>"%$filename%"))); } - else if(preg_match("/^(source)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { + else if(preg_match("/^(source)[=|:](.*)$/i", $event->term, $matches)) { $source = strtolower($matches[2]); if(preg_match("/^(any|none)$/", $source)){ From b856b132354f7c359d43ec1d214d3643187f2fbf Mon Sep 17 00:00:00 2001 From: Diftraku <diftraku@derpy.me> Date: Wed, 15 Jan 2014 23:06:27 +0200 Subject: [PATCH 28/30] Fix EXIF data throwing a notice when showing an image --- ext/handle_pixel/theme.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/handle_pixel/theme.php b/ext/handle_pixel/theme.php index 4099e97f..74f9af4c 100644 --- a/ext/handle_pixel/theme.php +++ b/ext/handle_pixel/theme.php @@ -13,6 +13,10 @@ class PixelFileHandlerTheme extends Themelet { foreach ($exif as $key => $section) { foreach ($section as $name => $val) { if($key == "IFD0") { + // Cheap fix for array'd values in EXIF-data + if (is_array($val)) { + $val = implode(',', $val); + } $head .= html_escape("$name: $val")."<br>\n"; } } From 85303d232eecccb4685d75b3ba63c1ef04a506d6 Mon Sep 17 00:00:00 2001 From: Diftraku <diftraku@derpy.me> Date: Wed, 15 Jan 2014 23:28:40 +0200 Subject: [PATCH 29/30] Fixing stuff with API output being output twice Also some code formatting and a redirect from post/show for clients such as CartonBox so you can actually view the image after opening it in the browser on the client. --- ext/ouroboros_api/main.php | 297 +++++++++++++++++++++---------------- 1 file changed, 168 insertions(+), 129 deletions(-) diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index b16abbc7..cbfb38ae 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -1,4 +1,5 @@ <?php + /* * Name: Ouroboros API * Author: Diftraku <diftraku[at]derpy.me> @@ -213,7 +214,7 @@ class _SafeOuroborosImage if (defined('ENABLED_EXTS')) { if (strstr(ENABLED_EXTS, 'rating') !== false) { // 'u' is not a "valid" rating - if($img->rating == 's' || $img->rating == 'q' || $img->rating == 'e') { + if ($img->rating == 's' || $img->rating == 'q' || $img->rating == 'e') { $this->rating = $img->rating; } } @@ -239,7 +240,9 @@ class _SafeOuroborosImage $this->sample_url = make_http($img->get_image_link()); } } -class OuroborosPost extends _SafeOuroborosImage { + +class OuroborosPost extends _SafeOuroborosImage +{ /** * Multipart File * @var array @@ -265,7 +268,8 @@ class OuroborosPost extends _SafeOuroborosImage { * @TODO implement more validation from OuroborosAPI * @param array $post */ - public function __construct(array $post) { + public function __construct(array $post) + { if (array_key_exists('tags', $post)) { $this->tags = $post['tags']; } @@ -305,6 +309,7 @@ class OuroborosPost extends _SafeOuroborosImage { } } } + class _SafeOuroborosTag { public $ambiguous = false; @@ -320,6 +325,7 @@ class _SafeOuroborosTag $this->name = $tag['tag']; } } + class OuroborosAPI extends Extension { private $event; @@ -369,8 +375,7 @@ class OuroborosAPI extends Extension $this->type = $matches[1]; if ($this->type == 'json') { $page->set_type('application/json; charset=utf-8'); - } - elseif ($this->type == 'xml') { + } elseif ($this->type == 'xml') { $page->set_type('text/xml; charset=utf-8'); } $page->set_mode('data'); @@ -380,59 +385,105 @@ class OuroborosAPI extends Extension if ($this->match('create')) { // Create // @TODO Should move the validation logic into OuroborosPost instead? - if($user->can("create_image")) { + if ($user->can("create_image")) { $post = array( - 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var($_REQUEST['post']['tags'], FILTER_SANITIZE_STRING) : 'tagme', - 'file' => !empty($_REQUEST['post']['file']) ? filter_var($_REQUEST['post']['file'], FILTER_UNSAFE_RAW) : null, - 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : 'q', - 'source' => !empty($_REQUEST['post']['source']) ? filter_var(urldecode($_REQUEST['post']['source']), FILTER_SANITIZE_URL) : null, - 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var(urldecode($_REQUEST['post']['sourceurl']), FILTER_SANITIZE_URL) : '', - 'description' => !empty($_REQUEST['post']['description']) ? filter_var($_REQUEST['post']['description'], FILTER_SANITIZE_STRING) : '', - 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var($_REQUEST['post']['is_rating_locked'], FILTER_SANITIZE_NUMBER_INT) : false, - 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var($_REQUEST['post']['is_note_locked'], FILTER_SANITIZE_NUMBER_INT) : false, - 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var($_REQUEST['post']['parent_id'], FILTER_SANITIZE_NUMBER_INT) : null, + 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var( + urldecode($_REQUEST['post']['tags']), + FILTER_SANITIZE_STRING + ) : 'tagme', + 'file' => !empty($_REQUEST['post']['file']) ? filter_var( + $_REQUEST['post']['file'], + FILTER_UNSAFE_RAW + ) : null, + 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var( + $_REQUEST['post']['rating'], + FILTER_SANITIZE_NUMBER_INT + ) : 'q', + 'source' => !empty($_REQUEST['post']['source']) ? filter_var( + urldecode($_REQUEST['post']['source']), + FILTER_SANITIZE_URL + ) : null, + 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var( + urldecode($_REQUEST['post']['sourceurl']), + FILTER_SANITIZE_URL + ) : '', + 'description' => !empty($_REQUEST['post']['description']) ? filter_var( + $_REQUEST['post']['description'], + FILTER_SANITIZE_STRING + ) : '', + 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var( + $_REQUEST['post']['is_rating_locked'], + FILTER_SANITIZE_NUMBER_INT + ) : false, + 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var( + $_REQUEST['post']['is_note_locked'], + FILTER_SANITIZE_NUMBER_INT + ) : false, + 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var( + $_REQUEST['post']['parent_id'], + FILTER_SANITIZE_NUMBER_INT + ) : null, ); $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null; $this->postCreate(new OuroborosPost($post), $md5); - } - else { + } else { $this->sendResponse(403, 'You cannot create new posts'); } - } - elseif ($this->match('update')) { + } elseif ($this->match('update')) { // Update //@todo add post update - } - elseif ($this->match('show')) { + } elseif ($this->match('show')) { // Show $id = !empty($_REQUEST['id']) ? filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT) : null; $this->postShow($id); - } - elseif ($this->match('index') || $this->match('list')) { + } elseif ($this->match('index') || $this->match('list')) { // List - $limit = !empty($_REQUEST['limit']) ? intval(filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)) : 45; - $p = !empty($_REQUEST['page']) ? intval(filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)) : 1; + $limit = !empty($_REQUEST['limit']) ? intval( + filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT) + ) : 45; + $p = !empty($_REQUEST['page']) ? intval( + filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT) + ) : 1; $tags = !empty($_REQUEST['tags']) ? filter_var($_REQUEST['tags'], FILTER_SANITIZE_STRING) : array(); if (!empty($tags)) { $tags = Tag::explode($tags); } $this->postIndex($limit, $p, $tags); } - } - elseif ($event->page_matches('tag')) { + } elseif ($event->page_matches('tag')) { if ($this->match('index') || $this->match('list')) { - $limit = !empty($_REQUEST['limit']) ? intval(filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)) : 50; - $p = !empty($_REQUEST['page']) ? intval(filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)) : 1; - $order = (!empty($_REQUEST['order']) && ($_REQUEST['order'] == 'date' || $_REQUEST['order'] == 'count' || $_REQUEST['order'] == 'name')) ? filter_var($_REQUEST['order'], FILTER_SANITIZE_STRING) : 'date'; - $id = !empty($_REQUEST['id']) ? intval(filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT)) : null; - $after_id = !empty($_REQUEST['after_id']) ? intval(filter_var($_REQUEST['after_id'], FILTER_SANITIZE_NUMBER_INT)) : null; + $limit = !empty($_REQUEST['limit']) ? intval( + filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT) + ) : 50; + $p = !empty($_REQUEST['page']) ? intval( + filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT) + ) : 1; + $order = (!empty($_REQUEST['order']) && ($_REQUEST['order'] == 'date' || $_REQUEST['order'] == 'count' || $_REQUEST['order'] == 'name')) ? filter_var( + $_REQUEST['order'], + FILTER_SANITIZE_STRING + ) : 'date'; + $id = !empty($_REQUEST['id']) ? intval( + filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT) + ) : null; + $after_id = !empty($_REQUEST['after_id']) ? intval( + filter_var($_REQUEST['after_id'], FILTER_SANITIZE_NUMBER_INT) + ) : null; $name = !empty($_REQUEST['name']) ? filter_var($_REQUEST['name'], FILTER_SANITIZE_STRING) : ''; - $name_pattern = !empty($_REQUEST['name_pattern']) ? filter_var($_REQUEST['name_pattern'], FILTER_SANITIZE_STRING) : ''; + $name_pattern = !empty($_REQUEST['name_pattern']) ? filter_var( + $_REQUEST['name_pattern'], + FILTER_SANITIZE_STRING + ) : ''; $this->tagIndex($limit, $p, $order, $id, $after_id, $name, $name_pattern); } } + } elseif ($event->page_matches('post/show')) { + $page->set_mode('redirect'); + $page->set_redirect(make_link(str_replace('post/show', 'post/view', implode('/', $event->args)))); + $page->display(); + die(); } + } /** @@ -444,12 +495,14 @@ class OuroborosAPI extends Extension * @param OuroborosPost $post * @param string $md5 */ - protected function postCreate(OuroborosPost $post, $md5 = '') { + protected function postCreate(OuroborosPost $post, $md5 = '') + { global $page, $config, $user; if (!empty($md5)) { $img = Image::by_hash($md5); if (!is_null($img)) { $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); + return; } } $meta = array(); @@ -461,42 +514,20 @@ class OuroborosAPI extends Extension } } // Check where we should try for the file - if (empty($post->file) && !empty($post->file_url) && filter_var($post->file_url, FILTER_VALIDATE_URL) !== false) { + if (empty($post->file) && !empty($post->file_url) && filter_var( + $post->file_url, + FILTER_VALIDATE_URL + ) !== false + ) { // Transload from source - $meta['file'] = tempnam('/tmp', 'shimmie_transload_'.$config->get_string('transload_engine')); + $meta['file'] = tempnam('/tmp', 'shimmie_transload_' . $config->get_string('transload_engine')); $meta['filename'] = basename($post->file_url); - if ($config->get_string('transload_engine') == 'fopen') { - $fp = fopen($post->file_url, 'r'); - if (!$fp) { - $this->sendResponse(500, 'fopen failed'); - } - - $data = ""; - $length = 0; - while (!feof($fp) && $length <= $config->get_int('upload_size')) { - $data .= fread($fp, 8192); - $length = strlen($data); - } - fclose($fp); - - $fp = fopen($meta['file'], 'w'); - fwrite($fp, $data); - fclose($fp); - } - elseif ($config->get_string('transload_engine') == 'curl') { - $ch = curl_init($post->file_url); - $fp = fopen($meta['file'], 'w'); - - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_setopt($ch, CURLOPT_HEADER, 0); - - curl_exec($ch); - curl_close($ch); - fclose($fp); + if (!transload($post->file_url, $meta['file'])) { + $this->sendResponse(500, 'Transloading failed'); + return; } $meta['hash'] = md5_file($meta['file']); - } - else { + } else { // Use file $meta['file'] = $post->file['tmp_name']; $meta['filename'] = $post->file['name']; @@ -504,11 +535,13 @@ class OuroborosAPI extends Extension } if (!empty($md5) && $md5 !== $meta['hash']) { $this->sendResponse(420, self::ERROR_POST_CREATE_MD5); + return; } if (!empty($meta['hash'])) { $img = Image::by_hash($meta['hash']); if (!is_null($img)) { $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); + return; } } $meta['extension'] = pathinfo($meta['filename'], PATHINFO_EXTENSION); @@ -517,15 +550,17 @@ class OuroborosAPI extends Extension send_event($upload); $image = Image::by_hash($meta['hash']); if (!is_null($image)) { - $this->sendResponse(200, make_link('post/view/'.$image->id), true); - } - else { + $this->sendResponse(200, make_link('post/view/' . $image->id), true); + return; + } else { // Fail, unsupported file? $this->sendResponse(500, 'Unknown error'); + return; } } catch (UploadException $e) { // Cleanup in case shit hit the fan $this->sendResponse(500, $e->getMessage()); + return; } } @@ -533,12 +568,12 @@ class OuroborosAPI extends Extension * Wrapper for getting a single post * @param int $id */ - protected function postShow($id = null) { + protected function postShow($id = null) + { if (!is_null($id)) { $post = new _SafeOuroborosImage(Image::by_id($id)); $this->sendData('post', $post); - } - else { + } else { $this->sendResponse(424, 'ID is mandatory'); } } @@ -549,8 +584,9 @@ class OuroborosAPI extends Extension * @param $page * @param $tags */ - protected function postIndex($limit, $page, $tags) { - $start = ( $page - 1 ) * $limit; + protected function postIndex($limit, $page, $tags) + { + $start = ($page - 1) * $limit; $results = Image::find_images(max($start, 0), min($limit, 100), $tags); $posts = array(); foreach ($results as $img) { @@ -576,35 +612,47 @@ class OuroborosAPI extends Extension * @param $name * @param $name_pattern */ - protected function tagIndex($limit, $page, $order, $id, $after_id, $name, $name_pattern) { + protected function tagIndex($limit, $page, $order, $id, $after_id, $name, $name_pattern) + { global $database, $config; - $start = ( $page - 1 ) * $limit; + $start = ($page - 1) * $limit; $tag_data = array(); switch ($order) { case 'name': - $tag_data = $database->get_col($database->scoreql_to_sql(" - SELECT DISTINCT - id, SCORE_STRNORM(substr(tag, 1, 1)), count - FROM tags - WHERE count >= :tags_min - ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items - "), array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + $tag_data = $database->get_col( + $database->scoreql_to_sql( + " + SELECT DISTINCT + id, SCORE_STRNORM(substr(tag, 1, 1)), count + FROM tags + WHERE count >= :tags_min + ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items + " + ), + array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit) + ); break; case 'count': - $tag_data = $database->get_all(" - SELECT id, tag, count - FROM tags - WHERE count >= :tags_min - ORDER BY count DESC, tag ASC LIMIT :start, :max_items - ", array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + $tag_data = $database->get_all( + " + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", + array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit) + ); break; case 'date': - $tag_data = $database->get_all(" - SELECT id, tag, count - FROM tags - WHERE count >= :tags_min - ORDER BY count DESC, tag ASC LIMIT :start, :max_items - ", array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + $tag_data = $database->get_all( + " + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", + array('tags_min' => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit) + ); break; } $tags = array(); @@ -628,19 +676,18 @@ class OuroborosAPI extends Extension * @param string $reason Reason for the code * @param bool $location Is $reason a location? (used mainly for post/create) */ - private function sendResponse($code = 200, $reason = '', $location = false) { + private function sendResponse($code = 200, $reason = '', $location = false) + { global $page; if ($code == 200) { $success = true; - } - else { + } else { $success = false; } if (empty($reason)) { if (defined("self::MSG_HTTP_{$code}")) { $reason = constant("self::MSG_HTTP_{$code}"); - } - else { + } else { $reason = self::MSG_HTTP_418; } } @@ -648,8 +695,7 @@ class OuroborosAPI extends Extension $proto = $_SERVER['SERVER_PROTOCOL']; if (defined("self::HEADER_HTTP_{$code}")) { $header = constant("self::HEADER_HTTP_{$code}"); - } - else { + } else { // I'm a teapot! $code = 418; $header = self::HEADER_HTTP_418; @@ -663,8 +709,7 @@ class OuroborosAPI extends Extension unset($response['reason']); } $response = json_encode($response); - } - elseif ($this->type == 'xml') { + } elseif ($this->type == 'xml') { // Seriously, XML sucks... $xml = new XMLWriter(); $xml->openMemory(); @@ -673,8 +718,7 @@ class OuroborosAPI extends Extension $xml->writeAttribute('success', var_export($success, true)); if ($location !== false) { $xml->writeAttribute('location', $reason); - } - else { + } else { $xml->writeAttribute('reason', $reason); } $xml->endElement(); @@ -683,7 +727,6 @@ class OuroborosAPI extends Extension unset($xml); } $page->set_data($response); - $page->display(); } /** @@ -692,18 +735,18 @@ class OuroborosAPI extends Extension * @param mixed $data * @param int $offset */ - private function sendData($type = '', $data = array(), $offset = 0) { + private function sendData($type = '', $data = array(), $offset = 0) + { global $page; $response = ''; if ($this->type == 'json') { $response = json_encode($data); - } - elseif ($this->type == 'xml') { + } elseif ($this->type == 'xml') { $xml = new XMLWriter(); $xml->openMemory(); $xml->startDocument('1.0', 'utf-8'); if (array_key_exists(0, $data)) { - $xml->startElement($type.'s'); + $xml->startElement($type . 's'); if ($type == 'post') { $xml->writeAttribute('count', count($data)); $xml->writeAttribute('offset', $offset); @@ -715,8 +758,7 @@ class OuroborosAPI extends Extension $this->createItemXML($xml, $type, $item); } $xml->endElement(); - } - else { + } else { $this->createItemXML($xml, $type, $data); } $xml->endDocument(); @@ -724,17 +766,15 @@ class OuroborosAPI extends Extension unset($xml); } $page->set_data($response); - $page->display(); - exit; } - private function createItemXML(XMLWriter &$xml, $type, $item) { + private function createItemXML(XMLWriter &$xml, $type, $item) + { $xml->startElement($type); foreach ($item as $key => $val) { if ($key == 'created_at' && $type == 'post') { $xml->writeAttribute($key, $val['s']); - } - else { + } else { if (is_bool($val)) { $val = $val ? 'true' : 'false'; } @@ -752,7 +792,8 @@ class OuroborosAPI extends Extension * @param void * @return void */ - private function tryAuth() { + private function tryAuth() + { global $config, $user; if (isset($_REQUEST['user']) && isset($_REQUEST['session'])) { @@ -762,22 +803,19 @@ class OuroborosAPI extends Extension $duser = User::by_session($name, $session); if (!is_null($duser)) { $user = $duser; - } - else { + } else { $user = User::by_id($config->get_int("anon_id", 0)); } - } - elseif (isset($_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'session']) && - isset($_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'user']) + } elseif (isset($_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'session']) && + isset($_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'user']) ) { //Auth by session data from cookies - $session = $_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'session']; - $user = $_COOKIE[$config->get_string('cookie_prefix', 'shm').'_'.'user']; + $session = $_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'session']; + $user = $_COOKIE[$config->get_string('cookie_prefix', 'shm') . '_' . 'user']; $duser = User::by_session($user, $session); if (!is_null($duser)) { $user = $duser; - } - else { + } else { $user = User::by_id($config->get_int("anon_id", 0)); } } @@ -788,7 +826,8 @@ class OuroborosAPI extends Extension * @param $page * @return bool */ - private function match($page) { + private function match($page) + { return (preg_match("%{$page}\.(xml|json)$%", implode('/', $this->event->args), $matches) === 1); } } From c07dc2e0abe0f387096ccdc9c0e427c37713e226 Mon Sep 17 00:00:00 2001 From: Daku <dakutree@codeanimu.net> Date: Thu, 16 Jan 2014 03:28:23 +0000 Subject: [PATCH 30/30] use resolve_aliases rather than resolve_alias --- core/imageboard.pack.php | 41 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 0bf145e9..6d5cd1cd 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -666,6 +666,8 @@ class Image { } } + $terms = Tag::resolve_aliases($terms); + // parse the words that are searched for into // various types of querylet foreach($terms as $term) { @@ -678,15 +680,6 @@ class Image { continue; } - $aliases = explode(" ", Tag::resolve_alias($term)); - $found = array_search($term, $aliases); - if($found !== false){ - unset($aliases[$found]); - }else{ - $term = array_shift($aliases); - } - foreach($aliases as $alias) array_push($terms, $alias); - $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); if($stpe->is_querylet_set()) { @@ -834,6 +827,8 @@ class Image { } } + $terms = Tag::resolve_aliases($terms); + reset($terms); // rewind to first element in array. // turn each term into a specific type of querylet @@ -843,15 +838,6 @@ class Image { $negative = true; $term = substr($term, 1); } - - $aliases = explode(" ", Tag::resolve_alias($term)); - $found = array_search($term, $aliases); - if($found !== false){ - unset($aliases[$found]); - }else{ - $term = array_shift($aliases); - } - foreach($aliases as $alias) array_push($terms, $alias); $stpe = new SearchTermParseEvent($term, $terms); send_event($stpe); @@ -1099,11 +1085,22 @@ class Tag { assert(is_array($tags)); $new = array(); - foreach($tags as $tag) { - $new_set = explode(' ', Tag::resolve_alias($tag)); - foreach($new_set as $new_one) { - $new[] = $new_one; + + $i = 0; + $tag_count = count($tags); + while($i<$tag_count) { + $aliases = explode(' ', Tag::resolve_alias($tags[$i])); + foreach($aliases as $alias){ + if(!in_array($alias, $new)){ + if($tags[$i] == $alias){ + $new[] = $alias; + }elseif(!in_array($alias, $tags)){ + $tags[] = $alias; + $tag_count++; + } + } } + $i++; } $new = array_iunique($new); // remove any duplicate tags