diff --git a/README.txt b/README.txt
index 799d93bf..da0df9fd 100644
--- a/README.txt
+++ b/README.txt
@@ -74,6 +74,14 @@ new UserClass("anonymous", "base", array(
 	"create_image_report" => True,
 ));
 
+For a moderator class, being a regular user who can delete images and
+comments:
+
+new UserClass("moderator", "user", array(
+	"delete_image" => True,
+	"delete_comment" => True,
+));
+
 For a list of permissions, see core/userclass.class.php
 
 
diff --git a/core/config.class.php b/core/config.class.php
index 7e76b66c..8fc7b4d9 100644
--- a/core/config.class.php
+++ b/core/config.class.php
@@ -103,7 +103,7 @@ abstract class BaseConfig implements Config {
 		return $this->get($name, $default);
 	}
 	public function get_bool(/*string*/ $name, $default=null) {
-		return undb_bool($this->get($name, $default));
+		return bool_escape($this->get($name, $default));
 	}
 	public function get_array(/*string*/ $name, $default=array()) {
 		return explode(",", $this->get($name, ""));
diff --git a/core/database.class.php b/core/database.class.php
index ecd617fd..9b254f31 100644
--- a/core/database.class.php
+++ b/core/database.class.php
@@ -412,12 +412,34 @@ class Database {
 		}
 	}
 
-
 	/**
 	 * Create a table from pseudo-SQL
 	 */
 	public function create_table($name, $data) {
 		$this->execute($this->engine->create_table_sql($name, $data));
 	}
+	
+	/**
+	 * Returns the number of tables present in the current database.
+	 */
+	public function count_tables() {
+		if($this->engine->name === "mysql") {
+			return count(
+						$this->get_all("SHOW TABLES")
+					);
+		} else if ($this->engine->name === "pgsql") {
+			return count(
+						$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
+					);
+		} else if ($this->engine->name === "sqlite") {
+			return count(
+						$this->get_all(".tables")
+					);
+		} else {
+			// Hard to find a universal way to do this...
+			return NULL;
+		}
+	}
+	
 }
 ?>
diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php
index cd356513..4e2b8cda 100644
--- a/core/imageboard.pack.php
+++ b/core/imageboard.pack.php
@@ -56,7 +56,7 @@ class Image {
 				$this->$name = $value; // hax
 			}
 			$this->posted_timestamp = strtotime($this->posted); // pray
-			$this->locked = undb_bool($this->locked);
+			$this->locked = bool_escape($this->locked);
 
 			assert(is_numeric($this->id));
 			assert(is_numeric($this->height));
