From f4a49b2fb12f314a808034be6a025f4efb4c8f83 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 18 May 2013 09:26:20 +0100 Subject: [PATCH 01/78] Current size as default for resize, if default is otherwise 0 - #281 --- ext/resize/main.php | 2 +- ext/resize/theme.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ext/resize/main.php b/ext/resize/main.php index fce3a422..1bd7e146 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -40,7 +40,7 @@ class ResizeImage extends Extension { global $user, $config; if($user->is_admin() && $config->get_bool("resize_enabled")) { /* Add a link to resize the image */ - $event->add_part($this->theme->get_resize_html($event->image->id)); + $event->add_part($this->theme->get_resize_html($event->image)); } } diff --git a/ext/resize/theme.php b/ext/resize/theme.php index 81f64559..228da3b8 100644 --- a/ext/resize/theme.php +++ b/ext/resize/theme.php @@ -4,16 +4,19 @@ class ResizeImageTheme extends Themelet { /* * Display a link to resize an image */ - public function get_resize_html(/*int*/ $image_id) { + public function get_resize_html(Image $image) { global $user, $config; - $i_image_id = int_escape($image_id); + $i_image_id = int_escape($image->id); $default_width = $config->get_int('resize_default_width'); $default_height = $config->get_int('resize_default_height'); + + if(!$default_width) $default_width = $image->width; + if(!$default_height) $default_height = $image->height; - $html .= " - ".make_form(make_link('resize/'.$i_image_id), 'POST')." - + $html = " + ".make_form(make_link("resize/{$image->id}"), 'POST')." + x From 5a9f69afbed89a0b7411e5a85e03e487ac52b858 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 18 May 2013 10:14:27 +0100 Subject: [PATCH 02/78] Numeric type and aspect keeping - #281 --- ext/resize/theme.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ext/resize/theme.php b/ext/resize/theme.php index 228da3b8..2dfa11c2 100644 --- a/ext/resize/theme.php +++ b/ext/resize/theme.php @@ -17,9 +17,12 @@ class ResizeImageTheme extends Themelet { $html = " ".make_form(make_link("resize/{$image->id}"), 'POST')." - x - - + + + x + +
+
"; From c530e41335496a36b156743d5c77ce85d00785e1 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 18 May 2013 13:46:27 +0100 Subject: [PATCH 03/78] new jquery.timeago with cutoff --- lib/jquery.timeago-0.10.0.min.js | 1 - lib/jquery.timeago-1.1.1.js | 189 +++++++++++++++++++++++++++++++ lib/shimmie.js | 2 + 3 files changed, 191 insertions(+), 1 deletion(-) delete mode 100644 lib/jquery.timeago-0.10.0.min.js create mode 100644 lib/jquery.timeago-1.1.1.js diff --git a/lib/jquery.timeago-0.10.0.min.js b/lib/jquery.timeago-0.10.0.min.js deleted file mode 100644 index 5c750501..00000000 --- a/lib/jquery.timeago-0.10.0.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(d){d.timeago=function(g){if(g instanceof Date){return a(g)}else{if(typeof g==="string"){return a(d.timeago.parse(g))}else{return a(d.timeago.datetime(g))}}};var f=d.timeago;d.extend(d.timeago,{settings:{refreshMillis:60000,allowFuture:false,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"a minute",minutes:"%d minutes",hour:"an hour",hours:"%d hours",day:"a day",days:"%d days",month:"a month",months:"%d months",year:"a year",years:"%d years",numbers:[]}},inWords:function(l){var m=this.settings.strings;var i=m.prefixAgo;var q=m.suffixAgo;if(this.settings.allowFuture){if(l<0){i=m.prefixFromNow;q=m.suffixFromNow}}var o=Math.abs(l)/1000;var g=o/60;var n=g/60;var p=n/24;var j=p/365;function h(r,t){var s=d.isFunction(r)?r(t,l):r;var u=(m.numbers&&m.numbers[t])||t;return s.replace(/%d/i,u)}var k=o<45&&h(m.seconds,Math.round(o))||o<90&&h(m.minute,1)||g<45&&h(m.minutes,Math.round(g))||g<90&&h(m.hour,1)||n<24&&h(m.hours,Math.round(n))||n<48&&h(m.day,1)||p<30&&h(m.days,Math.floor(p))||p<60&&h(m.month,1)||p<365&&h(m.months,Math.floor(p/30))||j<2&&h(m.year,1)||h(m.years,Math.floor(j));return d.trim([i,k,q].join(" "))},parse:function(h){var g=d.trim(h);g=g.replace(/\.\d\d\d+/,"");g=g.replace(/-/,"/").replace(/-/,"/");g=g.replace(/T/," ").replace(/Z/," UTC");g=g.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(g)},datetime:function(h){var i=d(h).get(0).tagName.toLowerCase()==="time";var g=i?d(h).attr("datetime"):d(h).attr("title");return f.parse(g)}});d.fn.timeago=function(){var h=this;h.each(c);var g=f.settings;if(g.refreshMillis>0){setInterval(function(){h.each(c)},g.refreshMillis)}return h};function c(){var g=b(this);if(!isNaN(g.datetime)){d(this).text(a(g.datetime))}return this}function b(g){g=d(g);if(!g.data("timeago")){g.data("timeago",{datetime:f.datetime(g)});var h=d.trim(g.text());if(h.length>0){g.attr("title",h)}}return g.data("timeago")}function a(g){return f.inWords(e(g))}function e(g){return(new Date().getTime()-g.getTime())}document.createElement("abbr");document.createElement("time")}(jQuery)); diff --git a/lib/jquery.timeago-1.1.1.js b/lib/jquery.timeago-1.1.1.js new file mode 100644 index 00000000..da181e00 --- /dev/null +++ b/lib/jquery.timeago-1.1.1.js @@ -0,0 +1,189 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * @name timeago + * @version 1.1.0 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowFuture: false, + localeTitle: false, + cutoff: 0, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }, + inWords: function(distanceMillis) { + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return $.trim([prefix, words, suffix].join(separator)); + }, + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + // functions that can be called via $(el).timeago('action') + // init is default when no action is given + // functions are called with context of a single element + var functions = { + init: function(){ + var refresh_el = $.proxy(refresh, this); + refresh_el(); + var $s = $t.settings; + if ($s.refreshMillis > 0) { + setInterval(refresh_el, $s.refreshMillis); + } + }, + update: function(time){ + $(this).data('timeago', { datetime: $t.parse(time) }); + refresh.apply(this); + } + }; + + $.fn.timeago = function(action, options) { + var fn = action ? functions[action] : functions.init; + if(!fn){ + throw new Error("Unknown function name '"+ action +"' for timeago"); + } + // each over objects here and call the requested function + this.each(function(){ + fn.call(this, options); + }); + return this; + }; + + function refresh() { + var data = prepareData(this); + var $s = $t.settings; + + if (!isNaN(data.datetime)) { + if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) { + $(this).text(inWords(data.datetime)); + } + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if ($t.settings.localeTitle) { + element.attr("title", element.data('timeago').datetime.toLocaleString()); + } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +})); diff --git a/lib/shimmie.js b/lib/shimmie.js index 4dc2e0ce..a800b753 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -1,6 +1,8 @@ // Adding jQuery ui stuff $(document).ready(function() { + var dayMS = 1000 * 60 * 60 * 24; + jQuery.timeago.settings.cutoff = 365 * dayMS; $("time").timeago(); $('.autocomplete_tags').autocomplete(base_href + '/api/internal/tag_list/complete', { From 261dee5a7ca0b063268d6048502a6c72ac3793e2 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 18 May 2013 13:52:11 +0100 Subject: [PATCH 04/78] hacky source history --- ext/source_history/main.php | 376 +++++++++++++++++++++++++++++++++++ ext/source_history/theme.php | 137 +++++++++++++ 2 files changed, 513 insertions(+) create mode 100644 ext/source_history/main.php create mode 100644 ext/source_history/theme.php diff --git a/ext/source_history/main.php b/ext/source_history/main.php new file mode 100644 index 00000000..68d748de --- /dev/null +++ b/ext/source_history/main.php @@ -0,0 +1,376 @@ +set_default_int("history_limit", -1); + + // shimmie is being installed so call install to create the table. + if($config->get_int("ext_source_history_version") < 3) { + $this->install(); + } + } + + public function onAdminBuilding(AdminBuildingEvent $event) { + $this->theme->display_admin_block(); + } + + public function onPageRequest(PageRequestEvent $event) { + global $config, $page, $user; + + if($event->page_matches("source_history/revert")) { + // this is a request to revert to a previous version of the source + if($user->can("edit_image_tag")) { + if(isset($_POST['revert'])) { + $this->process_revert_request($_POST['revert']); + } + } + } + else if($event->page_matches("source_history/bulk_revert")) { + if($user->can("bulk_edit_image_tag") && $user->check_auth_token()) { + $this->process_bulk_revert_request(); + } + } + else if($event->page_matches("source_history/all")) { + $page_id = int_escape($event->get_arg(0)); + $this->theme->display_global_page($page, $this->get_global_source_history($page_id), $page_id); + } + else if($event->page_matches("source_history") && $event->count_args() == 1) { + // must be an attempt to view a source history + $image_id = int_escape($event->get_arg(0)); + $this->theme->display_history_page($page, $image_id, $this->get_source_history_from_id($image_id)); + } + } + + public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { + $event->add_part(" +
+ +
+ ", 20); + } + + /* + // disk space is cheaper than manually rebuilding history, + // so let's default to -1 and the user can go advanced if + // they /really/ want to + public function onSetupBuilding(SetupBuildingEvent $event) { + $sb = new SetupBlock("Source History"); + $sb->add_label("Limit to "); + $sb->add_int_option("history_limit"); + $sb->add_label(" entires per image"); + $sb->add_label("
(-1 for unlimited)"); + $event->panel->add_block($sb); + } + */ + + public function onSourceSet(SourceSetEvent $event) { + $this->add_source_history($event->image, $event->source); + } + + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { + global $user; + if($user->can("bulk_edit_image_tag")) { + $event->add_link("Source Changes", make_link("source_history/all/1")); + } + } + + protected function install() { + global $database, $config; + + if($config->get_int("ext_source_history_version") < 1) { + $database->create_table("source_histories", " + id SCORE_AIPK, + image_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + user_ip SCORE_INET NOT NULL, + source TEXT NOT NULL, + date_set DATETIME NOT NULL, + INDEX(image_id), + FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + "); + $config->set_int("ext_source_history_version", 3); + } + + if($config->get_int("ext_source_history_version") == 1) { + $database->Execute("ALTER TABLE source_histories ADD COLUMN user_id INTEGER NOT NULL"); + $database->Execute("ALTER TABLE source_histories ADD COLUMN date_set DATETIME NOT NULL"); + $config->set_int("ext_source_history_version", 2); + } + + if($config->get_int("ext_source_history_version") == 2) { + $database->Execute("ALTER TABLE source_histories ADD COLUMN user_ip CHAR(15) NOT NULL"); + $config->set_int("ext_source_history_version", 3); + } + } + + /* + * this function is called when a revert request is received + */ + private function process_revert_request($revert_id) { + global $page; + + $revert_id = int_escape($revert_id); + + // check for the nothing case + if($revert_id < 1) { + $page->set_mode("redirect"); + $page->set_redirect(make_link()); + return; + } + + // lets get this revert id assuming it exists + $result = $this->get_source_history_from_revert($revert_id); + + if(empty($result)) { + // there is no history entry with that id so either the image was deleted + // while the user was viewing the history, someone is playing with form + // variables or we have messed up in code somewhere. + /* calling die() is probably not a good idea, we should throw an Exception */ + die("Error: No source history with specified id was found."); + } + + // lets get the values out of the result + $stored_result_id = $result['id']; + $stored_image_id = $result['image_id']; + $stored_source = $result['source']; + + log_debug("source_history", 'Reverting source of Image #'.$stored_image_id.' to ['.$stored_source.']'); + // all should be ok so we can revert by firing the SetUserSources event. + send_event(new SourceSetEvent(Image::by_id($stored_image_id), $stored_source)); + + // all should be done now so redirect the user back to the image + $page->set_mode("redirect"); + $page->set_redirect(make_link('post/view/'.$stored_image_id)); + } + + protected function process_bulk_revert_request() { + if (isset($_POST['revert_name']) && !empty($_POST['revert_name'])) { + $revert_name = $_POST['revert_name']; + } + else { + $revert_name = null; + } + + if (isset($_POST['revert_ip']) && !empty($_POST['revert_ip'])) { + $revert_ip = filter_var($_POST['revert_ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE); + + if ($revert_ip === false) { + // invalid ip given. + $this->theme->display_admin_block('Invalid IP'); + return; + } + } + else { + $revert_ip = null; + } + + if (isset($_POST['revert_date']) && !empty($_POST['revert_date'])) { + if (isValidDate($_POST['revert_date']) ){ + $revert_date = addslashes($_POST['revert_date']); // addslashes is really unnecessary since we just checked if valid, but better safe. + } + else { + $this->theme->display_admin_block('Invalid Date'); + return; + } + } + else { + $revert_date = null; + } + + set_time_limit(0); // reverting changes can take a long time, disable php's timelimit if possible. + + // Call the revert function. + $this->process_revert_all_changes($revert_name, $revert_ip, $revert_date); + // output results + $this->theme->display_revert_ip_results(); + } + + public function get_source_history_from_revert(/*int*/ $revert_id) { + global $database; + $row = $database->get_row(" + SELECT source_histories.*, users.name + FROM source_histories + JOIN users ON source_histories.user_id = users.id + WHERE source_histories.id = ?", array($revert_id)); + return ($row ? $row : null); + } + + public function get_source_history_from_id(/*int*/ $image_id) { + global $database; + $row = $database->get_all(" + SELECT source_histories.*, users.name + FROM source_histories + JOIN users ON source_histories.user_id = users.id + WHERE image_id = ? + ORDER BY source_histories.id DESC", + array($image_id)); + return ($row ? $row : array()); + } + + public function get_global_source_history($page_id) { + global $database; + $row = $database->get_all(" + SELECT source_histories.*, users.name + FROM source_histories + JOIN users ON source_histories.user_id = users.id + ORDER BY source_histories.id DESC + LIMIT 100 OFFSET :offset + ", array("offset" => ($page_id-1)*100)); + return ($row ? $row : array()); + } + + /* + * This function attempts to revert all changes by a given IP within an (optional) timeframe. + */ + public function process_revert_all_changes($name, $ip, $date) { + global $database; + + $select_code = array(); + $select_args = array(); + + if(!is_null($name)) { + $duser = User::by_name($name); + if(is_null($duser)) { + $this->theme->add_status($name, "user not found"); + return; + } + else { + $select_code[] = 'user_id = ?'; + $select_args[] = $duser->id; + } + } + + if(!is_null($date)) { + $select_code[] = 'date_set >= ?'; + $select_args[] = $date; + } + + if(!is_null($ip)) { + $select_code[] = 'user_ip = ?'; + $select_args[] = $ip; + } + + if(count($select_code) == 0) { + log_error("source_history", "Tried to mass revert without any conditions"); + return; + } + + log_info("source_history", 'Attempting to revert edits where '.implode(" and ", $select_code)." (".implode(" / ", $select_args).")"); + + // Get all the images that the given IP has changed source on (within the timeframe) that were last editied by the given IP + $result = $database->get_col(' + SELECT t1.image_id + FROM source_histories t1 + LEFT JOIN source_histories t2 ON (t1.image_id = t2.image_id AND t1.date_set < t2.date_set) + WHERE t2.image_id IS NULL + AND t1.image_id IN ( select image_id from source_histories where '.implode(" AND ", $select_code).') + ORDER BY t1.image_id + ', $select_args); + + foreach($result as $image_id) { + // Get the first source history that was done before the given IP edit + $row = $database->get_row(' + SELECT id, source + FROM source_histories + WHERE image_id='.$image_id.' + AND NOT ('.implode(" AND ", $select_code).') + ORDER BY date_set DESC LIMIT 1 + ', $select_args); + + if (empty($row)) { + // we can not revert this image based on the date restriction. + // Output a message perhaps? + } + else { + $revert_id = $row['id']; + $result = $this->get_source_history_from_revert($revert_id); + + if(empty($result)) { + // there is no history entry with that id so either the image was deleted + // while the user was viewing the history, or something messed up + /* calling die() is probably not a good idea, we should throw an Exception */ + die('Error: No source history with specified id ('.$revert_id.') was found in the database.'."\n\n". + 'Perhaps the image was deleted while processing this request.'); + } + + // lets get the values out of the result + $stored_result_id = $result['id']; + $stored_image_id = $result['image_id']; + $stored_source = $result['source']; + + log_debug("source_history", 'Reverting source of Image #'.$stored_image_id.' to ['.$stored_source.']'); + // all should be ok so we can revert by firing the SetSources event. + send_event(new SourceSetEvent(Image::by_id($stored_image_id), $stored_source)); + $this->theme->add_status('Reverted Change','Reverted Image #'.$image_id.' to Source History #'.$stored_result_id.' ('.$row['source'].')'); + } + } + + log_info("source_history", 'Reverted '.count($result).' edits.'); + } + + /* + * this function is called just before an images source is changed + */ + private function add_source_history($image, $source) { + global $database, $config, $user; + + $new_source = $source; + $old_source = $image->source; + + if($new_source == $old_source) return; + + if(empty($old_source)) { + /* no old source, so we are probably adding the image for the first time */ + log_debug("source_history", "adding new source history: [$new_source]"); + } + else { + log_debug("source_history", "adding source history: [$old_source] -> [$new_source]"); + } + + $allowed = $config->get_int("history_limit"); + if($allowed == 0) return; + + // if the image has no history, make one with the old source + $entries = $database->get_one("SELECT COUNT(*) FROM source_histories WHERE image_id = ?", array($image->id)); + if($entries == 0 && !empty($old_source)) { + $database->execute(" + INSERT INTO source_histories(image_id, source, user_id, user_ip, date_set) + VALUES (?, ?, ?, ?, now())", + array($image->id, $old_source, $config->get_int('anon_id'), '127.0.0.1')); + $entries++; + } + + // add a history entry + $row = $database->execute(" + INSERT INTO source_histories(image_id, source, user_id, user_ip, date_set) + VALUES (?, ?, ?, ?, now())", + array($image->id, $new_source, $user->id, $_SERVER['REMOTE_ADDR'])); + $entries++; + + // if needed remove oldest one + if($allowed == -1) return; + if($entries > $allowed) { + // TODO: Make these queries better + /* + MySQL does NOT allow you to modify the same table which you use in the SELECT part. + Which means that these will probably have to stay as TWO separate queries... + + http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html + http://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause + */ + $min_id = $database->get_one("SELECT MIN(id) FROM source_histories WHERE image_id = ?", array($image->id)); + $database->execute("DELETE FROM source_histories WHERE id = ?", array($min_id)); + } + } +} +?> diff --git a/ext/source_history/theme.php b/ext/source_history/theme.php new file mode 100644 index 00000000..29bcc019 --- /dev/null +++ b/ext/source_history/theme.php @@ -0,0 +1,137 @@ + + ".make_form(make_link("source_history/revert"))." +
    + "; + + $history_list = ""; + $n = 0; + foreach($history as $fields) + { + $n++; + $current_id = $fields['id']; + $current_source = html_escape($fields['source']); + $name = $fields['name']; + $h_ip = $user->can("view_ip") ? " ".show_ip($fields['user_ip'], "Sourcing Image #$image_id as '$current_source'") : ""; + $setter = "".html_escape($name)."$h_ip"; + + $selected = ($n == 2) ? " checked" : ""; + + $history_list .= " +
  • + + +
  • + "; + } + + $end_string = " +
+ + + + "; + $history_html = $start_string . $history_list . $end_string; + + $page->set_title('Image '.$image_id.' Source History'); + $page->set_heading('Source History: '.$image_id); + $page->add_block(new NavBlock()); + $page->add_block(new Block("Source History", $history_html, "main", 10)); + } + + public function display_global_page(Page $page, /*array*/ $history, /*int*/ $page_number) { + $start_string = " +
+ ".make_form(make_link("source_history/revert"))." +
    + "; + $end_string = " +
