From 2fd121dc15a731bc7503515f4a8673052ad3b9e5 Mon Sep 17 00:00:00 2001
From: barisusakli <barisusakli@gmail.com>
Date: Sat, 13 Feb 2016 09:35:48 +0200
Subject: [PATCH 01/13] #4154

---
 src/groups.js |  4 ++--
 src/search.js | 59 +++++++++++++++++++++++++--------------------------
 2 files changed, 31 insertions(+), 32 deletions(-)

diff --git a/src/groups.js b/src/groups.js
index 30fefa466f..c318db9942 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -241,8 +241,8 @@ var utils = require('../public/src/utils');
 		if (group) {
 			group.nameEncoded = encodeURIComponent(group.name);
 			group.displayName = validator.escape(group.name);
-			group.description = validator.escape(group.description);
-			group.userTitle = validator.escape(group.userTitle) || group.displayName;
+			group.description = validator.escape(group.description || '');
+			group.userTitle = validator.escape(group.userTitle || '') || group.displayName;
 		}
 	};
 
diff --git a/src/search.js b/src/search.js
index e6975a893e..ca457d8b0b 100644
--- a/src/search.js
+++ b/src/search.js
@@ -1,45 +1,44 @@
 'use strict';
 
-var async = require('async'),
-	validator = require('validator'),
-
-	db = require('./database'),
-	posts = require('./posts'),
-	topics = require('./topics'),
-	categories = require('./categories'),
-	user = require('./user'),
-	plugins = require('./plugins'),
-	privileges = require('./privileges'),
-	utils = require('../public/src/utils');
+var async = require('async');
+var validator = require('validator');
+
+var db = require('./database');
+var posts = require('./posts');
+var topics = require('./topics');
+var categories = require('./categories');
+var user = require('./user');
+var plugins = require('./plugins');
+var privileges = require('./privileges');
+var utils = require('../public/src/utils');
 
 var search = {};
 
 module.exports = search;
 
 search.search = function(data, callback) {
-	function done(err, result) {
-		if (err) {
-			return callback(err);
-		}
-
-		result.search_query = validator.escape(data.query);
-		result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2);
-		callback(null, result);
-	}
 
 	var start = process.hrtime();
-
 	var searchIn = data.searchIn || 'titlesposts';
 
