diff --git a/nodebb b/nodebb
index d33baeba04..26ec782725 100755
--- a/nodebb
+++ b/nodebb
@@ -56,7 +56,7 @@ case "$1" in
 		echo "Launching NodeBB in \"development\" mode."
 		echo "To run the production build of NodeBB, please use \"forever\"."
 		echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
-		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- loader "$@"
+		NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
 		;;
 
 	*)
diff --git a/public/src/forum/admin/index.js b/public/src/forum/admin/index.js
index 1870884328..7c9f6db3bd 100644
--- a/public/src/forum/admin/index.js
+++ b/public/src/forum/admin/index.js
@@ -35,6 +35,17 @@ define(function() {
 		$('.restart').on('click', function() {
 			socket.emit('admin.restart');
 		});
+
+		socket.emit('admin.getVisitorCount', function(err, data) {
+			if(err) {
+				return app.alertError(err.message);
+			}
+
+			var uniqueVisitors = $('#unique-visitors');
+			for(var key in data) {
+				uniqueVisitors.find('#' + key).text(data[key]);
+			}
+		});
 	};
 
 	Admin.updateRoomUsage = function(err, data) {
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 84eafc8621..9a86aaec32 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -141,16 +141,16 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 							}
 							loadingEl.remove();
 
-							categoriesEl.on('click', function(e) {
-								var el = $(e.target);
+							categoriesEl.on('click', 'li[data-cid]', function(e) {
+								var el = $(this);
 								if (el.is('li')) {
-									confirmCat.html(e.target.innerHTML);
+									confirmCat.html(el.html());
 									confirmDiv.css({display: 'block'});
 									targetCid = el.attr('data-cid');
-									targetCatLabel = e.html();
+									targetCatLabel = el.html();
 									commitEl.prop('disabled', false);
 								}
-							}, false);
+							});
 
 							commitEl.on('click', function() {
 								if (!commitEl.prop('disabled') && targetCid) {
@@ -322,11 +322,10 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 						localStorage.removeItem('topic:' + tid + ':bookmark');
 					}
 				});
-				updateHeader();
-			} else {
-				updateHeader();
 			}
 
+			updateHeader();
+
 			$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
 				if (!$(this).data('users-loaded')) {
 					$(this).data('users-loaded', "true");
diff --git a/public/templates/admin/index.tpl b/public/templates/admin/index.tpl
index 2d548c459c..3fac2b0917 100644
--- a/public/templates/admin/index.tpl
+++ b/public/templates/admin/index.tpl
@@ -36,4 +36,29 @@
 			</div>
 		</div>
 	</div>
+	<div class="col-sm-6">
+		<div class="panel panel-default">
+			<div class="panel-heading">Unique Visitors</div>
+			<div class="panel-body">
+				<div id="unique-visitors">
+					<div class="text-center pull-left">
+						<div id="day"></div>
+						<div>Day</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="week"></div>
+						<div>Week</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="month"></div>
+						<div>Month</div>
+					</div>
+					<div class="text-center pull-left">
+						<div id="alltime"></div>
+						<div>All Time</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
 </div>
\ No newline at end of file
diff --git a/src/meta.js b/src/meta.js
index f4411904fd..93f53e04d3 100644
--- a/src/meta.js
+++ b/src/meta.js
@@ -3,6 +3,7 @@ var fs = require('fs'),
 	async = require('async'),
 	winston = require('winston'),
 	nconf = require('nconf'),
+	_ = require('underscore'),
 
 	utils = require('./../public/src/utils'),
 	translator = require('./../public/src/translator'),
@@ -250,14 +251,19 @@ var fs = require('fs'),
 						jsPath = path.normalize(jsPath);
 
 						if (jsPath.substring(0, 7) === 'plugins') {
-							var paths = jsPath.split(path.sep),
-								mappedPath = paths[1];
+							var	matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
+								if (jsPath.match(mappedPath)) {
+									return mappedPath;
+								} else {
+									return null;
+								}
+							}).filter(function(a) { return a; });
 