+ + +
+ "; + + global $user; + $history_list = ""; + foreach($history as $fields) + { + $current_id = $fields['id']; + $image_id = $fields['image_id']; + $current_source = html_escape($fields['source']); + $name = $fields['name']; + $h_ip = $user->can("view_ip") ? " ".show_ip($fields['user_ip'], "Sourcing Image #$image_id as '$current_source'") : ""; + $setter = "".html_escape($name)."$h_ip"; + + $history_list .= ' +
  • + + '.$image_id.': + '.$current_source.' (Set by '.$setter.') +
  • + '; + } + + $history_html = $start_string . $history_list . $end_string; + $page->set_title("Global Source History"); + $page->set_heading("Global Source History"); + $page->add_block(new Block("Source History", $history_html, "main", 10)); + + + $h_prev = ($page_number <= 1) ? "Prev" : + 'Prev'; + $h_index = "Index"; + $h_next = 'Next'; + + $nav = $h_prev.' | '.$h_index.' | '.$h_next; + $page->add_block(new Block("Navigation", $nav, "left")); + } + + /* + * Add a section to the admin page. + */ + public function display_admin_block(/*string*/ $validation_msg='') { + global $page; + + if (!empty($validation_msg)) { + $validation_msg = '
    '. $validation_msg .''; + } + + $html = ' + Revert source changes/edit by a specific IP address or username. +
    You can restrict the time frame to revert these edits as well. +
    (Date format: 2011-10-23) + '.$validation_msg.' + +

    '.make_form(make_link("source_history/bulk_revert"), 'POST')." + + + + + +
    Username
    IP Address
    Date range
    + + "; + $page->add_block(new Block("Mass Source Revert", $html)); + } + + /* + * Show a standard page for results to be put into + */ + public function display_revert_ip_results() { + global $page; + $html = implode($this->messages, "\n"); + $page->add_block(new Block("Bulk Revert Results", $html)); + } + + public function add_status(/*string*/ $title, /*string*/ $body) { + $this->messages[] = '

    '. $title .'
    '. $body .'

    '; + } +} +?> From 344e8afd9803eadcc4b00ccdfc83350fcb3a0b01 Mon Sep 17 00:00:00 2001 From: Zarek Jenkinson Date: Wed, 29 May 2013 19:50:00 +1200 Subject: [PATCH 05/78] Add reset_image_ids from shish/shimmie2-utils to the admin extension --- ext/admin/main.php | 41 +++++++++++++++++++++++++++++++++++++++++ ext/admin/theme.php | 1 + 2 files changed, 42 insertions(+) diff --git a/ext/admin/main.php b/ext/admin/main.php index 179a13c7..b5542c26 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -213,5 +213,46 @@ class AdminPage extends Extension { //TODO: Delete file after downloaded? return false; // we do want a redirect, but a manual one } + + private function reset_image_ids() { + global $database; + + //This might be a bit laggy on boards with lots of images (?) + //Seems to work fine with 1.2k~ images though. + $i = 0; + $image = $database->get_all("SELECT * FROM images ORDER BY images.id ASC"); + /*$score_log = $database->get_all("SELECT message FROM score_log");*/ + foreach($image as $img){ + $xid = $img[0]; + $i = $i + 1; + $table = array( //Might be missing some tables? + "image_tags", "tag_histories", "image_reports", "comments", "user_favorites", "tag_histories", + "numeric_score_votes", "pool_images", "slext_progress_cache", "notes"); + + $sql = + "SET FOREIGN_KEY_CHECKS=0; + UPDATE images + SET id=".$i. + " WHERE id=".$xid.";"; //id for images + + foreach($table as $tbl){ + $sql .= " + UPDATE ".$tbl." + SET image_id=".$i." + WHERE image_id=".$xid.";"; + } + + /*foreach($score_log as $sl){ + //This seems like a bad idea. + //TODO: Might be better for log_info to have an $id option (which would then affix the id to the table?) + preg_replace(".Image \\#[0-9]+.", "Image #".$i, $sl); + }*/ + $sql .= " SET FOREIGN_KEY_CHECKS=1;"; + $database->execute($sql); + } + $count = (count($image)) + 1; + $database->execute("ALTER TABLE images AUTO_INCREMENT=".$count); + return true; + } } ?> diff --git a/ext/admin/theme.php b/ext/admin/theme.php index 7069afc0..f7d2ddb2 100644 --- a/ext/admin/theme.php +++ b/ext/admin/theme.php @@ -41,6 +41,7 @@ class AdminPageTheme extends Themelet { $html .= $this->button("Recount tag use", "recount_tag_user", false); $html .= $this->button("Download all images", "image_dump", false); $html .= $this->button("Download database contents", "database_dump", false); + $html .= $this->button("Reset image IDs", "reset_image_ids", false); $page->add_block(new Block("Misc Admin Tools", $html)); $html = make_form(make_link("admin/set_tag_case"), "POST"); From ac764d0e9c90bf7683981288e9cd6224b86e924a Mon Sep 17 00:00:00 2001 From: Zarek Jenkinson Date: Wed, 29 May 2013 23:00:01 +1200 Subject: [PATCH 06/78] protect reset_image_ids & move protected checkbox --- ext/admin/theme.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/admin/theme.php b/ext/admin/theme.php index f7d2ddb2..b354a7db 100644 --- a/ext/admin/theme.php +++ b/ext/admin/theme.php @@ -16,8 +16,8 @@ class AdminPageTheme extends Themelet { $c_protected = $protected ? " protected" : ""; $html = make_form(make_link("admin/$action"), "POST", false, false, false, "admin$c_protected"); if($protected) { - $html .= ""; $html .= ""; + $html .= ""; } else { $html .= ""; @@ -41,7 +41,7 @@ class AdminPageTheme extends Themelet { $html .= $this->button("Recount tag use", "recount_tag_user", false); $html .= $this->button("Download all images", "image_dump", false); $html .= $this->button("Download database contents", "database_dump", false); - $html .= $this->button("Reset image IDs", "reset_image_ids", false); + $html .= $this->button("Reset image IDs", "reset_image_ids", true); $page->add_block(new Block("Misc Admin Tools", $html)); $html = make_form(make_link("admin/set_tag_case"), "POST"); From b562de3e94cedd2a985dcddf2f17b3b3fc51dd71 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 30 May 2013 10:18:56 +0100 Subject: [PATCH 07/78] redirect to referrer after adding image hash ban --- ext/image_hash_ban/main.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ext/image_hash_ban/main.php b/ext/image_hash_ban/main.php index c418fcb9..513ee1cc 100644 --- a/ext/image_hash_ban/main.php +++ b/ext/image_hash_ban/main.php @@ -64,18 +64,15 @@ class ImageBan extends Extension { if($hash) { send_event(new AddImageHashBanEvent($hash, $reason)); - flash_message("Image ban added"); - $page->set_mode("redirect"); - $page->set_redirect(make_link("image_hash_ban/list/1")); if($image) { send_event(new ImageDeletionEvent($image)); - flash_message("Image deleted"); - $page->set_mode("redirect"); - $page->set_redirect(make_link("post/list")); } + + $page->set_mode("redirect"); + $page->set_redirect($_SERVER['HTTP_REFERER']); } } else if($event->get_arg(0) == "remove") { @@ -84,7 +81,7 @@ class ImageBan extends Extension { flash_message("Image ban removed"); $page->set_mode("redirect"); - $page->set_redirect(make_link("image_hash_ban/list/1")); + $page->set_redirect($_SERVER['HTTP_REFERER']); } } else if($event->get_arg(0) == "list") { From 51ab897655ea0fd8afb477c8ceb69c301e3230de Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 18 May 2013 15:30:00 +0100 Subject: [PATCH 08/78] need a tiny bit of jquery UI for flashing... --- lib/jquery.ui-1.10.3.custom.min.js | 6 ++++++ lib/shimmie.js | 1 + 2 files changed, 7 insertions(+) create mode 100644 lib/jquery.ui-1.10.3.custom.min.js diff --git a/lib/jquery.ui-1.10.3.custom.min.js b/lib/jquery.ui-1.10.3.custom.min.js new file mode 100644 index 00000000..21b9171a --- /dev/null +++ b/lib/jquery.ui-1.10.3.custom.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.10.3 - 2013-05-18 +* http://jqueryui.com +* Includes: jquery.ui.effect.js, jquery.ui.effect-highlight.js +* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ + +(function(t,e){var i="ui-effects-";t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,a){var o,r=a.re.exec(i),h=r&&a.parse(r),l=a.space||"rgba";return h?(o=s[l](h),s[c[l].cache]=o[c[l].cache],n=s._rgba=o._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,a.transparent),s):a[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

    ")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,o,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(o),o=e);var u=this,d=t.type(n),p=this._rgba=[];return o!==e&&(n=[n,o,r,h],d="array"),"string"===d?this.parse(s(n)||a._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var a=s.cache;f(s.props,function(t,e){if(!u[a]&&s.to){if("alpha"===t||null==n[t])return;u[a]=s.to(u._rgba)}u[a][e.idx]=i(n[t],e,!0)}),u[a]&&0>t.inArray(null,u[a].slice(0,3))&&(u[a][3]=1,s.from&&(u._rgba=s.from(u[a])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),a=c[n],o=0===this.alpha()?l("transparent"):this,r=o[a.cache]||a.to(o._rgba),h=r.slice();return s=s[a.cache],f(a.props,function(t,n){var a=n.idx,o=r[a],l=s[a],c=u[n.type]||{};null!==l&&(null===o?h[a]=l:(c.mod&&(l-o>c.mod/2?o+=c.mod:o-l>c.mod/2&&(o-=c.mod)),h[a]=i((l-o)*e+o,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,a=t[2]/255,o=t[3],r=Math.max(s,n,a),h=Math.min(s,n,a),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-a)/l+360:n===r?60*(a-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==o?1:o]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],a=t[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,e+1/3)),Math.round(255*n(r,o,e)),Math.round(255*n(r,o,e-1/3)),a]},f(c,function(s,n){var a=n.props,o=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[o]&&(this[o]=h(this._rgba)),s===e)return this[o].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[o].slice();return f(a,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[o]=d,n):l(d)},f(a,function(e,i){l.fn[e]||(l.fn[e]=function(n){var a,o=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===o?c:("function"===o&&(n=n.call(this,c),o=t.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=c+parseFloat(a[2])*("+"===a[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var a,o,r="";if("transparent"!==n&&("string"!==t.type(n)||(a=s(n)))){if(n=l(a||n),!d.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&o&&o.style;)try{r=t.css(o,"backgroundColor"),o=o.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(o),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},a=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function s(e,i){var s,n,o={};for(s in i)n=i[s],e[s]!==n&&(a[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(o[s]=n));return o}var n=["add","remove","toggle"],a={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(jQuery.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(e,a,o,r){var h=t.speed(a,o,r);return this.queue(function(){var a,o=t(this),r=o.attr("class")||"",l=h.children?o.find("*").addBack():o;l=l.map(function(){var e=t(this);return{el:e,start:i(this)}}),a=function(){t.each(n,function(t,i){e[i]&&o[i+"Class"](e[i])})},a(),l=l.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),o.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){a(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(o[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,a){return s?t.effects.animateClass.call(this,{add:i},s,n,a):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,a){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,a):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(i){return function(s,n,a,o,r){return"boolean"==typeof n||n===e?a?t.effects.animateClass.call(this,n?{add:s}:{remove:s},a,o,r):i.apply(this,arguments):t.effects.animateClass.call(this,{toggle:s},n,a,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,a){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,a)}})}(),function(){function s(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function n(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.10.3",save:function(t,e){for(var s=0;e.length>s;s++)null!==e[s]&&t.data(i+e[s],t[0].style[e[s]])},restore:function(t,s){var n,a;for(a=0;s.length>a;a++)null!==s[a]&&(n=t.data(i+s[a]),n===e&&(n=""),t.css(s[a],n))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return e.wrap(s),(e[0]===a||t.contains(e[0],a))&&t(a).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var a=e.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),t.fn.extend({effect:function(){function e(e){function s(){t.isFunction(a)&&a.call(n[0]),t.isFunction(e)&&e()}var n=t(this),a=i.complete,r=i.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),s()):o.call(n[0],i,s)}var i=s.apply(this,arguments),n=i.mode,a=i.queue,o=t.effects.effect[i.effect];return t.fx.off||!o?n?this[n](i.duration,i.complete):this.each(function(){i.complete&&i.complete.call(this)}):a===!1?this.each(e):this.queue(a||"fx",e)},show:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(n(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}()})(jQuery);(function(t){t.effects.effect.highlight=function(e,i){var s=t(this),n=["backgroundImage","backgroundColor","opacity"],a=t.effects.setMode(s,e.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),t.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(o,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&s.hide(),t.effects.restore(s,n),i()}})}})(jQuery); \ No newline at end of file diff --git a/lib/shimmie.js b/lib/shimmie.js index a800b753..e7b5cc7f 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -31,6 +31,7 @@ $(document).ready(function() { $(elm).attr("href", target_id); // highlight it when clicked $(elm).click(function(e) { + // This needs jQuery UI $(target_id).effect('highlight', {}, 5000); }); // vanilla target name should already be in the URL tag, but this From e01b9b22c45ca6790113ba9cdcc2be0a2b6c2aff Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 30 May 2013 14:19:03 +0100 Subject: [PATCH 09/78] don't try to set tags / source if they aren't sent --- ext/tag_edit/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php index 442d826a..38a70e5a 100644 --- a/ext/tag_edit/main.php +++ b/ext/tag_edit/main.php @@ -108,10 +108,10 @@ class TagEdit extends Extension { $owner = User::by_name($_POST['tag_edit__owner']); send_event(new OwnerSetEvent($event->image, $owner)); } - if($this->can_tag($event->image)) { + if($this->can_tag($event->image) && isset($_POST['tag_edit__tags'])) { send_event(new TagSetEvent($event->image, $_POST['tag_edit__tags'])); } - if($this->can_source($event->image)) { + if($this->can_source($event->image) && isset($_POST['tag_edit__source'])) { send_event(new SourceSetEvent($event->image, $_POST['tag_edit__source'])); } if($user->can("edit_image_lock")) { From 19c0868b2c642277208e5c9857ca4c8a444735d2 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 13 Jun 2013 10:34:47 +0100 Subject: [PATCH 10/78] sqlite got case sensitive by default at some point - lowercase strings for comparing them --- core/database.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/database.class.php b/core/database.class.php index f38af98a..1e8da970 100644 --- a/core/database.class.php +++ b/core/database.class.php @@ -145,7 +145,7 @@ class SQLite extends DBEngine { $data = str_replace("SCORE_BOOL_N", "'N'", $data); $data = str_replace("SCORE_BOOL", "CHAR(1)", $data); $data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data); - $data = str_replace("SCORE_STRNORM", "", $data); + $data = str_replace("SCORE_STRNORM", "lower", $data); $data = str_replace("SCORE_ILIKE", "LIKE", $data); return $data; } From df2f90016f4763351a96821e85ea9a452adfc4b5 Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 13 Jun 2013 12:20:27 +0100 Subject: [PATCH 11/78] trim leading slashes more efficiently --- core/event.class.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/event.class.php b/core/event.class.php index 2f3b84dd..81dda8ee 100644 --- a/core/event.class.php +++ b/core/event.class.php @@ -34,9 +34,7 @@ class PageRequestEvent extends Event { global $config; // trim starting slashes - while(strlen($path) > 0 && $path[0] == '/') { - $path = substr($path, 1); - } + $path = ltrim($path, "/"); // if path is not specified, use the default front page if(strlen($path) === 0) { From 2859d5819173bf48c4b79caf86c57841fe53dbfc Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 13 Jun 2013 12:42:30 +0100 Subject: [PATCH 12/78] regex and trimming updates for danbooru and lite themes --- themes/danbooru/layout.class.php | 6 +++--- themes/lite/layout.class.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/themes/danbooru/layout.class.php b/themes/danbooru/layout.class.php index 2a54ae20..dbe89cf6 100644 --- a/themes/danbooru/layout.class.php +++ b/themes/danbooru/layout.class.php @@ -125,7 +125,7 @@ class Layout { global $user; $username = url_escape($user->name); // hack - $qp = explode("/", @$_GET["q"]); + $qp = explode("/", ltrim(@$_GET["q"], "/")); $hw = class_exists("Wiki"); // php sucks switch($qp[0]) { @@ -247,10 +247,10 @@ EOD; * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) */ $html = null; - $url = $_GET['q']; + $url = ltrim($_GET['q'], "/"); $re1='.*?'; - $re2='((?:[a-z][a-z]+))'; + $re2='((?:[a-z][a-z_]+))'; if ($c=preg_match_all ("/".$re1.$re2."/is", $url, $matches)) { $url=$matches[1][0]; diff --git a/themes/lite/layout.class.php b/themes/lite/layout.class.php index cd498298..2e132e44 100644 --- a/themes/lite/layout.class.php +++ b/themes/lite/layout.class.php @@ -80,7 +80,7 @@ class Layout { global $user; $username = url_escape($user->name); // hack - $qp = explode("/", @$_GET["q"]); + $qp = explode("/", ltrim(@$_GET["q"], "/")); $hw = class_exists("Wiki"); // php sucks switch($qp[0]) { @@ -236,10 +236,10 @@ EOD; * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) */ $html = null; - $url = $_GET['q']; + $url = ltrim($_GET['q'], "/"); $re1='.*?'; - $re2='((?:[a-z][a-z]+))'; + $re2='((?:[a-z][a-z_]+))'; if ($c=preg_match_all ("/".$re1.$re2."/is", $url, $matches)) { $url=$matches[1][0]; From e7ddd2960bd9219db7ddbb11fa70df649555a741 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 15 Jun 2013 19:53:23 +0100 Subject: [PATCH 13/78] use upload_tmp_dir as temp place for transloading, see #297 --- ext/upload/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index d6396b12..91429cf2 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -332,7 +332,7 @@ class Upload extends Extension { // PHP falls back to system default if /tmp fails, can't we just // use the system default to start with? :-/ - $tmp_filename = tempnam("/tmp", "shimmie_transload"); + $tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload"); $filename = basename($url); if(!transload($url, $tmp_filename)) { From c8b12a04595ae8c911d9c1d60d1ed783e7b7ba51 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 15 Jun 2013 19:57:15 +0100 Subject: [PATCH 14/78] update comment --- ext/upload/main.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index 91429cf2..657b9d5c 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -330,8 +330,6 @@ class Upload extends Extension { $rating = ""; } - // PHP falls back to system default if /tmp fails, can't we just - // use the system default to start with? :-/ $tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload"); $filename = basename($url); From b6235695da0478aecf0e41f3ea27f9984a296b23 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 16 Jun 2013 12:20:12 +0100 Subject: [PATCH 15/78] unique IDs for 'posted' search params --- ext/index/main.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/index/main.php b/ext/index/main.php index ee76203b..34298b06 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -135,7 +135,7 @@ class PostListBuildingEvent extends Event { } class Index extends Extension { - var $val_id = 0; + var $stpen = 0; // search term parse event number public function onInitExt(InitExtEvent $event) { global $config; @@ -241,27 +241,26 @@ class Index extends Extension { } else if(preg_match("/^ratio(<|>|<=|>=|=)(\d+):(\d+)$/", $event->term, $matches)) { $cmp = $matches[1]; - $args = array("width"=>int_escape($matches[2]), "height"=>int_escape($matches[3])); - $event->add_querylet(new Querylet('width / height '.$cmp.' :width / :height', $args)); + $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)) { - $this->val_id++; $col = $matches[1]; $cmp = $matches[2]; $val = parse_shorthand_int($matches[3]); - $event->add_querylet(new Querylet("images.$col $cmp :val{$this->val_id}", array("val{$this->val_id}"=>$val))); + $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)) { $hash = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.hash = "'.$hash.'"')); + $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)) { $ext = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.ext = "'.$ext.'"')); + $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)) { $filename = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.filename LIKE :fn', array("fn"=>"%$filename%"))); + $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]); @@ -270,13 +269,15 @@ class Index extends Extension { else if(preg_match("/^posted(<|>|<=|>=|=)([0-9-]*)$/", $event->term, $matches)) { $cmp = $matches[1]; $val = $matches[2]; - $event->add_querylet(new Querylet("images.posted $cmp :val", array("val"=>$val))); + $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]; - $args = array("width"=>int_escape($matches[2]), "height"=>int_escape($matches[3])); - $event->add_querylet(new Querylet('width '.$cmp.' :width AND height '.$cmp.' :height', $args)); + $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)); } + + $this->stpen++; } } ?> From 619382d1b970c27702402cb4694c4e91c6576770 Mon Sep 17 00:00:00 2001 From: vomitcuddle Date: Tue, 18 Jun 2013 21:56:46 +0100 Subject: [PATCH 16/78] Include image count and offset in root tag --- ext/danbooru_api/main.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index f8501ecb..1a674ed5 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -265,7 +265,7 @@ class DanbooruApi extends Extension { * id: id to search for (comma delimited) * tags: what tags to search for * limit: limit - * offset: offset + * page: page number * after_id: limit results to posts added after this id */ if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) @@ -290,12 +290,13 @@ class DanbooruApi extends Extension { $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; $start = (isset($_GET['page']) ? int_escape($_GET['page'])-1 : 0) * $limit; $tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array(); + $count = Image::count_images($tags); $results = Image::find_images(max($start, 0), min($limit, 100), $tags); } // Now we have the array $results filled with Image objects // Let's display them - $xml = "\n"; + $xml = "\n"; foreach($results as $img) { // Sanity check to see if $img is really an image object From 911caadc84bc5fe5562f1a46e70b7f1be569174c Mon Sep 17 00:00:00 2001 From: vomitcuddle Date: Tue, 18 Jun 2013 22:14:56 +0100 Subject: [PATCH 17/78] Support Gelbooru API page offsets --- ext/danbooru_api/main.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index 1a674ed5..edfd43e6 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -288,7 +288,15 @@ class DanbooruApi extends Extension { } else { $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; - $start = (isset($_GET['page']) ? int_escape($_GET['page'])-1 : 0) * $limit; + + // Calculate start offset. + if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1 + $start = (int_escape($_GET['page'])-1) * $limit; + else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0 + $start = int_escape($_GET['pid']) * $limit; + else + $start = 0; + $tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array(); $count = Image::count_images($tags); $results = Image::find_images(max($start, 0), min($limit, 100), $tags); From 35f7ccb3ba1c816ba444116f8f6133095315afd1 Mon Sep 17 00:00:00 2001 From: vomitcuddle Date: Tue, 18 Jun 2013 22:21:56 +0100 Subject: [PATCH 18/78] Include thumbnail dimensions --- ext/danbooru_api/main.php | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index edfd43e6..f5d8773e 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -313,21 +313,24 @@ class DanbooruApi extends Extension { continue; $taglist = $img->get_tag_list(); $owner = $img->get_owner(); + $previewsize = get_thumbnail_size($img->width, $img->height); $xml .= xml_tag("post", array( - "id" => $img->id, - "md5" => $img->hash, - "file_name" => $img->filename, - "file_url" => $img->get_image_link(), - "height" => $img->height, - "width" => $img->width, - "preview_url" => $img->get_thumb_link(), - "rating" => "u", - "date" => $img->posted, - "is_warehoused" => false, - "tags" => $taglist, - "source" => $img->source, - "score" => 0, - "author" => $owner->name + "id" => $img->id, + "md5" => $img->hash, + "file_name" => $img->filename, + "file_url" => $img->get_image_link(), + "height" => $img->height, + "width" => $img->width, + "preview_url" => $img->get_thumb_link(), + "preview_height" => $previewsize[1], + "preview_width" => $previewsize[0], + "rating" => "u", + "date" => $img->posted, + "is_warehoused" => false, + "tags" => $taglist, + "source" => $img->source, + "score" => 0, + "author" => $owner->name )); } $xml .= ""; From 06dd72fe66e723ba86f92d3a5ec92ffc1d6b09ac Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Wed, 19 Jun 2013 20:09:22 +0100 Subject: [PATCH 19/78] Danbooru2 theme --- themes/danbooru2/admin.theme.php | 11 + themes/danbooru2/comment.theme.php | 124 +++++++++ themes/danbooru2/custompage.class.php | 9 + themes/danbooru2/ext_manager.theme.php | 14 + themes/danbooru2/index.theme.php | 60 +++++ themes/danbooru2/layout.class.php | 295 +++++++++++++++++++++ themes/danbooru2/style.css | 348 +++++++++++++++++++++++++ themes/danbooru2/tag_list.theme.php | 9 + themes/danbooru2/themelet.class.php | 73 ++++++ themes/danbooru2/upload.theme.php | 14 + themes/danbooru2/user.theme.php | 102 ++++++++ themes/danbooru2/view.theme.php | 67 +++++ 12 files changed, 1126 insertions(+) create mode 100644 themes/danbooru2/admin.theme.php create mode 100644 themes/danbooru2/comment.theme.php create mode 100644 themes/danbooru2/custompage.class.php create mode 100644 themes/danbooru2/ext_manager.theme.php create mode 100644 themes/danbooru2/index.theme.php create mode 100644 themes/danbooru2/layout.class.php create mode 100644 themes/danbooru2/style.css create mode 100644 themes/danbooru2/tag_list.theme.php create mode 100644 themes/danbooru2/themelet.class.php create mode 100644 themes/danbooru2/upload.theme.php create mode 100644 themes/danbooru2/user.theme.php create mode 100644 themes/danbooru2/view.theme.php diff --git a/themes/danbooru2/admin.theme.php b/themes/danbooru2/admin.theme.php new file mode 100644 index 00000000..b08d12c3 --- /dev/null +++ b/themes/danbooru2/admin.theme.php @@ -0,0 +1,11 @@ +disable_left(); + parent::display_page($page); + } +} + +?> diff --git a/themes/danbooru2/comment.theme.php b/themes/danbooru2/comment.theme.php new file mode 100644 index 00000000..f9fcdfca --- /dev/null +++ b/themes/danbooru2/comment.theme.php @@ -0,0 +1,124 @@ +disable_left(); + + // parts for the whole page + $prev = $page_number - 1; + $next = $page_number + 1; + + $h_prev = ($page_number <= 1) ? "Prev" : + "Prev"; + $h_index = "Index"; + $h_next = ($page_number >= $total_pages) ? "Next" : + "Next"; + + $nav = "$h_prev | $h_index | $h_next"; + + $page->set_title("Comments"); + $page->set_heading("Comments"); + $page->add_block(new Block("Navigation", $nav, "left")); + $this->display_paginator($page, "comment/list", null, $page_number, $total_pages); + + // parts for each image + $position = 10; + + $comment_captcha = $config->get_bool('comment_captcha'); + $comment_limit = $config->get_int("comment_list_count", 10); + + foreach($images as $pair) { + $image = $pair[0]; + $comments = $pair[1]; + + $thumb_html = $this->build_thumb_html($image); + + $s = "   "; + $un = $image->get_owner()->name; + $t = ""; + foreach($image->get_tag_array() as $tag) { + $u_tag = url_escape($tag); + $t .= "".html_escape($tag)." "; + } + $p = autodate($image->posted); + + $r = class_exists("Ratings") ? "Rating ".Ratings::rating_to_human($image->rating) : ""; + $comment_html = "Date $p $s User $un $s $r
    Tags $t

     "; + + $comment_count = count($comments); + if($comment_limit > 0 && $comment_count > $comment_limit) { + $hidden = $comment_count - $comment_limit; + $comment_html .= "

    showing $comment_limit of $comment_count comments

    "; + $comments = array_slice($comments, -$comment_limit); + } + foreach($comments as $comment) { + $comment_html .= $this->comment_to_html($comment); + } + if($can_post) { + if(!$user->is_anonymous()) { + $comment_html .= $this->build_postbox($image->id); + } + else { + if(!$comment_captcha) { + $comment_html .= $this->build_postbox($image->id); + } + else { + $comment_html .= "Add Comment"; + } + } + } + + $html = " + + + +
    $thumb_html$comment_html
    + "; + + + $page->add_block(new Block(" ", $html, "main", $position++)); + } + } + + public function display_recent_comments($comments) { + // no recent comments in this theme + } + + + protected function comment_to_html($comment, $trim=false) { + global $user; + + $tfe = new TextFormattingEvent($comment->comment); + send_event($tfe); + + $i_uid = int_escape($comment->owner_id); + $h_name = html_escape($comment->owner_name); + $h_poster_ip = html_escape($comment->poster_ip); + $h_comment = ($trim ? substr($tfe->stripped, 0, 50)."..." : $tfe->formatted); + $i_comment_id = int_escape($comment->comment_id); + $i_image_id = int_escape($comment->image_id); + $h_posted = autodate($comment->posted); + + $stripped_nonl = str_replace("\n", "\\n", substr($tfe->stripped, 0, 50)); + $stripped_nonl = str_replace("\r", "\\r", $stripped_nonl); + $h_userlink = "$h_name"; + $h_del = $user->can("delete_comment") ? + ' - Del' : ''; + $h_imagelink = $trim ? ">>>\n" : ""; + if($trim) { + return "

    $h_userlink $h_del
    $h_posted
    $h_comment

    "; + } + else { + return " + + + +
    $h_userlink
    $h_posted$h_del
    $h_comment
    + "; + } + } +} +?> diff --git a/themes/danbooru2/custompage.class.php b/themes/danbooru2/custompage.class.php new file mode 100644 index 00000000..1e71720b --- /dev/null +++ b/themes/danbooru2/custompage.class.php @@ -0,0 +1,9 @@ +left_enabled = false; + } +} +?> diff --git a/themes/danbooru2/ext_manager.theme.php b/themes/danbooru2/ext_manager.theme.php new file mode 100644 index 00000000..67449dcf --- /dev/null +++ b/themes/danbooru2/ext_manager.theme.php @@ -0,0 +1,14 @@ +disable_left(); + parent::display_table($page, $extensions, $editable); + } + public function display_doc(Page $page, ExtensionInfo $info) { + $page->disable_left(); + parent::display_doc($page, $info); + } +} + +?> diff --git a/themes/danbooru2/index.theme.php b/themes/danbooru2/index.theme.php new file mode 100644 index 00000000..429bd0fe --- /dev/null +++ b/themes/danbooru2/index.theme.php @@ -0,0 +1,60 @@ +search_terms) == 0) { + $query = null; + $page_title = $config->get_string('title'); + } + else { + $search_string = implode(' ', $this->search_terms); + $query = url_escape($search_string); + $page_title = html_escape($search_string); + } + + $nav = $this->build_navigation($this->page_number, $this->total_pages, $this->search_terms); + $page->set_title($page_title); + $page->set_heading($page_title); + $page->add_block(new Block("Search", $nav, "left", 0)); + if(count($images) > 0) { + if($query) { + $page->add_block(new Block("Images", $this->build_table($images, "search=$query"), "main", 10)); + $this->display_paginator($page, "post/list/$query", null, $this->page_number, $this->total_pages); + } + else { + $page->add_block(new Block("Images", $this->build_table($images, null), "main", 10)); + $this->display_paginator($page, "post/list", null, $this->page_number, $this->total_pages); + } + } + else { + $page->add_block(new Block("No Images Found", "No images were found to match the search criteria")); + } + } + + + protected function build_navigation($page_number, $total_pages, $search_terms) { + $h_search_string = count($search_terms) == 0 ? "" : html_escape(implode(" ", $search_terms)); + $h_search_link = make_link(); + $h_search = " +

    + + + +
    +
    "; + + return $h_search; + } + + protected function build_table($images, $query) { + $table = "
    "; + foreach($images as $image) { + $table .= "\t" . $this->build_thumb_html($image, $query) . "\n"; + } + $table .= "
    "; + return $table; + } +} +?> diff --git a/themes/danbooru2/layout.class.php b/themes/danbooru2/layout.class.php new file mode 100644 index 00000000..e39c6623 --- /dev/null +++ b/themes/danbooru2/layout.class.php @@ -0,0 +1,295 @@ +, updated by Daniel Oaks +* Link: http://trac.shishnet.org/shimmie2/ +* License: GPLv2 +* Description: This is a simple theme changing the css to make shimme +* look more like danbooru as well as adding a custom links +* bar and title to the top of every page. +*/ +//Small changes added by zshall +//Changed CSS and layout to make shimmie look even more like danbooru +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +Danbooru 2 Theme - Notes (Bzchan) + +Files: default.php, style.css + +How to use a theme +- Copy the danbooru2 folder with all its contained files into the "themes" + directory in your shimmie installation. +- Log into your shimmie and change the Theme in the Board Config to your + desired theme. + +Changes in this theme include +- Adding and editing various elements in the style.css file. +- $site_name and $front_name retreival from config added. +- $custom_link and $title_link preparation just before html is outputed. +- Altered outputed html to include the custom links and removed heading + from being displayed (subheading is still displayed) +- Note that only the sidebar has been left aligned. Could not properly + left align the main block because blocks without headers currently do + not have ids on there div elements. (this was a problem because + paginator block must be centered and everything else left aligned) + +Tips +- You can change custom links to point to whatever pages you want as well as adding + more custom links. +- The main title link points to the Front Page set in your Board Config options. +- The text of the main title is the Title set in your Board Config options. +- Themes make no changes to your database or main code files so you can switch + back and forward to other themes all you like. + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +class Layout { + public function display_page($page) { + global $config, $user; + + $theme_name = $config->get_string('theme'); + $base_href = $config->get_string('base_href'); + $data_href = get_base_href(); + $contact_link = $config->get_string('contact_link'); + + + $header_html = ""; + ksort($page->html_headers); + foreach($page->html_headers as $line) { + $header_html .= "\t\t$line\n"; + } + + $left_block_html = ""; + $user_block_html = ""; + $main_block_html = ""; + $sub_block_html = ""; + + foreach($page->blocks as $block) { + switch($block->section) { + case "left": + $left_block_html .= $block->get_html(true); + break; + case "user": + $user_block_html .= $block->body; // $this->block_to_html($block, true); + break; + case "subheading": + $sub_block_html .= $block->body; // $this->block_to_html($block, true); + break; + case "main": + if($block->header == "Images") { + $block->header = " "; + } + $main_block_html .= $block->get_html(false); + break; + default: + print "

    error: {$block->header} using an unknown section ({$block->section})"; + break; + } + } + + $debug = get_debug_info(); + + $contact = empty($contact_link) ? "" : "
    Contact"; + + if(empty($this->subheading)) { + $subheading = ""; + } + else { + $subheading = "

    {$this->subheading}
    "; + } + + $site_name = $config->get_string('title'); // bzchan: change from normal default to get title for top of page + $main_page = $config->get_string('main_page'); // bzchan: change from normal default to get main page for top of page + + // bzchan: CUSTOM LINKS are prepared here, change these to whatever you like + $custom_links = ""; + if($user->is_anonymous()) { + $custom_links .= $this->navlinks(make_link('user_admin/login'), "Sign in", array("user", "user_admin", "setup", "admin")); + } + else { + $custom_links .= $this->navlinks(make_link('user'), "My Account", array("user", "user_admin")); + } + if($user->is_admin()) { + $custom_links .= $this->navlinks(make_link('admin'), "Admin", array("admin", "ext_manager", "setup")); + } + $custom_links .= $this->navlinks(make_link('post/list'), "Posts", array("post", "upload", "", "random_image")); + $custom_links .= $this->navlinks(make_link('comment/list'), "Comments", array("comment")); + $custom_links .= $this->navlinks(make_link('tags'), "Tags", array("tags", "alias")); + if(class_exists("Pools")) { + $custom_links .= $this->navlinks(make_link('pool/list'), "Pools", array("pool")); + } + if(class_exists("Wiki")) { + $custom_links .= $this->navlinks(make_link('wiki'), "Wiki", array("wiki")); + $custom_links .= $this->navlinks(make_link('wiki/more'), "More »", array("wiki/more")); + } + + $custom_sublinks = ""; + // hack + global $user; + $username = url_escape($user->name); + // hack + $qp = explode("/", ltrim(@$_GET["q"], "/")); + $hw = class_exists("Wiki"); + // php sucks + switch($qp[0]) { + default: + case "ext_doc": + $custom_sublinks .= $user_block_html; + break; + case "user": + case "user_admin": + if($user->is_anonymous()) { + $custom_sublinks .= "
  • Sign up
  • "; + // $custom_sublinks .= "
  • Reset Password
  • "; + // $custom_sublinks .= "
  • Login Reminder
  • "; + } else { + $custom_sublinks .= "
  • Sign out
  • "; + } + break; + case "": + # FIXME: this assumes that the front page is + # post/list; in 99% of case it will either be + # post/list or home, and in the latter case + # the subnav links aren't shown, but it would + # be nice to be correct + case "random_image": + case "post": + case "upload": + if(class_exists("NumericScore")){ $custom_sublinks .= "
  • Popular by Day/Month/Year
  • ";} + $custom_sublinks .= "
  • Listing
  • "; + if(class_exists("Favorites")){ $custom_sublinks .= "
  • My Favorites
  • ";} + if(class_exists("RSS_Images")){ $custom_sublinks .= "
  • Feed
  • ";} + if(class_exists("RandomImage")){ $custom_sublinks .= "
  • Random
  • ";} + $custom_sublinks .= "
  • Upload
  • "; + if($hw){ $custom_sublinks .= "
  • Help
  • "; + }else{ $custom_sublinks .= "
  • Help
  • ";} + break; + case "comment": + $custom_sublinks .= "
  • All
  • "; + $custom_sublinks .= "
  • Help
  • "; + break; + case "pool": + $custom_sublinks .= "
  • List
  • "; + $custom_sublinks .= "
  • Create
  • "; + $custom_sublinks .= "
  • Changes
  • "; + $custom_sublinks .= "
  • Help
  • "; + break; + case "wiki": + $custom_sublinks .= "
  • Index
  • "; + $custom_sublinks .= "
  • Rules
  • "; + $custom_sublinks .= "
  • Help
  • "; + break; + case "tags": + case "alias": + $custom_sublinks .= "
  • Map
  • "; + $custom_sublinks .= "
  • Alphabetic
  • "; + $custom_sublinks .= "
  • Popularity
  • "; + $custom_sublinks .= "
  • Categories
  • "; + $custom_sublinks .= "
  • Aliases
  • "; + $custom_sublinks .= "
  • Help
  • "; + break; + case "admin": + case "ext_manager": + case "setup": + if($user->is_admin()) { + $custom_sublinks .= "
  • Extension Manager
  • "; + $custom_sublinks .= "
  • Board Config
  • "; + $custom_sublinks .= "
  • Alias Editor
  • "; + } else { + $custom_sublinks .= "
  • I think you might be lost
  • "; + } + break; + } + + + // bzchan: failed attempt to add heading after title_link (failure was it looked bad) + //if($this->heading==$site_name)$this->heading = ''; + //$title_link = "

    $site_name/$this->heading

    "; + + // bzchan: prepare main title link + $title_link = "

    $site_name

    "; + + if($page->left_enabled) { + $left = ""; + $withleft = "withleft"; + } + else { + $left = ""; + $withleft = "noleft"; + } + + $flash = get_prefixed_cookie("flash_message"); + $flash_html = ""; + if($flash) { + $flash_html = "".nl2br(html_escape($flash))." [X]"; + set_prefixed_cookie("flash_message", "", -1, "/"); + } + + print << + + + + + + {$page->title} +$header_html + + + + +
    + $title_link + + +
    + $subheading + $sub_block_html + $left +
    + $flash_html + $main_block_html +
    +
    + Running Shimmie – + Images © their respective owners – + Shimmie © + Shish & + The Team + 2007-2012, + based on the Danbooru concept
    + $debug + $contact +
    + + +EOD; + } + + private function navlinks($link, $desc, $pages_matched) { + /** + * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) + */ + $html = null; + $url = $_GET['q']; + + $re1='.*?'; + $re2='((?:[a-z][a-z_]+))'; + + if ($c=preg_match_all ("/".$re1.$re2."/is", $url, $matches)) { + $url=$matches[1][0]; + } + + for($i=0;$i diff --git a/themes/danbooru2/style.css b/themes/danbooru2/style.css new file mode 100644 index 00000000..efa39edf --- /dev/null +++ b/themes/danbooru2/style.css @@ -0,0 +1,348 @@ +.noleft{ +padding-left:2rem; +} +HEADER { +margin-bottom:0.9rem; +} +HEADER #site-title { +padding:0.6rem 2rem 0.25rem; +} +HEADER ul#navbar, HEADER ul#subnavbar { +font-family:Verdana,Helvetica,sans-serif; +font-size:110%; +} +HEADER ul#navbar { +margin:0; +padding:0 1rem 0 2rem; +} +HEADER ul#navbar li { +display:inline-block; +margin:0 0.15rem; +padding:0.4rem 0.6rem; +} +HEADER ul#navbar li:first-child { +margin-left: -0.6rem; +} +HEADER ul#navbar li:first-child a { +color: #FF3333; +font-weight: bold; +} +HEADER ul#navbar li.current-page { +background-color:#EEEEFF; +border-radius:0.2rem 0.2rem 0 0; +} +HEADER ul#navbar li.current-page a { +font-weight:bold; +} +HEADER ul#subnavbar { +margin:0 0 0.5rem; +padding:0 1rem 0 2rem; +background-color:#EEEEFF; +} +HEADER ul#subnavbar li { +display:inline-block; +margin:0 0.15rem; +padding:0.4rem 0.6rem; +} +HEADER ul#subnavbar li:first-child { +margin-left: -0.6rem; +} +body { +background-color:#FFFFFF; +font-weight:normal; +font-style:normal; +font-variant:normal; +font-size-adjust:none; +font-stretch:normal; +font-size:80%; +line-height:normal; +-x-system-font:none; +} +h1 { +margin-top:0; +margin-bottom:0; +padding:0.3rem; +font-size:2.2rem; +} +h1 a { +color:black; +} +h3 { +margin-top:0; +margin-bottom:0; +padding:0.2rem 0.2rem 0.2rem 0; +font-size:1rem; +} +h4 { +font-size:1.4rem; +} +h5 { +font-size:1.2rem; +} +table.zebra {border-spacing: 0;border-collapse: collapse;} +table.zebra > tbody > tr:hover {background: #FFD;} +table.zebra th { padding-right: 0.4rem;color: #171BB3;} +table.zebra td {margin: 0;padding-right: 0.6rem;border: 1px dotted #EEE;} +table.zebra th {margin: 0;text-align: left;} +thead { +font-weight:bold; +-moz-background-clip:border; +-moz-background-inline-policy:continuous; +-moz-background-origin:padding; +} +td { +vertical-align:top; +} +#subtitle { +margin:auto; +width:256px; +border-top:medium none; +text-align:center; +font-size:0.75em; +} +FOOTER { +clear:both; +border-top:solid 1px #E7E7F7; +margin-top:1rem; +text-align:center; +color:#555555; +font-size:0.8rem; +} +FOOTER > DIV { +margin: 1rem 2rem; +} +form { +margin:0; +} +a { +text-decoration:none; +} +a:hover { +text-decoration:underline; +} +NAV { +float:left; +padding:0 1rem 0.2rem 2rem; +width:11.5rem; +text-align:left; +} +NAV section + section { +margin-top:1rem; +} +NAV table { +width:15rem; +} +NAV td { +vertical-align:middle; +} +NAV input { +padding:0; +width:100%; +} +NAV select { +padding:0; +width:100%; +} +NAV h3 { +text-align:left; +} +#comments p { +overflow:hidden; +max-width:150px; +width:15rem; +text-align:left; +} +.tag_count { +display:inline-block; +margin-left:0.4rem; +color:#AAAAAA; +} +.more { +content:"More â"; +} +.comment { +margin-bottom:8px; +} +.comment .meta { +width: 15rem; +color: gray; +} +.comment TD { +text-align: left; +} +.withleft { +margin-left:14.5rem; +} +div#paginator { +display:block; +clear:both; +padding:2em 0 1em; +text-align:center; +font-weight:bold; +font-size:1em; +} +.paginator { +margin:16px; +text-align:center; +} +div#paginator b { +margin:3px; +padding:4px 8px; +} +div#paginator a { +margin:3px; +padding:4px 8px; +border:1px solid #EEEEEE; +} +div#paginator a:hover { +border:1px solid #EEEEEE; +background:blue none repeat scroll 0 0; +color:white; +-moz-background-clip:border; +-moz-background-inline-policy:continuous; +-moz-background-origin:padding; +} +span.thumb { +display:inline-block; +float:left; +width:220px; +height:220px; +text-align:center; +} +#pagelist { +margin-top:32px; +} +#large_upload_form { +width:600px; +} +.setupblock, .tagcategoryblock { +margin:0.6rem 1rem 0.6rem 0; +padding:0.5rem 0.6rem 0.7rem; +width:18rem; +border:1px solid #AAAAAA; +border-radius:0.25rem; +display:inline-block; +} +.tagcategoryblock table { +width:100%; +border-spacing:0; +} +.tagcategoryblock input, .tagcategoryblock span { +width:100%; +height:100%; +} +.tagcategoryblock td:first-child { +padding:0.3rem 0.7rem 0.4rem 0; +text-align:right; +width:40%; +} +.tagcategoryblock td:last-child { +width:60%; +} +.tagcategoryblock td:last-child span { +padding:0.24rem 0.7rem 0.5rem 0; +display:block; +} +.tagcategoryblock button { +width:100%; +margin-top:0.4rem; +padding:0.2rem 0.6rem; +} +.helpable { +border-bottom:1px dashed gray; +} +.ok { +background:#AAFFAA none repeat scroll 0 0; +-moz-background-clip:border; +-moz-background-inline-policy:continuous; +-moz-background-origin:padding; +} +.bad { +background:#FFAAAA none repeat scroll 0 0; +-moz-background-clip:border; +-moz-background-inline-policy:continuous; +-moz-background-origin:padding; +} +.comment .username { +font-weight:bold; +font-size:1.5em; +} +HEADER { +text-align:left; +} +HEADER h1 { +text-align:left; +} +* { +margin:0; +padding:0; +font-family:Tahoma,Verdana,Helvetica,sans-serif; +} +a:link { +color:#006FFA; +text-decoration:none; +} +a:visited { +color:#006FFA; +text-decoration:none; +} +a:hover { +color:#33CFFF; +text-decoration:none; +} +a:active { +color:#006FFA; +text-decoration:none; +} +ul.flat-list { +display:block; +margin:0; +padding:0; +} +ul.flat-list * { +display:inline; +text-align:left; +} +ul.flat-list li { +margin:0 1.3em 0 0; +list-style-type:none; +text-align:left; +font-weight:bold; +} +ul.flat-list li a { +font-weight:normal; +} +#tips { +margin-left:16px; +} +#blotter1 { +position: relative; +margin-right:16px; +margin-left:16px; +font-size: 90%; +} +#blotter2 { +margin-right:16px; +margin-left:16px; +font-size: 90%; +} +#flash { +background:#FDF5D9; +border:1px solid #FCEEC1; +margin:1rem 0; +padding:1rem; +text-align:center; +border-radius:0.5rem; +} +ARTICLE { +margin-right:1rem; +} +ARTICLE section + section { +margin-top:1rem; +} +form + form { +margin-top:0.5rem; +} +#Imagemain h3 { +display:none; +} diff --git a/themes/danbooru2/tag_list.theme.php b/themes/danbooru2/tag_list.theme.php new file mode 100644 index 00000000..3c168380 --- /dev/null +++ b/themes/danbooru2/tag_list.theme.php @@ -0,0 +1,9 @@ +disable_left(); + parent::display_page($page); + } +} +?> diff --git a/themes/danbooru2/themelet.class.php b/themes/danbooru2/themelet.class.php new file mode 100644 index 00000000..6ef57ee8 --- /dev/null +++ b/themes/danbooru2/themelet.class.php @@ -0,0 +1,73 @@ +id}", $query); + $h_thumb_link = $image->get_thumb_link(); + $h_tip = html_escape($image->get_tooltip()); + $i_id = int_escape($image->id); + $h_tags = strtolower($image->get_tag_list()); + + // If file is flash or svg then sets thumbnail to max size. + if($image->ext == 'swf' || $image->ext == 'svg') { + $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); + } + else{ + $tsize = get_thumbnail_size($image->width, $image->height); + } + + return "$h_tip"; + } + + + public function display_paginator(Page $page, $base, $query, $page_number, $total_pages) { + if($total_pages == 0) $total_pages = 1; + $body = $this->build_paginator($page_number, $total_pages, $base, $query); + $page->add_block(new Block(null, $body, "main", 90)); + } + + private function gen_page_link($base_url, $query, $page, $name) { + $link = make_link("$base_url/$page", $query); + return "$name"; + } + + private function gen_page_link_block($base_url, $query, $page, $current_page, $name) { + $paginator = ""; + if($page == $current_page) $paginator .= "$page"; + else $paginator .= $this->gen_page_link($base_url, $query, $page, $name); + return $paginator; + } + + private function build_paginator($current_page, $total_pages, $base_url, $query) { + $next = $current_page + 1; + $prev = $current_page - 1; + $rand = rand(1, $total_pages); + + $at_start = ($current_page <= 3 || $total_pages <= 3); + $at_end = ($current_page >= $total_pages -2); + + $first_html = $at_start ? "" : $this->gen_page_link($base_url, $query, 1, "1"); + $prev_html = $at_start ? "" : $this->gen_page_link($base_url, $query, $prev, "<<"); + $next_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $next, ">>"); + $last_html = $at_end ? "" : $this->gen_page_link($base_url, $query, $total_pages, "$total_pages"); + + $start = $current_page-2 > 1 ? $current_page-2 : 1; + $end = $current_page+2 <= $total_pages ? $current_page+2 : $total_pages; + + $pages = array(); + foreach(range($start, $end) as $i) { + $pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, $i); + } + $pages_html = implode(" ", $pages); + + if(strlen($first_html) > 0) $pdots = "..."; + else $pdots = ""; + + if(strlen($last_html) > 0) $ndots = "..."; + else $ndots = ""; + + return "
    $prev_html $first_html $pdots $pages_html $ndots $last_html $next_html
    "; + } +} +?> diff --git a/themes/danbooru2/upload.theme.php b/themes/danbooru2/upload.theme.php new file mode 100644 index 00000000..7e5f75bf --- /dev/null +++ b/themes/danbooru2/upload.theme.php @@ -0,0 +1,14 @@ +add_block(new Block("Upload", $this->build_upload_block(), "left", 20)); + } + + public function display_page(Page $page) { + $page->disable_left(); + parent::display_page($page); + } +} +?> diff --git a/themes/danbooru2/user.theme.php b/themes/danbooru2/user.theme.php new file mode 100644 index 00000000..ca62caa8 --- /dev/null +++ b/themes/danbooru2/user.theme.php @@ -0,0 +1,102 @@ +set_title("Login"); + $page->set_heading("Login"); + $page->disable_left(); + $html = " +
    + + + + + + + + + + +
    +
    + "; + if($config->get_bool("login_signup_enabled")) { + $html .= "Create Account"; + } + $page->add_block(new Block("Login", $html, "main", 90)); + } + + public function display_user_links(Page $page, User $user, $parts) { + // no block in this theme + } + public function display_login_block(Page $page) { + // no block in this theme + } + + public function display_user_block(Page $page, User $user, $parts) { + $h_name = html_escape($user->name); + $html = ""; + $blocked = array("Pools", "Pool Changes", "Alias Editor", "My Profile"); + foreach($parts as $part) { + if(in_array($part["name"], $blocked)) continue; + $html .= "
  • {$part["name"]}"; + } + $page->add_block(new Block("User Links", $html, "user", 90)); + } + + public function display_signup_page(Page $page) { + global $config; + $tac = $config->get_string("login_tac", ""); + + $tfe = new TextFormattingEvent($tac); + send_event($tfe); + $tac = $tfe->formatted; + + $reca = "".captcha_get_html().""; + + if(empty($tac)) {$html = "";} + else {$html = "

    $tac

    ";} + + $html .= " +
    + + + + + + $reca; + +
    Name
    Password
    Repeat Password
    Email (Optional)
    +
    + "; + + $page->set_title("Create Account"); + $page->set_heading("Create Account"); + $page->disable_left(); + $page->add_block(new Block("Signup", $html)); + } + + public function display_ip_list(Page $page, $uploads, $comments) { + $html = ""; + $html .= ""; + $html .= "
    Uploaded from: "; + foreach($uploads as $ip => $count) { + $html .= "
    $ip ($count)"; + } + $html .= "
    Commented from:"; + foreach($comments as $ip => $count) { + $html .= "
    $ip ($count)"; + } + $html .= "
    (Most recent at top)
    "; + + $page->add_block(new Block("IPs", $html)); + } + + public function display_user_page(User $duser, $stats) { + global $page; + $page->disable_left(); + parent::display_user_page($duser, $stats); + } +} +?> diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php new file mode 100644 index 00000000..11956fc9 --- /dev/null +++ b/themes/danbooru2/view.theme.php @@ -0,0 +1,67 @@ +set_title("Image {$image->id}: ".html_escape($image->get_tag_list())); + $page->set_heading(html_escape($image->get_tag_list())); + $page->add_block(new Block("Search", $this->build_navigation($image), "left", 0)); + $page->add_block(new Block("Information", $this->build_information($image), "left", 15)); + $page->add_block(new Block(null, $this->build_info($image, $editor_parts), "main", 15)); + } + + private function build_information(Image $image) { + $h_owner = html_escape($image->get_owner()->name); + $h_ownerlink = "$h_owner"; + $h_ip = html_escape($image->owner_ip); + $h_date = autodate($image->posted); + $h_filesize = to_shorthand_int($image->filesize); + + global $user; + if($user->can("view_ip")) { + $h_ownerlink .= " ($h_ip)"; + } + + $html = " + ID: {$image->id} +
    Uploader: $h_ownerlink +
    Date: $h_date +
    Size: $h_filesize ({$image->width}x{$image->height}) + "; + + if(!is_null($image->source)) { + $h_source = html_escape($image->source); + if(substr($image->source, 0, 7) != "http://") { + $h_source = "http://" . $h_source; + } + $html .= "
    Source: link"; + } + + if(class_exists("Ratings")) { + if($image->rating == null || $image->rating == "u"){ + $image->rating = "u"; + } + if(class_exists("Ratings")) { + $h_rating = Ratings::rating_to_human($image->rating); + $html .= "
    Rating: $h_rating"; + } + } + + return $html; + } + + protected function build_navigation(Image $image) { + //$h_pin = $this->build_pin($image); + $h_search = " +
    + + + + +
    + "; + + return "$h_search"; + } +} +?> From 668ebdcb2e46df28e1942aea572a6e1ce80c5efe Mon Sep 17 00:00:00 2001 From: Drudex Software Date: Wed, 19 Jun 2013 20:56:19 +0100 Subject: [PATCH 20/78] Arrowkey Navigation --- ext/arrow_nav/main.php | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 ext/arrow_nav/main.php diff --git a/ext/arrow_nav/main.php b/ext/arrow_nav/main.php new file mode 100644 index 00000000..3ab437e2 --- /dev/null +++ b/ext/arrow_nav/main.php @@ -0,0 +1,89 @@ + + * Link: http://www.drudexsoftware.com/ + * License: GPLv2 + * Description: Allows viewers no navigate between images using the left & right arrow keys. + * Documentation: + * Simply enable this extention in the extention manager to enable arrow key navigation. + */ +class ArrowkeyNavigation extends Extension { + # Adds functionality for post/view on images + public function onDisplayingImage(DisplayingImageEvent $event) { + $prev_url = make_http(make_link("post/prev/".$event->image->id)); + $next_url = make_http(make_link("post/next/".$event->image->id)); + $this->add_arrowkeys_code($prev_url, $next_url); + } + + # Adds functionality for post/list + public function onPageRequest(PageRequestEvent $event) { + if($event->page_matches("post/list")) { + $pageinfo = $this->get_list_pageinfo($event); + $prev_url = make_http(make_link("post/list/".$pageinfo["prev"])); + $next_url = make_http(make_link("post/list/".$pageinfo["next"])); + $this->add_arrowkeys_code($prev_url, $next_url); + } + } + + # adds the javascript to the page with the given urls + private function add_arrowkeys_code($prev_url, $next_url) { + global $page; + + $page->add_html_header(""); + } + + # returns info about the current page number + private function get_list_pageinfo($event) { + global $config, $database; + + // get the amount of images per page + $images_per_page = $config->get_int('index_images'); + + // if there are no tags, use default + if ($event->get_arg(1) == null){ + $prefix = ""; + $page_number = (int)$event->get_arg(0); + $total_pages = ceil($database->get_one( + "SELECT COUNT(*) FROM images") / $images_per_page); + } + + else { // if there are tags, use pages with tags + $prefix = $event->get_arg(0)."/"; + $page_number = (int)$event->get_arg(1); + $total_pages = ceil($database->get_one( + "SELECT count FROM tags WHERE tag=:tag", + array("tag"=>$event->get_arg(0))) / $images_per_page); + } + + // creates previous & next values + // When previous first page, go to last page + if ($page_number <= 1) $prev = $total_pages; + else $prev = $page_number-1; + if ($page_number >= $total_pages) $next = 1; + else $next = $page_number+1; + + // Create return array + $pageinfo = array( + "prev" => $prefix.$prev, + "next" => $prefix.$next, + ); + + return $pageinfo; + } +} +?> From a3486f9b299297b3ea91b1fcb9a54c85137014eb Mon Sep 17 00:00:00 2001 From: Drudex Software Date: Wed, 19 Jun 2013 20:57:54 +0100 Subject: [PATCH 21/78] Bulk Remove --- ext/bulk_remove/main.php | 132 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 ext/bulk_remove/main.php diff --git a/ext/bulk_remove/main.php b/ext/bulk_remove/main.php new file mode 100644 index 00000000..e7cbad9e --- /dev/null +++ b/ext/bulk_remove/main.php @@ -0,0 +1,132 @@ + + * Link: http://www.drudexsoftware.com/ + * License: GPLv2 + * Description: Allows admin to delete many images at once through Board Admin. + * Documentation: + * + */ +//todo: removal by tag returns 1 less image in test for some reason, actually a combined search doesn't seem to work for shit either + +class BulkRemove extends Extension { + public function onPageRequest(PageRequestEvent $event) { + global $page, $user; + if($event->page_matches("bulk_remove") && $user->is_admin() && $user->check_auth_token()) { + if ($event->get_arg(0) == "confirm") $this->do_bulk_remove(); + else $this->show_confirm(); + } + } + + public function onAdminBuilding(AdminBuildingEvent $event) { + global $page, $user; + $html = "Be extremely careful when using this!
    + Once an image is removed there is no way to recover it so it is recommended that + you first take when removing a large amount of images.
    + Note: Entering both an ID range and tags will only remove images between the given ID's that have the given tags. + +

    ".make_form(make_link("bulk_remove"))." + + + + + + + + +
    Remove images by ID
    From
    Until
    Where tags are
    + +
    + + "; + $page->add_block(new Block("Bulk Remove", $html)); + } + + // returns a list of images to be removed + private function determine_images() + { + // set vars + $images_for_removal = array(); + $error = ""; + + $min_id = $_POST['remove_id_min']; + $max_id = $_POST['remove_id_max']; + $tags = $_POST['remove_tags']; + + + // if using id range to remove (comined removal with tags) + if ($min_id != "" && $max_id != "") + { + // error if values are not correctly entered + if (!is_numeric($min_id) || !is_numeric($max_id) || + intval($max_id) < intval($min_id)) + $error = "Values not correctly entered for removal between id."; + + else { // if min & max id are valid + + // Grab the list of images & place it in the removing array + foreach (Image::find_images(intval($min_id), intval($max_id)) as $image) + array_push($images_for_removal, $image); + } + } + + // refine previous results or create results from tags + if ($tags != "") + { + $tags_arr = explode(" ", $_POST['remove_tags']); + + // Search all images with the specified tags & add to list + foreach (Image::find_images(1, 2147483647, $tags_arr) as $image) + array_push($images_for_removal, $image); + } + + + // if no images were found with the given info + if (count($images_for_removal) == 0 && $html == "") + $error = "No images selected for removal"; + + var_dump($tags_arr); + return array( + "error" => $error, + "images_for_removal" => $images_for_removal); + } + + // displays confirmation to admin before removal + private function show_confirm() + { + global $page; + + // set vars + $determined_imgs = $this->determine_images(); + $error = $determined_imgs["error"]; + $images_for_removal = $determined_imgs["images_for_removal"]; + + // if there was an error in determine_images() + if ($error != "") { + $page->add_block(new Block("Cannot remove images", $error)); + return; + } + // generates the image array & places it in $_POST["bulk_remove_images"] + $_POST["bulk_remove_images"] = $images_for_removal; + + // Display confirmation message + $html = make_form(make_link("bulk_remove")). + "Are you sure you want to PERMANENTLY remove ". + count($images_for_removal) ." images?
    "; + $page->add_block(new Block("Confirm Removal", $html)); + } + + private function do_bulk_remove() + { + // display error if user didn't go through admin board + if (!isset($_POST["bulk_remove_images"])) { + $page->add_block(new Block("Bulk Remove Error", + "Please use Board Admin to use bulk remove.")); + } + + // + $image_arr = $_POST["bulk_remove_images"]; + } +} +?> From 4a2c47459c451a6c7d4fda1076e99c6780ade996 Mon Sep 17 00:00:00 2001 From: Shish Date: Wed, 19 Jun 2013 20:59:59 +0100 Subject: [PATCH 22/78] Chatbox --- ext/chatbox/cp/ajax.php | 459 +++++++++++++++ ext/chatbox/cp/css/style.css | 386 ++++++++++++ ext/chatbox/cp/index.php | 42 ++ ext/chatbox/cp/js/admincp.js | 373 ++++++++++++ ext/chatbox/css/dark.yshout.css | 389 +++++++++++++ ext/chatbox/css/overlay.css | 93 +++ ext/chatbox/css/style.css | 113 ++++ ext/chatbox/history/css/style.css | 85 +++ ext/chatbox/history/index.php | 133 +++++ ext/chatbox/history/js/history.js | 281 +++++++++ ext/chatbox/include.php | 10 + ext/chatbox/js/jquery.js | 154 +++++ ext/chatbox/js/yshout.js | 805 ++++++++++++++++++++++++++ ext/chatbox/logs/.htaccess | 4 + ext/chatbox/logs/log.1.txt | 1 + ext/chatbox/logs/yshout.bans.txt | 1 + ext/chatbox/logs/yshout.prefs.txt | 1 + ext/chatbox/main.php | 36 ++ ext/chatbox/php/ajaxcall.class.php | 279 +++++++++ ext/chatbox/php/filestorage.class.php | 85 +++ ext/chatbox/php/functions.php | 152 +++++ ext/chatbox/php/json.class.php | 805 ++++++++++++++++++++++++++ ext/chatbox/php/yshout.class.php | 253 ++++++++ ext/chatbox/preferences.php | 74 +++ ext/chatbox/smileys/biggrin.gif | Bin 0 -> 172 bytes ext/chatbox/smileys/confused.gif | Bin 0 -> 171 bytes ext/chatbox/smileys/cool.gif | Bin 0 -> 172 bytes ext/chatbox/smileys/cry.gif | Bin 0 -> 498 bytes ext/chatbox/smileys/eek.gif | Bin 0 -> 170 bytes ext/chatbox/smileys/evil.gif | Bin 0 -> 236 bytes ext/chatbox/smileys/lol.gif | Bin 0 -> 336 bytes ext/chatbox/smileys/mad.gif | Bin 0 -> 174 bytes ext/chatbox/smileys/mrgreen.gif | Bin 0 -> 349 bytes ext/chatbox/smileys/neutral.gif | Bin 0 -> 171 bytes ext/chatbox/smileys/razz.gif | Bin 0 -> 176 bytes ext/chatbox/smileys/redface.gif | Bin 0 -> 650 bytes ext/chatbox/smileys/rolleyes.gif | Bin 0 -> 485 bytes ext/chatbox/smileys/sad.gif | Bin 0 -> 171 bytes ext/chatbox/smileys/smile.gif | Bin 0 -> 174 bytes ext/chatbox/smileys/surprised.gif | Bin 0 -> 174 bytes ext/chatbox/smileys/twisted.gif | Bin 0 -> 238 bytes ext/chatbox/smileys/wink.gif | Bin 0 -> 170 bytes ext/chatbox/yshout.php | 39 ++ 43 files changed, 5053 insertions(+) create mode 100644 ext/chatbox/cp/ajax.php create mode 100644 ext/chatbox/cp/css/style.css create mode 100644 ext/chatbox/cp/index.php create mode 100644 ext/chatbox/cp/js/admincp.js create mode 100644 ext/chatbox/css/dark.yshout.css create mode 100644 ext/chatbox/css/overlay.css create mode 100644 ext/chatbox/css/style.css create mode 100644 ext/chatbox/history/css/style.css create mode 100644 ext/chatbox/history/index.php create mode 100644 ext/chatbox/history/js/history.js create mode 100644 ext/chatbox/include.php create mode 100644 ext/chatbox/js/jquery.js create mode 100644 ext/chatbox/js/yshout.js create mode 100644 ext/chatbox/logs/.htaccess create mode 100644 ext/chatbox/logs/log.1.txt create mode 100644 ext/chatbox/logs/yshout.bans.txt create mode 100644 ext/chatbox/logs/yshout.prefs.txt create mode 100644 ext/chatbox/main.php create mode 100644 ext/chatbox/php/ajaxcall.class.php create mode 100644 ext/chatbox/php/filestorage.class.php create mode 100644 ext/chatbox/php/functions.php create mode 100644 ext/chatbox/php/json.class.php create mode 100644 ext/chatbox/php/yshout.class.php create mode 100644 ext/chatbox/preferences.php create mode 100644 ext/chatbox/smileys/biggrin.gif create mode 100644 ext/chatbox/smileys/confused.gif create mode 100644 ext/chatbox/smileys/cool.gif create mode 100644 ext/chatbox/smileys/cry.gif create mode 100644 ext/chatbox/smileys/eek.gif create mode 100644 ext/chatbox/smileys/evil.gif create mode 100644 ext/chatbox/smileys/lol.gif create mode 100644 ext/chatbox/smileys/mad.gif create mode 100644 ext/chatbox/smileys/mrgreen.gif create mode 100644 ext/chatbox/smileys/neutral.gif create mode 100644 ext/chatbox/smileys/razz.gif create mode 100644 ext/chatbox/smileys/redface.gif create mode 100644 ext/chatbox/smileys/rolleyes.gif create mode 100644 ext/chatbox/smileys/sad.gif create mode 100644 ext/chatbox/smileys/smile.gif create mode 100644 ext/chatbox/smileys/surprised.gif create mode 100644 ext/chatbox/smileys/twisted.gif create mode 100644 ext/chatbox/smileys/wink.gif create mode 100644 ext/chatbox/yshout.php diff --git a/ext/chatbox/cp/ajax.php b/ext/chatbox/cp/ajax.php new file mode 100644 index 00000000..adc757ce --- /dev/null +++ b/ext/chatbox/cp/ajax.php @@ -0,0 +1,459 @@ + false, + 'html' => cp() + ); + + echo jsonEncode($result); + return; + } + + login(md5($_POST['password'])); + $result = array(); + if (loggedIn()) { + $result['error'] = false; + $result['html'] = cp(); + } else + $result['error'] = 'invalid'; + + echo jsonEncode($result); +} + +function doLogout() { + logout(); + + $result = array( + 'error' => false + ); + + echo jsonEncode($result); +} + +function doUnban() { + global $kioskMode; + + if ($kioskMode) { + $result = array( + 'error' => false + ); + + echo jsonEncode($result); + return; + } + + if (!loggedIn()) return; + + $ys = ys(); + $result = array(); + + $ip = $_POST['ip']; + + if ($ys->banned($ip)) { + $ys->unban($ip); + $result['error'] = false; + } else + $result['error'] = 'notbanned'; + + + echo jsonEncode($result); +} + +function doUnbanAll() { + global $kioskMode; + + if ($kioskMode) { + $result = array( + 'error' => false + ); + + echo jsonEncode($result); + return; + } + + if (!loggedIn()) return; + + $ys = ys(); + $ys->unbanAll(); + + $result = array( + 'error' => false + ); + + echo jsonEncode($result); +} + + +function doSetPreference() { + global $prefs, $kioskMode; + + if ($kioskMode) { + $result = array( + 'error' => false + ); + + echo jsonEncode($result); + return; + } + + if (!loggedIn()) return; + + $pref = $_POST['preference']; + $value = magic($_POST['value']); + + if ($value === 'true') $value = true; + if ($value === 'false') $value = false; + + $prefs[$pref] = $value; + + savePrefs($prefs); + + if ($pref == 'password') login(md5($value)); + + $result = array( + 'error' => false + ); + + echo jsonEncode($result); +} + + +function doResetPreferences() { + global $prefs, $kioskMode; + + if ($kioskMode) { + $result = array( + 'error' => false + ); + + echo jsonEncode($result); + return; + } + + if (!loggedIn()) return; + + resetPrefs(); + login(md5($prefs['password'])); + + // $prefs['password'] = 'lol no'; + $result = array( + 'error' => false, + 'prefs' => $prefs + ); + + echo jsonEncode($result); +} + +/* CP Display */ + +function cp() { + global $kioskMode; + + if (!loggedIn() && !$kioskMode) return 'You\'re not logged in!'; + + return ' + +

    + +
    +

    YShout.Preferences

    + Logout +
    + + + + ' . preferencesForm() . ' +
    + +
    +
    +

    YShout.About

    + Logout +
    + + + + ' . about() . ' +
    + +
    +
    +

    YShout.Bans

    + Logout +
    + + + + ' . bansList() . ' + +
    '; +} + +function bansList() { + global $kioskMode; + + $ys = ys(); + $bans = $ys->bans(); + + $html = '
      '; + + $hasBans = false; + foreach($bans as $ban) { + $hasBans = true; + $html .= ' +
    • + ' . $ban['nickname']. ' + (' . ($kioskMode ? '[No IP in Kiosk Mode]' : $ban['ip']) . ') + Unban +
    • + '; + } + + if (!$hasBans) + $html = '

      No one is banned.

      '; + else + $html .= '
    '; + + return $html; +} + +function preferencesForm() { + global $prefs, $kioskMode; + + return ' +
    +
    +
    +
    Control Panel
    +
      +
    1. + + +
    2. +
    +
    + +
    +
    Flood Control
    +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    5. + + +
    6. +
    7. + + +
    8. +
    9. + + +
    10. +
    +
    + +
    +
    History
    +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    +
    + +
    +
    Miscellaneous
    +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    +
    +
    + +
    +
    +
    Form
    +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    5. + + +
    6. +
    7. + + +
    8. +
    9. + + +
    10. +
    11. + + +
    12. +
    13. + + +
    14. +
    15. + + +
    16. +
    +
    + +
    +
    Shouts
    +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    5. + + +
    6. +
    7. + + +
    8. +
    9. + + +
    10. +
    +
    +
    +
    + '; +} + +function about() { + global $prefs; + + $html = ' +
    +

    About YShout

    +

    YShout was created and developed by Yuri Vishnevsky. Version 5 is the first one with an about page, so you\'ll have to excuse the lack of appropriate information — I\'m not quite sure what it is that goes on "About" pages anyway.

    +

    Other than that obviously important tidbit of information, there\'s really nothing else that I can think of putting here... If anyone knows what a good and proper about page should contain, please contact me! +

    + +
    +

    Contact Yuri

    +

    If you have any questions or comments, you can contact me by email at yurivish@gmail.com, or on AIM at yurivish42.

    +

    I hope you\'ve enjoyed using YShout!

    +
    + '; + + + return $html; +} + +?> \ No newline at end of file diff --git a/ext/chatbox/cp/css/style.css b/ext/chatbox/cp/css/style.css new file mode 100644 index 00000000..b37a885e --- /dev/null +++ b/ext/chatbox/cp/css/style.css @@ -0,0 +1,386 @@ +* { + margin: 0; + padding: 0; +} + +html, body {height: 100%;} + +body { + background: #1a1a1a url(../images/bg.gif) center center no-repeat; + color: #a7a7a7; + font: 11px/1 Tahoma, Arial, sans-serif; + text-shadow: 0 0 0 #273541; + overflow: hidden; +} + +a { + outline: none; + color: #fff; + text-decoration: none; +} + +a:hover{ + color: #fff; +} + +input { + font-size: 11px; + background: #e5e5e5; + border: 1px solid #f5f5f5; + padding: 2px; +} + +select { + font-size: 11px; +} + +#cp { + height: 440px; + width: 620px; + position: absolute; + top: 50%; + left: 50%; + margin-top: -220px; + margin-left: -310px; +} + +#nav { + height: 65px; + width: 100%; + background: url(../images/bg-nav.gif) repeat-x; + position: absolute; + bottom: 0; +} + + #nav ul { + display: none; + width: 240px; + height: 65px; + margin: 0 auto; + list-style: none; + } + + #nav li { + width: 80px; + float: left; + text-align: center; + } + + #nav a { + display: block; + height: 65px; + text-indent: -4200px; + outline: none; + } + + #nav a:active { + background-position: 0 -65px; + } + + #n-prefs a { background: 0 0 url("../images/n-prefs.gif") no-repeat; } + #n-bans a { background: 0 0 url("../images/n-bans.gif") no-repeat; } + #n-about a { background: 0 0 url("../images/n-about.gif") no-repeat; } + +.subnav { + height: 25px; + background: url(../images/bg-subnav.gif) repeat-x; + list-style: none; +} + + .subnav input { + float: left; + margin-top: 2px; + margin-right: 10px; + } + + .subnav li { + width: 85px; + float: left; + text-indent: -4200px; + } + + .subnav a { + display: block; + height: 25px; + } + + .subnav a:hover { + background-position: bottom left !important; + } + + #sn-administration a { background: url(../images/sn-administration.gif) no-repeat; } + #sn-display a { background: url(../images/sn-display.gif) no-repeat; } + #sn-form a { background: url(../images/sn-form.gif) no-repeat; } + #sn-resetall a { background: url(../images/sn-resetall.gif) no-repeat; } + #sn-ban a { background: url(../images/sn-ban.gif) no-repeat; } + #sn-unbanall a { background: url(../images/sn-unbanall.gif) no-repeat; } + #sn-deleteall a { background: url(../images/sn-deleteall.gif) no-repeat; } + #sn-about a { background: url(../images/sn-about.gif) no-repeat; } + #sn-contact a { background: url(../images/sn-contact.gif) no-repeat; } + + + + .sn-loading { + display: block; + height: 25px; + width: 25px; + float: right; + text-indent: -4200px; + background: url(../images/sn-spinny.gif) no-repeat; + _position: absolute; + _right: 20px; + _top: 50px; + } + + @media { .sn-loading { + position: absolute; + right: 15px; + top: 41px; + }} + +#content { + position: relative; + height: 375px; + overflow: hidden; +} + + .header { + height: 33px; + padding-bottom: 2px; + border-bottom: 1px solid #444; + } + + #login .header { border-bottom: 1px solid #4c657b; } + + h1 { + float: left; + height: 32px; + width: 185px; + text-indent: -4200px; + } + + #login h1 { background: url(../images/h-login.gif) no-repeat; } + #preferences h1 { background: url(../images/h-preferences.gif) no-repeat; } + #bans h1 { background: url(../images/h-bans.gif) no-repeat; } + #about h1 { background: url(../images/h-about.gif) no-repeat; } + + .logout { + display: block; + height: 32px; + width: 45px; + float: right; + text-indent: -4200px; + background: url(../images/a-logout.gif) no-repeat; + } + + .logout:hover { + background-position: bottom left; + } + + .section { + clear: both; + width: 590px; + height: 355px; + padding: 15px; + padding-top: 5px; + position: absolute; + } + +#login { + left: 0; + background: url(../images/bg-login.gif) repeat-x; + z-index: 5; +} + + #login-form { + height: 45px; + width: 300px; + position: absolute; + top: 50%; + left: 50%; + margin-top: -45px; + margin-left: -150px; + background: url(../images/bg-login-form.gif) no-repeat; + } + + #login-form label { + display: none; + } + + #login-form input { + position: absolute; + left: 127px; + top: 13px; + width: 153px; + z-index: 2; + border: 1px solid #d4e7fa; + background: #e7eef6; + } + + #login-loading { + display: block; + position: absolute; + top: 12px; + right: 8px; + height: 25px; + width: 25px; + text-indent: -4200px; + background: url(../images/login-spinny.gif) no-repeat; + z-index: 1; + } + +#preferences { + left: 0; + background: url(../images/bg-prefs.gif) repeat-x; +} + + #preferences-form { } + + #preferences-form fieldset { + margin-top: 10px; + width: 295px; + border: none; + } + + #preferences-form fieldset.odd { + float: right; + } + + #preferences-form fieldset.even { + float: left; + } + + #preferences-form .legend { + display: block; + width: 265px; + color: #fff; + padding-bottom: 3px; + border-bottom: 1px solid #80a147; + } + + /* IE7 */ + @media {#preferences-form legend { + margin-left: -7px; + }} + + #preferences-form ol { + list-style: none; + margin-top: 15px; + } + + #preferences-form li { + width: 295px; + padding-bottom: 10px; + } + + #preferences-form label { + display: block; + width: 130px; + float: left; + } + + #preferences-form input { + width: 129px; + } + + #preferences-form select { + width: 135px; + } + + .cp-pane { + position: absolute; + width: 590px; + display: none; + } + + #cp-pane-administration { + display: block; + } + +#bans { + left: 0; + background: url(../images/bg-bans.gif) repeat-x; + line-height: 1.3; +} + + #cp #bans-list a { + color: #d9d9d9; + border-bottom: 1px solid transparent; + _border-bottom: none; + } + + #cp #bans-list a:hover { + color: #fff; + border-bottom: 1px solid #de4147; + } + + #bans-list { + padding-top: 10px; + list-style: none; + height: 280px; + overflow: auto; + } + + #bans-list li { + clear: both; + padding: 3px 5px; + + } + + #bans-list .nickname { + color: #fff; + font-size: 12px; + } + + #bans-list .unban-link { + position: absolute; + right: 20px; + + } + + #no-bans { + margin-top: 100px; + text-align: center; + font-size: 22px; + color: #383838; + } + +#about { + left: 0; + background: url(../images/bg-about.gif) repeat-x; + line-height: 1.6; +} + + #about h2 { + color: #fff; + font: Arial, sans-serif; + font-size: 14px; + font-weight: normal; + margin-bottom: 5px; + } + + #about p { + margin-bottom: 5px; + } + + + #cp-pane-about { + margin-top: 10px; + display: block; + } + + #cp-pane-contact { + margin-top: 10px; + } + + #cp-pane-about a, + #cp-pane-contact a { + color: #d9d9d9; + padding-bottom: 2px; + } + + #cp-pane-about a:hover, + #cp-pane-contact a:hover { + color: #fff; + border-bottom: 1px solid #f3982d; + } diff --git a/ext/chatbox/cp/index.php b/ext/chatbox/cp/index.php new file mode 100644 index 00000000..71663ba7 --- /dev/null +++ b/ext/chatbox/cp/index.php @@ -0,0 +1,42 @@ + + + + + + YShout: Admin CP + + + + + +
    + + +
    +
    +
    +

    YShout.Preferences

    +
    + +
    + + + Loading... +
    +
    + +
    +
    + + \ No newline at end of file diff --git a/ext/chatbox/cp/js/admincp.js b/ext/chatbox/cp/js/admincp.js new file mode 100644 index 00000000..ba4ca788 --- /dev/null +++ b/ext/chatbox/cp/js/admincp.js @@ -0,0 +1,373 @@ +Array.prototype.inArray = function (value) { + for (var i = 0; i < this.length; i++) + if (this[i] === value) + return true; + + return false; +}; + +var AdminCP = function() { + var self = this; + var args = arguments; + $(function(){ + self.init.apply(self, args); + }); +}; + +AdminCP.prototype = { + z: 5, + animSpeed: 'normal', + curSection: 'login', + curPrefPane: 'administration', + curAboutPane: 'about', + + init: function(options) { + this.initializing = true; + this.loginForm(); + this.initEvents(); + if (this.loaded()) this.afterLogin(); + else { + $('#login-password')[0].focus(); + } + + this.initializing = false; + }, + + loginForm: function() { + $('#login-loading').fadeTo(1, 0); + }, + + initEvents: function() { + var self = this; + + $('#login-form').submit(function() { self.login(); return false; }); + $('#n-prefs').click(function() { self.show('preferences'); return false; }); + $('#n-bans').click(function() { self.show('bans'); return false; }); + $('#n-about').click(function() { self.show('about'); return false; }); + }, + + afterLogin: function() { + var self = this; + + // Login and logout + $('#login-password')[0].blur(); + $('.logout').click(function() { self.logout(); return false; }); + + // Show the nav + if (this.initializing) + $('#nav ul').css('display', 'block'); + else + $('#nav ul').slideDown(); + + // Some css for betterlookingness + $('#preferences-form fieldset:odd').addClass('odd'); + $('#preferences-form fieldset:even').addClass('even'); + + $('#bans-list li:odd').addClass('odd'); + $('#bans-list li:even').addClass('even'); + + // Hide the loading thingie + $('.sn-loading').fadeTo(1, 0); + + // Events after load + this.initEventsAfter(); + + // If they want to go directly to a section + var anchor = this.getAnchor(); + + if (anchor.length > 0 && ['preferences', 'bans', 'about'].inArray(anchor)) + self.show(anchor); + else + self.show('preferences'); + }, + + initEventsAfter: function() { + var self = this; + + // Navigation + $('#sn-administration').click(function() { self.showPrefPane('administration'); return false; }); + $('#sn-display').click(function() { self.showPrefPane('display'); return false; }); + $('#sn-about').click(function() { self.showAboutPane('about'); return false; }); + $('#sn-contact').click(function() { self.showAboutPane('contact'); return false; }); + $('#sn-resetall').click(function() { self.resetPrefs(); return false; }); + $('#sn-unbanall').click(function() { self.unbanAll(); return false; }); + + // Bans + $('.unban-link').click(function() { + self.unban($(this).parent().find('.ip').html(), $(this).parent()); + return false; + }); + + // Preferences + $('#preferences-form input').keypress(function(e) { + var key = window.event ? e.keyCode : e.which; + if (key == 13 || key == 3) { + self.changePref.apply(self, [$(this).attr('rel'), this.value]); + return false; + } + }).focus(function() { + this.name = this.value; + }).blur(function() { + if (this.name != this.value) + self.changePref.apply(self, [$(this).attr('rel'), this.value]); + }); + + $('#preferences-form select').change(function() { + self.changePref.apply(self, [$(this).attr('rel'), $(this).find('option:selected').attr('rel')]); + }); + }, + + changePref: function(pref, value) { + this.loading(); + var pars = { + mode: 'setpreference', + preference: pref, + 'value': value + }; + this.ajax(function(json) { + if (!json.error) + this.done(); + else + alert(json.error); + }, pars); + }, + + resetPrefs: function() { + this.loading(); + + var pars = { + mode: 'resetpreferences' + } + + this.ajax(function(json) { + this.done(); + if (json.prefs) + for(pref in json.prefs) { + var value = json.prefs[pref]; + var el = $('#preferences-form input[@rel=' + pref + '], select[@rel=' + pref + ']')[0]; + + if (el.type == 'text') + el.value = value; + else { + if (value == true) value = 'true'; + if (value == false) value = 'false'; + + $('#preferences-form select[@rel=' + pref + ']') + .find('option') + .removeAttr('selected') + .end() + .find('option[@rel=' + value + ']') + .attr('selected', 'yeah'); + + } + } + }, pars); + + }, + + invalidPassword: function() { + // Shake the login form + $('#login-form') + .animate({ marginLeft: -145 }, 100) + .animate({ marginLeft: -155 }, 100) + .animate({ marginLeft: -145 }, 100) + .animate({ marginLeft: -155 }, 100) + .animate({ marginLeft: -150 }, 50); + + $('#login-password').val('').focus(); + }, + + login: function() { + if (this.loaded()) { + alert('Something _really_ weird has happened. Refresh and pretend nothing ever happened.'); + return; + } + + var self = this; + var pars = { + mode: 'login', + password: $('#login-password').val() + }; + + this.loginLoading(); + + this.ajax(function() { + this.ajax(function(json) { + self.loginDone(); + if (json.error) { + self.invalidPassword(); + return; + } + + $('#content').append(json.html); + self.afterLogin.apply(self); + }, pars); + }, pars); + + }, + + logout: function() { + var self = this; + var pars = { + mode: 'logout' + }; + + this.loading(); + + this.ajax(function() { + $('#login-password').val(''); + $('#nav ul').slideUp(); + self.show('login', function() { + $('#login-password')[0].focus(); + $('.section').not('#login').remove(); + self.done(); + }); + }, pars); + }, + + show: function(section, callback) { +// var sections = ['login', 'preferences', 'bans', 'about']; +// if (!sections.inArray(section)) section = 'preferences'; + + if ($.browser.msie) { + if (section == 'preferences') + $('#preferences select').css('display', 'block'); + else + $('#preferences select').css('display', 'none'); + } + + if (section == this.curSection) return; + this.curSection = section; + + $('#' + section)[0].style.zIndex = ++this.z; + if (this.initializing) + $('#' + section).css('display', 'block'); + else + $('#' + section).fadeIn(this.animSpeed, callback); + }, + + showPrefPane: function(pane) { + var self = this; + + if (pane == this.curPrefPane) return; + this.curPrefPane = pane; + $('#preferences .cp-pane').css('display', 'none'); + $('#cp-pane-' + pane).css('display', 'block').fadeIn(this.animSpeed, function() { + if (self.curPrefPane == pane) + $('#preferences .cp-pane').not('#cp-pane-' + pane).css('display', 'none'); + else + $('#cp-pane-' + pane).css('display', 'none'); + + }); + }, + + showAboutPane: function(pane) { + var self = this; + + if (pane == this.curAboutPane) return; + this.curAboutPane = pane; + $('#about .cp-pane').css('display', 'none'); + $('#cp-pane-' + pane).css('display', 'block').fadeIn(this.animSpeed, function() { + if (self.curAboutPane == pane) + $('#about .cp-pane').not('#cp-pane-' + pane).css('display', 'none'); + else + $('#cp-pane-' + pane).css('display', 'none'); + + }); + }, + + ajax: function(callback, pars, html) { + var self = this; + + $.post('ajax.php', pars, function(parse) { + // alert(parse); + if (parse) + if (html) + callback.apply(self, [parse]); + else + callback.apply(self, [self.json(parse)]); + else + callback.apply(self); + }); + }, + + json: function(parse) { + var json = eval('(' + parse + ')'); + return json; + }, + + loaded: function() { + return ($('#cp-loaded').length == 1); + }, + + loading: function() { + $('#' + this.curSection + ' .sn-loading').fadeTo(this.animSpeed, 1); + }, + + done: function() { + $('#' + this.curSection + ' .sn-loading').fadeTo(this.animSpeed, 0); + }, + + loginLoading: function() { + $('#login-password').animate({ + width: 134 + }); + + $('#login-loading').fadeTo(this.animSpeed, 1); + + }, + + loginDone: function() { + $('#login-password').animate({ + width: 157 + }); + $('#login-loading').fadeTo(this.animSpeed, 0); + }, + + getAnchor: function() { + var href = window.location.href; + if (href.indexOf('#') > -1 ) + return href.substr(href.indexOf('#') + 1).toLowerCase(); + return ''; + }, + + unban: function(ip, el) { + var self = this; + + this.loading(); + var pars = { + mode: 'unban', + 'ip': ip + }; + + this.ajax(function(json) { + if (!json.error) { + $(el).fadeOut(function() { + $(this).remove(); + $('#bans-list li:odd').removeClass('even').addClass('odd'); + $('#bans-list li:even').removeClass('odd').addClass('even'); + }, this.animSpeed); + } + self.done(); + }, pars); + }, + + unbanAll: function() { + this.loading(); + + var pars = { + mode: 'unbanall' + } + + this.ajax(function(json) { + this.done(); + $('#bans-list').fadeOut(this.animSpeed, function() { + $('#bans-list').children().remove(); + $('#bans-list').fadeIn(); + }); + }, pars); + } + +}; + +var cp = new AdminCP(); \ No newline at end of file diff --git a/ext/chatbox/css/dark.yshout.css b/ext/chatbox/css/dark.yshout.css new file mode 100644 index 00000000..41e7899c --- /dev/null +++ b/ext/chatbox/css/dark.yshout.css @@ -0,0 +1,389 @@ +/* + +YShout HTML Structure: + +
    +
    +
    + + Yurivish: + Hey! + + + Info | + Delete | + Ban + +
    + +
    + + Hello. + + + Info | + Delete | + Ban + +
    + +
    + + Yup... + + + Info | + Delete | + Ban + +
    +
    +
    + +
    +
    +
    + + + [View History|Admin CP] + +
    +
    +
    + + + +*/ + + +#yshout * { + margin: 0; + padding: 0; +} + +#yshout a { + text-decoration: none; + color: #989898; +} + +#yshout a:hover { + color: #fff; +} + +#yshout a:active { + color: #e5e5e5; +} + +/* Adjust the width here +-------------------------- */ + +#yshout { + position: relative; + overflow: hidden; + font: 11px/1.4 Arial, Helvetica, sans-serif; +} + +/* Posts +------------------------------------- */ + +#yshout #ys-posts { + position: relative; + background: #1a1a1a; +} + +#yshout .ys-post { + border-bottom: 1px solid #212121; + margin: 0 5px; + padding: 5px; + position: relative; + overflow: hidden; + text-align: left; +} + + +#yshout .ys-admin-post .ys-post-nickname { + padding-left: 11px; + background: url(../images/star-dark.gif) 0 2px no-repeat; +} + + +#yshout .ys-post-timestamp { + color: #333; +} + +#yshout .ys-post-nickname { + color: #e5e5e5; +} + +#yshout .ys-post-message { + color: #595959; +} + + +/* Banned +------------------------------------- */ + +#yshout .ys-banned-post .ys-post-nickname, +#yshout .ys-banned-post .ys-post-message, +#yshout .ys-banned-post { + color: #b3b3b3 !important; +} + +#yshout #ys-banned { + position: absolute; + z-index: 75; + height: 100%; + _height: 430px; + top: 0; + left: 0; + margin: 0 5px; + background: #1a1a1a; +} + +#yshout #ys-banned span { + position: absolute; + display: block; + height: 20px; + margin-top: -10px; + top: 50%; + padding: 0 20px; + color: #666; + text-align: center; + font-size: 13px; + z-index: 80; +} + +#yshout #ys-banned a { + color: #999; +} + +#yshout #ys-banned a:hover { + color: #666; +} + +/* Hover Controls +------------------------------------- */ + +#yshout .ys-post-actions { + display: none; + position: absolute; + top: 0; + right: 0; + padding: 5px; + font-size: 11px; + z-index: 50; + background: #1a1a1a; + color: #666; +} + +#yshout .ys-post-actions a { + color: #989898; +} + +#yshout .ys-post-actions a:hover { + color: #fff; +} + +#yshout .ys-post:hover .ys-post-actions { + display: block; +} + +#yshout .ys-post-info { + color: #595959; +} + +#yshout .ys-post-info em { + font-style: normal; + color: #1a1a1a; +} + +#yshout .ys-info-overlay { + display: none; + position: absolute; + z-index: 45; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #1a1a1a; + padding: 5px; +} + +#yshout .ys-info-inline { + display: none; + margin-top: 2px; + padding-top: 3px; + border-top: 1px solid #f2f2f2; +} + +/* Post Form +------------------------------------- */ + +#yshout #ys-post-form { + height: 40px; + line-height: 40px; + background: #262626; + text-align: left; +} + + #yshout #ys-input-nickname, + #yshout #ys-input-message { + font-size: 11px; + padding: 2px; + background: #333; + border: 1px solid #404040; + } + + #yshout #ys-post-form fieldset { + _position: absolute; + border: none; + padding: 0 10px; + _margin-top: 10px; + } + + #yshout #ys-input-nickname { + width: 105px; + margin-left: 5px; + } + + #yshout #ys-input-message { + margin-left: 5px; + width: 400px; + } + + #yshout #ys-input-submit { + font-size: 11px; + width: 64px; + margin-left: 5px; + } + + #yshout #ys-input-submit:hover { + cursor: pointer; + } + + #yshout .ys-before-focus { + color: #4d4d4d; + } + + #yshout .ys-after-focus { + color: #e5e5e5; + } + + #yshout .ys-input-invalid { + + } + + #yshout .ys-post-form-link { + margin-left: 5px; + + } + + +/* Overlays - This should go in all YShout styles +------------------------------------- */ + +#ys-overlay { + position: fixed; + _position: absolute; + z-index: 100; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: #000; + filter: alpha(opacity=60); + -moz-opacity: 0.6; + opacity: 0.6; +} + +* html body { + height: 100%; + width: 100%; +} + +#ys-closeoverlay-link, +#ys-switchoverlay-link { + display: block; + font-weight: bold; + height: 13px; + font: 11px/1 Arial, Helvetica, sans-serif; + color: #fff; + text-decoration: none; + margin-bottom: 1px; + outline: none; + float: left; +} + +#ys-switchoverlay-link { + float: right; +} + +.ys-window { + z-index: 102; + position: fixed; + _position: absolute; + top: 50%; + left: 50%; +} + + #ys-cp { + margin-top: -220px; + margin-left: -310px; + width: 620px; + } + + #ys-yshout { + margin-top: -250px; + margin-left: -255px; + width: 500px; + } + + #ys-history { + margin-top: -220px; + margin-left: -270px; + width: 540px; + } + +#yshout .ys-browser { + border: none !important; + outline: none !important; + z-index: 102; + overflow: auto; + background: transparent !important; +} + + #yshout-browser { + height: 580px; + width: 510px; + } + + #cp-browser { + height: 440px; + width: 620px; + _height: 450px; + _width: 440px; + } + + #history-browser { + height: 440px; + width: 540px; + border-top: 1px solid #545454; + border-left: 1px solid #545454; + border-bottom: 1px solid #444; + border-right: 1px solid #444; + } \ No newline at end of file diff --git a/ext/chatbox/css/overlay.css b/ext/chatbox/css/overlay.css new file mode 100644 index 00000000..a2c00179 --- /dev/null +++ b/ext/chatbox/css/overlay.css @@ -0,0 +1,93 @@ +/* Overlays - Use this stylesheet if you want to only use yLink. +------------------------------------- */ + +#ys-overlay { + position: fixed; + _position: absolute; + z-index: 100; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: #000; + filter: alpha(opacity=60); + -moz-opacity: 0.6; + opacity: 0.6; +} + +* html body { + height: 100%; + width: 100%; +} + +#ys-closeoverlay-link, +#ys-switchoverlay-link { + display: block; + font-weight: bold; + height: 13px; + font: 11px/1 Arial, Helvetica, sans-serif; + color: #fff; + text-decoration: none; + margin-bottom: 1px; + outline: none; + float: left; +} + +#ys-switchoverlay-link { + float: right; +} + +.ys-window { + z-index: 102; + position: fixed; + _position: absolute; + top: 50%; + left: 50%; +} + + #ys-cp { + margin-top: -220px; + margin-left: -310px; + width: 620px; + } + + #ys-yshout { + margin-top: -250px; + margin-left: -255px; + width: 500px; + } + + #ys-history { + margin-top: -220px; + margin-left: -270px; + width: 540px; + } + +#yshout .ys-browser { + border: none !important; + outline: none !important; + z-index: 102; + overflow: auto; + background: transparent !important; +} + + #yshout-browser { + height: 580px; + width: 510px; + } + + #cp-browser { + height: 440px; + width: 620px; + _height: 450px; + _width: 440px; + } + + #history-browser { + height: 440px; + width: 540px; + border-top: 1px solid #545454; + border-left: 1px solid #545454; + border-bottom: 1px solid #444; + border-right: 1px solid #444; + } \ No newline at end of file diff --git a/ext/chatbox/css/style.css b/ext/chatbox/css/style.css new file mode 100644 index 00000000..3ad07b80 --- /dev/null +++ b/ext/chatbox/css/style.css @@ -0,0 +1,113 @@ +* { + margin: 0; + padding: 0; +} + +body { + background: #182635 url(../images/bg.gif) fixed repeat-x; + font: 11px/1.6 Arial, Helvetica, sans-serif; + color: #92b5ce; +} + +a { + color: #d5edff; + text-decoration: none; +} + +a:hover { + color: #fff !important; + text-decoration: underline; +} + +h2 { + font-weight: normal; + color: #fff; + font-size: 14px; + margin-bottom: 5px; + margin-top:10px; +} + +p { + margin-bottom: 5px; +} + +pre { + padding: 3px; + margin-top: 5px; + margin-bottom: 10px; + background: url(../images/bg-code.png); + _background: none; + color: #b4d4eb; +} + +code { + color: #fff; +} + +pre code { + padding: 0; + color: #b4d4eb; +} + +ul { + list-style: none; +} + +li { + margin-bottom: 5px; +} + +em { + font-weight: normal; + font-style: normal; + color: #fff; +} + +#container { + width: 510px; + margin: 0 auto; +} + + #top { + width: 510px; + margin-top: 25px; + height: 20px; + border-bottom: 1px solid #567083; + font-size: 11px; + overflow: hidden; + + } + + h1 { + text-indent: -4200px; + height: 13px; + width: 120px; + background: url(../images/h-welcome.gif) no-repeat; + float: left; + } + + #nav { + color: #93b3ca; + float: right; + line-height: 1.6; + } + +#footer { + width: 510px; + margin: 20px auto 10px auto; + padding-top: 5px; + border-top: 1px solid #273e56; + color: #384858; +} + +#footer:hover { + color: #92b5ce; +} + +#footer:hover a { + color: #fff; +} + +#footer a { + color: #425d7a; +} \ No newline at end of file diff --git a/ext/chatbox/history/css/style.css b/ext/chatbox/history/css/style.css new file mode 100644 index 00000000..dc76f214 --- /dev/null +++ b/ext/chatbox/history/css/style.css @@ -0,0 +1,85 @@ +* { + margin: 0; + padding: 0; +} + +body { + background: #202020 url(../images/bg.gif) fixed repeat-x; + color: #5c5c5c; + font: 11px/1.6 Arial, Helvetica, sans-serif; +} + +#top { + height: 25px; + width: 510px; + margin: 0 auto; + margin-top: 20px; + border-bottom: 1px solid #444; + overflow: none; + line-height: 1.0; +} + + h1 { + text-indent: -4200px; + background: url(../images/h-history.gif) no-repeat; + width: 105px; + height: 17px; + margin-top: 5px; + float: left; + overflow: none; + _position: absolute; + } + + #top a, #bottom a { + color: #7d7d7d; + text-decoration: none; + } + + #top a { + line-height: 25px; + } + + #top a:hover, #bottom a:hover { + color: #fff; + border-bottom-color: #5e5e5e; + } + + + #log { + font-size: 11px; + margin-left: 10px; + border: 1px solid #767676; + border-right: none; + width: 60px; + + } + + #controls { + float: right; + } + + +#yshout { + margin: 0 auto; + margin-top: 10px; +} + +#bottom { + width:510px; + margin: 10px auto; +} + + #bottom #to-top { + margin-left: 5px; + } + +/* Inane IE Compatibility PNG fixes +------------------------------------- */ + +#yshout #ys-before-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/ys-bg-posts-top.png',sizingMethod='crop'); } +#yshout #ys-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-posts.png',sizingMethod='scale'); } +#yshout #ys-after-posts { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/ys-bg-posts-bottom.png',sizingMethod='crop'); } +#yshout #ys-banned { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-banned.png',sizingMethod='scale'); } +#yshout #ys-post-form { _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src='../example/images/bg-form.png',sizingMethod='crop'); } +#yshout .ys-post { _height: 1%; } + diff --git a/ext/chatbox/history/index.php b/ext/chatbox/history/index.php new file mode 100644 index 00000000..681dfc8c --- /dev/null +++ b/ext/chatbox/history/index.php @@ -0,0 +1,133 @@ +'; + + $admin = loggedIn(); + + if (isset($_GET['log'])) + $log = $_GET['log']; + + if (isset($_POST['log'])) + $log = $_POST['log']; + + if (!isset($log)) + $log = 1; + + $ys = ys($log); + $posts = $ys->posts(); + + if (sizeof($posts) == 0) + $html .= ' +
    + + Yurivish: + Hey, there aren\'t any posts in this log. +
    + '; + + $id = 0; + + foreach($posts as $post) { + $id++; + + $banned = $ys->banned($post['adminInfo']['ip']); + $html .= '
    ' . "\n"; + switch($prefs['timestamp']) { + case 12: + $ts = date('h:i', $post['timestamp']); + break; + case 24: + $ts = date('H:i', $post['timestamp']); + break; + case 0: + $ts = ''; + break; + } + + $html .= ' ' . "\n"; + $html .= ' ' . $post['nickname'] . '' . $prefs['nicknameSeparator'] . ' ' . "\n"; + $html .= ' ' . $post['message'] . '' . "\n"; + $html .= ' ' . "\n"; + + $html .= ' ' . "\n"; + $html .= ' Info' . ($admin ? ' | Delete | ' . ($banned ? 'Unban' : 'Ban') : '') . "\n"; + $html .= ' ' . "\n"; + + if ($admin) { + $html .= ''; + } + + $html .= '
    ' . "\n"; + } + + $html .= '' . "\n"; + + +if (isset($_POST['p'])) { + echo $html; + exit; +} + +?> + + + + + + YShout: History + + + + + + + + + +
    +

    YShout.History

    +
    + + Clear this log, or + Clear all logs. + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + + + \ No newline at end of file diff --git a/ext/chatbox/history/js/history.js b/ext/chatbox/history/js/history.js new file mode 100644 index 00000000..c301bde4 --- /dev/null +++ b/ext/chatbox/history/js/history.js @@ -0,0 +1,281 @@ +var History = function() { + var self = this; + var args = arguments; + $(function(){ + self.init.apply(self, args); + }); +}; + +History.prototype = { + animSpeed: 'normal', + noPosts: '
    \n\nYurivish:\nHey, there aren\'t any posts in this log.\n
    ', + + init: function(options) { + this.prefsInfo = options.prefsInfo; + this.log = options.log; + this.initEvents(); + $('body').ScrollToAnchors({ duration: 800 }); + }, + + + initEvents: function() { + var self = this; + + this.initLogEvents(); + + + // Select log + $('#log').change(function() { + var logIndex = $(this).find('option[@selected]').attr('rel'); + + var pars = { + p: 'yes', + log: logIndex + } + + self.ajax(function(html) { + $('#ys-posts').html(html) + $('#yshout').fadeIn(); + self.initLogEvents(); + }, pars, true, 'index.php'); + + + }); + + // Clear the log + $('#clear-log').click(function() { + var el = this; + var pars = { + reqType: 'clearlog' + }; + + self.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the admin CP to clear the log.'); + el.innerHTML = 'Clear this log'; + return; + break; + } + } + + $('#ys-posts').html(self.noPosts); + self.initLogEvents(); + el.innerHTML = 'Clear this log' + }, pars); + + this.innerHTML = 'Clearing...'; + return false; + }); + + // Clear all logs + $('#clear-logs').click(function() { + var el = this; + var pars = { + reqType: 'clearlogs' + }; + + self.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + el.innerHTML = 'Clear all logs' + self.error('You\'re not an admin. Log in through the admin CP to clear logs.'); + return; + break; + } + } + + $('#ys-posts').html(self.noPosts); + self.initLogEvents(); + el.innerHTML = 'Clear all logs' + }, pars); + + this.innerHTML = 'Clearing...'; + return false; + }); + }, + + initLogEvents: function() { + var self = this; + + $('#yshout .ys-post') + .find('.ys-info-link').toggle( + function() { self.showInfo.apply(self, [$(this).parent().parent()[0].id, this]); return false; }, + function() { self.hideInfo.apply(self, [$(this).parent().parent()[0].id, this]); return false; }) + .end() + .find('.ys-ban-link').click( + function() { self.ban.apply(self, [$(this).parent().parent()[0]]); return false; }) + .end() + .find('.ys-delete-link').click( + function() { self.del.apply(self, [$(this).parent().parent()[0]]); return false; }); + }, + + showInfo: function(id, el) { + var jEl = $('#' + id + ' .ys-post-info'); + + if (jEl.length == 0) return false; + + if (this.prefsInfo == 'overlay') + jEl.css('display', 'block').fadeIn(this.animSpeed); + else + jEl.slideDown(this.animSpeed); + + el.innerHTML ='Close Info' + return false; + }, + + hideInfo: function(id, el) { + var jEl = $('#' + id + ' .ys-post-info'); + + if (jEl.length == 0) return false; + + if (this.prefsInfo == 'overlay') + jEl.fadeOut(this.animSpeed); + else + jEl.slideUp(this.animSpeed); + + el.innerHTML = 'Info'; + return false; + }, + + ban: function(post) { + var self = this; + var link = $('#' + post.id).find('.ys-ban-link')[0]; + + switch(link.innerHTML) { + case 'Ban': + var pIP = $(post).find('.ys-h-ip').html(); + var pNickname = $(post).find('.ys-h-nickname').html(); + + var pars = { + log: self.log, + reqType: 'ban', + ip: pIP, + nickname: pNickname + }; + + this.ajax(function(json) { + if (json.error) { + switch (json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the admin CP to ban people.'); + break; + } + return; + } + + $('#yshout .ys-post[@rel="' + pars.ip + '"]') + .addClass('ys-banned-post') + .find('.ys-ban-link') + .html('Unban'); + + }, pars); + + link.innerHTML = 'Banning...'; + return false; + break; + + case 'Banning...': + return false; + break; + + case 'Unban': + var pIP = $(post).find('.ys-h-ip').html(); + var pars = { + reqType: 'unban', + ip: pIP + }; + + this.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the admin CP to unban people.'); + return; + break; + } + } + + $('#yshout .ys-post[@rel="' + pars.ip + '"]') + .removeClass('ys-banned-post') + .find('.ys-ban-link') + .html('Ban'); + + }, pars); + + link.innerHTML = 'Unbanning...'; + return false; + break; + + case 'Unbanning...': + return false; + break; + } + }, + + del: function(post) { + var self = this; + + var link = $('#' + post.id).find('.ys-delete-link')[0]; + if (link.innerHTML == 'Deleting...') return; + + var pUID = $(post).find('.ys-h-uid').html(); + + var pars = { + reqType: 'delete', + uid: pUID + }; + + self.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the admin CP to ban people.'); + return; + break; + } + } + + $(post).slideUp(self.animSpeed); + + }, pars); + + link.innerHTML = 'Deleting...'; + return false; + + }, + + json: function(parse) { + var json = eval('(' + parse + ')'); + return json; + }, + + ajax: function(callback, pars, html, page) { + pars = jQuery.extend({ + reqFor: 'history', + log: this.log + }, pars); + + var self = this; + + if (page == null) page = '../yshout.php'; + + $.post(page, pars, function(parse) { + if (parse) + if (html) + callback.apply(self, [parse]); + else + callback.apply(self, [self.json(parse)]); + else + callback.apply(self); + }); + }, + + error: function(err) { + alert(err); + } + +}; + diff --git a/ext/chatbox/include.php b/ext/chatbox/include.php new file mode 100644 index 00000000..d3b18b7e --- /dev/null +++ b/ext/chatbox/include.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/ext/chatbox/js/jquery.js b/ext/chatbox/js/jquery.js new file mode 100644 index 00000000..48a88b8f --- /dev/null +++ b/ext/chatbox/js/jquery.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
    a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

    ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
    ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
    ","
    "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
    ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
    "; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); \ No newline at end of file diff --git a/ext/chatbox/js/yshout.js b/ext/chatbox/js/yshout.js new file mode 100644 index 00000000..69f13e2e --- /dev/null +++ b/ext/chatbox/js/yshout.js @@ -0,0 +1,805 @@ +String.prototype.sReplace = function(find, replace) { + return this.split(find).join(replace); +}; + +String.prototype.repeat = function(times) { + var rep = new Array(times + 1); + return rep.join(this); +} + +var YShout = function() { + var self = this; + var args = arguments; + $(document).ready(function() { + self.init.apply(self, args); + }); +} + +var yShout; + +YShout.prototype = { + animSpeed: 300, + p: [], + + init: function(options) { + yShout = this; + var self = this; + + this.initializing = true; + + var dOptions = { + yPath: 'yshout/', + log: 1 + }; + + this.options = jQuery.extend(dOptions, options); + + this.postNum = 0; + this.floodAttempt = 0; + + // Correct for missing trailing / + if ((this.options.yPath.length > 0) && (this.options.yPath.charAt(this.options.yPath.length - 1) != '/')) + this.options.yPath += '/'; + + if (this.options.yLink) { + if (this.options.yLink.charAt(0) != '#') + this.options.yLink = '#' + this.options.yLink; + + $(this.options.yLink).click(function() { + self.openYShout.apply(self); + return false; + }); + } + + // Load YShout from a link, in-page + if (this.options.h_loadlink) { + $(this.options.h_loadlink).click(function() { + $('#yshout').css('display', 'block'); + $(this).unbind('click').click(function() { return false; }); + return false; + }); + this.load(true); + } else + this.load(); + + + }, + + load: function(hidden) { + if ($('#yshout').length == 0) return; + + if (hidden) $('#yshout').css('display', 'none'); + + + + + + this.ajax(this.initialLoad, { + reqType: 'init', + yPath: this.options.yPath, + log: this.options.log + }); + }, + + initialLoad: function(updates) { + + + + + if (updates.yError) alert('There appears to be a problem: \n' + updates.yError + '\n\nIf you haven\'t already, try chmodding everything inside the YShout directory to 777.'); + + + + var self = this; + + + + this.prefs = jQuery.extend(updates.prefs, this.options.prefs); + this.initForm(); + this.initRefresh(); + this.initLinks(); + if (this.prefs.flood) this.initFlood(); + + if (updates.nickname) + $('#ys-input-nickname') + .removeClass('ys-before-focus') + .addClass( 'ys-after-focus') + .val(updates.nickname); + + if (updates) + this.updates(updates); + + + if (!this.prefs.doTruncate) { + $('#ys-posts').css('height', $('#ys-posts').height + 'px'); + } + + if (!this.prefs.inverse) { + var postsDiv = $('#ys-posts')[0]; + postsDiv.scrollTop = postsDiv.scrollHeight; + } + + this.markEnds(); + + this.initializing = false; + }, + + initForm: function() { + this.d('In initForm'); + + var postForm = + '
    ' + + '' + + '' + + (this.prefs.showSubmit ? '' : '') + + (this.prefs.postFormLink == 'cp' ? 'Admin CP' : '') + + (this.prefs.postFormLink == 'history' ? 'View History' : '') + + '
    '; + + var postsDiv = '
    '; + + if (this.prefs.inverse) $('#yshout').html(postForm + postsDiv); + else $('#yshout').html(postsDiv + postForm); + + $('#ys-posts') + .before('
    ') + .after('
    '); + + $('#ys-post-form') + .before('
    ') + .after('
    '); + + var self = this; + + var defaults = { + 'ys-input-nickname': self.prefs.defaultNickname, + 'ys-input-message': self.prefs.defaultMessage + }; + + var keypress = function(e) { + var key = window.event ? e.keyCode : e.which; + if (key == 13 || key == 3) { + self.send.apply(self); + return false; + } + }; + + var focus = function() { + if (this.value == defaults[this.id]) + $(this).removeClass('ys-before-focus').addClass( 'ys-after-focus').val(''); + }; + + var blur = function() { + if (this.value == '') + $(this).removeClass('ys-after-focus').addClass('ys-before-focus').val(defaults[this.id]); + }; + + $('#ys-input-message').keypress(keypress).focus(focus).blur(blur); + $('#ys-input-nickname').keypress(keypress).focus(focus).blur(blur); + + $('#ys-input-submit').click(function(){ self.send.apply(self) }); + $('#ys-post-form').submit(function(){ return false }); + }, + + initRefresh: function() { + var self = this; + if (this.refreshTimer) clearInterval(this.refreshTimer) + this.refreshTimer = setInterval(function() { + self.ajax(self.updates, { reqType: 'refresh' }); + }, this.prefs.refresh); // ! 3000..? + }, + + initFlood: function() { + this.d('in initFlood'); + var self = this; + this.floodCount = 0; + this.floodControl = false; + + this.floodTimer = setInterval(function() { + self.floodCount = 0; + }, this.prefs.floodTimeout); + }, + + initLinks: function() { + if ($.browser.msie) return; + + var self = this; + + $('#ys-cp-link').click(function() { + self.openCP.apply(self); + return false; + }); + + $('#ys-history-link').click(function() { + self.openHistory.apply(self); + return false; + }); + + }, + + openCP: function() { + var self = this; + if (this.cpOpen) return; + this.cpOpen = true; + + var url = this.options.yPath + 'cp/index.php'; + + $('body').append('
    CloseView HistorySomething went horribly wrong.
    '); + + $('#ys-overlay, #ys-closeoverlay-link').click(function() { + self.reload.apply(self, [true]); + self.closeCP.apply(self); + return false; + }); + + $('#ys-switchoverlay-link').click(function() { + self.closeCP.apply(self); + self.openHistory.apply(self); + return false; + }); + + }, + + closeCP: function() { + this.cpOpen = false; + $('#ys-overlay, #ys-cp').remove(); + }, + + openHistory: function() { + var self = this; + if (this.hOpen) return; + this.hOpen = true; + var url = this.options.yPath + 'history/index.php?log='+ this.options.log; + $('body').append('
    CloseView Admin CPSomething went horribly wrong.
    '); + + $('#ys-overlay, #ys-closeoverlay-link').click(function() { + self.reload.apply(self, [true]); + self.closeHistory.apply(self); + return false; + }); + + $('#ys-switchoverlay-link').click(function() { + self.closeHistory.apply(self); + self.openCP.apply(self); + return false; + }); + + }, + + closeHistory: function() { + this.hOpen = false; + $('#ys-overlay, #ys-history').remove(); + }, + + openYShout: function() { + var self = this; + if (this.ysOpen) return; + this.ysOpen = true; + url = this.options.yPath + 'example/yshout.html'; + + $('body').append('
    CloseSomething went horribly wrong.
    '); + + $('#ys-overlay, #ys-closeoverlay-link').click(function() { + self.reload.apply(self, [true]); + self.closeYShout.apply(self); + return false; + }); + }, + + closeYShout: function() { + this.ysOpen = false; + $('#ys-overlay, #ys-yshout').remove(); + }, + + send: function() { + if (!this.validate()) return; + if (this.prefs.flood && this.floodControl) return; + + var postNickname = $('#ys-input-nickname').val(), postMessage = $('#ys-input-message').val(); + + if (postMessage == '/cp') + this.openCP(); + else if (postMessage == '/history') + this.openHistory(); + else + this.ajax(this.updates, { + reqType: 'post', + nickname: postNickname, + message: postMessage + }); + + $('#ys-input-message').val('') + + if (this.prefs.flood) this.flood(); + }, + + validate: function() { + var nickname = $('#ys-input-nickname').val(), + message = $('#ys-input-message').val(), + error = false; + + var showInvalid = function(input) { + $(input).removeClass('ys-input-valid').addClass('ys-input-invalid')[0].focus(); + error = true; + } + + var showValid = function(input) { + $(input).removeClass('ys-input-invalid').addClass('ys-input-valid'); + } + + if (nickname == '' || nickname == this.prefs.defaultNickname) + showInvalid('#ys-input-nickname'); + else + showValid('#ys-input-nickname'); + + if (message == '' || message == this.prefs.defaultMessage) + showInvalid('#ys-input-message'); + else + showValid('#ys-input-message'); + + return !error; + }, + + flood: function() { + var self = this; + this.d('in flood'); + if (this.floodCount < this.prefs.floodMessages) { + this.floodCount++; + return; + } + + this.floodAttempt++; + this.disable(); + + if (this.floodAttempt == this.prefs.autobanFlood) + this.banSelf('You have been banned for flooding the shoutbox!'); + + setTimeout(function() { + self.floodCount = 0; + self.enable.apply(self); + }, this.prefs.floodDisable); + }, + + disable: function () { + $('#ys-input-submit')[0].disabled = true; + this.floodControl = true; + }, + + enable: function () { + $('#ys-input-submit')[0].disabled = false; + this.floodControl = false; + }, + + findBySame: function(ip) { + if (!$.browser.safari) return; + + var same = []; + for (var i = 0; i < this.p.length; i++) + if (this.p[i].adminInfo.ip == ip) + same.push(this.p[i]); + + for (var i = 0; i < same.length; i++) { + $('#' + same[i].id).fadeTo(this.animSpeed, .8).fadeTo(this.animSpeed, 1); + } + }, + + updates: function(updates) { + if (!updates) return; + if (updates.prefs) this.prefs = updates.prefs; + if (updates.posts) this.posts(updates.posts); + if (updates.banned) this.banned(); + }, + + banned: function() { + var self = this; + clearInterval(this.refreshTimer); + clearInterval(this.floodTimer); + if (this.initializing) + $('#ys-post-form').css('display', 'none'); + else + $('#ys-post-form').fadeOut(this.animSpeed); + + if ($('#ys-banned').length == 0) { + $('#ys-input-message')[0].blur(); + $('#ys-posts').append('
    You\'re banned. Click here to unban yourself if you\'re an admin. If you\'re not, go log in!
    '); + + $('#ys-banned-cp-link').click(function() { + self.openCP.apply(self); + return false; + }); + + $('#ys-unban-self').click(function() { + self.ajax(function(json) { + if (!json.error) + self.unbanned(); + else if (json.error == 'admin') + alert('You can only unban yourself if you\'re an admin.'); + }, { reqType: 'unbanself' }); + return false; + }); + } + }, + + unbanned: function() { + var self = this; + $('#ys-banned').fadeOut(function() { $(this).remove(); }); + this.initRefresh(); + $('#ys-post-form').css('display', 'block').fadeIn(this.animSpeed, function(){ + self.reload(); + }); + }, + + posts: function(p) { + for (var i = 0; i < p.length; i++) { + this.post(p[i]); + } + + this.truncate(); + + if (!this.prefs.inverse) { + var postsDiv = $('#ys-posts')[0]; + postsDiv.scrollTop = postsDiv.scrollHeight; + } + }, + + post: function(post) { + var self = this; + + var pad = function(n) { return n > 9 ? n : '0' + n; }; + var date = function(ts) { return new Date(ts * 1000); }; + var time = function(ts) { + var d = date(ts); + var h = d.getHours(), m = d.getMinutes(); + + if (self.prefs.timestamp == 12) { + h = (h > 12 ? h - 12 : h); + if (h == 0) h = 12; + } + + return pad(h) + ':' + pad(m); + }; + + var dateStr = function(ts) { + var t = date(ts); + + var Y = t.getFullYear(); + var M = t.getMonth(); + var D = t.getDay(); + var d = t.getDate(); + var day = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][D]; + var mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][M]; + + return day + ' ' + mon + '. ' + d + ', ' + Y; + }; + + var self = this; + + this.postNum++; + var id = 'ys-post-' + this.postNum; + post.id = id; + + post.message = this.links(post.message); + post.message = this.smileys(post.message); + post.message = this.bbcode(post.message); + var html = + '
    ' + + (this.prefs.timestamp> 0 ? ' ' : '') + + '' + post.nickname + this.prefs.nicknameSeparator + ' ' + + '' + post.message + ' ' + + '' + + 'Info' + (post.adminInfo ? ' | Delete | ' + (post.banned ? 'Unban' : 'Ban') : '') + '' + + '
    '; + if (this.prefs.inverse) $('#ys-posts').prepend(html); + else $('#ys-posts').append(html); + + this.p.push(post); + + $('#' + id) + .find('.ys-post-nickname').click(function() { + if (post.adminInfo) + self.findBySame(post.adminInfo.ip); + }).end() + .find('.ys-info-link').toggle( + function() { self.showInfo.apply(self, [id, this]); return false; }, + function() { self.hideInfo.apply(self, [id, this]); return false; }) + .end() + .find('.ys-ban-link').click( + function() { self.ban.apply(self, [post, id]); return false; }) + .end() + .find('.ys-delete-link').click( + function() { self.del.apply(self, [post, id]); return false; }); + + }, + + showInfo: function(id, el) { + var jEl = $('#' + id + ' .ys-post-info'); + if (this.prefs.info == 'overlay') + jEl.css('display', 'block').fadeIn(this.animSpeed); + else + jEl.slideDown(this.animSpeed); + + el.innerHTML ='Close Info' + return false; + }, + + hideInfo: function(id, el) { + var jEl = $('#' + id + ' .ys-post-info'); + if (this.prefs.info == 'overlay') + jEl.fadeOut(this.animSpeed); + else + jEl.slideUp(this.animSpeed); + + el.innerHTML = 'Info'; + return false; + }, + + ban: function(post, id) { + var self = this; + + var link = $('#' + id).find('.ys-ban-link')[0]; + + switch(link.innerHTML) { + case 'Ban': + var pars = { + reqType: 'ban', + ip: post.adminInfo.ip, + nickname: post.nickname + }; + + this.ajax(function(json) { + if (json.error) { + switch (json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the Admin CP to ban people.'); + break; + } + return; + } + //alert('p: ' + this.p + ' / ' + this.p.length); + if (json.bannedSelf) + self.banned(); // ? + + else + $.each(self.p, function(i) { + if (this.adminInfo && this.adminInfo.ip == post.adminInfo.ip) + $('#' + this.id) + .addClass('ys-banned-post') + .find('.ys-ban-link').html('Unban'); + }); + + }, pars); + + link.innerHTML = 'Banning...'; + return false; + break; + + case 'Banning...': + return false; + break; + + case 'Unban': + var pars = { + reqType: 'unban', + ip: post.adminInfo.ip + }; + + this.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the Admin CP to unban people.'); + return; + break; + } + } + + $.each(self.p, function(i) { + if (this.adminInfo && this.adminInfo.ip == post.adminInfo.ip) + $('#' + this.id) + .removeClass('ys-banned-post') + .find('.ys-ban-link').html('Ban'); + }); + + }, pars); + + link.innerHTML = 'Unbanning...'; + return false; + break; + + case 'Unbanning...': + return false; + break; + } + }, + + del: function(post, id) { + var self = this; + var link = $('#' + id).find('.ys-delete-link')[0]; + + if (link.innerHTML == 'Deleting...') return; + + var pars = { + reqType: 'delete', + uid: post.uid + }; + + self.ajax(function(json) { + if (json.error) { + switch(json.error) { + case 'admin': + self.error('You\'re not an admin. Log in through the Admin CP to ban people.'); + return; + break; + } + } + self.reload(); + }, pars); + + link.innerHTML = 'Deleting...'; + return false; + + }, + + banSelf: function(reason) { + var self = this; + + this.ajax(function(json) { + if (json.error == false) + self.banned(); + }, { + reqType: 'banself', + nickname: $('#ys-input-nickname').val() + }); + }, + + bbcode: function(s) { + s = s.sReplace('[i]', ''); + s = s.sReplace('[/i]', ''); + s = s.sReplace('[I]', ''); + s = s.sReplace('[/I]', ''); + + s = s.sReplace('[b]', ''); + s = s.sReplace('[/b]', ''); + s = s.sReplace('[B]', ''); + s = s.sReplace('[/B]', ''); + + s = s.sReplace('[u]', ''); + s = s.sReplace('[/u]', ''); + s = s.sReplace('[U]', ''); + s = s.sReplace('[/U]', ''); + + return s; + }, + + smileys: function(s) { + var yp = this.options.yPath; + + var smile = function(str, smiley, image) { + return str.sReplace(smiley, ''); + }; + + s = smile(s, ':twisted:', 'twisted.gif'); + s = smile(s, ':cry:', 'cry.gif'); + s = smile(s, ':\'(', 'cry.gif'); + s = smile(s, ':shock:', 'eek.gif'); + s = smile(s, ':evil:', 'evil.gif'); + s = smile(s, ':lol:', 'lol.gif'); + s = smile(s, ':mrgreen:', 'mrgreen.gif'); + s = smile(s, ':oops:', 'redface.gif'); + s = smile(s, ':roll:', 'rolleyes.gif'); + + s = smile(s, ':?', 'confused.gif'); + s = smile(s, ':D', 'biggrin.gif'); + s = smile(s, '8)', 'cool.gif'); + s = smile(s, ':x', 'mad.gif'); + s = smile(s, ':|', 'neutral.gif'); + s = smile(s, ':P', 'razz.gif'); + s = smile(s, ':(', 'sad.gif'); + s = smile(s, ':)', 'smile.gif'); + s = smile(s, ':o', 'surprised.gif'); + s = smile(s, ';)', 'wink.gif'); + + return s; + }, + + links: function(s) { + return s.replace(/((https|http|ftp|ed2k):\/\/[\S]+)/gi, '$1'); + }, + + truncate: function(clearAll) { + var truncateTo = clearAll ? 0 : this.prefs.truncate; + var posts = $('#ys-posts .ys-post').length; + if (posts <= truncateTo) return; + //alert(this.initializing); + if (this.prefs.doTruncate || this.initializing) { + var diff = posts - truncateTo; + for (var i = 0; i < diff; i++) + this.p.shift(); + + // $('#ys-posts .ys-post:gt(' + truncateTo + ')').remove(); + + if (this.prefs.inverse) + $('#ys-posts .ys-post:gt(' + (truncateTo - 1) + ')').remove(); + else + $('#ys-posts .ys-post:lt(' + (posts - truncateTo) + ')').remove(); + } + + this.markEnds(); + }, + + markEnds: function() { + $('#ys-posts') + .find('.ys-first').removeClass('ys-first').end() + .find('.ys-last').removeClass('ys-last'); + + $('#ys-posts .ys-post:first-child').addClass('ys-first'); + $('#ys-posts .ys-post:last-child').addClass('ys-last'); + }, + + reload: function(everything) { + var self = this; + this.initializing = true; + + if (everything) { + this.ajax(function(json) { + $('#yshout').html(''); + clearInterval(this.refreshTimer); + clearInterval(this.floodTimer); + this.initialLoad(json); + }, { + reqType: 'init', + yPath: this.options.yPath, + log: this.options.log + }); + } else { + this.ajax(function(json) { this.truncate(true); this.updates(json); this.initializing = false; }, { + reqType: 'reload' + }); + } + }, + + error: function(str) { + alert(str); + }, + + json: function(parse) { + this.d('In json: ' + parse); + var json = eval('(' + parse + ')'); + if (!this.checkError(json)) return json; + }, + + checkError: function(json) { + if (!json.yError) return false; + + this.d('Error: ' + json.yError); + return true; + }, + + ajax: function(callback, pars, html) { + pars = jQuery.extend({ + reqFor: 'shout' + }, pars); + + var self = this; + + $.ajax({ + type: 'POST', + url: this.options.yPath + 'yshout.php', + dataType: html ? 'text' : 'json', + data: pars, + success: function(parse) { +var arr = [parse]; + + callback.apply(self, arr); + + } + }); + }, + + d: function(message) { + // console.log(message); + $('#debug').css('display', 'block').prepend('

    ' + message + '

    '); + return message; + } +}; diff --git a/ext/chatbox/logs/.htaccess b/ext/chatbox/logs/.htaccess new file mode 100644 index 00000000..fdb803ca --- /dev/null +++ b/ext/chatbox/logs/.htaccess @@ -0,0 +1,4 @@ + +order allow,deny +deny from all + \ No newline at end of file diff --git a/ext/chatbox/logs/log.1.txt b/ext/chatbox/logs/log.1.txt new file mode 100644 index 00000000..7b63d5b6 --- /dev/null +++ b/ext/chatbox/logs/log.1.txt @@ -0,0 +1 @@ +a:2:{s:4:"info";a:1:{s:15:"latestTimestamp";d:1365655195.8733589649200439453125;}s:5:"posts";a:1:{i:0;a:6:{s:8:"nickname";s:7:"YaoiFox";s:7:"message";s:42:"I hope enjoy this chatbox based on YShout!";s:9:"timestamp";d:1365655195.8733589649200439453125;s:5:"admin";b:0;s:3:"uid";s:32:"ee9e9a7a01909be8065571655dad044d";s:9:"adminInfo";a:1:{s:2:"ip";s:11:"84.193.78.8";}}}} \ No newline at end of file diff --git a/ext/chatbox/logs/yshout.bans.txt b/ext/chatbox/logs/yshout.bans.txt new file mode 100644 index 00000000..c856afcf --- /dev/null +++ b/ext/chatbox/logs/yshout.bans.txt @@ -0,0 +1 @@ +a:0:{} \ No newline at end of file diff --git a/ext/chatbox/logs/yshout.prefs.txt b/ext/chatbox/logs/yshout.prefs.txt new file mode 100644 index 00000000..d76446b3 --- /dev/null +++ b/ext/chatbox/logs/yshout.prefs.txt @@ -0,0 +1 @@ +a:23:{s:8:"password";s:8:"fortytwo";s:7:"refresh";i:6000;s:4:"logs";i:5;s:7:"history";i:200;s:7:"inverse";b:0;s:8:"truncate";i:15;s:10:"doTruncate";b:1;s:9:"timestamp";i:12;s:15:"defaultNickname";s:8:"Nickname";s:14:"defaultMessage";s:12:"Message Text";s:13:"defaultSubmit";s:6:"Shout!";s:10:"showSubmit";b:1;s:14:"nicknameLength";i:25;s:13:"messageLength";i:175;s:17:"nicknameSeparator";s:1:":";s:5:"flood";b:1;s:12:"floodTimeout";i:5000;s:13:"floodMessages";i:4;s:12:"floodDisable";i:8000;s:12:"autobanFlood";i:0;s:11:"censorWords";s:19:"fuck shit bitch ass";s:12:"postFormLink";s:7:"history";s:4:"info";s:6:"inline";} \ No newline at end of file diff --git a/ext/chatbox/main.php b/ext/chatbox/main.php new file mode 100644 index 00000000..2289d771 --- /dev/null +++ b/ext/chatbox/main.php @@ -0,0 +1,36 @@ + + * Link: http://www.drudexsoftware.com + * License: GPLv2 + * Description: Places an ajax chatbox at the bottom of each page + * Documentation: + * This chatbox uses YShout 5 as core. + */ +class Chatbox extends Extension { + public function onPageRequest(PageRequestEvent $event) { + global $page, $user; + + // Adds header to enable chatbox + $root = make_http(); + $yPath = "$root/ext/chatbox/"; + $page->add_html_header(" + + + + + + + "); + + // loads the chatbox at the set location + $html = "
    "; + $chatblock = new Block("Chatbox", $html, "main", 97); + $page->add_block($chatblock); + } +} +?> diff --git a/ext/chatbox/php/ajaxcall.class.php b/ext/chatbox/php/ajaxcall.class.php new file mode 100644 index 00000000..6753830d --- /dev/null +++ b/ext/chatbox/php/ajaxcall.class.php @@ -0,0 +1,279 @@ +reqType = $_POST['reqType']; + } + + function process() { + switch($this->reqType) { + case 'init': + + $this->initSession(); + $this->sendFirstUpdates(); + break; + + case 'post': + $nickname = $_POST['nickname']; + $message = $_POST['message']; + cookie('yNickname', $nickname); + $ys = ys($_SESSION['yLog']); + + if ($ys->banned(ip())) { $this->sendBanned(); break; } + if ($post = $ys->post($nickname, $message)) // To use $post somewheres later + $this->sendUpdates(); + break; + + case 'refresh': + $ys = ys($_SESSION['yLog']); + if ($ys->banned(ip())) { $this->sendBanned(); break; } + + $this->sendUpdates(); + break; + + case 'reload': + $this->reload(); + break; + + case 'ban': + $this->doBan(); + break; + + case 'unban': + $this->doUnban(); + break; + + case 'delete': + $this->doDelete(); + break; + + case 'banself': + $this->banSelf(); + break; + + case 'unbanself': + $this->unbanSelf(); + break; + + case 'clearlog': + $this->clearLog(); + break; + + case 'clearlogs': + $this->clearLogs(); + break; + } + } + + function doBan() { + $ip = $_POST['ip']; + $nickname = $_POST['nickname']; + $send = array(); + $ys = ys($_SESSION['yLog']); + + switch(true) { + case !loggedIn(): + $send['error'] = 'admin'; + break; + case $ys->banned($ip): + $send['error'] = 'already'; + break; + default: + $ys->ban($ip, $nickname); + if ($ip == ip()) + $send['bannedSelf'] = true; + $send['error'] = false; + } + + echo jsonEncode($send); + } + + function doUnban() { + $ip = $_POST['ip']; + $send = array(); + $ys = ys($_SESSION['yLog']); + + switch(true) { + case !loggedIn(): + $send['error'] = 'admin'; + break; + case !$ys->banned($ip): + $send['error'] = 'already'; + break; + default: + $ys->unban($ip); + $send['error'] = false; + } + + echo jsonEncode($send); + } + + function doDelete() { + $uid = $_POST['uid']; + $send = array(); + $ys = ys($_SESSION['yLog']); + + switch(true) { + case !loggedIn(): + $send['error'] = 'admin'; + break; + default: + $ys->delete($uid); + $send['error'] = false; + } + + echo jsonEncode($send); + } + + function banSelf() { + $ys = ys($_SESSION['yLog']); + $nickname = $_POST['nickname']; + $ys->ban(ip(), $nickname); + + $send = array(); + $send['error'] = false; + + echo jsonEncode($send); + + } + + function unbanSelf() { + if (loggedIn()) { + $ys = ys($_SESSION['yLog']); + $ys->unban(ip()); + + $send = array(); + $send['error'] = false; + } else { + $send = array(); + $send['error'] = 'admin'; + } + + echo jsonEncode($send); + } + + function reload() { + global $prefs; + $ys = ys($_SESSION['yLog']); + + $posts = $ys->latestPosts($prefs['truncate']); + $this->setSessTimestamp($posts); + $this->updates['posts'] = $posts; + echo jsonEncode($this->updates); + } + + function initSession() { + $_SESSION['yLatestTimestamp'] = 0; + $_SESSION['yYPath'] = $_POST['yPath']; + $_SESSION['yLog'] = $_POST['log']; + $loginHash = cookieGet('yLoginHash') ; + if (isset($loginHash) && $loginHash != '') { + login($loginHash); + } + } + + function sendBanned() { + $this->updates = array( + 'banned' => true + ); + + echo jsonEncode($this->updates); + } + + function sendUpdates() { + global $prefs; + $ys = ys($_SESSION['yLog']); + if (!$ys->hasPostsAfter($_SESSION['yLatestTimestamp'])) return; + + $posts = $ys->postsAfter($_SESSION['yLatestTimestamp']); + $this->setSessTimestamp($posts); + + $this->updates['posts'] = $posts; + + echo jsonEncode($this->updates); + } + + function setSessTimestamp(&$posts) { + if (!$posts) return; + + $latest = array_slice( $posts, -1, 1); + $_SESSION['yLatestTimestamp'] = $latest[0]['timestamp']; + } + + function sendFirstUpdates() { + global $prefs, $overrideNickname; + + $this->updates = array(); + + $ys = ys($_SESSION['yLog']); + + $posts = $ys->latestPosts($prefs['truncate']); + $this->setSessTimestamp($posts); + + $this->updates['posts'] = $posts; + $this->updates['prefs'] = $this->cleanPrefs($prefs); + + if ($nickname = cookieGet('yNickname')) + $this->updates['nickname'] = $nickname; + + if ($overrideNickname) + $this->updates['nickname'] = $overrideNickname; + + if ($ys->banned(ip())) + $this->updates['banned'] = true; + + echo jsonEncode($this->updates); + } + + function cleanPrefs($prefs) { + unset($prefs['password']); + return $prefs; + } + + function clearLog() { + $log = $_POST['log']; + $send = array(); + $ys = ys($_SESSION['yLog']); + + switch(true) { + case !loggedIn(): + $send['error'] = 'admin'; + break; + default: + $ys->clear(); + $send['error'] = false; + } + + echo jsonEncode($send); + } + + function clearLogs() { + global $prefs; + + $log = $_POST['log']; + $send = array(); + + $ys = ys($_SESSION['yLog']); + + switch(true) { + case !loggedIn(): + $send['error'] = 'admin'; + break; + default: + for ($i = 1; $i <= $prefs['logs']; $i++) { + $ys = ys($i); + $ys->clear(); + } + + $send['error'] = false; + } + + echo jsonEncode($send); + } + } + +?> \ No newline at end of file diff --git a/ext/chatbox/php/filestorage.class.php b/ext/chatbox/php/filestorage.class.php new file mode 100644 index 00000000..ac462111 --- /dev/null +++ b/ext/chatbox/php/filestorage.class.php @@ -0,0 +1,85 @@ +shoutLog = $shoutLog; + $folder = 'logs'; + if (!is_dir($folder)) $folder = '../' . $folder; + if (!is_dir($folder)) $folder = '../' . $folder; + + $this->path = $folder . '/' . $path . '.txt'; + } + + function open($lock = false) { + $this->handle = fopen($this->path, 'a+'); + + if ($lock) { + $this->lock(); + return $this->load(); + } + } + + function close(&$array) { + if (isset($array)) + $this->save($array); + + $this->unlock(); + fclose($this->handle); + unset($this->handle); + } + + function load() { + if (($contents = $this->read($this->path)) == null) + return $this->resetArray(); + + return unserialize($contents); + } + + function save(&$array, $unlock = true) { + $contents = serialize($array); + $this->write($contents); + if ($unlock) $this->unlock(); + } + + function unlock() { + if (isset($this->handle)) + flock($this->handle, LOCK_UN); + } + + function lock() { + if (isset($this->handle)) + flock($this->handle, LOCK_EX); + } + + function read() { + fseek($this->handle, 0); + //return stream_get_contents($this->handle); + return file_get_contents($this->path); + + } + + function write($contents) { + ftruncate($this->handle, 0); + fwrite($this->handle, $contents); + } + + function resetArray() { + if ($this->shoutLog) + $default = array( + 'info' => array( + 'latestTimestamp' => -1 + ), + + 'posts' => array() + ); + else + $default = array(); + + $this->save($default, false); + return $default; + } + +} + +?> \ No newline at end of file diff --git a/ext/chatbox/php/functions.php b/ext/chatbox/php/functions.php new file mode 100644 index 00000000..edfd521f --- /dev/null +++ b/ext/chatbox/php/functions.php @@ -0,0 +1,152 @@ +encode($array); + } else + return 'ar'; + } + + function jsonDecode($encoded) { + $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + return $json->decode($encoded); + } + + function validIP($ip) { + if ($ip == long2ip(ip2long($ip))) + return true; + return false; + } + + function ts() { + // return microtime(true); + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); + + } + + function len($string) { + $i = 0; $count = 0; + $len = strlen($string); + + while ($i < $len) { + $chr = ord($string[$i]); + $count++; + $i++; + + if ($i >= $len) break; + if ($chr & 0x80) { + $chr <<= 1; + while ($chr & 0x80) { + $i++; + $chr <<= 1; + } + } + } + + return $count; + } + + function error($err) { + echo 'Error: ' . $err; + exit; + } + + function ys($log = 1) { + global $yShout, $prefs; + if ($yShout) return $yShout; + + if ($log > $prefs['logs'] || $log < 0 || !is_numeric($log)) $log = 1; + + $log = 'log.' . $log; + return new YShout($log, loggedIn()); + } + + function dstart() { + global $ts; + + $ts = ts(); + } + + function dstop() { + global $ts; + echo 'Time elapsed: ' . ((ts() - $ts) * 100000); + exit; + } + + function login($hash) { + // echo 'login: ' . $hash . "\n"; + + $_SESSION['yLoginHash'] = $hash; + cookie('yLoginHash', $hash); + // return loggedIn(); + } + + function logout() { + $_SESSION['yLoginHash'] = ''; + cookie('yLoginHash', ''); +// cookieClear('yLoginHash'); + } + + function loggedIn() { + global $prefs; + + $loginHash = cookieGet('yLoginHash', false); +// echo 'loggedin: ' . $loginHash . "\n"; +// echo 'pw: ' . $prefs['password'] . "\n"; + + if (isset($loginHash)) return $loginHash == md5($prefs['password']); + + if (isset($_SESSION['yLoginHash'])) + return $_SESSION['yLoginHash'] == md5($prefs['password']); + + return false; + + } +?> \ No newline at end of file diff --git a/ext/chatbox/php/json.class.php b/ext/chatbox/php/json.class.php new file mode 100644 index 00000000..21deb10c --- /dev/null +++ b/ext/chatbox/php/json.class.php @@ -0,0 +1,805 @@ + +* @author Matt Knapp +* @author Brett Stimmerman +* @copyright 2005 Michal Migurski +* @version CVS: $Id: JSON.php,v 1.30 2006/03/08 16:10:20 migurski Exp $ +* @license http://www.opensource.org/licenses/bsd-license.php +* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 +*/ + +/** +* Marker constant for Services_JSON::decode(), used to flag stack state +*/ +define('SERVICES_JSON_SLICE', 1); + +/** +* Marker constant for Services_JSON::decode(), used to flag stack state +*/ +define('SERVICES_JSON_IN_STR', 2); + +/** +* Marker constant for Services_JSON::decode(), used to flag stack state +*/ +define('SERVICES_JSON_IN_ARR', 3); + +/** +* Marker constant for Services_JSON::decode(), used to flag stack state +*/ +define('SERVICES_JSON_IN_OBJ', 4); + +/** +* Marker constant for Services_JSON::decode(), used to flag stack state +*/ +define('SERVICES_JSON_IN_CMT', 5); + +/** +* Behavior switch for Services_JSON::decode() +*/ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** +* Behavior switch for Services_JSON::decode() +*/ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** +* Converts to and from JSON format. +* +* Brief example of use: +* +* +* // create a new instance of Services_JSON +* $json = new Services_JSON(); +* +* // convert a complexe value to JSON notation, and send it to the browser +* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); +* $output = $json->encode($value); +* +* print($output); +* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] +* +* // accept incoming POST data, assumed to be in JSON notation +* $input = file_get_contents('php://input', 1000000); +* $value = $json->decode($input); +* +*/ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> \ No newline at end of file diff --git a/ext/chatbox/php/yshout.class.php b/ext/chatbox/php/yshout.class.php new file mode 100644 index 00000000..b02f6689 --- /dev/null +++ b/ext/chatbox/php/yshout.class.php @@ -0,0 +1,253 @@ +storage = new $storage($path, true); + $this->admin = $admin; + } + + function posts() { + global $null; + $this->storage->open(); + $s = $this->storage->load(); + $this->storage->close($null); + + if ($s) + return $s['posts']; + } + + function info() { + global $null; + $s = $this->storage->open(true); + + $this->storage->close($null); + + if ($s) + return $s['info']; + } + + function postsAfter($ts) { + $allPosts = $this->posts(); + + $posts = array(); + + /* for ($i = sizeof($allPosts) - 1; $i > -1; $i--) { + $post = $allPosts[$i]; + + if ($post['timestamp'] > $ts) + $posts[] = $post; + } */ + + foreach($allPosts as $post) { + if ($post['timestamp'] > $ts) + $posts[] = $post; + } + + $this->postProcess($posts); + return $posts; + } + + function latestPosts($num) { + $allPosts = $this->posts(); + $posts = array_slice($allPosts, -$num, $num); + + $this->postProcess($posts); + return array_values($posts); + } + + function hasPostsAfter($ts) { + $info = $this->info(); + $timestamp = $info['latestTimestamp']; + return $timestamp > $ts; + } + + function post($nickname, $message) { + global $prefs; + + if ($this->banned(ip()) /* && !$this->admin*/) return false; + + if (!$this->validate($message, $prefs['messageLength'])) return false; + if (!$this->validate($nickname, $prefs['nicknameLength'])) return false; + + $message = trim(clean($message)); + $nickname = trim(clean($nickname)); + + if ($message == '') return false; + if ($nickname == '') return false; + + $timestamp = ts(); + + $message = $this->censor($message); + $nickname = $this->censor($nickname); + + $post = array( + 'nickname' => $nickname, + 'message' => $message, + 'timestamp' => $timestamp, + 'admin' => $this->admin, + 'uid' => md5($timestamp . ' ' . $nickname), + 'adminInfo' => array( + 'ip' => ip() + ) + ); + + $s = $this->storage->open(true); + + $s['posts'][] = $post; + + if (sizeof($s['posts']) > $prefs['history']) + $this->truncate($s['posts']); + + $s['info']['latestTimestamp'] = $post['timestamp']; + + $this->storage->close($s); + $this->postProcess($post); + return $post; + } + + function truncate(&$array) { + global $prefs; + + $array = array_slice($array, -$prefs['history']); + $array = array_values($array); + } + + function clear() { + global $null; + + $this->storage->open(true); + $this->storage->resetArray(); + // ? Scared to touch it... Misspelled though. Update: Touched! Used to be $nulls... + $this->storage->close($null); + } + + function bans() { + global $storage, $null; + + $s = new $storage('yshout.bans'); + $s->open(); + $bans = $s->load(); + $s->close($null); + + return $bans; + } + + function ban($ip, $nickname = '', $info = '') { + global $storage; + + $s = new $storage('yshout.bans'); + $bans = $s->open(true); + + $bans[] = array( + 'ip' => $ip, + 'nickname' => $nickname, + 'info' => $info, + 'timestamp' => ts() + ); + + $s->close($bans); + } + + function banned($ip) { + global $storage, $null; + + $s = new $storage('yshout.bans'); + $bans = $s->open(true); + $s->close($null); + + foreach($bans as $ban) { + if ($ban['ip'] == $ip) + return true; + } + + return false; + } + + function unban($ip) { + global $storage; + + $s = new $storage('yshout.bans'); + $bans = $s->open(true); + + foreach($bans as $key=>$value) + if ($value['ip'] == $ip) { + unset($bans[$key]); + } + + $bans = array_values($bans); + $s->close($bans); + + } + + function unbanAll() { + global $storage, $null; + + $s = new $storage('yshout.bans'); + $s->open(true); + $s->resetArray(); + $s->close($null); + } + + function delete($uid) { + global $prefs, $storage; + + + $s = $this->storage->open(true); + + $posts = $s['posts']; + + foreach($posts as $key=>$value) { + if (!isset($value['uid'])) + unset($posts['key']); + else + if($value['uid'] == $uid) + unset($posts[$key]); + } + + $s['posts'] = array_values($posts); + $this->storage->close($s); + + return true; + } + + function validate($str, $maxLen) { + return len($str) <= $maxLen; + } + + function censor($str) { + global $prefs; + + $cWords = explode(' ', $prefs['censorWords']); + $words = explode(' ', $str); + $endings = '|ed|es|ing|s|er|ers'; + $arrEndings = explode('|', $endings); + + foreach ($cWords as $cWord) foreach ($words as $i=>$word) { + $pattern = '/^(' . $cWord . ')+(' . $endings . ')\W*$/i'; + $words[$i] = preg_replace($pattern, str_repeat('*', strlen($word)), $word); + } + + return implode(' ', $words); + } + + function postProcess(&$post) { + if (isset($post['message'])) { + if ($this->banned($post['adminInfo']['ip'])) $post['banned'] = true; + if (!$this->admin) unset($post['adminInfo']); + } else { + foreach($post as $key=>$value) { + if ($this->banned($value['adminInfo']['ip'])) $post[$key]['banned'] = true; + if (!$this->admin) unset($post[$key]['adminInfo']); + } + } + } + +} + + +?> \ No newline at end of file diff --git a/ext/chatbox/preferences.php b/ext/chatbox/preferences.php new file mode 100644 index 00000000..7d394c85 --- /dev/null +++ b/ext/chatbox/preferences.php @@ -0,0 +1,74 @@ +open(); + $prefs = $s->load(); + $s->close($null); + } + + function savePrefs($newPrefs) { + global $prefs, $storage; + + $s = new $storage('yshout.prefs'); + $s->open(true); + $s->close($newPrefs); + $prefs = $newPrefs; + } + + function resetPrefs() { + $defaultPrefs = array( + 'password' => 'fortytwo', // The password for the CP + + 'refresh' => 6000, // Refresh rate + + 'logs' => 5, // Amount of different log files to allow + 'history' => 200, // Shouts to keep in history + + 'inverse' => false, // Inverse shoutbox / form on top + + 'truncate' => 15, // Truncate messages client-side + 'doTruncate' => true, // Truncate messages? + + 'timestamp' => 12, // Timestamp format 12- or 24-hour + + 'defaultNickname' => 'Nickname', + 'defaultMessage' => 'Message Text', + 'defaultSubmit' => 'Shout!', + 'showSubmit' => true, + + 'nicknameLength' => 25, + 'messageLength' => 175, + + 'nicknameSeparator' => ':', + + 'flood' => true, + 'floodTimeout' => 5000, + 'floodMessages' => 4, + 'floodDisable' => 8000, + 'floodDelete' => false, + + 'autobanFlood' => 0, // Autoban people for flooding after X messages + + 'censorWords' => 'fuck shit bitch ass', + + 'postFormLink' => 'history', + + 'info' => 'inline' + ); + + savePrefs($defaultPrefs); + } + + resetPrefs(); + //loadPrefs(); + +?> diff --git a/ext/chatbox/smileys/biggrin.gif b/ext/chatbox/smileys/biggrin.gif new file mode 100644 index 0000000000000000000000000000000000000000..d3527723c6d8a0ddfa7ca0bfe1ab8fce0055918c GIT binary patch literal 172 zcmZ?wbhEHbgxLc6$23b2ZD19|4%af-@@>3F2nzSlmGt}{{R2@|NpQ5 zKLsg(0L7myj0_CC3_2h#$P5OS$PJ#HS#x(>T`Hg?&Q{P6{IT@*|fe Qt&nb$oR(_V$-rO@0AZ~_>i_@% literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/confused.gif b/ext/chatbox/smileys/confused.gif new file mode 100644 index 0000000000000000000000000000000000000000..0c49e06983f1fff4bc0834b4d86ce39b8a36a914 GIT binary patch literal 171 zcmZ?wbhEHbZXiyV-#U P9L#x)b-Z(h7#XYqI2k)k literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/cool.gif b/ext/chatbox/smileys/cool.gif new file mode 100644 index 0000000000000000000000000000000000000000..cead0306c0e38e57bdb0cc85a407b995dcbdc656 GIT binary patch literal 172 zcmZ?wbhEHb+|z`)C(1LA_rU|@;d;3=6kcSq>7bCTj>1sqIsoCSRx)8`fJ$~w+{ zJX`yX^?~=R4m2(o_)+HS{ItMiUIk0)fwE>-?dDgxjscmQ{AM&TEaGA7z8`VSO88#> SHjRW8(oK@nQq4LU7_0%kd_$xF literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/cry.gif b/ext/chatbox/smileys/cry.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d54b1f994bb20c2a17c6e9e53edb39e0444b380 GIT binary patch literal 498 zcmZ?wbhEHbivPL&TtkAL9RpmA^bD98ftnS6varfA2r%e?3<5cWfi*@! zrOPq0Pa{O{R3VSL)CLZwJI;bVj@ow|9P^f2{b_KV;g#sD#JTIhK{X4eRSZoFOA4&E zE?xGWjgRM;Ia^8kzR>%TOS23r6eRl<`~6;g|7yg@*31^DX`|+%tO&MH1Y{u#11keN z5Hhd|DyTR-@tBu#X^wBxK7*WjP8?uWTo6?pKs7*@va%&Sa#%9C1z8HDof#;_rJ&H~ zFsWx*ry9h3B%L5>ZUqL1B|!6=oIFuX25Dyj%JU{%Vn~@hA!8Mj(`_aU6Ueh0) literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/eek.gif b/ext/chatbox/smileys/eek.gif new file mode 100644 index 0000000000000000000000000000000000000000..5d3978106a2da37441ed17c9d05383b367570d46 GIT binary patch literal 170 zcmZ?wbhEHbd`4m;$l6XN^_D8SRJJ2FugV_ z;@-1W`~Ak%x7W!T7_8VEVD?C1V$(vmHm-x7t|ENQXTMbHG-$9e9m-TqP;V|s+-sI@ SDB66dp*Gz_Y_|#%gEasOPe3vN literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/evil.gif b/ext/chatbox/smileys/evil.gif new file mode 100644 index 0000000000000000000000000000000000000000..ab1aa8e123fe263608d06126ce08c560ad419f97 GIT binary patch literal 236 zcmZ?wbhEHbgxLc6$23bKgaO@B*VYC4FCTM|KGy!A1Lx~^8f$G|7kJ& z%VnN6ZQB2*4FCVX1}P;4DE?$&WMB|r&;e-z*~!3~slec{BxBNqoiq9;sWfXDwJwx! zTfpypu*1~B&zgxLc6$23b2ZD19|4%af-@@>3F2n!7!vFtG{{R2@|NpQ5 zKLsg(0LA~@ey$E)D;vwK1$ZJE z)->GK{b0hvv^Z;7;HB1zIS=v}c`BF7qB`mkh55UYFL448CX~YJS?+%vC0^DXyTAT=mC2Y$ux)_YXI57TebiI literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/mad.gif b/ext/chatbox/smileys/mad.gif new file mode 100644 index 0000000000000000000000000000000000000000..1f6c3c2fb401596ec44f4a1189bde2cbc45364aa GIT binary patch literal 174 zcmZ?wbhEHb3F2nzSlmGt}{tp7j|Nnpe z|0z%&3KV~`FfuUkGU$N#ATtF%wyNW~cLKD&UCI$nR5;ND%aoaG8jR+sH5 z;LGade6U~GQPko`gGJNAq7#;kr@G#2#;~znnVhs^*T0km#>C~4JR&=FdsEeR+|3R= Q;9xFbtmB<4#K>R`0GZ1@LjV8( literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/razz.gif b/ext/chatbox/smileys/razz.gif new file mode 100644 index 0000000000000000000000000000000000000000..29da2a2fccc79981bc54db7513ca6d2374592f9d GIT binary patch literal 176 zcmZ?wbhEHb^3h|5y0`zsdjqAOHXV zwXw1B|EEADAfWh@g^_`Qk3k2-0hz(T5_`c@GHdRQt4o8V#Kj6YnC2W7^l?<@Q2(%O z^|TpzE=L6K%vq%4Qv0AgI88 ZPHWn(3w4)`M4Rs!oY|mbDlEug4FCb$KH>lX literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/redface.gif b/ext/chatbox/smileys/redface.gif new file mode 100644 index 0000000000000000000000000000000000000000..ad7628320c3d15756c84794c8c0523f1072da640 GIT binary patch literal 650 zcmZ?wbhEHb&Qw$Gs7{2#K|NnmWcSgp)ZKdDmF#NyN{%1+%|LxoV z&6x4L*vr+`_5YJ6|9^e`f9A~pR}4QI8vg%z`Rh8vzsC$emo5AMNBH0A=KudDKaP*T z>0!pe!0`X+%wGqqfegj}+zPv3R$%UgcxPlMx(u7qxxxfZd?$&YQ;PGndxWo3ZY ziH`8-T?`B&DNk$k=JGRs=w-6FQO@h2ko&aap$ji#VOF!1O><{wyHRCMkfx2Ai?Slv zYA%q~j0}tnyg^Qv|J?fJ`y}Jo`B}5IcZU0i%+^)m>vp{!Bqu6#)+xQyYu&8=mRY(7 z)}5aCNL5gG*%{+AHF>K87ridk_T^KXg3qiu^$yZP~SpxtUz5smy literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/rolleyes.gif b/ext/chatbox/smileys/rolleyes.gif new file mode 100644 index 0000000000000000000000000000000000000000..d7f5f2f4b18f8a141c7a5dd1e09ff106a2f9fa1e GIT binary patch literal 485 zcmZ?wbhEHbgxLc6$23bKgaO@B*Xvz|NqTp_`ikW|G&xq{~!PVSNQ)^ zpx|qe3NTRo&+X?L671|4;A*62z|05~RQ$=p%EiFTpaW6>GM0fQZi6Rh*4!PT(*&f% z*$TRZ<}@?I^CYG*?eW$Pd$isBan+(fO1gn0b6dW=x%w~q}~tKE&`I~ zWZ+_80zw8BmJOU2m9Ux1yJ3o3D&v+$ZG#t4ZLVMy3?Qu_ZQMY+8CYaDcq&S-H42Fh z{c)CE%Sn@EnS+n-#i)qLlhWcv5Vs)dU<2w9+u*4hy}}}Vq4ah~j;>ScENX^v3nduQ d4TmZP8gAjeGaoDCD!-THXQ zaD}Q`!Ef~@t%4VS^H`jpHe@qRnmW&T1rN_M_11z^Th4v=m-`t@{9)jl*%hj`3F2nzSlmGt}{tp7jjg5`} z|9}1eDNqRrDE?$&WMJT9&;fBkW-zcsU+|R7ntLO3**QsZu>uaJIn9DTj_MrhAC|2y zn^DM@#l<(@zDcWq`B+oyDaGR~0+SdoFXD`_(PqAxy<2o0Q>0^(jhH~$tb+>(~Mkip?iSJVUgxLc6$23bKgaNIF2n!7!vD80{0DM@;NRr`CxMLP|FjsU zO`B$HZ2T{m`TzgdAk{j*8D$QDUE*nK$ z7w|KG>@aokb7%R&!0WI`LE+V`jZbtHGVEq^HDzd8gshBhJhWk{9N&@6Gj#R{?N7d_ u@Ss3Q_U6Qmr`{j;vSH$AZf0`Va5JoNi_%bKV4KJmrQFw{HdRBB!5RP#AxjVd literal 0 HcmV?d00001 diff --git a/ext/chatbox/smileys/wink.gif b/ext/chatbox/smileys/wink.gif new file mode 100644 index 0000000000000000000000000000000000000000..d1482880421dde677d3302940aa875ff22a11b06 GIT binary patch literal 170 zcmZ?wbhEHba9D@n#Zhbsv zutHU>;J1F0R>6zE2`tV}8xE^#?@bGklrZsUYAp!;dbG*vY{CM88Jk-qvwbqQUtQFY Pm?x1IA$CiJiNP8Goi0B( literal 0 HcmV?d00001 diff --git a/ext/chatbox/yshout.php b/ext/chatbox/yshout.php new file mode 100644 index 00000000..8b35afd5 --- /dev/null +++ b/ext/chatbox/yshout.php @@ -0,0 +1,39 @@ +process(); + break; + + case 'history': + + // echo $_POST['log']; + $ajax = new AjaxCall($_POST['log']); + $ajax->process(); + break; + + default: + exit; + } +else + include 'example.html'; + +function errorOccurred($num, $str, $file, $line) { + $err = array ( + 'yError' => "$str. \n File: $file \n Line: $line" + ); + + if (function_exists('jsonEncode')) + echo jsonEncode($err); + else + echo $err['yError']; + exit; +} + +?> \ No newline at end of file From 5c13fcac62a90eaab3a0739dffb194255deeb4ca Mon Sep 17 00:00:00 2001 From: Shish Date: Thu, 20 Jun 2013 22:59:42 +0100 Subject: [PATCH 23/78] resolve aliases on input as well, so the target shows in the url --- ext/index/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/index/main.php b/ext/index/main.php index 34298b06..972be784 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -147,7 +147,7 @@ class Index extends Extension { global $config, $database, $page, $user; if($event->page_matches("post/list")) { if(isset($_GET['search'])) { - $search = url_escape(trim($_GET['search'])); + $search = url_escape(implode(" ", Tag::resolve_list(trim($_GET['search'])))); if(empty($search)) { $page->set_mode("redirect"); $page->set_redirect(make_link("post/list/1")); From e5e12220b3585f011b242279d29672b0d2443754 Mon Sep 17 00:00:00 2001 From: Aki Jenkinson Date: Sat, 22 Jun 2013 18:30:10 +1200 Subject: [PATCH 24/78] Made reset image IDs button only appear on MySQL --- ext/admin/theme.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/admin/theme.php b/ext/admin/theme.php index b354a7db..e16b1d73 100644 --- a/ext/admin/theme.php +++ b/ext/admin/theme.php @@ -40,8 +40,9 @@ class AdminPageTheme extends Themelet { $html .= $this->button("All tags to lowercase", "lowercase_all_tags", true); $html .= $this->button("Recount tag use", "recount_tag_user", false); $html .= $this->button("Download all images", "image_dump", false); - $html .= $this->button("Download database contents", "database_dump", false); - $html .= $this->button("Reset image IDs", "reset_image_ids", true); + $html .= $this->button("Download database contents", "database_dump", false); + if($database->get_driver_name() == "mysql") + $html .= $this->button("Reset image IDs", "reset_image_ids", true); $page->add_block(new Block("Misc Admin Tools", $html)); $html = make_form(make_link("admin/set_tag_case"), "POST"); From bf0146cc417356d0630609a896121833ba314aad Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sat, 15 Jun 2013 01:25:05 +1000 Subject: [PATCH 25/78] Danbooru2 theme, initial tag categories extension, tag list update/rework --- ext/tag_categories/main.php | 118 ++++++++++++++++++++ ext/tag_categories/theme.php | 103 +++++++++++++++++ ext/tag_list/main.php | 32 +++++- ext/tag_list/theme.php | 210 ++++++++++++++++++++++++----------- themes/default/style.css | 33 ++++++ 5 files changed, 429 insertions(+), 67 deletions(-) create mode 100644 ext/tag_categories/main.php create mode 100644 ext/tag_categories/theme.php diff --git a/ext/tag_categories/main.php b/ext/tag_categories/main.php new file mode 100644 index 00000000..b5d35883 --- /dev/null +++ b/ext/tag_categories/main.php @@ -0,0 +1,118 @@ + + * Link: http://code.shishnet.org/shimmie2/ + * Description: Let tags be split into 'categories', like Danbooru's tagging + */ + +class TagCategories extends Extension { + public function onInitExt(InitExtEvent $event) { + global $config, $database; + + // whether we split out separate categories on post view by default + // note: only takes effect if /post/view shows the image's exact tags + $config->set_default_bool("tag_categories_split_on_view", true); + + $database->execute('CREATE TABLE IF NOT EXISTS image_tag_categories (category TEXT PRIMARY KEY, display_singular TEXT, display_multiple SINGULAR, color TEXT(7));'); + + $number_of_db_rows = $database->execute('SELECT COUNT(*) FROM image_tag_categories;')->fetchColumn(); + + // if empty, add our default values + if ($number_of_db_rows == 0) { + $database->execute('INSERT INTO image_tag_categories VALUES ("artist", "Artist", "Artists", "#BB6666");'); + $database->execute('INSERT INTO image_tag_categories VALUES ("series", "Series", "Series", "#AA00AA");'); + $database->execute('INSERT INTO image_tag_categories VALUES ("character", "Character", "Characters", "#66BB66");'); + } + } + + public function onPageRequest(PageRequestEvent $event) { + global $page, $database, $user; + + if($event->page_matches("tags")) { + switch($event->get_arg(0)) { + case 'categories': + if(class_exists("TagCategories") and ($user->is_admin())) { + $this->page_update(); + $this->show_tag_categories($page); + } + break; + } + } + } + + public function getDict() { + global $database; + + $tc_dict = $database->get_all('SELECT * FROM image_tag_categories;'); + + return $tc_dict; + } + + public function getKeyedDict($key_with = 'category') { + $tc_dict = $this->getDict(); + $tc_keyed_dict = array(); + + foreach ($tc_dict as $row) { + $key = $row[$key_with]; + $tc_keyed_dict[$key] = $row; + } + + return $tc_keyed_dict; + } + + public function page_update() { + global $user, $database; + + if(!$user->is_admin()) { + return false; + } + + if(!isset($_POST['tc_status']) and + !isset($_POST['tc_category']) and + !isset($_POST['tc_display_singular']) and + !isset($_POST['tc_display_multiple']) and + !isset($_POST['tc_color'])) { + return false; + } + + if($_POST['tc_status'] == 'edit') { + $is_success = $database->execute('UPDATE image_tag_categories + SET display_singular=:display_singular, + display_multiple=:display_multiple, + color=:color + WHERE category=:category', + array( + 'category' => html_escape($_POST['tc_category']), + 'display_singular' => html_escape($_POST['tc_display_singular']), + 'display_multiple' => html_escape($_POST['tc_display_multiple']), + 'color' => html_escape($_POST['tc_color']), + )); + } + else if($_POST['tc_status'] == 'new') { + $is_success = $database->execute('INSERT INTO image_tag_categories + VALUES (:category, :display_singular, :display_multiple, :color)', + array( + 'category' => html_escape($_POST['tc_category']), + 'display_singular' => html_escape($_POST['tc_display_singular']), + 'display_multiple' => html_escape($_POST['tc_display_multiple']), + 'color' => html_escape($_POST['tc_color']), + )); + } + else if($_POST['tc_status'] == 'delete') { + $is_success = $database->execute('DELETE FROM image_tag_categories + WHERE category=:category', + array( + 'category' => html_escape($_POST['tc_category']) + )); + } + + return $is_success; + } + + public function show_tag_categories($page) { + $this->theme->show_tag_categories($page, $this->getDict()); + } +} + +?> diff --git a/ext/tag_categories/theme.php b/ext/tag_categories/theme.php new file mode 100644 index 00000000..2856570c --- /dev/null +++ b/ext/tag_categories/theme.php @@ -0,0 +1,103 @@ + +
    + + + + + + + + + + + + + + + + + +
    Category + '.$tag_category.' + + +
    Name – Single + '.$tag_single_name.' + +
    Name – Multiple + '.$tag_multiple_name.' + +
    Color + '.$tag_color.' + +
    + + + + +
    + + '; + } + + // new + $tag_category = 'example'; + $tag_single_name = 'Example'; + $tag_multiple_name = 'Examples'; + $tag_color = '#EE5542'; + $html .= ' +
    +
    + + + + + + + + + + + + + + + + + +
    Category + +
    Name – Single + +
    Name – Multiple + +
    Color + +
    + +
    +
    + '; + + // add html to stuffs + $page->add_block(new Block("Editing", $html, "main", 10)); + } +} +?> \ No newline at end of file diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php index 6dd293ab..e3990e9b 100644 --- a/ext/tag_list/main.php +++ b/ext/tag_list/main.php @@ -40,7 +40,7 @@ class TagList extends Extension { break; case 'categories': $this->theme->set_heading("Popular Categories"); - $this->theme->set_tag_list($this->build_tag_categories()); + $this->theme->set_tag_list($this->build_tag_list()); break; } $this->theme->display_page($page); @@ -81,7 +81,12 @@ class TagList extends Extension { $this->add_related_block($page, $event->image); } else { - $this->add_tags_block($page, $event->image); + if(class_exists("TagCategories") and $config->get_bool('tag_categories_split_on_view')) { + $this->add_split_tags_block($page, $event->image); + } + else { + $this->add_tags_block($page, $event->image); + } } } } @@ -290,7 +295,7 @@ class TagList extends Extension { return $html; } - private function build_tag_categories() { + private function build_tag_list() { global $database; $tags_min = $this->get_tags_min(); @@ -348,6 +353,25 @@ class TagList extends Extension { } } + private function add_split_tags_block(Page $page, Image $image) { + global $database; + global $config; + + $query = " + SELECT tags.tag, tags.count as calc_count + FROM tags, image_tags + WHERE tags.id = image_tags.tag_id + AND image_tags.image_id = :image_id + ORDER BY calc_count DESC + "; + $args = array("image_id"=>$image->id); + + $tags = $database->get_all($query, $args); + if(count($tags) > 0) { + $this->theme->display_split_related_block($page, $tags); + } + } + private function add_tags_block(Page $page, Image $image) { global $database; global $config; @@ -374,7 +398,7 @@ class TagList extends Extension { $tags = $database->cache->get("popular_tags"); if(empty($tags)) { $query = " - SELECT tag, count + SELECT tag, count as calc_count FROM tags WHERE count > 0 ORDER BY count DESC diff --git a/ext/tag_list/theme.php b/ext/tag_list/theme.php index 0d175730..f68aa122 100644 --- a/ext/tag_list/theme.php +++ b/ext/tag_list/theme.php @@ -25,6 +25,64 @@ class TagListTheme extends Themelet { // ======================================================================= + /* + * $tag_infos = array( + * array('tag' => $tag, 'count' => $number_of_uses), + * ... + * ) + */ + public function display_split_related_block(Page $page, $tag_infos) { + global $config; + + if($config->get_string('tag_list_related_sort') == 'alphabetical') asort($tag_infos); + + if(class_exists('TagCategories')) { + $this->tagcategories = new TagCategories; + $tag_category_dict = $this->tagcategories->getKeyedDict(); + } + else { + $tag_category_dict = array(); + } + $tag_categories_html = array(); + $tag_categories_count = array(); + + foreach($tag_infos as $row) { + $split = self::return_tag($row, $tag_category_dict); + $category = $split[0]; + $tag_html = $split[1]; + if(!isset($tag_categories_html[$category])) { + $tag_categories_html[$category] = ''; + } + $tag_categories_html[$category] .= $tag_html . '
    '; + + if(!isset($tag_categories_count[$category])) { + $tag_categories_count[$category] = 0; + } + $tag_categories_count[$category] += 1; + } + + asort($tag_categories_html); + $main_html = $tag_categories_html[' ']; + unset($tag_categories_html[' ']); + + foreach(array_keys($tag_categories_html) as $category) { + if($tag_categories_count[$category] < 2) { + $category_display_name = $tag_category_dict[$category]['display_singular']; + } + else{ + $category_display_name = $tag_category_dict[$category]['display_multiple']; + } + $page->add_block(new Block($category_display_name, $tag_categories_html[$category], "left", 9)); + } + + if($config->get_string('tag_list_image_type')=="tags") { + $page->add_block(new Block("Tags", $main_html, "left", 10)); + } + else { + $page->add_block(new Block("Related Tags", $main_html, "left", 10)); + } + } + /* * $tag_infos = array( * array('tag' => $tag, 'count' => $number_of_uses), @@ -34,32 +92,29 @@ class TagListTheme extends Themelet { public function display_related_block(Page $page, $tag_infos) { global $config; - $html = ""; - $n = 0; if($config->get_string('tag_list_related_sort') == 'alphabetical') asort($tag_infos); - foreach($tag_infos as $row) { - $tag = $row['tag']; - $h_tag = html_escape($tag); - $h_tag_no_underscores = str_replace("_", " ", $h_tag); - $count = $row['calc_count']; - if($n++) $html .= "\n
    "; - if(!is_null($config->get_string('info_link'))) { - $link = str_replace('$tag', $tag, $config->get_string('info_link')); - $html .= " ?"; - } - $link = $this->tag_link($row['tag']); - $html .= " $h_tag_no_underscores"; - if($config->get_bool("tag_list_numbers")) { - $html .= " $count"; - } - } - - if($config->get_string('tag_list_image_type')=="tags") { - $page->add_block(new Block("Tags", $html, "left", 10)); + if(class_exists('TagCategories')) { + $this->tagcategories = new TagCategories; + $tag_category_dict = $this->tagcategories->getKeyedDict(); } else { - $page->add_block(new Block("Related Tags", $html, "left", 10)); + $tag_category_dict = array(); + } + $main_html = ''; + + foreach($tag_infos as $row) { + $split = $this->return_tag($row, $tag_category_dict); + $category = $split[0]; + $tag_html = $split[1]; + $main_html .= $tag_html . '
    '; + } + + if($config->get_string('tag_list_image_type')=="tags") { + $page->add_block(new Block("Tags", $main_html, "left", 10)); + } + else { + $page->add_block(new Block("Related Tags", $main_html, "left", 10)); } } @@ -72,34 +127,27 @@ class TagListTheme extends Themelet { */ public function display_popular_block(Page $page, $tag_infos) { global $config; - - // store local copies for speed. - $info_link = $config->get_string('info_link'); - $tag_list_num = $config->get_bool("tag_list_numbers"); - $html = ""; - $n = 0; if($config->get_string('tag_list_popular_sort') == 'alphabetical') asort($tag_infos); - + + if(class_exists('TagCategories')) { + $this->tagcategories = new TagCategories; + $tag_category_dict = $this->tagcategories->getKeyedDict(); + } + else { + $tag_category_dict = array(); + } + $main_html = ''; + foreach($tag_infos as $row) { - $tag = $row['tag']; - $h_tag = html_escape($tag); - $h_tag_no_underscores = str_replace("_", " ", $h_tag); - $count = $row['count']; - if($n++) $html .= "\n
    "; - if(!is_null($info_link)) { - $link = str_replace('$tag', $tag, $info_link); - $html .= ' ?'; - } - $link = $this->tag_link($row['tag']); - $html .= ' '.$h_tag_no_underscores.''; - if($tag_list_num) { - $html .= ' '.$count.''; - } + $split = self::return_tag($row, $tag_category_dict); + $category = $split[0]; + $tag_html = $split[1]; + $main_html .= $tag_html . '
    '; } - $html .= "
     
    Full List\n"; - $page->add_block(new Block("Popular Tags", $html, "left", 60)); + $main_html .= " 
    Full List\n"; + $page->add_block(new Block("Popular Tags", $main_html, "left", 60)); } /* @@ -112,27 +160,63 @@ class TagListTheme extends Themelet { public function display_refine_block(Page $page, $tag_infos, $search) { global $config; - // store local copy for speed. - $info_link = $config->get_string('info_link'); - - $html = ""; - $n = 0; - + if($config->get_string('tag_list_popular_sort') == 'alphabetical') asort($tag_infos); + + if(class_exists('TagCategories')) { + $this->tagcategories = new TagCategories; + $tag_category_dict = $this->tagcategories->getKeyedDict(); + } + else { + $tag_category_dict = array(); + } + $main_html = ''; + foreach($tag_infos as $row) { - $tag = $row['tag']; - $h_tag = html_escape($tag); - $h_tag_no_underscores = str_replace("_", " ", $h_tag); - if($n++) $html .= "\n
    "; - if(!is_null($info_link)) { - $link = str_replace('$tag', $tag, $info_link); - $html .= ' ?'; - } - $link = $this->tag_link($row['tag']); - $html .= ' '.$h_tag_no_underscores.''; - $html .= $this->ars($tag, $search); + $split = self::return_tag($row, $tag_category_dict); + $category = $split[0]; + $tag_html = $split[1]; + $main_html .= $tag_html . '
    '; } - $page->add_block(new Block("Refine Search", $html, "left", 60)); + $main_html .= " 
    Full List\n"; + $page->add_block(new Block("refine Search", $main_html, "left", 60)); + } + + public function return_tag($row, $tag_category_dict) { + global $config; + + $display_html = ''; + $tag = $row['tag']; + $h_tag = html_escape($tag); + + $tag_category_css = ''; + $tag_category_style = ''; + $h_tag_split = explode(':', html_escape($tag), 2); + $category = ' '; + + // we found a tag, see if it's valid! + if((count($h_tag_split) > 1) and array_key_exists($h_tag_split[0], $tag_category_dict)) { + $category = $h_tag_split[0]; + $h_tag = $h_tag_split[1]; + $tag_category_css .= ' tag_category_'.$category; + $tag_category_style .= 'style="color:'.$tag_category_dict[$category]['color'].';" '; + } + + $h_tag_no_underscores = str_replace("_", " ", $h_tag); + $count = $row['calc_count']; + // if($n++) $display_html .= "\n
    "; + if(!is_null($config->get_string('info_link'))) { + $link = str_replace('$tag', $tag, $config->get_string('info_link')); + $display_html .= ' ?'; + } + $link = $this->tag_link($row['tag']); + $display_html .= ' '.$h_tag_no_underscores.''; + + if($config->get_bool("tag_list_numbers")) { + $display_html .= " $count"; + } + + return [$category, $display_html]; } protected function ars(/*string*/ $tag, /*array(string)*/ $tags) { diff --git a/themes/default/style.css b/themes/default/style.css index 284119b8..3056deac 100644 --- a/themes/default/style.css +++ b/themes/default/style.css @@ -163,3 +163,36 @@ ARTICLE TABLE { -webkit-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* webkit haven't committed yet */ -moz-box-shadow: 2px 2px 6px rgba(0,0,0,0.6); /* mozilla haven't committed yet */ } +.tagcategoryblock { +margin:0.6rem 1rem 0.6rem 0; +padding:0.5rem 0.6rem 0.7rem; +width:18rem; +border:1px solid #AAAAAA; +border-radius:0.25rem; +display:inline-block; +} +.tagcategoryblock table { +width:100%; +border-spacing:0; +} +.tagcategoryblock input, .tagcategoryblock span { +width:100%; +height:100%; +} +.tagcategoryblock td:first-child { +padding:0.3rem 0.7rem 0.4rem 0; +text-align:right; +width:40%; +} +.tagcategoryblock td:last-child { +width:60%; +} +.tagcategoryblock td:last-child span { +padding:0.24rem 0.7rem 0.5rem 0; +display:block; +} +.tagcategoryblock button { +width:100%; +margin-top:0.4rem; +padding:0.2rem 0.6rem; +} From 2a51a9d6baab11c176b3447dc6442ed1aaa83df1 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 23 Jun 2013 10:07:12 +1000 Subject: [PATCH 26/78] Fixed things Shish pointed out --- ext/tag_categories/main.php | 47 +++++++++++++++++++++---------------- ext/tag_list/theme.php | 6 ++--- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/ext/tag_categories/main.php b/ext/tag_categories/main.php index b5d35883..4a5dcffa 100644 --- a/ext/tag_categories/main.php +++ b/ext/tag_categories/main.php @@ -9,16 +9,27 @@ class TagCategories extends Extension { public function onInitExt(InitExtEvent $event) { global $config, $database; - + // whether we split out separate categories on post view by default // note: only takes effect if /post/view shows the image's exact tags $config->set_default_bool("tag_categories_split_on_view", true); - $database->execute('CREATE TABLE IF NOT EXISTS image_tag_categories (category TEXT PRIMARY KEY, display_singular TEXT, display_multiple SINGULAR, color TEXT(7));'); + if($config->get_int("ext_tag_categories_version") < 1) { + // primary extension database, holds all our stuff! + $database->create_table('image_tag_categories', + 'category VARCHAR(60) PRIMARY KEY, + display_singular TEXT(60), + display_multiple TEXT(60), + color TEXT(7)'); - $number_of_db_rows = $database->execute('SELECT COUNT(*) FROM image_tag_categories;')->fetchColumn(); + $config->set_int("ext_tag_categories_version", 1); + + log_info("tag_categories", "extension installed"); + } // if empty, add our default values + $number_of_db_rows = $database->execute('SELECT COUNT(*) FROM image_tag_categories;')->fetchColumn(); + if ($number_of_db_rows == 0) { $database->execute('INSERT INTO image_tag_categories VALUES ("artist", "Artist", "Artists", "#BB6666");'); $database->execute('INSERT INTO image_tag_categories VALUES ("series", "Series", "Series", "#AA00AA");'); @@ -29,14 +40,10 @@ class TagCategories extends Extension { public function onPageRequest(PageRequestEvent $event) { global $page, $database, $user; - if($event->page_matches("tags")) { - switch($event->get_arg(0)) { - case 'categories': - if(class_exists("TagCategories") and ($user->is_admin())) { - $this->page_update(); - $this->show_tag_categories($page); - } - break; + if($event->page_matches("tags/categories")) { + if($user->is_admin()) { + $this->page_update(); + $this->show_tag_categories($page); } } } @@ -83,27 +90,27 @@ class TagCategories extends Extension { color=:color WHERE category=:category', array( - 'category' => html_escape($_POST['tc_category']), - 'display_singular' => html_escape($_POST['tc_display_singular']), - 'display_multiple' => html_escape($_POST['tc_display_multiple']), - 'color' => html_escape($_POST['tc_color']), + 'category' => $_POST['tc_category'], + 'display_singular' => $_POST['tc_display_singular'], + 'display_multiple' => $_POST['tc_display_multiple'], + 'color' => $_POST['tc_color'], )); } else if($_POST['tc_status'] == 'new') { $is_success = $database->execute('INSERT INTO image_tag_categories VALUES (:category, :display_singular, :display_multiple, :color)', array( - 'category' => html_escape($_POST['tc_category']), - 'display_singular' => html_escape($_POST['tc_display_singular']), - 'display_multiple' => html_escape($_POST['tc_display_multiple']), - 'color' => html_escape($_POST['tc_color']), + 'category' => $_POST['tc_category'], + 'display_singular' => $_POST['tc_display_singular'], + 'display_multiple' => $_POST['tc_display_multiple'], + 'color' => $_POST['tc_color'], )); } else if($_POST['tc_status'] == 'delete') { $is_success = $database->execute('DELETE FROM image_tag_categories WHERE category=:category', array( - 'category' => html_escape($_POST['tc_category']) + 'category' => $_POST['tc_category'] )); } diff --git a/ext/tag_list/theme.php b/ext/tag_list/theme.php index f68aa122..23df90d7 100644 --- a/ext/tag_list/theme.php +++ b/ext/tag_list/theme.php @@ -67,10 +67,10 @@ class TagListTheme extends Themelet { foreach(array_keys($tag_categories_html) as $category) { if($tag_categories_count[$category] < 2) { - $category_display_name = $tag_category_dict[$category]['display_singular']; + $category_display_name = html_escape($tag_category_dict[$category]['display_singular']); } else{ - $category_display_name = $tag_category_dict[$category]['display_multiple']; + $category_display_name = html_escape($tag_category_dict[$category]['display_multiple']); } $page->add_block(new Block($category_display_name, $tag_categories_html[$category], "left", 9)); } @@ -199,7 +199,7 @@ class TagListTheme extends Themelet { $category = $h_tag_split[0]; $h_tag = $h_tag_split[1]; $tag_category_css .= ' tag_category_'.$category; - $tag_category_style .= 'style="color:'.$tag_category_dict[$category]['color'].';" '; + $tag_category_style .= 'style="color:'.html_escape($tag_category_dict[$category]['color']).';" '; } $h_tag_no_underscores = str_replace("_", " ", $h_tag); From e6146d82ea65a541fe49229afea058da1de28a45 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jul 2013 08:05:30 +0100 Subject: [PATCH 27/78] Update theme.php --- ext/tag_list/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/tag_list/theme.php b/ext/tag_list/theme.php index 23df90d7..07737c68 100644 --- a/ext/tag_list/theme.php +++ b/ext/tag_list/theme.php @@ -216,7 +216,7 @@ class TagListTheme extends Themelet { $display_html .= " $count"; } - return [$category, $display_html]; + return ($category, $display_html); } protected function ars(/*string*/ $tag, /*array(string)*/ $tags) { From e547b1362f8441a4c01583a04156751fde93fb68 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 1 Jul 2013 09:45:32 +0100 Subject: [PATCH 28/78] Update theme.php --- ext/tag_list/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/tag_list/theme.php b/ext/tag_list/theme.php index 07737c68..87866476 100644 --- a/ext/tag_list/theme.php +++ b/ext/tag_list/theme.php @@ -216,7 +216,7 @@ class TagListTheme extends Themelet { $display_html .= " $count"; } - return ($category, $display_html); + return array($category, $display_html); } protected function ars(/*string*/ $tag, /*array(string)*/ $tags) { From cbd927ffa2a525bb5e99eace424a46dc0a463389 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Tue, 2 Jul 2013 04:31:06 +0200 Subject: [PATCH 29/78] Allow transload of image urls with querystrings And remove them from the filename and fileext --- ext/handle_pixel/main.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index cb330076..0eb2dc8f 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -9,6 +9,7 @@ class PixelFileHandler extends DataHandlerExtension { protected function supported_ext($ext) { $exts = array("jpg", "jpeg", "gif", "png"); + $ext = (($pos = strpos($ext,'?')) !== false) ? substr($ext,0,$pos) : $ext; return in_array(strtolower($ext), $exts); } @@ -25,8 +26,8 @@ class PixelFileHandler extends DataHandlerExtension { $image->filesize = $metadata['size']; $image->hash = $metadata['hash']; - $image->filename = $metadata['filename']; - $image->ext = $metadata['extension']; + $image->filename = (($pos = strpos($metadata['filename'],'?')) !== false) ? substr($metadata['filename'],0,$pos) : $metadata['filename']; + $image->ext = (($pos = strpos($metadata['extension'],'?')) !== false) ? substr($metadata['extension'],0,$pos) : $metadata['extension']; $image->tag_array = Tag::explode($metadata['tags']); $image->source = $metadata['source']; From 71117cf8fa45fd1791d87981f1840f5425fb54df Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Tue, 2 Jul 2013 04:35:18 +0200 Subject: [PATCH 30/78] Look for the source in _GET and not in _POST when uploading via _GET --- ext/upload/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index 657b9d5c..9c92cb19 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -193,7 +193,7 @@ class Upload extends Extension { } else if(!empty($_GET['url'])) { $url = $_GET['url']; - $source = isset($_POST['source']) ? $_POST['source'] : $url; + $source = isset($_GET['source']) ? $_GET['source'] : $url; $tags = array('tagme'); if(!empty($_GET['tags']) && $_GET['tags'] != "null") { $tags = Tag::explode($_GET['tags']); From 616aa3300a216e4e4d0edaba09e020742666fb34 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Tue, 2 Jul 2013 04:44:27 +0200 Subject: [PATCH 31/78] Don't check the certificate when transloading This allows the download of images via https even if the cert is self-signed. --- core/util.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/util.inc.php b/core/util.inc.php index 75e28ccd..42d285af 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -805,6 +805,7 @@ function transload($url, $mfile) { curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_REFERER, $url); curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_exec($ch); curl_close($ch); @@ -816,7 +817,7 @@ function transload($url, $mfile) { if($config->get_string("transload_engine") == "wget") { $s_url = escapeshellarg($url); $s_mfile = escapeshellarg($mfile); - system("wget $s_url --output-document=$s_mfile"); + system("wget --no-check-certificate $s_url --output-document=$s_mfile"); return file_exists($mfile); } From 65fcbcb9ba46ab247bcc32469021f55e8d1f8361 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Tue, 2 Jul 2013 06:46:54 +0200 Subject: [PATCH 32/78] Actually set the cookie prefix When COOKIE_PREFIX was not shm (the default) the script tried to read cookies prefixed with the new prefix but the script was setting the old default prefix from before the constants were implemented. --- core/util.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/util.inc.php b/core/util.inc.php index 42d285af..6dd9dbc3 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -713,7 +713,7 @@ function get_prefixed_cookie(/*string*/ $name) { */ function set_prefixed_cookie($name, $value, $time, $path) { global $config; - $full_name = $config->get_string('cookie_prefix','shm')."_".$name; + $full_name = COOKIE_PREFIX."_".$name; setcookie($full_name, $value, $time, $path); } From a87aedcef972203e3b04eca396d33defb306e0ca Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Wed, 3 Jul 2013 10:21:21 +0200 Subject: [PATCH 33/78] Checking if mod_headers is loaded before trying to set Cache-Control headers --- .htaccess | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.htaccess b/.htaccess index ba0fda88..ec345a41 100644 --- a/.htaccess +++ b/.htaccess @@ -31,7 +31,9 @@ DefaultType image/jpeg ExpiresActive On - Header set Cache-Control "public, max-age=2629743" + + Header set Cache-Control "public, max-age=2629743" + ExpiresDefault "access plus 1 month" #ExpiresByType text/html "now" From b8cf3562b009854cf0d321db958f8995e73ed722 Mon Sep 17 00:00:00 2001 From: vomitcuddle Date: Thu, 4 Jul 2013 01:30:48 +0100 Subject: [PATCH 34/78] Remove duplicate arrow key navigation extension --- ext/arrow_nav/main.php | 89 -------------------------------- ext/arrowkey_navigation/main.php | 59 +++++---------------- 2 files changed, 14 insertions(+), 134 deletions(-) delete mode 100644 ext/arrow_nav/main.php diff --git a/ext/arrow_nav/main.php b/ext/arrow_nav/main.php deleted file mode 100644 index 3ab437e2..00000000 --- a/ext/arrow_nav/main.php +++ /dev/null @@ -1,89 +0,0 @@ - - * Link: http://www.drudexsoftware.com/ - * License: GPLv2 - * Description: Allows viewers no navigate between images using the left & right arrow keys. - * Documentation: - * Simply enable this extention in the extention manager to enable arrow key navigation. - */ -class ArrowkeyNavigation extends Extension { - # Adds functionality for post/view on images - public function onDisplayingImage(DisplayingImageEvent $event) { - $prev_url = make_http(make_link("post/prev/".$event->image->id)); - $next_url = make_http(make_link("post/next/".$event->image->id)); - $this->add_arrowkeys_code($prev_url, $next_url); - } - - # Adds functionality for post/list - public function onPageRequest(PageRequestEvent $event) { - if($event->page_matches("post/list")) { - $pageinfo = $this->get_list_pageinfo($event); - $prev_url = make_http(make_link("post/list/".$pageinfo["prev"])); - $next_url = make_http(make_link("post/list/".$pageinfo["next"])); - $this->add_arrowkeys_code($prev_url, $next_url); - } - } - - # adds the javascript to the page with the given urls - private function add_arrowkeys_code($prev_url, $next_url) { - global $page; - - $page->add_html_header(""); - } - - # returns info about the current page number - private function get_list_pageinfo($event) { - global $config, $database; - - // get the amount of images per page - $images_per_page = $config->get_int('index_images'); - - // if there are no tags, use default - if ($event->get_arg(1) == null){ - $prefix = ""; - $page_number = (int)$event->get_arg(0); - $total_pages = ceil($database->get_one( - "SELECT COUNT(*) FROM images") / $images_per_page); - } - - else { // if there are tags, use pages with tags - $prefix = $event->get_arg(0)."/"; - $page_number = (int)$event->get_arg(1); - $total_pages = ceil($database->get_one( - "SELECT count FROM tags WHERE tag=:tag", - array("tag"=>$event->get_arg(0))) / $images_per_page); - } - - // creates previous & next values - // When previous first page, go to last page - if ($page_number <= 1) $prev = $total_pages; - else $prev = $page_number-1; - if ($page_number >= $total_pages) $next = 1; - else $next = $page_number+1; - - // Create return array - $pageinfo = array( - "prev" => $prefix.$prev, - "next" => $prefix.$next, - ); - - return $pageinfo; - } -} -?> diff --git a/ext/arrowkey_navigation/main.php b/ext/arrowkey_navigation/main.php index b2a12ec6..3ab437e2 100644 --- a/ext/arrowkey_navigation/main.php +++ b/ext/arrowkey_navigation/main.php @@ -8,28 +8,22 @@ * Documentation: * Simply enable this extention in the extention manager to enable arrow key navigation. */ -class arrowkey_navigation extends Extension { +class ArrowkeyNavigation extends Extension { + # Adds functionality for post/view on images + public function onDisplayingImage(DisplayingImageEvent $event) { + $prev_url = make_http(make_link("post/prev/".$event->image->id)); + $next_url = make_http(make_link("post/next/".$event->image->id)); + $this->add_arrowkeys_code($prev_url, $next_url); + } + # Adds functionality for post/list public function onPageRequest(PageRequestEvent $event) { - if ($event->page_matches("post/view")) { - $pageinfo = $this->get_view_pageinfo($event); - $prev_url = make_http(make_link("post/prev/".$pageinfo)); - $next_url = make_http(make_link("post/next/".$pageinfo)); - $this->add_arrowkeys_code($prev_url, $next_url); - } - - else if ($event->page_matches("post/list")) { + if($event->page_matches("post/list")) { $pageinfo = $this->get_list_pageinfo($event); $prev_url = make_http(make_link("post/list/".$pageinfo["prev"])); $next_url = make_http(make_link("post/list/".$pageinfo["next"])); $this->add_arrowkeys_code($prev_url, $next_url); } - - // for random_list extension - else if ($event->page_matches("random")) { - $randomurl = make_http(make_link("random")); - $this->add_arrowkeys_code($randomurl, $randomurl); - } } # adds the javascript to the page with the given urls @@ -46,8 +40,8 @@ class arrowkey_navigation extends Extension { if (e.srcElement.tagName != \"INPUT\") { - if(keycode==\"37\") window.location.href='$prev_url' + window.location.hash; - else if(keycode==\"39\") window.location.href='$next_url' + window.location.hash; + if(keycode==\"37\") window.location.href='$prev_url'; + else if(keycode==\"39\") window.location.href='$next_url'; } } "); @@ -60,16 +54,8 @@ class arrowkey_navigation extends Extension { // get the amount of images per page $images_per_page = $config->get_int('index_images'); - // this occurs when viewing post/list without page number - if ($event->get_arg(0) == null) {// no page listed - $prefix = ""; - $page_number = 1; - $total_pages = ceil($database->get_one( - "SELECT COUNT(*) FROM images") / $images_per_page); - } - // if there are no tags, use default - else if ($event->get_arg(1) == null){ + if ($event->get_arg(1) == null){ $prefix = ""; $page_number = (int)$event->get_arg(0); $total_pages = ceil($database->get_one( @@ -93,28 +79,11 @@ class arrowkey_navigation extends Extension { // Create return array $pageinfo = array( - "prev" => $prefix.$prev.$after, - "next" => $prefix.$next.$after, + "prev" => $prefix.$prev, + "next" => $prefix.$next, ); return $pageinfo; } - - # returns url ext with any tags - private function get_view_pageinfo($event) { - // if there are no tags, use default - if ($event->get_arg(1) == null){ - $prefix = ""; - $image_id = (int)$event->get_arg(0); - } - - else { // if there are tags, use pages with tags - $prefix = $event->get_arg(0)."/"; - $image_id = (int)$event->get_arg(1); - } - - // returns result - return $prefix.$image_id; - } } ?> From 47447ee9821ef32c3a7cab775124a3cc127903dc Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 5 Jul 2013 20:55:05 +0100 Subject: [PATCH 35/78] generate rss thumbs in a different function, with cache --- ext/rss_images/main.php | 58 +++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php index 3120e498..f9d4b330 100644 --- a/ext/rss_images/main.php +++ b/ext/rss_images/main.php @@ -33,7 +33,6 @@ class RSS_Images extends Extension { } } - private function do_rss($images, $search_terms, /*int*/ $page_number) { global $page; global $config; @@ -42,28 +41,7 @@ class RSS_Images extends Extension { $data = ""; foreach($images as $image) { - $link = make_http(make_link("post/view/{$image->id}")); - $tags = html_escape($image->get_tag_list()); - $owner = $image->get_owner(); - $thumb_url = $image->get_thumb_link(); - $image_url = $image->get_image_link(); - $posted = date(DATE_RSS, $image->posted_timestamp); - $content = html_escape( - "

    " . $this->theme->build_thumb_html($image) . "

    " . - "

    Uploaded by " . html_escape($owner->name) . "

    " - ); - - $data .= " - - {$image->id} - $tags - $link - $link - $posted - $content - - - - "; + $data .= $this->thumb($image); } $title = $config->get_string('title'); @@ -99,5 +77,39 @@ class RSS_Images extends Extension { "; $page->set_data($xml); } + + private function thumb(Image $image) { + global $database; + + $cached = $database->cache->get("rss-thumb:{$image->id}"); + if($cached) return $cached; + + $link = make_http(make_link("post/view/{$image->id}")); + $tags = html_escape($image->get_tag_list()); + $owner = $image->get_owner(); + $thumb_url = $image->get_thumb_link(); + $image_url = $image->get_image_link(); + $posted = date(DATE_RSS, $image->posted_timestamp); + $content = html_escape( + "

    " . $this->theme->build_thumb_html($image) . "

    " . + "

    Uploaded by " . html_escape($owner->name) . "

    " + ); + + $data = " + + {$image->id} - $tags + $link + $link + $posted + $content + + + + "; + + $database->cache->set("rss-thumb:{$image->id}", $data, 3600); + + return $data; + } } ?> From 30886b3a692a9eb1f746d7a8906e3d3dfa81b4a4 Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 5 Jul 2013 22:19:12 +0100 Subject: [PATCH 36/78] cache lookup debugging --- core/database.class.php | 3 +++ core/sys_config.inc.php | 1 + 2 files changed, 4 insertions(+) diff --git a/core/database.class.php b/core/database.class.php index 1e8da970..97f7c717 100644 --- a/core/database.class.php +++ b/core/database.class.php @@ -199,6 +199,9 @@ class MemcacheCache implements CacheEngine { public function get($key) { assert(!is_null($key)); + if((DEBUG_CACHE === true) || (is_null(DEBUG_CACHE) && @$_GET['DEBUG_CACHE'])) { + file_put_contents("data/cache.log", "Cache lookup: $key\n", FILE_APPEND); + } $val = $this->memcache->get($key); if($val !== false) { $this->hits++; diff --git a/core/sys_config.inc.php b/core/sys_config.inc.php index 6473ebc5..4190358f 100644 --- a/core/sys_config.inc.php +++ b/core/sys_config.inc.php @@ -24,6 +24,7 @@ _d("DATABASE_KA", true); // string Keep database connection alive _d("CACHE_DSN", null); // string cache connection details _d("DEBUG", false); // boolean print various debugging details _d("DEBUG_SQL", false); // boolean dump SQL queries to data/sql.log +_d("DEBUG_CACHE", false); // boolean dump cache queries to data/cache.log _d("COVERAGE", false); // boolean activate xdebug coverage monitor _d("CONTEXT", null); // string file to log performance data into _d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers From e4bfa7df705a4f2e7b837ca72068167d23dac79f Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 5 Jul 2013 22:22:39 +0100 Subject: [PATCH 37/78] further breakdown for page stats --- ext/statsd/main.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ext/statsd/main.php b/ext/statsd/main.php index d9a79679..4a9e4b81 100644 --- a/ext/statsd/main.php +++ b/ext/statsd/main.php @@ -34,13 +34,25 @@ class StatsDInterface extends Extension { public function onPageRequest($event) { $this->_stats("overall"); - if($event->page_matches("post/list")) { - $this->_stats("post-list"); - } - else if($event->page_matches("post/view")) { + if($event->page_matches("post/view")) { # 40% $this->_stats("post-view"); } + else if($event->page_matches("post/list")) { # 30% + $this->_stats("post-list"); + } + else if($event->page_matches("user")) { + $this->_stats("user"); + } + else if($event->page_matches("upload")) { + $this->_stats("upload"); + } + else if($event->page_matches("rss")) { + $this->_stats("rss"); + } else { + #global $_load_start; + #$time = microtime(true) - $_load_start; + #file_put_contents("data/other.log", "{$_SERVER['REQUEST_URI']} $time\n", FILE_APPEND); $this->_stats("other"); } From 47c1b5d0948319bb66a327741d38ae9cd69d604c Mon Sep 17 00:00:00 2001 From: Shish Date: Fri, 5 Jul 2013 22:32:01 +0100 Subject: [PATCH 38/78] move query-string-passing to JS rather than embedding in the HTML, so that the HTML can be commonised and cached better --- core/basethemelet.class.php | 4 ++-- ext/index/theme.php | 5 +++-- lib/shimmie.js | 17 +++++++++++++++++ themes/danbooru/index.theme.php | 5 +++-- themes/danbooru/themelet.class.php | 4 ++-- themes/danbooru2/index.theme.php | 5 +++-- themes/danbooru2/themelet.class.php | 4 ++-- themes/futaba/themelet.class.php | 4 ++-- themes/lite/themelet.class.php | 4 ++-- 9 files changed, 36 insertions(+), 16 deletions(-) diff --git a/core/basethemelet.class.php b/core/basethemelet.class.php index 21720222..9666d2d8 100644 --- a/core/basethemelet.class.php +++ b/core/basethemelet.class.php @@ -37,7 +37,7 @@ class BaseThemelet { * Generic thumbnail code; returns HTML rather than adding * a block since thumbs tend to go inside blocks... */ - public function build_thumb_html(Image $image, $query=null) { + public function build_thumb_html(Image $image) { global $config; $i_id = (int) $image->id; $h_view_link = make_link('post/view/'.$i_id, $query); @@ -55,7 +55,7 @@ class BaseThemelet { $tsize = get_thumbnail_size($image->width, $image->height); } - return "". + return "". "$h_tip". "". "\n"; diff --git a/ext/index/theme.php b/ext/index/theme.php index 8ef8bff5..0b7f5ee5 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -95,9 +95,10 @@ and of course start organising your images :-) } protected function build_table($images, $query) { - $table = "
    "; + $h_query = html_escape($query); + $table = "
    "; foreach($images as $image) { - $table .= $this->build_thumb_html($image, $query); + $table .= $this->build_thumb_html($image); } $table .= "
    "; return $table; diff --git a/lib/shimmie.js b/lib/shimmie.js index e7b5cc7f..e52a7700 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -86,6 +86,23 @@ $(document).ready(function() { a = document.getElementById("nextlink"); a.href = a.href + '?' + query; } + + /* + * If an image list has a data-query attribute, append + * that query string to all thumb links inside the list. + * This allows us to cache the same thumb for all query + * strings, adding the query in the browser. + */ + $(".shm-image-list").each(function(idx, elm) { + var query = $(this).data("query"); + if(query) { + if(window.console) {console.log("Found list with query: "+query);} + $(this).find(".shm-thumb-link").each(function(idx2, elm2) { + if(window.console) {console.log("Appending to url: "+$(this).attr("href"));} + $(this).attr("href", $(this).attr("href") + query); + }); + } + }); }); diff --git a/themes/danbooru/index.theme.php b/themes/danbooru/index.theme.php index 155e4a24..b27aa028 100644 --- a/themes/danbooru/index.theme.php +++ b/themes/danbooru/index.theme.php @@ -49,9 +49,10 @@ class CustomIndexTheme extends IndexTheme { } protected function build_table($images, $query) { - $table = "
    "; + $h_query = html_escape($query); + $table = "
    "; foreach($images as $image) { - $table .= "\t" . $this->build_thumb_html($image, $query) . "\n"; + $table .= "\t" . $this->build_thumb_html($image) . "\n"; } $table .= "
    "; return $table; diff --git a/themes/danbooru/themelet.class.php b/themes/danbooru/themelet.class.php index 6ef57ee8..c74dece9 100644 --- a/themes/danbooru/themelet.class.php +++ b/themes/danbooru/themelet.class.php @@ -1,6 +1,6 @@ id}", $query); $h_thumb_link = $image->get_thumb_link(); @@ -16,7 +16,7 @@ class Themelet extends BaseThemelet { $tsize = get_thumbnail_size($image->width, $image->height); } - return "$h_tip$h_tip"; } diff --git a/themes/danbooru2/index.theme.php b/themes/danbooru2/index.theme.php index 429bd0fe..4d0ca68f 100644 --- a/themes/danbooru2/index.theme.php +++ b/themes/danbooru2/index.theme.php @@ -49,9 +49,10 @@ class CustomIndexTheme extends IndexTheme { } protected function build_table($images, $query) { - $table = "
    "; + $h_query = html_escape($query); + $table = "
    "; foreach($images as $image) { - $table .= "\t" . $this->build_thumb_html($image, $query) . "\n"; + $table .= "\t" . $this->build_thumb_html($image) . "\n"; } $table .= "
    "; return $table; diff --git a/themes/danbooru2/themelet.class.php b/themes/danbooru2/themelet.class.php index 6ef57ee8..c74dece9 100644 --- a/themes/danbooru2/themelet.class.php +++ b/themes/danbooru2/themelet.class.php @@ -1,6 +1,6 @@ id}", $query); $h_thumb_link = $image->get_thumb_link(); @@ -16,7 +16,7 @@ class Themelet extends BaseThemelet { $tsize = get_thumbnail_size($image->width, $image->height); } - return "$h_tip$h_tip"; } diff --git a/themes/futaba/themelet.class.php b/themes/futaba/themelet.class.php index cfff9d50..35034e4e 100644 --- a/themes/futaba/themelet.class.php +++ b/themes/futaba/themelet.class.php @@ -4,7 +4,7 @@ class Themelet extends BaseThemelet { * Generic thumbnail code; returns HTML rather than adding * a block since thumbs tend to go inside blocks... */ - public function build_thumb_html(Image $image, $query=null) { + public function build_thumb_html(Image $image) { global $config; $h_view_link = make_link("post/view/{$image->id}", $query); $h_thumb_link = $image->get_thumb_link(); @@ -20,7 +20,7 @@ class Themelet extends BaseThemelet { $tsize = get_thumbnail_size($image->width, $image->height); } - return "$h_tip$h_tip"; } diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php index c61c9e4a..41a5ba5f 100644 --- a/themes/lite/themelet.class.php +++ b/themes/lite/themelet.class.php @@ -4,7 +4,7 @@ class Themelet extends BaseThemelet { * Generic thumbnail code; returns HTML rather than adding * a block since thumbs tend to go inside blocks... */ - public function build_thumb_html(Image $image, $query=null) { + public function build_thumb_html(Image $image) { global $config; $i_id = (int) $image->id; $h_view_link = make_link('post/view/'.$i_id, $query); @@ -22,7 +22,7 @@ class Themelet extends BaseThemelet { } return '
    \n"; From db64370815b30f5d473f43971f3662a7d5448659 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Sat, 6 Jul 2013 00:12:25 +0200 Subject: [PATCH 39/78] Fixing csv upload form The 2nd argument of make_form is the method. Also, I don't see any point in defining the $multipart variable. Just true as argument is enough. C&P mistake?! --- ext/alias_editor/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/alias_editor/theme.php b/ext/alias_editor/theme.php index 34e66d8d..b8b2f9a7 100644 --- a/ext/alias_editor/theme.php +++ b/ext/alias_editor/theme.php @@ -57,7 +57,7 @@ class AliasEditorTheme extends Themelet { "; $bulk_html = " - ".make_form(make_link("alias/import"), $multipart=True)." + ".make_form(make_link("alias/import"), 'post', true)." From 03b0c828876a7031e2768598448e195b1b7fe0f7 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Sat, 6 Jul 2013 00:33:31 +0200 Subject: [PATCH 40/78] Better CSV validation When uploading a CSV, check every entry before executing the INSERT. The checks are the same as with the normal add except that no errors are shown. --- ext/alias_editor/main.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index 4ef4e565..0173ff05 100644 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -138,7 +138,12 @@ class AliasEditor extends Extension { foreach(explode("\n", $csv) as $line) { $parts = explode(",", $line); if(count($parts) == 2) { - $database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", array("oldtag" => $parts[0], "newtag" => $parts[1])); + $pair = array("oldtag" => $parts[0], "newtag" => $parts[1]); + if(!$database->get_row("SELECT * FROM aliases WHERE oldtag=:oldtag AND lower(newtag)=lower(:newtag)", $pair)){ + if(!$database->get_row("SELECT * FROM aliases WHERE oldtag=:newtag", array("newtag" => $pair['newtag']))){ + $database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", $pair); + } + } } } } From 48e40a6712d7d5cd3ba724d59a4803988fbd5e60 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 6 Jul 2013 10:41:19 +0100 Subject: [PATCH 41/78] trim whitespace when adding aliases --- ext/alias_editor/main.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index 0173ff05..fb52ce2e 100644 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -15,8 +15,8 @@ class AddAliasEvent extends Event { var $newtag; public function AddAliasEvent($oldtag, $newtag) { - $this->oldtag = $oldtag; - $this->newtag = $newtag; + $this->oldtag = trim($oldtag); + $this->newtag = trim($newtag); } } From bc253eef04ac202133eb806abc3851a390fa05b3 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 6 Jul 2013 10:42:06 +0100 Subject: [PATCH 42/78] load list of voters in-place with ajax --- ext/numeric_score/main.php | 5 +++-- ext/numeric_score/theme.php | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index f1545b3b..ebb912c2 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -52,14 +52,15 @@ class NumericScore extends Extension { $x = $database->get_all( "SELECT users.name as username, user_id, score FROM numeric_score_votes + ORDER BY score JOIN users ON numeric_score_votes.user_id=users.id WHERE image_id=?", array($image_id)); - $html = ""; + $html = "
    "; foreach($x as $vote) { $html .= ""; } diff --git a/ext/numeric_score/theme.php b/ext/numeric_score/theme.php index fdd2fa90..baefad39 100644 --- a/ext/numeric_score/theme.php +++ b/ext/numeric_score/theme.php @@ -38,7 +38,12 @@ class NumericScoreTheme extends Themelet { -
    See All Votes +
    "; } return $html; From b9a00e4c28f4ec6c90b996cfb904d0a761498062 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 7 Jul 2013 03:24:21 +0100 Subject: [PATCH 43/78] this doesn't work --- ext/numeric_score/main.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index ebb912c2..0c1522d4 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -52,7 +52,6 @@ class NumericScore extends Extension { $x = $database->get_all( "SELECT users.name as username, user_id, score FROM numeric_score_votes - ORDER BY score JOIN users ON numeric_score_votes.user_id=users.id WHERE image_id=?", array($image_id)); From 9edbaf2ec77ec64ec8132db32de506a4162a2125 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Sun, 7 Jul 2013 09:08:50 +0200 Subject: [PATCH 44/78] Let the user define how much space should be free --- core/sys_config.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys_config.inc.php b/core/sys_config.inc.php index 4190358f..c70acb93 100644 --- a/core/sys_config.inc.php +++ b/core/sys_config.inc.php @@ -35,6 +35,7 @@ _d("NICE_URLS", false); // boolean force niceurl mode _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse _d("VERSION", 'trunk'); // string shimmie version _d("TIMEZONE", null); // string timezone +_d("MIN_FREE_SPACE",100*1024*1024); // int disable uploading if there's less than MIN_FREE_SPACE bytes free space _d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable _d("EXTRA_EXTS", ""); // optional extra extensions From 17efb92b4e33257c6a4e9e09dce68fb287602c75 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Sun, 7 Jul 2013 09:13:14 +0200 Subject: [PATCH 45/78] Update main.php --- ext/upload/main.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/upload/main.php b/ext/upload/main.php index 9c92cb19..2e7a073e 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -56,9 +56,7 @@ class Upload extends Extension { $this->is_full = false; } else { - // TODO: This size limit should be configureable by the admin... - // currently set to 100 MB - $this->is_full = $free_num < 100*1024*1024; + $this->is_full = $free_num < MIN_FREE_SPACE; } } From 39753fe7a94a2aab7c3e0272fc927bf8eb695424 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 7 Jul 2013 10:33:15 +0100 Subject: [PATCH 46/78] Finish the process of removing $query from thumb links. (Forgot to remove $query from the thumb links) --- core/basethemelet.class.php | 2 +- themes/danbooru/themelet.class.php | 2 +- themes/danbooru2/themelet.class.php | 2 +- themes/futaba/themelet.class.php | 2 +- themes/lite/themelet.class.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/basethemelet.class.php b/core/basethemelet.class.php index 9666d2d8..09b123a8 100644 --- a/core/basethemelet.class.php +++ b/core/basethemelet.class.php @@ -40,7 +40,7 @@ class BaseThemelet { public function build_thumb_html(Image $image) { global $config; $i_id = (int) $image->id; - $h_view_link = make_link('post/view/'.$i_id, $query); + $h_view_link = make_link('post/view/'.$i_id); $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $h_tags = strtolower($image->get_tag_list()); diff --git a/themes/danbooru/themelet.class.php b/themes/danbooru/themelet.class.php index c74dece9..db9b4116 100644 --- a/themes/danbooru/themelet.class.php +++ b/themes/danbooru/themelet.class.php @@ -2,7 +2,7 @@ class Themelet extends BaseThemelet { public function build_thumb_html(Image $image) { global $config; - $h_view_link = make_link("post/view/{$image->id}", $query); + $h_view_link = make_link("post/view/{$image->id}"); $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $i_id = int_escape($image->id); diff --git a/themes/danbooru2/themelet.class.php b/themes/danbooru2/themelet.class.php index c74dece9..db9b4116 100644 --- a/themes/danbooru2/themelet.class.php +++ b/themes/danbooru2/themelet.class.php @@ -2,7 +2,7 @@ class Themelet extends BaseThemelet { public function build_thumb_html(Image $image) { global $config; - $h_view_link = make_link("post/view/{$image->id}", $query); + $h_view_link = make_link("post/view/{$image->id}"); $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $i_id = int_escape($image->id); diff --git a/themes/futaba/themelet.class.php b/themes/futaba/themelet.class.php index 35034e4e..5fc79d3c 100644 --- a/themes/futaba/themelet.class.php +++ b/themes/futaba/themelet.class.php @@ -6,7 +6,7 @@ class Themelet extends BaseThemelet { */ public function build_thumb_html(Image $image) { global $config; - $h_view_link = make_link("post/view/{$image->id}", $query); + $h_view_link = make_link("post/view/{$image->id}"); $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $i_id = int_escape($image->id); diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php index 41a5ba5f..8788d9a2 100644 --- a/themes/lite/themelet.class.php +++ b/themes/lite/themelet.class.php @@ -7,7 +7,7 @@ class Themelet extends BaseThemelet { public function build_thumb_html(Image $image) { global $config; $i_id = (int) $image->id; - $h_view_link = make_link('post/view/'.$i_id, $query); + $h_view_link = make_link('post/view/'.$i_id); $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $h_tags = strtolower($image->get_tag_list()); From 1c5d717d27c7502303b3cf5eaf8ba95b347210c0 Mon Sep 17 00:00:00 2001 From: Shish Date: Sat, 13 Jul 2013 09:35:26 +0100 Subject: [PATCH 47/78] put untags in the database --- ext/not_a_tag/main.php | 98 +++++++++++++++++++++++++++++++++++------ ext/not_a_tag/theme.php | 59 +++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 ext/not_a_tag/theme.php diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php index d2696da3..0aa72f19 100644 --- a/ext/not_a_tag/main.php +++ b/ext/not_a_tag/main.php @@ -9,6 +9,17 @@ class NotATag extends Extension { public function get_priority() {return 30;} // before ImageUploadEvent and tag_history + public function onInitExt(InitExtEvent $event) { + global $config, $database; + if($config->get_int("ext_notatag_version") < 1) { + $database->create_table("untags", " + tag VARCHAR(128) NOT NULL PRIMARY KEY, + redirect VARCHAR(255) NOT NULL + "); + $config->set_int("ext_notatag_version", 1); + } + } + public function onImageAddition(ImageAdditionEvent $event) { $this->scan($event->image->get_tag_array()); } @@ -18,18 +29,13 @@ class NotATag extends Extension { } private function scan(/*array*/ $tags_mixed) { - global $config; - - $text = $config->get_string("not_a_tag_untags"); - if(empty($text)) return; + global $config, $database; $tags = array(); foreach($tags_mixed as $tag) $tags[] = strtolower($tag); - $pairs = explode("\n", $text); - foreach($pairs as $pair) { - $tag_url = explode(",", $pair); - if(count($tag_url) != 2) continue; + $pairs = $database->get_all("SELECT * FROM untags"); + foreach($pairs as $tag_url) { $tag = strtolower($tag_url[0]); $url = $tag_url[1]; if(in_array($tag, $tags)) { @@ -39,14 +45,78 @@ class NotATag extends Extension { } } - public function onSetupBuilding(SetupBuildingEvent $event) { - $sb = new SetupBlock("Un-Tags"); + public function onUserBlockBuilding(UserBlockBuildingEvent $event) { + global $user; + if($user->can("ban_image")) { + $event->add_link("UnTags", make_link("untag/list/1")); + } + } - $sb->add_label("List tag,url pairs"); - $sb->add_longtext_option("not_a_tag_untags"); - $sb->add_label("
    (eg. 'deleteme,/wiki/reporting-images')"); + public function onPageRequest(PageRequestEvent $event) { + global $config, $database, $page, $user; - $event->panel->add_block($sb); + if($event->page_matches("untag")) { + if($user->can("ban_image")) { + if($event->get_arg(0) == "add") { + $tag = isset($_POST["tag"]) ? $_POST["tag"] : $image->tag; + $redirect = isset($_POST['redirect']) ? $_POST['redirect'] : "DNP"; + + $database->Execute( + "INSERT INTO untags(tag, redirect) VALUES (?, ?)", + array($tag, $redirect)); + + $page->set_mode("redirect"); + $page->set_redirect($_SERVER['HTTP_REFERER']); + } + else if($event->get_arg(0) == "remove") { + if(isset($_POST['tag'])) { + $database->Execute("DELETE FROM untags WHERE tag = ?", array($_POST['tag'])); + + flash_message("Image ban removed"); + $page->set_mode("redirect"); + $page->set_redirect($_SERVER['HTTP_REFERER']); + } + } + else if($event->get_arg(0) == "list") { + $page_num = 0; + if($event->count_args() == 2) { + $page_num = int_escape($event->get_arg(1)); + } + $page_size = 100; + $page_count = ceil($database->get_one("SELECT COUNT(tag) FROM untags")/$page_size); + $this->theme->display_untags($page, $page_num, $page_count, $this->get_untags($page_num, $page_size)); + } + } + } + } + + public function get_untags($page, $size=100) { + global $database; + + // FIXME: many + $size_i = int_escape($size); + $offset_i = int_escape($page-1)*$size_i; + $where = array("(1=1)"); + $args = array(); + if(!empty($_GET['tag'])) { + $where[] = 'tag SCORE_ILIKE ?'; + $args[] = "%".$_GET['tag']."%"; + } + if(!empty($_GET['redirect'])) { + $where[] = 'redirect SCORE_ILIKE ?'; + $args[] = "%".$_GET['redirect']."%"; + } + $where = implode(" AND ", $where); + $bans = $database->get_all($database->scoreql_to_sql(" + SELECT * + FROM untags + WHERE $where + ORDER BY tag + LIMIT $size_i + OFFSET $offset_i + "), $args); + if($bans) {return $bans;} + else {return array();} } } ?> diff --git a/ext/not_a_tag/theme.php b/ext/not_a_tag/theme.php new file mode 100644 index 00000000..05a1a251 --- /dev/null +++ b/ext/not_a_tag/theme.php @@ -0,0 +1,59 @@ + + ".make_form(make_link("untag/remove"))." +
    + + + + + "; + } + $html = " +
    "; $html .= "{$vote['username']}"; - $html .= ""; + $html .= ""; $html .= $vote['score']; $html .= "
    {$ban['tag']}{$ban['redirect']} + + +
    + + + + + + + + + + + $h_bans + + ".make_form(make_link("untag/add"))." + + + + + +
    HashReasonAction
    + "; + + $prev = $page_number - 1; + $next = $page_number + 1; + + $h_prev = ($page_number <= 1) ? "Prev" : "Prev"; + $h_index = "Index"; + $h_next = ($page_number >= $page_count) ? "Next" : "Next"; + + $nav = "$h_prev | $h_index | $h_next"; + + $page->set_title("UnTags"); + $page->set_heading("UnTags"); + $page->add_block(new Block("Edit UnTags", $html)); + $page->add_block(new Block("Navigation", $nav, "left", 0)); + $this->display_paginator($page, "untag/list", null, $page_number, $page_count); + } +} +?> From e9b0553876ea246f11c2491c1fb3f6562ef52847 Mon Sep 17 00:00:00 2001 From: HungryFeline Date: Sun, 21 Jul 2013 02:10:17 +0200 Subject: [PATCH 48/78] use upload_tmp_dir as temp place, see #297 We changed a line like this some time ago in upload/main --- ext/rotate/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/rotate/main.php b/ext/rotate/main.php index 79b3199d..cde59505 100644 --- a/ext/rotate/main.php +++ b/ext/rotate/main.php @@ -179,7 +179,7 @@ class RotateImage extends Extension { $image_rotated = imagerotate($image, $deg, 0); /* Temp storage while we rotate */ - $tmp_filename = tempnam("/tmp", 'shimmie_rotate'); + $tmp_filename = tempnam(ini_get('upload_tmp_dir'), 'shimmie_rotate'); if (empty($tmp_filename)) { throw new ImageRotateException("Unable to save temporary image file."); } From 2ef76708c5d57fc3f608fca1140c0ab824b2ba7a Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 15 Jul 2013 08:25:23 +0100 Subject: [PATCH 49/78] untag table titles --- ext/not_a_tag/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/not_a_tag/theme.php b/ext/not_a_tag/theme.php index 05a1a251..993bd0dc 100644 --- a/ext/not_a_tag/theme.php +++ b/ext/not_a_tag/theme.php @@ -20,7 +20,7 @@ class NotATagTheme extends Themelet { $html = " - + From f98e0d1927dd540d42daa6ea88cf6c300a9b7614 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 15 Jul 2013 08:25:40 +0100 Subject: [PATCH 50/78] remove debugging logs from js --- lib/shimmie.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/shimmie.js b/lib/shimmie.js index e52a7700..c3b663e9 100644 --- a/lib/shimmie.js +++ b/lib/shimmie.js @@ -96,9 +96,7 @@ $(document).ready(function() { $(".shm-image-list").each(function(idx, elm) { var query = $(this).data("query"); if(query) { - if(window.console) {console.log("Found list with query: "+query);} $(this).find(".shm-thumb-link").each(function(idx2, elm2) { - if(window.console) {console.log("Appending to url: "+$(this).attr("href"));} $(this).attr("href", $(this).attr("href") + query); }); } From 9c70d1bd3f97e6972b64d2a2440d7c8a9d3a6997 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 4 Aug 2013 02:39:53 +0100 Subject: [PATCH 51/78] ban from mass delete --- ext/admin/main.php | 4 ++++ ext/admin/theme.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/ext/admin/main.php b/ext/admin/main.php index b5542c26..35c6b5ef 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -110,11 +110,15 @@ class AdminPage extends Extension { private function delete_by_query() { global $page, $user; $query = $_POST['query']; + $reason = @$_POST['reason']; assert(strlen($query) > 1); log_warning("admin", "Mass deleting: $query"); $count = 0; foreach(Image::find_images(0, 1000000, Tag::explode($query)) as $image) { + if($reason && class_exists("ImageBan")) { + send_event(new AddImageHashBanEvent($image->hash, $reason)); + } send_event(new ImageDeletionEvent($image)); $count++; } diff --git a/ext/admin/theme.php b/ext/admin/theme.php index e16b1d73..96ac08e8 100644 --- a/ext/admin/theme.php +++ b/ext/admin/theme.php @@ -54,9 +54,14 @@ class AdminPageTheme extends Themelet { public function dbq_html($terms) { $h_terms = html_escape($terms); + $h_reason = ""; + if(class_exists("ImageBan")) { + $h_reason = ""; + } $html = make_form(make_link("admin/delete_by_query"), "POST") . " + $h_reason "; From 7e894811051455d3519e9aea4f3a8f9b8fc2625e Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 4 Aug 2013 18:11:02 +0100 Subject: [PATCH 52/78] have Tag::resolve_list always take an array --- core/imageboard.pack.php | 13 ++++++++----- ext/index/main.php | 2 +- ext/mass_tagger/main.php | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index c978c732..b8127e06 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -460,6 +460,8 @@ class Image { public function set_tags($tags) { global $database; + assert(is_array($tags)); + $tags = Tag::resolve_list($tags); assert(is_array($tags)); @@ -965,11 +967,11 @@ class Tag { /** * Turn any string or array into a valid tag array */ - public static function explode($tags) { + public static function explode($tags, $tagme=true) { assert(is_string($tags) || is_array($tags)); if(is_string($tags)) { - $tags = explode(' ', $tags); + $tags = explode(' ', trim($tags)); } //else if(is_array($tags)) { // do nothing @@ -983,7 +985,7 @@ class Tag { } } - if(count($tag_array) == 0) { + if(count($tag_array) == 0 && $tagme) { $tag_array = array("tagme"); } @@ -1053,8 +1055,8 @@ class Tag { * @return Array of tags */ public static function resolve_list($tags) { - $tags = Tag::explode($tags); - reset($tags); // rewind array to the first element. + assert(is_array($tags)); + $new = array(); foreach($tags as $tag) { $new_set = explode(' ', Tag::resolve_alias($tag)); @@ -1062,6 +1064,7 @@ class Tag { $new[] = $new_one; } } + $new = array_map(array('Tag', 'sanitise'), $new); $new = array_iunique($new); // remove any duplicate tags return $new; diff --git a/ext/index/main.php b/ext/index/main.php index 972be784..0b7ae1d0 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -147,7 +147,7 @@ class Index extends Extension { global $config, $database, $page, $user; if($event->page_matches("post/list")) { if(isset($_GET['search'])) { - $search = url_escape(implode(" ", Tag::resolve_list(trim($_GET['search'])))); + $search = url_escape(Tag::implode(Tag::resolve_list(Tag::explode($_GET['search'], false)))); if(empty($search)) { $page->set_mode("redirect"); $page->set_redirect(make_link("post/list/1")); diff --git a/ext/mass_tagger/main.php b/ext/mass_tagger/main.php index 31e6f21a..03a08c50 100644 --- a/ext/mass_tagger/main.php +++ b/ext/mass_tagger/main.php @@ -42,13 +42,13 @@ class MassTagger extends Extension { $_POST['setadd'] == 'set') { foreach($images as $image) { - $image->set_tags($tag); + $image->set_tags(Tag::explode($tag)); } } else { foreach($images as $image) { - $image->set_tags($tag . " " . $image->get_tag_list()); + $image->set_tags(Tag::explode($tag . " " . $image->get_tag_list())); } } From 2b628a395fd3789243003539a08fc856a541d219 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 4 Aug 2013 18:13:50 +0100 Subject: [PATCH 53/78] resolve_list -> resolve_aliases, to better describe what it actually does --- core/imageboard.pack.php | 4 ++-- ext/index/main.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index b8127e06..2a4b6a23 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -462,7 +462,7 @@ class Image { assert(is_array($tags)); - $tags = Tag::resolve_list($tags); + $tags = Tag::resolve_aliases($tags); assert(is_array($tags)); assert(count($tags) > 0); @@ -1054,7 +1054,7 @@ class Tag { * @param $tags Array of tags * @return Array of tags */ - public static function resolve_list($tags) { + public static function resolve_aliases($tags) { assert(is_array($tags)); $new = array(); diff --git a/ext/index/main.php b/ext/index/main.php index 0b7ae1d0..39493921 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -147,7 +147,7 @@ class Index extends Extension { global $config, $database, $page, $user; if($event->page_matches("post/list")) { if(isset($_GET['search'])) { - $search = url_escape(Tag::implode(Tag::resolve_list(Tag::explode($_GET['search'], false)))); + $search = url_escape(Tag::implode(Tag::resolve_aliases(Tag::explode($_GET['search'], false)))); if(empty($search)) { $page->set_mode("redirect"); $page->set_redirect(make_link("post/list/1")); From 8b22652aa08e4c13c8ada8c651e804d5c7fe0509 Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 4 Aug 2013 18:19:23 +0100 Subject: [PATCH 54/78] resolve negative aliases --- core/imageboard.pack.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 2a4b6a23..71427512 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -1011,15 +1011,20 @@ class Tag { public static function resolve_alias($tag) { assert(is_string($tag)); + $negative = false; + if(!empty($tag) && ($tag[0] == '-')) { + $negative = true; + $tag = substr($tag, 1); + } + global $database; $newtag = $database->get_one( $database->scoreql_to_sql("SELECT newtag FROM aliases WHERE SCORE_STRNORM(oldtag)=SCORE_STRNORM(:tag)"), array("tag"=>$tag)); - if(!empty($newtag)) { - return $newtag; - } else { - return $tag; + if(empty($newtag)) { + $newtag = $tag; } + return $negative ? "-$newtag" : $newtag; } public static function resolve_wildcard($tag) { From e50d9c4dbb6715e05719991b2d5549acb2188e8a Mon Sep 17 00:00:00 2001 From: Shish Date: Sun, 4 Aug 2013 18:21:52 +0100 Subject: [PATCH 55/78] only sanitise when saving tags - when searching, '*' is important --- core/imageboard.pack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 71427512..b1950473 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -462,6 +462,7 @@ class Image { assert(is_array($tags)); + $tags = array_map(array('Tag', 'sanitise'), $tags); $tags = Tag::resolve_aliases($tags); assert(is_array($tags)); @@ -1070,7 +1071,6 @@ class Tag { } } - $new = array_map(array('Tag', 'sanitise'), $new); $new = array_iunique($new); // remove any duplicate tags return $new; } From 8bbf7e21715d4451a1f17f214f01ca30fe1eaf66 Mon Sep 17 00:00:00 2001 From: Shish Date: Mon, 5 Aug 2013 20:10:54 +0100 Subject: [PATCH 56/78] hide https variable warning --- ext/setup/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/setup/main.php b/ext/setup/main.php index 010b0807..e9fc3249 100644 --- a/ext/setup/main.php +++ b/ext/setup/main.php @@ -218,7 +218,7 @@ class Setup extends Extension { $host .= ":" . $_SERVER["SERVER_PORT"]; } } - $full = ($_SERVER["HTTPS"] ? "https://" : "http://") . $host . $_SERVER["PHP_SELF"]; + $full = (@$_SERVER["HTTPS"] ? "https://" : "http://") . $host . $_SERVER["PHP_SELF"]; $test_url = str_replace("/index.php", "/nicetest", $full); $nicescript = "
    HashReasonActionTagRedirectAction