@@ -385,16 +385,66 @@ class Image {
 	/**
 	 * Get the image's mime type
 	 *
-	 * FIXME: now we handle more than just images
-	 *
 	 * @retval string
 	 */
 	public function get_mime_type() {
-		$type = strtolower($this->ext);
-		if($type === "jpg") $type = "jpeg";
-		return 'image/'.$type;
+		return __getMimeType( get_image_filename() );
 	}
 
+	/**
+	* Get MIME type for file
+	*
+	* The contents of this function are taken from the __getMimeType() function
+	* from the "Amazon S3 PHP class" which is Copyright (c) 2008, Donovan Sch�nknecht
+	* and released under the 'Simplified BSD License'.
+	*
+	* @internal Used to get mime types
+	* @param string &$file File path
+	* @return string
+	*/
+	public static function __getMimeType(&$file)
+	{
+		$type = false;
+		// Fileinfo documentation says fileinfo_open() will use the
+		// MAGIC env var for the magic file
+		if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
+		($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
+		{
+			if (($type = finfo_file($finfo, $file)) !== false)
+			{
+				// Remove the charset and grab the last content-type
+				$type = explode(' ', str_replace('; charset=', ';charset=', $type));
+				$type = array_pop($type);
+				$type = explode(';', $type);
+				$type = trim(array_shift($type));
+			}
+			finfo_close($finfo);
+
+		// If anyone is still using mime_content_type()
+		} elseif (function_exists('mime_content_type'))
+			$type = trim(mime_content_type($file));
+
+		if ($type !== false && strlen($type) > 0) return $type;
+
+		// Otherwise do it the old fashioned way
+		static $exts = array(
+			'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png',
+			'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon',
+			'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf',
+			'zip' => 'application/zip', 'gz' => 'application/x-gzip',
+			'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
+			'bz2' => 'application/x-bzip2', 'txt' => 'text/plain',
+			'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
+			'css' => 'text/css', 'js' => 'text/javascript',
+			'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
+			'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
+			'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
+			'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
+		);
+		$ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
+		return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
+	}
+	
 	/**
 	 * Get the image's filename extension
 	 *
@@ -439,7 +489,7 @@ class Image {
 		$sln = $database->engine->scoreql_to_sql('SCORE_BOOL_'.$ln);
 		$sln = str_replace("'", "", $sln);
 		$sln = str_replace('"', "", $sln);
-		if(undb_bool($sln) !== $this->locked) {
+		if(bool_escape($sln) !== $this->locked) {
 			$database->execute("UPDATE images SET locked=:yn WHERE id=:id", array("yn"=>$sln, "id"=>$this->id));
 			log_info("core-image", "Setting Image #{$this->id} lock to: $ln");
 		}
diff --git a/core/util.inc.php b/core/util.inc.php
index dd815671..0505a228 100644
--- a/core/util.inc.php
+++ b/core/util.inc.php
@@ -34,6 +34,24 @@ function int_escape($input) {
  * @retval string
  */
 function url_escape($input) {
+	/*
+		Shish: I have a feeling that these three lines are important, possibly for searching for tags with slashes in them like fate/stay_night
+		green-ponies: indeed~
+
+	$input = str_replace('^', '^^', $input);
+	$input = str_replace('/', '^s', $input);
+	$input = str_replace('\\', '^b', $input);
+
+	/* The function idn_to_ascii is used to support Unicode domains / URLs as well.
+	   See here for more:  http://php.net/manual/en/function.filter-var.php
+	   However, it is only supported by PHP version 5.3 and up
+
+	if (function_exists('idn_to_ascii')) {
+			return filter_var(idn_to_ascii($input), FILTER_SANITIZE_URL);
+	} else {
+			return filter_var($input, FILTER_SANITIZE_URL);
+	}
+	*/
 	if(is_null($input)) {
 		return "";
 	}
@@ -61,16 +79,32 @@ function sql_escape($input) {
  * @retval boolean
  */
 function bool_escape($input) {
-	$input = strtolower($input);
-	return (
-		$input === "y" ||
-		$input === "yes" ||
-		$input === "t" ||
-		$input === "true" ||
-		$input === "on" ||
-		$input === 1 ||
-		$input === true
-	);
+	/*
+	 Sometimes, I don't like PHP -- this, is one of those times...
+	  "a boolean FALSE is not considered a valid boolean value by this function."
+	 Yay for Got'chas!	
+	 http://php.net/manual/en/filter.filters.validate.php
+	*/
+	if (is_bool($input)) {
+		return $input;
+	} else if (is_numeric($input)) {
+		return ($input === 1);
+	} else {
+		$value = filter_var($input, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+		if (!is_null($value)) {
+			return $value;
+		} else {
+			$input = strtolower( trim($input) );
+			return (
+				$input === "y" ||
+				$input === "yes" ||
+				$input === "t" ||
+				$input === "true" ||
+				$input === "on" ||
+				$input === "1"
+			);
+		}
+	}
 }
 
 /**
@@ -205,15 +239,6 @@ function show_ip($ip, $ban_reason) {
 	return $ip;
 }
 
-/**
- * Different databases have different ways to represent booleans; this
- * will try and standardise them
- */
-function undb_bool($val) {
-	if($val === true  || $val == 'Y' || $val == 'y' || $val == 'T' || $val == 't' || $val === 1) return true;
-	if($val === false || $val == 'N' || $val == 'n' || $val == 'F' || $val == 'f' || $val === 0) return false;
-}
-
 /**
  * Checks if a given string contains another at the beginning.
  *
diff --git a/ext/ban_words/main.php b/ext/ban_words/main.php
index f17b76bf..88fe247a 100644
--- a/ext/ban_words/main.php
+++ b/ext/ban_words/main.php
@@ -54,9 +54,29 @@ xanax
 	}
 
 	public function onCommentPosting(CommentPostingEvent $event) {
+		$this->test_text($event->comment, new CommentPostingException("Comment contains banned terms"));
+	}
+
+	public function onSourceSet(SourceSetEvent $event) {
+		$this->test_text($event->source, new SCoreException("Source contains banned terms"));
+	}
+
+	public function onTagSet(TagSetEvent $event) {
+		$this->test_text(Tag::implode($event->tags), new SCoreException("Tags contain banned terms"));
+	}
+
+	public function onSetupBuilding(SetupBuildingEvent $event) {
+		$sb = new SetupBlock("Banned Phrases");
+		$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
+		$sb->add_longtext_option("banned_words");
+		$event->panel->add_block($sb);
+	}
+
+	private function test_text($comment, $ex) {
 		global $config;
+
 		$banned = $config->get_string("banned_words");
-		$comment = strtolower($event->comment);
+		$comment = strtolower($comment);
 
 		foreach(explode("\n", $banned) as $word) {
 			$word = trim(strtolower($word));
@@ -67,25 +87,18 @@ xanax
 			else if($word[0] == '/') {
 				// lines that start with slash are regex
 				if(preg_match($word, $comment)) {
-					throw new CommentPostingException("Comment contains banned terms");
+					throw $ex;
 				}
 			}
 			else {
 				// other words are literal
 				if(strpos($comment, $word) !== false) {
-					throw new CommentPostingException("Comment contains banned terms");
+					throw $ex;
 				}
 			}
 		}
 	}
 
-	public function onSetupBuilding(SetupBuildingEvent $event) {
-		$sb = new SetupBlock("Banned Phrases");
-		$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
-		$sb->add_longtext_option("banned_words");
-		$event->panel->add_block($sb);
-	}
-
 	public function get_priority() {return 30;}
 }
 ?>
diff --git a/ext/et/main.php b/ext/et/main.php
index 44245c6a..6b087d57 100644
--- a/ext/et/main.php
+++ b/ext/et/main.php
@@ -49,6 +49,12 @@ class ET extends Extension {
 		$info['sys_disk']    = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
 		                       to_shorthand_int(disk_total_space("./"));
 		$info['sys_server']  = $_SERVER["SERVER_SOFTWARE"];
+		
+		$info['thumb_engine']	= $config->get_string("thumb_engine");
+		$info['thumb_quality']	= $config->get_int('thumb_quality');
+		$info['thumb_width']	= $config->get_int('thumb_width');
+		$info['thumb_height']	= $config->get_int('thumb_height');
+		$info['thumb_mem']		= $config->get_int("thumb_max_memory");
 
 		$info['stat_images']   = $database->get_one("SELECT COUNT(*) FROM images");
 		$info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments");
diff --git a/ext/et/theme.php b/ext/et/theme.php
index 1c5b00ae..265b019a 100644
--- a/ext/et/theme.php
+++ b/ext/et/theme.php
@@ -32,6 +32,13 @@ Database: {$info['sys_db']}
 Server: {$info['sys_server']}
 Disk use: {$info['sys_disk']}
 
+Thumbnail Generation:
+Engine: {$info['thumb_engine']}
+Memory: {$info['thumb_mem']}
+Quality: {$info['thumb_quality']}
+Width: {$info['thumb_width']}
+Height: {$info['thumb_height']}
+
 Shimmie stats:
 Images: {$info['stat_images']}
 Comments: {$info['stat_comments']}
diff --git a/ext/featured/main.php b/ext/featured/main.php
index dda66100..57c9f4f8 100644
--- a/ext/featured/main.php
+++ b/ext/featured/main.php
@@ -42,7 +42,7 @@ class Featured extends Extension {
 				$image = Image::by_id($config->get_int("featured_id"));
 				if(!is_null($image)) {
 					$page->set_mode("data");
-					$page->set_type("image/jpeg");
+					$page->set_type($image->get_mime_type());
 					$page->set_data(file_get_contents($image->get_image_filename()));
 				}
 			}
diff --git a/ext/handle_archive/main.php b/ext/handle_archive/main.php
index 2fb2498b..5f0ea3fa 100644
--- a/ext/handle_archive/main.php
+++ b/ext/handle_archive/main.php
@@ -35,6 +35,7 @@ class ArchiveFileHandler extends Extension {
 			exec($cmd);
 			$this->add_dir($tmpdir);
 			deltree($tmpdir);
+			$event->image_id = -2; // default -1 = upload wasn't handled
 		}
 	}
 
diff --git a/ext/index/main.php b/ext/index/main.php
index ce3dbbce..f5244d54 100644
--- a/ext/index/main.php
+++ b/ext/index/main.php
@@ -129,6 +129,8 @@ class PostListBuildingEvent extends Event {
 }
 
 class Index extends Extension {
+	var $val_id = 0;
+
 	public function onInitExt(InitExtEvent $event) {
 		global $config;
 		$config->set_default_int("index_images", 24);
@@ -206,10 +208,11 @@ class Index extends Extension {
 			$event->add_querylet(new Querylet('width / height '.$cmp.' :width / :height', $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", array("val"=>$val)));
+			$event->add_querylet(new Querylet("images.$col $cmp :val{$this->val_id}", array("val{$this->val_id}"=>$val)));
 		}
 		else if(preg_match("/^(hash|md5)=([0-9a-fA-F]*)$/i", $event->term, $matches)) {
 			$hash = strtolower($matches[2]);
diff --git a/ext/index/script.js b/ext/index/script.js
index 459118fb..6c3078c0 100644
--- a/ext/index/script.js
+++ b/ext/index/script.js
@@ -1,13 +1,15 @@
 $(function() {
 	var blocked_tags = ($.cookie("ui-blocked-tags") || $.cookie("blocked-tags") || "").split(" ");
-	var themecheck = $(".thumb[data-tags~='tagme']").parent().attr('class');
+	var themecheck = $(".thumb[data-tags]").parent().attr('class');
 	var needs_refresh = false;
-	for(i in blocked_tags) {
+	for(i=0; i<blocked_tags.length; i++) {
 		var tag = blocked_tags[i];
 		if(tag) {
-			$(".thumb[data-tags~='"+tag+"']").hide();
 			if(themecheck == "thumbblock") {
-				$(".thumb[data-tags~='tagme']").parent().height(0); //required for lite theme
+				$(".thumb[data-tags~='"+tag+"']").parent().hide();
+				$(".thumb[data-tags~='"+tag+"']").parent().height(0); //required for lite theme
+			}else{
+				$(".thumb[data-tags~='"+tag+"']").hide();
 			}
 			needs_refresh = true;
 		}
@@ -16,8 +18,13 @@ $(function() {
 	// text-align: justify with element margins and doesn't recalculate
 	// these margins when part of the line disappears...
 	if(needs_refresh) {
-		$('#image-list').hide();
-		$('#image-list').show();
+		if(themecheck == "thumbblock") {
+			$('.blockbody').hide();
+			$('.blockbody').show();
+		}else{
+			$('#image-list').hide();
+			$('#image-list').show();
+		}
 	}
 });
 
diff --git a/ext/mass_tagger/script.js b/ext/mass_tagger/script.js
index 77b7c2e7..91ae12f5 100644
--- a/ext/mass_tagger/script.js
+++ b/ext/mass_tagger/script.js
@@ -2,10 +2,14 @@ function find_thumb_link_containers () {
     
     var post_link = "a[href*='/post/view/']";
     var has_thumb_img = ":has(img[src*='/thumb/'])";
-
     var list = $( post_link + has_thumb_img ).parent();
 
-    return list;
+	if (list) { return list; }
+	
+    has_thumb_img = ":has(img[src*='_thumbs/'])";
+    list = $( post_link + has_thumb_img ).parent();
+	
+	return list;
 }
 
 function toggle_tag( button, id ) {
diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php
index d8226f8b..5ce8640e 100644
--- a/ext/numeric_score/main.php
+++ b/ext/numeric_score/main.php
@@ -192,6 +192,8 @@ class NumericScore extends Extension {
 
 		$image_ids = $database->get_col("SELECT image_id FROM numeric_score_votes WHERE user_id=?", array($user_id));
 
+		if(count($image_ids) == 0) return;
+
 		$database->execute(
 				"DELETE FROM numeric_score_votes WHERE user_id=? AND image_id IN (".implode(",", $image_ids).")",
 				array($user_id));
diff --git a/ext/pm/main.php b/ext/pm/main.php
index d33c975d..ad37b06a 100644
--- a/ext/pm/main.php
+++ b/ext/pm/main.php
@@ -28,7 +28,7 @@ class PM {
 			$this->sent_date = $a["sent_date"];
 			$this->subject = $a["subject"];
 			$this->message = $a["message"];
-			$this->is_read = undb_bool($a["is_read"]);
+			$this->is_read = bool_escape($a["is_read"]);
 		}
 		else {
 			$this->id      = -1;
diff --git a/ext/random_image/main.php b/ext/random_image/main.php
index 5a77bb25..ced59d67 100644
--- a/ext/random_image/main.php
+++ b/ext/random_image/main.php
@@ -41,7 +41,7 @@ class RandomImage extends Extension {
 			if($action === "download") {
 				if(!is_null($image)) {
 					$page->set_mode("data");
-					$page->set_type("image/jpeg");
+					$page->set_type($image->get_mime_type());
 					$page->set_data(file_get_contents($image->get_image_filename()));
 				}
 			}
diff --git a/ext/setup/main.php b/ext/setup/main.php
index 195c0904..83f7c235 100644
--- a/ext/setup/main.php
+++ b/ext/setup/main.php
@@ -291,6 +291,10 @@ class Setup extends Extension {
 			}
 		}
 		log_warning("setup", "Configuration updated");
+		foreach(glob("data/cache/*.css") as $css_cache) {
+			unlink($css_cache);
+		}
+		log_warning("setup", "Cache cleared");
 	}
 
 	public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
diff --git a/install.php b/install.php
index a08ac4d0..b177fd5d 100644
--- a/install.php
+++ b/install.php
@@ -300,6 +300,14 @@ function create_tables() { // {{{
 	try {
 		$db = new Database();
 		
+		if ( $db->count_tables() > 0 ) {
+			echo "
+				<p>Warning: The Database schema is not empty!</p>
+				<p>Please ensure that the database you are installing Shimmie with is empty before continuing.</p>
+				<p>Once you have emptied the database of any tables, please hit 'refresh' to continue.</p>";
+			exit;
+		}
+		
 		$db->create_table("aliases", "
 			oldtag VARCHAR(128) NOT NULL PRIMARY KEY,
 			newtag VARCHAR(128) NOT NULL,
diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php
index 718e95cd..c5823dbd 100644
--- a/themes/danbooru/view.theme.php
+++ b/themes/danbooru/view.theme.php
@@ -42,8 +42,10 @@ class CustomViewImageTheme extends ViewImageTheme {
 			if($image->rating == null || $image->rating == "u"){
 				$image->rating = "u";
 			}
+			if(class_exists("Ratings")) {
 				$h_rating = Ratings::rating_to_human($image->rating);
 				$html .= "<br>Rating: $h_rating";
+			}
 		}
 
 		return $html;