-							if (plugins.staticDirs[mappedPath]) {
-								jsPath = jsPath.replace(path.join('plugins', mappedPath), '');
-								return path.join(plugins.staticDirs[mappedPath], jsPath);
+							if (matches.length) {
+								var	relPath = jsPath.slice(new String('plugins/' + matches[0]).length);
+								return plugins.staticDirs[matches[0]] + relPath;
 							} else {
-								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + mappedPath + '. Are you sure it is defined by a plugin?');
+								winston.warn('[meta.scripts.get] Could not resolve mapped path: ' + jsPath + '. Are you sure it is defined by a plugin?');
 								return null;
 							}
 						} else {
diff --git a/src/plugins.js b/src/plugins.js
index 4d01840498..f334d8b0da 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -246,24 +246,31 @@ var fs = require('fs'),
 				`data.priority`, the relative priority of the method when it is eventually called (default: 10)
 		*/
 
-		if (data.hook && data.method) {
+		var method;
+
+		if (data.hook && data.method && typeof data.method === 'string' && data.method.length > 0) {
 			data.id = id;
 			if (!data.priority) data.priority = 10;
-			data.method = data.method.split('.').reduce(function(memo, prop) {
-				if (memo[prop]) {
+			method = data.method.split('.').reduce(function(memo, prop) {
+				if (memo !== null && memo[prop]) {
 					return memo[prop];
 				} else {
-					// Couldn't find method by path, assuming property with periods in it (evil!)
-					Plugins.libraries[data.id][data.method];
+					// Couldn't find method by path, aborting
+					return null;
 				}
 			}, Plugins.libraries[data.id]);
 
+			if (method === null) {
+				winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
+				return callback();
+			}
+
+			// Write the actual method reference to the hookObj
+			data.method = method;
+
 			Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
 			Plugins.loadedHooks[data.hook].push(data);
 
-			if (global.env === 'development') {
-				winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
-			}
 			callback();
 		} else return;
 	};
diff --git a/src/postTools.js b/src/postTools.js
index a097df2d30..b610e19415 100644
--- a/src/postTools.js
+++ b/src/postTools.js
@@ -90,9 +90,7 @@ var winston = require('winston'),
 
 									topics.setTopicField(tid, 'thumb', options.topic_thumb);
 
-									db.searchRemove('topic', tid, function() {
-										db.searchIndex('topic', title, tid);
-									});
+									plugins.fireHook('action:topic.edit', tid);
 								}
 
 								posts.getPostData(pid, function(err, postData) {
diff --git a/src/posts.js b/src/posts.js
index 18d71deb0f..ac428870d0 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -92,10 +92,10 @@ var db = require('./database'),
 						return next(err);
 					}
 
-					postData.content = content;
-
 					plugins.fireHook('action:post.save', postData);
 
+					postData.content = content;
+
 					next(null, postData);
 				});
 			}
diff --git a/src/routes/api.js b/src/routes/api.js
index a57f17d2b1..3d61acedf3 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -30,6 +30,8 @@ var path = require('path'),
 					user.updateLastOnlineTime(req.user.uid);
 				}
 
+				db.sortedSetAdd('ip:recent', Date.now(), req.ip || 'Unknown');
+
 				next();
 			});
 
@@ -222,15 +224,13 @@ var path = require('path'),
 					return res.redirect('/404');
 				}
 
-				var limit = 50;
-
 				function searchPosts(callback) {
 					Plugins.fireHook('filter:search.query', {
 						index: 'post',
-						query: req.params.terms
+						query: req.params.term
 					}, function(err, pids) {
 						if (err) {
-							return callback(err, null);
+							return callback(err);
 						}
 
 						posts.getPostSummaryByPids(pids, false, callback);
@@ -240,10 +240,10 @@ var path = require('path'),
 				function searchTopics(callback) {
 					Plugins.fireHook('filter:search.query', {
 						index: 'topic',
-						query: req.params.terms
+						query: req.params.term
 					}, function(err, tids) {
 						if (err) {
-							return callback(err, null);
+							return callback(err);
 						}
 
 						topics.getTopicsByTids(tids, 0, callback);
diff --git a/src/routes/plugins.js b/src/routes/plugins.js
index 774fee7217..00f37d8d02 100644
--- a/src/routes/plugins.js
+++ b/src/routes/plugins.js
@@ -44,7 +44,7 @@ var	nconf = require('nconf'),
 
 			if (matches) {
 				async.map(matches, function(mappedPath, next) {
-					var	filePath = path.join(plugins.staticDirs[mappedPath], relPath.slice(mappedPath.length));
+					var	filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
 
 					fs.exists(filePath, function(exists) {
 						if (exists) {
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 3d5fd87726..316d77a347 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -9,6 +9,7 @@ var	groups = require('../groups'),
 	categories = require('../categories'),
 	CategoryTools = require('../categoryTools'),
 	logger = require('../logger'),
+	db = require('../database'),
 	admin = {
 		user: require('../admin/user'),
 		categories: require('../admin/categories')
@@ -35,6 +36,30 @@ SocketAdmin.restart = function(socket, data, callback) {
 	meta.restart();
 };
 
+
+SocketAdmin.getVisitorCount = function(socket, data, callback) {
+	var terms = {
+		day: 86400000,
+		week: 604800000,
+		month: 2592000000
+	};
+	var now = Date.now();
+	async.parallel({
+		day: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.day, now, next);
+		},
+		week: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.week, now, next);
+		},
+		month: function(next) {
+			db.sortedSetCount('ip:recent', now - terms.month, now, next);
+		},
+		alltime: function(next) {
+			db.sortedSetCount('ip:recent', 0, now, next);
+		}
+	}, callback);
+}
+
 /* Topics */
 
 SocketAdmin.topics = {};
diff --git a/src/webserver.js b/src/webserver.js
index 220cb6e9b3..44b2186d93 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -392,9 +392,6 @@ process.on('uncaughtException', function(err) {
 					// Disable framing
 					res.setHeader('X-Frame-Options', 'SAMEORIGIN');
 
-					// Log IP address
-					db.sortedSetAdd('ip:recent', +new Date(), req.ip || 'Unknown');
-
 					next();
 				});