order=width -- find all images sorted from highest > lowest width
+ *
order=filesize_asc -- find all images sorted from lowest > highest filesize
+ *
*
*
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.
+ *
Metatags can be followed by ":" rather than "=" if you prefer.
+ * I.E: "posted:2014-01-01", "id:>=500" etc.
*
Some search methods provided by extensions:
*
- *
Danbooru API
- *
- *
md5:[hash] -- same as "hash=", but the API calls it by a different name
- *
*
Numeric Score
*
*
score (=, <, >, <=, >=) number -- seach by score
*
upvoted_by=Username -- search for a user's likes
*
downvoted_by=Username -- search for a user's dislikes
+ *
upvoted_by_id=UserID -- search for a user's likes by user ID
+ *
downvoted_by_id=UserID -- search for a user's dislikes by user ID
*
*
Image Rating
*
@@ -81,11 +117,29 @@
*
Favorites
*
*
favorites (=, <, >, <=, >=) number -- search for images favourited a certain number of times
- *
favourited_by=Username -- search for a user's choices
+ *
favourited_by=Username -- search for a user's choices by username
+ *
favorited_by_userno=UserID -- search for a user's choice by userID
*
*
Notes
*
*
notes (=, <, >, <=, >=) number -- search by the number of notes an image has
+ *
notes_by=Username -- search for images containing notes created by username
+ *
notes_by_userno=UserID -- search for images containing notes created by userID
+ *
+ *
Artists
+ *
+ *
author=ArtistName -- search for images by artist
+ *
+ *
Image Comments
+ *
+ *
comments (=, <, >, <=, >=) number -- search for images by number of comments
+ *
commented_by=Username -- search for images containing user's comments by username
+ *
commented_by_userno=UserID -- search for images containing user's comments by userID
+ *
+ *
Pools
+ *
+ *
pool=(PoolID, any, none) -- search for images in a pool by PoolID.
+ *
pool_by_name=PoolName -- search for images in a pool by PoolName. underscores are replaced with spaces
*
*
*/
@@ -141,6 +195,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) {
@@ -240,48 +295,70 @@ 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)[=|:](.*)$/i", $event->term, $matches)) {
+ $source = strtolower($matches[2]);
+
+ 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 = $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));
}
+ 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]))));
+ }
+ else if(preg_match("/^order[=|:](id|width|height|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)){
+ global $order_sql;
+ $ord = strtolower($matches[1]);
+ $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
+ }
$this->stpen++;
}
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/numeric_score/main.php b/ext/numeric_score/main.php
index ed7597ab..2da74577 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);
}
}
@@ -219,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(
@@ -234,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(
@@ -244,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)",
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 @@
id);
$i_score = int_escape($image->numeric_score);
@@ -46,11 +46,11 @@ class NumericScoreTheme extends Themelet {
";
}
- 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 = "
";
- return $html;
+ $page->add_block(new Block("Votes", $html, "main", 60));
}
public function view_popular($images, $dte) {
diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php
index a2413e42..cbfb38ae 100644
--- a/ext/ouroboros_api/main.php
+++ b/ext/ouroboros_api/main.php
@@ -1,8 +1,10 @@
* Description: Ouroboros-like API for Shimmie
+ * Version: 0.2
* Documentation:
* Currently working features
*
@@ -10,6 +12,7 @@
*
*
Index/List
*
Show
+ *
Create
*
*
*
Tag:
@@ -210,7 +213,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 +240,76 @@ 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)) {
+ 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(
+ $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;
@@ -249,19 +325,43 @@ class _SafeOuroborosTag
$this->name = $tag['tag'];
}
}
+
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,120 +372,462 @@ 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
- $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) : '',
- '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;
+ // @TODO Should move the validation logic into OuroborosPost instead?
+ if ($user->can("create_image")) {
+ $post = array(
+ '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 {
+ $this->sendResponse(403, 'You cannot create new posts');
+ }
- }
- elseif ($this->match('update')) {
+ } elseif ($this->match('update')) {
// Update
- }
- elseif ($this->match('show')) {
+ //@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()));
- }
- }
- elseif ($this->match('index') || $this->match('list')) {
+ $id = !empty($_REQUEST['id']) ? filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT) : null;
+ $this->postShow($id);
+ } 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);
}
- $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')) {
+ 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;
+ $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
+ ) : '';
+ $this->tagIndex($limit, $p, $order, $id, $after_id, $name, $name_pattern);
}
}
- 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;
- $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));
+ } 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();
+ }
+
+ }
+
+ /**
+ * 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);
+ return;
+ }
+ }
+ $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 (!transload($post->file_url, $meta['file'])) {
+ $this->sendResponse(500, 'Transloading failed');
+ return;
+ }
+ $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);
+ 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);
+ 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);
+ 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ 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));
}
}
}
- private function match($page) {
+ /**
+ * 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);
}
}
diff --git a/ext/pools/main.php b/ext/pools/main.php
index b161e8bb..6bcd8760 100644
--- a/ext/pools/main.php
+++ b/ext/pools/main.php
@@ -71,6 +71,13 @@ 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);");
+ $database->Execute("ALTER TABLE pools ADD lastupdated TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;");
+
+ $config->set_int("ext_pools_version", 2);
+ }
}
// Add a block to the Board Config / Setup
@@ -111,7 +118,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;
@@ -122,7 +129,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;
@@ -168,7 +175,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;
@@ -177,7 +184,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;
@@ -187,7 +194,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;
@@ -197,7 +204,18 @@ 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;
+
+ 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;
@@ -210,7 +228,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;
@@ -275,6 +293,45 @@ class Pools extends Extension {
}
}
+ public function onSearchTermParse(SearchTermParseEvent $event) {
+ $matches = array();
+ if(preg_match("/^pool[=|:]([0-9]+|any|none)$/", $event->term, $matches)) {
+ $poolID = $matches[1];
+
+ 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]);
+
+ $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
+ 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 -------------- */
/* ------------------------------------------------- */
@@ -309,13 +366,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)
);
@@ -336,7 +406,10 @@ class Pools extends Extension {
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";
@@ -361,7 +434,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
@@ -372,6 +445,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
@@ -476,6 +559,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/script.js b/ext/pools/script.js
new file mode 100644
index 00000000..505718a9
--- /dev/null
+++ b/ext/pools/script.js
@@ -0,0 +1,10 @@
+$(function() {
+ var order_pool = $.cookie("shm_ui-order-pool") || "created";
+ $("#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 d9c397c7..444e76b5 100644
--- a/ext/pools/theme.php
+++ b/ext/pools/theme.php
@@ -67,11 +67,21 @@ class PoolsTheme extends Themelet {
Pool Changes
';
+ $order_html = '
+
+ ';
+
$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);
}
@@ -156,6 +166,13 @@ class PoolsTheme extends Themelet {
$pool_images .= "\n".$thumb_html."\n";
}
+ $nav_html = '
+ Index
+ Create Pool
+ Pool Changes
+ ';
+
+ $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);
}
@@ -312,8 +329,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"))."
+
+
+
+
+ ";
+
+ /* REMOVE POOLS */
$pool_images = "\n";
+ $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));
}
@@ -389,20 +418,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));
- }
}
?>
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/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("
Rating: (.*) 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("
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
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;
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'"));
}