-	if (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts') {
-		searchInContent(data, done);
-	} else if (searchIn === 'users') {
-		user.search(data, done);
-	} else if (searchIn === 'tags') {
-		topics.searchAndLoadTags(data, done);
-	} else {
-		callback(new Error('[[error:unknown-search-filter]]'));
-	}
+	async.waterfall([
+		function (next) {
+			if (searchIn === 'posts' || searchIn === 'titles' || searchIn === 'titlesposts') {
+				searchInContent(data, next);
+			} else if (searchIn === 'users') {
+				user.search(data, next);
+			} else if (searchIn === 'tags') {
+				topics.searchAndLoadTags(data, next);
+			} else {
+				next(new Error('[[error:unknown-search-filter]]'));
+			}
+		},
+		function (result, next) {
+			result.search_query = validator.escape(data.query || '');
+			result.time = (process.elapsedTimeSince(start) / 1000).toFixed(2);
+			next(null, result);
+		}
+	], callback);
 };
 
 function searchInContent(data, callback) {

From 8276090d717f25c91a220eb99bce8233c68d20c8 Mon Sep 17 00:00:00 2001
From: barisusakli <barisusakli@gmail.com>
Date: Sat, 13 Feb 2016 14:39:44 +0200
Subject: [PATCH 02/13] closes #4180

---
 src/socket.io/user/profile.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js
index 8f8811ef06..bd73b7ff7c 100644
--- a/src/socket.io/user/profile.js
+++ b/src/socket.io/user/profile.js
@@ -54,7 +54,13 @@ module.exports = function(SocketUser) {
 		async.parallel({
 			isAdmin: async.apply(user.isAdministrator, uid),
 			hasPassword: async.apply(user.hasPassword, data.uid),
-			passwordMatch: async.apply(user.isPasswordCorrect, data.uid, data.password)
+			passwordMatch: function(next) {
+				if (data.password) {
+					user.isPasswordCorrect(data.uid, data.password, next);
+				} else {
+					next(null, false);
+				}
+			}
 		}, function(err, results) {
 			if (err) {
 				return callback(err);

From 90b89a488e4e888a1e59e274087b0ba72b5ef785 Mon Sep 17 00:00:00 2001
From: barisusakli <barisusakli@gmail.com>
Date: Sat, 13 Feb 2016 15:09:09 +0200
Subject: [PATCH 03/13] closes #4175

---
 public/src/admin/admin.js | 5 +++++
 public/src/app.js         | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js
index 8d2535eaff..22258d4f66 100644
--- a/public/src/admin/admin.js
+++ b/public/src/admin/admin.js
@@ -5,6 +5,11 @@
 	$(document).ready(function() {
 		setupKeybindings();
 
+		// on page reload show correct tab if url has #
+		if (window.location.hash) {
+			$('.nav-pills a[href=' + window.location.hash + ']').tab('show');
+		}
+
 		if(!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
 			require(['admin/modules/search'], function(search) {
 				search.init();
diff --git a/public/src/app.js b/public/src/app.js
index 9fbe1c9f14..36f5c67b5e 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -25,7 +25,7 @@ app.cacheBuster = null;
 
 	app.load = function() {
 		$('document').ready(function () {
-			var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search, true);
+			var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search + window.location.hash, true);
 			ajaxify.end(url, app.template);
 
 			handleStatusChange();

From bc9c5646500405aa67b7a3dbdecb8b73ec88f334 Mon Sep 17 00:00:00 2001
From: barisusakli <barisusakli@gmail.com>
Date: Sat, 13 Feb 2016 15:24:22 +0200
Subject: [PATCH 04/13] closes #4169

---
 public/src/modules/notifications.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js
index a8f090028b..ec54fc0812 100644
--- a/public/src/modules/notifications.js
+++ b/public/src/modules/notifications.js
@@ -51,7 +51,7 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
 
 			socket.emit('notifications.mark' + (unread ? 'Read' : 'Unread'), liEl.attr('data-nid'), function(err) {
 				if (err) {
-					app.alertError(err.message);
+					return app.alertError(err.message);
 				}
 
 				liEl.toggleClass('unread');
@@ -131,7 +131,7 @@ define('notifications', ['sounds', 'translator', 'components'], function(sound,
 
 	Notifications.updateNotifCount = function(count) {
 		var notifIcon = components.get('notifications/icon');
-
+		count = Math.max(0, count);
 		if (count > 0) {
 			notifIcon.removeClass('fa-bell-o').addClass('fa-bell');
 		} else {

From 30736c0ee909043914a62b008a6f6acf12b9a2c3 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 15 Feb 2016 11:52:57 -0500
Subject: [PATCH 05/13] allowed 'system' to be passed into group creation now

---
 src/groups/create.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/groups/create.js b/src/groups/create.js
index 2b929c1e14..2910d96294 100644
--- a/src/groups/create.js
+++ b/src/groups/create.js
@@ -9,7 +9,7 @@ var async = require('async'),
 module.exports = function(Groups) {
 
 	Groups.create = function(data, callback) {
-		var system = data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' || Groups.isPrivilegeGroup(data.name);
+		var system = data.system === true || parseInt(data.system, 10) === 1 || data.name === 'administrators' || data.name === 'registered-users' || data.name === 'Global Moderators' || Groups.isPrivilegeGroup(data.name);
 		var groupData;
 		var timestamp = data.timestamp || Date.now();
 

From 10024d7b93c9972cfc126ad92e327c16cf7e1b56 Mon Sep 17 00:00:00 2001
From: Julian Lam <julian@designcreateplay.com>
Date: Mon, 15 Feb 2016 15:32:01 -0500
Subject: [PATCH 06/13] fixed #4186

---
 src/user/picture.js | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/user/picture.js b/src/user/picture.js
index bb3d24dd4e..60a314759b 100644
--- a/src/user/picture.js
+++ b/src/user/picture.js
@@ -3,6 +3,7 @@
 var async = require('async'),
 	path = require('path'),
 	fs = require('fs'),
+	os = require('os'),
 	nconf = require('nconf'),
 	crypto = require('crypto'),
 	winston = require('winston'),
@@ -160,20 +161,23 @@ module.exports = function(User) {
 				md5sum.update(data.imageData);
 				md5sum = md5sum.digest('hex');
 
-				tempPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), md5sum);
+				data.file = {
+					path: path.join(os.tmpdir(), md5sum)
+				};
+
 				var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64');
 
-				fs.writeFile(tempPath, buffer, {
+				fs.writeFile(data.file.path, buffer, {
 					encoding: 'base64'
 				}, next);
 			},
 			function(next) {
-				file.isFileTypeAllowed(tempPath, next);
+				file.isFileTypeAllowed(data.file.path, next);
 			},
 			function(tempPath, next) {
 				var image = {
 					name: 'profileCover',
-					path: data.file ? data.file.path : tempPath,
+					path: tempPath,
 					uid: data.uid
 				};
 
@@ -198,7 +202,7 @@ module.exports = function(User) {
 				User.setUserField(data.uid, 'cover:url', uploadData.url, next);
 			},
 			function(next) {
-				require('fs').unlink(data.file ? data.file.path : tempPath, function(err) {
+				fs.unlink(data.file.path, function(err) {
 					if (err) {
 						winston.error(err);
 					}
@@ -207,7 +211,7 @@ module.exports = function(User) {
 			}
 		], function(err) {
 			if (err) {
-				return fs.unlink(tempPath, function(unlinkErr) {
+				return fs.unlink(data.file.path, function(unlinkErr) {
 					callback(err);	// send back the original error
 				});
 			}

From d851443ea7109c565681640029e7c5a6efa12a0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Mon, 15 Feb 2016 22:46:00 +0200
Subject: [PATCH 07/13] closes #4187

---
 public/src/client/groups/list.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js
index c02b9904eb..4293d0de69 100644
--- a/public/src/client/groups/list.js
+++ b/public/src/client/groups/list.js
@@ -80,6 +80,12 @@ define('forum/groups/list', ['forum/infinitescroll'], function(infinitescroll) {
 				filterHidden: true
 			}
 		}, function(err, groups) {
+			if (err) {
+				return app.alertError(err.message);
+			}
+			groups = groups.filter(function(group) {
+				return group.name !== 'registered-users' && group.name !== 'guests';
+			});
 			templates.parse('partials/groups/list', {
 				groups: groups
 			}, function(html) {

From 0516418da6c0ef00cfd6fb5f91b34afbad937572 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Mon, 15 Feb 2016 22:52:43 +0200
Subject: [PATCH 08/13] up themes

---
 package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 53d790d293..4265c82c02 100644
--- a/package.json
+++ b/package.json
@@ -52,8 +52,8 @@
     "nodebb-plugin-spam-be-gone": "0.4.5",
     "nodebb-rewards-essentials": "0.0.6",
     "nodebb-theme-lavender": "3.0.6",
-    "nodebb-theme-persona": "4.0.70",
-    "nodebb-theme-vanilla": "5.0.42",
+    "nodebb-theme-persona": "4.0.71",
+    "nodebb-theme-vanilla": "5.0.43",
     "nodebb-widget-essentials": "2.0.5",
     "nodemailer": "2.0.0",
     "nodemailer-sendmail-transport": "1.0.0",

From 82875de32de0c6a6be2288da95865e7edb4f4c0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Tue, 16 Feb 2016 09:19:31 +0200
Subject: [PATCH 09/13] remove / from startsWith

---
 public/src/app.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/src/app.js b/public/src/app.js
index 36f5c67b5e..db240575e7 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -276,7 +276,7 @@ app.cacheBuster = null;
 			if (err) {
 				return app.alertError(err.message);
 			}
-			if (!ajaxify.currentPage.startsWith('chats/')) {
+			if (!ajaxify.currentPage.startsWith('chats')) {
 				app.openChat(roomId);
 			} else {
 				ajaxify.go('chats/' + roomId);

From 88e4591f880b8a88f75f08164ef71427b7da54e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Tue, 16 Feb 2016 18:04:02 +0200
Subject: [PATCH 10/13] closes #4194

---
 public/src/client/account/edit.js   |  2 +-
 public/src/modules/uploader.js      |  2 +-
 src/controllers/accounts/edit.js    | 26 +++++-----
 src/controllers/accounts/helpers.js | 19 +++++---
 src/middleware/middleware.js        |  2 +-
 src/socket.io/user.js               | 18 +++++--
 src/socket.io/user/profile.js       | 10 ++--
 src/user/picture.js                 | 75 ++++++++++++++---------------
 8 files changed, 79 insertions(+), 75 deletions(-)

diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js
index c60c974b96..e239c142f2 100644
--- a/public/src/client/account/edit.js
+++ b/public/src/client/account/edit.js
@@ -78,6 +78,7 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 				if (err) {
 					return app.alertError(err.message);
 				}
+			
 				templates.parse('partials/modals/change_picture_modal', {
 					pictures: pictures,
 					uploaded: !!ajaxify.data.uploadedpicture,
@@ -191,7 +192,6 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
 			$('#user-current-picture, img.avatar').attr('src', urlOnServer);
 			updateHeader(urlOnServer);
 			uploadedPicture = urlOnServer;
-			ajaxify.refresh();
 		}
 
 		function onRemoveComplete(urlOnServer) {
diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js
index d15d5ddda0..ce66d37c17 100644
--- a/public/src/modules/uploader.js
+++ b/public/src/modules/uploader.js
@@ -58,7 +58,7 @@ define('uploader', ['csrf', 'translator'], function(csrf, translator) {
 					},
 					error: function(xhr) {
 						xhr = maybeParse(xhr);
-						showAlert('error', xhr.responseJSON ? xhr.responseJSON.error : 'error uploading, code : ' + xhr.status);
+						showAlert('error', xhr.responseJSON ? (xhr.responseJSON.error || xhr.statusText) : 'error uploading, code : ' + xhr.status);
 					},
 
 					uploadProgress: function(event, position, total, percent) {
diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js
index 98bbd2d2e3..06eb2126b7 100644
--- a/src/controllers/accounts/edit.js
+++ b/src/controllers/accounts/edit.js
@@ -24,6 +24,7 @@ editController.get = function(req, res, callback) {
 		userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10);
 		userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads) === 1;
 		userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1;
+		
 
 		userData.title = '[[pages:account/edit, ' + userData.username + ']]';
 		userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]);
@@ -94,30 +95,25 @@ function getUserData(req, next, callback) {
 editController.uploadPicture = function (req, res, next) {
 	var userPhoto = req.files.files[0];
 
-	var updateUid = req.uid;
+	var updateUid;
 
 	async.waterfall([
 		function(next) {
 			user.getUidByUserslug(req.params.userslug, next);
 		},
 		function(uid, next) {
-			if (parseInt(updateUid, 10) === parseInt(uid, 10)) {
-				return next();
+			updateUid = uid;
+			if (parseInt(req.uid, 10) === parseInt(uid, 10)) {
+				return next(null, true);
 			}
 
-			user.isAdministrator(req.uid, function(err, isAdmin) {
-				if (err) {
-					return next(err);
-				}
-
-				if (!isAdmin) {
-					return helpers.notAllowed(req, res);
-				}
-				updateUid = uid;
-				next();
-			});
+			user.isAdminOrGlobalMod(req.uid, next);
 		},
-		function(next) {
+		function(isAllowed, next) {
+			if (!isAllowed) {
+				return helpers.notAllowed(req, res);
+			}
+			
 			user.uploadPicture(updateUid, userPhoto, next);
 		}
 	], function(err, image) {
diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js
index 631230b5bd..f2c579a05e 100644
--- a/src/controllers/accounts/helpers.js
+++ b/src/controllers/accounts/helpers.js
@@ -57,23 +57,24 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
 			var userData = results.userData;
 			var userSettings = results.userSettings;
 			var isAdmin = results.isAdmin;
+			var isGlobalModerator = results.isGlobalModerator;
 			var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
 
 			userData.joindateISO = utils.toISOString(userData.joindate);
 			userData.lastonlineISO = utils.toISOString(userData.lastonline || userData.joindate);
 			userData.age = Math.max(0, userData.birthday ? Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000) : 0);
 
-			if (!(isAdmin || self || (userData.email && userSettings.showemail))) {
+			if (!(isAdmin || isGlobalModerator || self || (userData.email && userSettings.showemail))) {
 				userData.email = '';
 			}
 
 			userData.emailClass = (self && !userSettings.showemail) ? '' : 'hide';
 
-			if (!isAdmin && !self && !userSettings.showfullname) {
+			if (!isAdmin && !isGlobalModerator && !self && !userSettings.showfullname) {
 				userData.fullname = '';
 			}
 
-			if (isAdmin || self) {
+			if (isAdmin || isGlobalModerator || self) {
 				userData.ips = results.ips;
 			}
 
@@ -81,10 +82,11 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
 			userData.yourid = callerUID;
 			userData.theirid = userData.uid;
 			userData.isAdmin = isAdmin;
-			userData.isGlobalModerator = results.isGlobalModerator;
-			userData.canBan = isAdmin || results.isGlobalModerator;
+			userData.isGlobalModerator = isGlobalModerator;
+			userData.canBan = isAdmin || isGlobalModerator;
+			userData.canChangePassword = isAdmin || self;
 			userData.isSelf = self;
-			userData.showHidden = self || isAdmin;
+			userData.showHidden = self || isAdmin || isGlobalModerator;
 			userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : [];
 			userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1;
 			userData['email:confirmed'] = !!parseInt(userData['email:confirmed'], 10);
@@ -133,6 +135,9 @@ helpers.getBaseUser = function(userslug, callerUID, callback) {
 				isAdmin: function(next) {
 					user.isAdministrator(callerUID, next);
 				},
+				isGlobalModerator: function(next) {
+					user.isGlobalModerator(callerUID, next);
+				},
 				profile_links: function(next) {
 					plugins.fireHook('filter:user.profileLinks', [], next);
 				}
@@ -147,7 +152,7 @@ helpers.getBaseUser = function(userslug, callerUID, callback) {
 			results.user.theirid = results.user.uid;
 			results.user.status = user.getStatus(results.user);
 			results.user.isSelf = parseInt(callerUID, 10) === parseInt(results.user.uid, 10);
-			results.user.showHidden = results.user.isSelf || results.isAdmin;
+			results.user.showHidden = results.user.isSelf || results.isAdmin || results.isGlobalModerator;
 			results.user.profile_links = filterLinks(results.profile_links, results.user.isSelf);
 
 			results.user['cover:url'] = results.user['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(results.user.uid);
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 10090e02ce..fb36635a38 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -145,7 +145,7 @@ middleware.checkAccountPermissions = function(req, res, next) {
 				return next(null, true);
 			}
 
-			user.isAdministrator(req.uid, next);
+			user.isAdminOrGlobalMod(req.uid, next);
 		}
 	], function (err, allowed) {
 		if (err || allowed) {
diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index 95158deaef..be884d4c54 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -188,12 +188,20 @@ SocketUser.saveSettings = function(socket, data, callback) {
 		return callback(new Error('[[error:invalid-data]]'));
 	}
 
-	user.isAdminOrSelf(socket.uid, data.uid, function(err) {
-		if (err) {
-			return callback(err);
+	async.waterfall([
+		function(next) {
+			if (socket.uid === parseInt(data.uid, 10)) {
+				return next(null, true);
+			}
+			user.isAdminOrGlobalMod(socket.uid, next);
+		}, 
+		function(allowed, next) {
+			if (!allowed) {
+				return next(new Error('[[error:no-privileges]]'));
+			}
+			user.saveSettings(data.uid, data.settings, next);
 		}
-		user.saveSettings(data.uid, data.settings, callback);
-	});
+	], callback);
 };
 
 SocketUser.setTopicSort = function(socket, sort, callback) {
diff --git a/src/socket.io/user/profile.js b/src/socket.io/user/profile.js
index bd73b7ff7c..52c9fcd801 100644
--- a/src/socket.io/user/profile.js
+++ b/src/socket.io/user/profile.js
@@ -122,18 +122,18 @@ module.exports = function(SocketUser) {
 					return next(new Error('[[error:invalid-data]]'));
 				}
 
-				user.isAdministrator(socket.uid, next);
+				user.isAdminOrGlobalMod(socket.uid, next);
 			},
-			function(isAdmin, next) {
-				if (!isAdmin && socket.uid !== parseInt(data.uid, 10)) {
+			function(isAdminOrGlobalMod, next) {
+				if (!isAdminOrGlobalMod && socket.uid !== parseInt(data.uid, 10)) {
 					return next(new Error('[[error:no-privileges]]'));
 				}
 
-				if (!isAdmin && parseInt(meta.config['username:disableEdit'], 10) === 1) {
+				if (!isAdminOrGlobalMod && parseInt(meta.config['username:disableEdit'], 10) === 1) {
 					data.username = oldUserData.username;
 				}
 
-				if (!isAdmin && parseInt(meta.config['email:disableEdit'], 10) === 1) {
+				if (!isAdminOrGlobalMod && parseInt(meta.config['email:disableEdit'], 10) === 1) {
 					data.email = oldUserData.email;
 				}
 
diff --git a/src/user/picture.js b/src/user/picture.js
index 60a314759b..db0ea234f1 100644
--- a/src/user/picture.js
+++ b/src/user/picture.js
@@ -25,6 +25,7 @@ module.exports = function(User) {
 		var updateUid = uid;
 		var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128;
 		var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
+		var uploadedImage;
 
 		async.waterfall([
 			function(next) {
@@ -53,48 +54,42 @@ module.exports = function(User) {
 				} else {
 					next();
 				}
-			}
-		], function(err) {
-			function done(err, image) {
-				if (err) {
-					return callback(err);
+			},
+			function(next) {
+				if (plugins.hasListeners('filter:uploadImage')) {
+					return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next);
 				}
 
-				User.setUserFields(updateUid, {uploadedpicture: image.url, picture: image.url});
-
-				callback(null, image);
-			}
-
-			if (err) {
-				return callback(err);
-			}
-
-			if (plugins.hasListeners('filter:uploadImage')) {
-				return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, done);
+				var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension);
+				
+				async.waterfall([
+					function(next) {
+						User.getUserField(updateUid, 'uploadedpicture', next);
+					},
+					function(oldpicture, next) {
+						if (!oldpicture) {
+							return file.saveFileToLocal(filename, 'profile', picture.path, next);
+						}		
+						var oldpicturePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'profile', path.basename(oldpicture));
+
+						fs.unlink(oldpicturePath, function (err) {
+							if (err) {
+								winston.error(err);
+							}
+
+							file.saveFileToLocal(filename, 'profile', picture.path, next);
+						});
+					},					
+				], next);
+			},
+			function(_image, next) {
+				uploadedImage = _image;
+				User.setUserFields(updateUid, {uploadedpicture: uploadedImage.url, picture: uploadedImage.url}, next);
+			},
+			function(next) {
+				next(null, uploadedImage);
 			}
-
-			var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension);
-
-			User.getUserField(updateUid, 'uploadedpicture', function (err, oldpicture) {
-				if (err) {
-					return callback(err);
-				}
-
-				if (!oldpicture) {
-					return file.saveFileToLocal(filename, 'profile', picture.path, done);
-				}
-
-				var absolutePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'profile', path.basename(oldpicture));
-
-				fs.unlink(absolutePath, function (err) {
-					if (err) {
-						winston.error(err);
-					}
-
-					file.saveFileToLocal(filename, 'profile', picture.path, done);
-				});
-			});
-		});
+		], callback);
 	};
 
 	User.uploadFromUrl = function(uid, url, callback) {
@@ -135,7 +130,7 @@ module.exports = function(User) {
 	};
 
 	User.updateCoverPicture = function(data, callback) {
-		var tempPath, url, md5sum;
+		var url, md5sum;
 
 		if (!data.imageData && data.position) {
 			return User.updateCoverPosition(data.uid, data.position, callback);

From 8f25994482a56fee948b00937647f93661af0e65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Tue, 16 Feb 2016 20:25:23 +0200
Subject: [PATCH 11/13] fix for async whilst derp

---
 src/messaging.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/messaging.js b/src/messaging.js
index 1a8dabb9b2..e78664d6d0 100644
--- a/src/messaging.js
+++ b/src/messaging.js
@@ -418,7 +418,9 @@ var async = require('async'),
 							next();
 						}
 					});
-				}, next);
+				}, function(err) {
+					next(err, roomId);
+				});
 			}
 		], callback);
 	};

From 141d1b8ece5b506dfed16e9d1c605412a26f0564 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
 <baris@nodebb.org>
Date: Tue, 16 Feb 2016 23:26:06 +0200
Subject: [PATCH 12/13] dont update visible:memberCount if group is hidden,
 closes #4195

---
 src/groups/membership.js | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/src/groups/membership.js b/src/groups/membership.js
index 535e46f358..a9ce84405f 100644
--- a/src/groups/membership.js
+++ b/src/groups/membership.js
@@ -15,18 +15,27 @@ module.exports = function(Groups) {
 		function join() {
 			var tasks = [
 				async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid),
-				async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount'),
-				async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', 1, groupName)
+				async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount')				
 			];
 
 			async.waterfall([
 				function(next) {
-					user.isAdministrator(uid, next);
+					async.parallel({
+						isAdmin: function(next) {
+							user.isAdministrator(uid, next);		
+						},
+						isHidden: function(next) {
+							Groups.isHidden(groupName, next);
+						}
+					}, next);					
 				},
-				function(isAdmin, next) {
-					if (isAdmin) {
+				function(results, next) {
+					if (results.isAdmin) {
 						tasks.push(async.apply(db.setAdd, 'group:' + groupName + ':owners', uid));
 					}
+					if (!results.isHidden) {
+						tasks.push(async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', 1, groupName));
+					}
 					async.parallel(tasks, next);
 				},
 				function(results, next) {
@@ -181,7 +190,6 @@ module.exports = function(Groups) {
 
 		var tasks = [
 			async.apply(db.sortedSetRemove, 'group:' + groupName + ':members', uid),
-			async.apply(db.sortedSetIncrBy, 'groups:visible:memberCount', -1, groupName),
 			async.apply(db.setRemove, 'group:' + groupName + ':owners', uid),
 			async.apply(db.decrObjectField, 'group:' + groupName, 'memberCount')
 		];
@@ -204,7 +212,11 @@ module.exports = function(Groups) {
 				if (parseInt(groupData.hidden, 10) === 1 && parseInt(groupData.memberCount, 10) === 0) {
 					Groups.destroy(groupName, callback);
 				} else {
-					callback();
+					if (parseInt(groupData.hidden, 10) !== 1) {
+						db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, next);			
+					} else {
+						callback();	
+					}					
 				}
 			});
 		});

From 88ded50115c4b8ca0b35184b0250a800fb95321e Mon Sep 17 00:00:00 2001
From: psychobunny <rodrigues.andrew@gmail.com>
Date: Tue, 16 Feb 2016 18:03:22 -0500
Subject: [PATCH 13/13] up persona

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4265c82c02..0809a2d2de 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
     "nodebb-plugin-spam-be-gone": "0.4.5",
     "nodebb-rewards-essentials": "0.0.6",
     "nodebb-theme-lavender": "3.0.6",
-    "nodebb-theme-persona": "4.0.71",
+    "nodebb-theme-persona": "4.0.72",
     "nodebb-theme-vanilla": "5.0.43",
     "nodebb-widget-essentials": "2.0.5",
     "nodemailer": "2.0.0",