From ccad95cf73db1ee042b5588ec0edbed1093ca7cf 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, 18 Jun 2018 14:37:32 -0400
Subject: [PATCH] closes #6579

---
 install/package.json                   |  8 +--
 public/src/client/popular.js           |  9 ++--
 public/src/client/recent.js            |  6 ++-
 public/src/client/top.js               |  1 +
 src/controllers/helpers.js             | 59 +++++++++++++++++++--
 src/controllers/popular.js             | 73 ++++----------------------
 src/controllers/recent.js              | 57 ++++++++++++++------
 src/controllers/top.js                 | 73 ++------------------------
 src/controllers/unread.js              |  8 +--
 src/routes/api.js                      |  2 +-
 src/routes/feeds.js                    | 31 +++++++----
 src/routes/index.js                    |  8 +--
 src/socket.io/topics/infinitescroll.js | 31 ++++++-----
 src/topics.js                          |  2 -
 src/topics/popular.js                  | 14 -----
 src/topics/top.js                      | 16 ------
 src/user/digest.js                     |  8 ++-
 test/controllers.js                    |  4 +-
 test/topics.js                         | 13 +++--
 19 files changed, 188 insertions(+), 235 deletions(-)
 delete mode 100644 src/topics/popular.js
 delete mode 100644 src/topics/top.js

diff --git a/install/package.json b/install/package.json
index d83641ec68..89c86454d7 100644
--- a/install/package.json
+++ b/install/package.json
@@ -75,10 +75,10 @@
         "nodebb-plugin-spam-be-gone": "0.5.4",
         "nodebb-rewards-essentials": "0.0.11",
         "nodebb-theme-lavender": "5.0.5",
-        "nodebb-theme-persona": "9.0.15",
-        "nodebb-theme-slick": "1.2.4",
-        "nodebb-theme-vanilla": "10.0.13",
-        "nodebb-widget-essentials": "4.0.6",
+        "nodebb-theme-persona": "9.0.16",
+        "nodebb-theme-slick": "1.2.5",
+        "nodebb-theme-vanilla": "10.0.14",
+        "nodebb-widget-essentials": "4.0.7",
         "nodemailer": "^4.6.5",
         "passport": "^0.4.0",
         "passport-local": "1.0.0",
diff --git a/public/src/client/popular.js b/public/src/client/popular.js
index c94b8d912f..f6af68cc22 100644
--- a/public/src/client/popular.js
+++ b/public/src/client/popular.js
@@ -7,10 +7,7 @@ define('forum/popular', ['forum/recent', 'components', 'forum/infinitescroll'],
 	Popular.init = function () {
 		app.enterRoom('popular_topics');
 
-		components.get('popular/tab')
-			.removeClass('active')
-			.find('a[href="' + window.location.pathname + '"]')
-			.parent().addClass('active');
+		recent.handleCategorySelection();
 
 		if (!config.usePagination) {
 			infinitescroll.init(loadMoreTopics);
@@ -25,7 +22,9 @@ define('forum/popular', ['forum/recent', 'components', 'forum/infinitescroll'],
 		infinitescroll.loadMore('topics.loadMorePopularTopics', {
 			after: $('[component="category"]').attr('data-nextstart'),
 			count: config.topicsPerPage,
-			term: ajaxify.data.term,
+			cid: utils.params().cid,
+			term: ajaxify.data.selectedTerm.term,
+			filter: ajaxify.data.selectedFilter.filter,
 		}, function (data, done) {
 			if (data.topics && data.topics.length) {
 				recent.onTopicsLoaded('popular', data.topics, false, direction, done);
diff --git a/public/src/client/recent.js b/public/src/client/recent.js
index 71342661df..72eb7dd9ce 100644
--- a/public/src/client/recent.js
+++ b/public/src/client/recent.js
@@ -124,9 +124,11 @@ define('forum/recent', ['forum/infinitescroll', 'components', 'handleBack'], fun
 			});
 
 			if (changed) {
-				var url = ajaxify.data.selectedFilter.url;
+				var url = window.location.pathname;
+				var currentParams = utils.params();
 				if (cids.length) {
-					url += '?' + decodeURIComponent($.param({ cid: cids }));
+					currentParams.cid = cids;
+					url += '?' + decodeURIComponent($.param(currentParams));
 				}
 				ajaxify.go(url);
 			}
diff --git a/public/src/client/top.js b/public/src/client/top.js
index e0c975fdea..95e2cfdf44 100644
--- a/public/src/client/top.js
+++ b/public/src/client/top.js
@@ -36,6 +36,7 @@ define('forum/top', ['forum/recent', 'forum/infinitescroll'], function (recent,
 			after: $('[component="category"]').attr('data-nextstart'),
 			count: config.topicsPerPage,
 			cid: utils.params().cid,
+			term: ajaxify.data.selectedTerm.term,
 			filter: ajaxify.data.selectedFilter.filter,
 		}, function (data, done) {
 			if (data.topics && data.topics.length) {
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 21dccd0044..4b41c62dfb 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -4,6 +4,7 @@ var nconf = require('nconf');
 var async = require('async');
 var validator = require('validator');
 var winston = require('winston');
+var querystring = require('querystring');
 
 var user = require('../user');
 var privileges = require('../privileges');
@@ -34,30 +35,78 @@ helpers.noScriptErrors = function (req, res, error, httpStatus) {
 
 helpers.validFilters = { '': true, new: true, watched: true, unreplied: true };
 
-helpers.buildFilters = function (url, filter) {
+helpers.terms = {
+	daily: 'day',
+	weekly: 'week',
+	monthly: 'month',
+};
+
+helpers.buildQueryString = function (cid, filter, term) {
+	var qs = {};
+	if (cid) {
+		qs.cid = cid;
+	}
+	if (filter) {
+		qs.filter = filter;
+	}
+	if (term) {
+		qs.term = term;
+	}
+
+	if (Object.keys(qs).length) {
+		return '?' + querystring.stringify(qs);
+	}
+	return '';
+};
+
+helpers.buildFilters = function (url, filter, query) {
 	return [{
 		name: '[[unread:all-topics]]',
-		url: url,
+		url: url + helpers.buildQueryString(query.cid, '', query.term),
 		selected: filter === '',
 		filter: '',
 	}, {
 		name: '[[unread:new-topics]]',
-		url: url + '/new',
+		url: url + helpers.buildQueryString(query.cid, 'new', query.term),
 		selected: filter === 'new',
 		filter: 'new',
 	}, {
 		name: '[[unread:watched-topics]]',
-		url: url + '/watched',
+		url: url + helpers.buildQueryString(query.cid, 'watched', query.term),
 		selected: filter === 'watched',
 		filter: 'watched',
 	}, {
 		name: '[[unread:unreplied-topics]]',
-		url: url + '/unreplied',
+		url: url + helpers.buildQueryString(query.cid, 'unreplied', query.term),
 		selected: filter === 'unreplied',
 		filter: 'unreplied',
 	}];
 };
 
+helpers.buildTerms = function (url, term, query) {
+	return [{
+		name: '[[recent:alltime]]',
+		url: url + helpers.buildQueryString(query.cid, query.filter, ''),
+		selected: term === 'alltime',
+		term: 'alltime',
+	}, {
+		name: '[[recent:day]]',
+		url: url + helpers.buildQueryString(query.cid, query.filter, 'daily'),
+		selected: term === 'day',
+		term: 'day',
+	}, {
+		name: '[[recent:week]]',
+		url: url + helpers.buildQueryString(query.cid, query.filter, 'weekly'),
+		selected: term === 'week',
+		term: 'week',
+	}, {
+		name: '[[recent:month]]',
+		url: url + helpers.buildQueryString(query.cid, query.filter, 'monthly'),
+		selected: term === 'month',
+		term: 'month',
+	}];
+};
+
 helpers.notAllowed = function (req, res, error) {
 	plugins.fireHook('filter:helpers.notAllowed', {
 		req: req,
diff --git a/src/controllers/popular.js b/src/controllers/popular.js
index cdb0bebe5b..f07fdc780f 100644
--- a/src/controllers/popular.js
+++ b/src/controllers/popular.js
@@ -4,81 +4,28 @@
 var async = require('async');
 var nconf = require('nconf');
 
-var topics = require('../topics');
-var meta = require('../meta');
-var user = require('../user');
+
 var helpers = require('./helpers');
-var pagination = require('../pagination');
+var recentController = require('./recent');
 
 var popularController = module.exports;
 
-var anonCache = {};
-var lastUpdateTime = 0;
-
-var terms = {
-	daily: 'day',
-	weekly: 'week',
-	monthly: 'month',
-};
-
 popularController.get = function (req, res, next) {
-	var page = parseInt(req.query.page, 10) || 1;
-	var term = terms[req.params.term];
-
-	if (!term && req.params.term) {
-		return next();
-	}
-	term = term || 'alltime';
-
-	var termToBreadcrumb = {
-		day: '[[recent:day]]',
-		week: '[[recent:week]]',
-		month: '[[recent:month]]',
-		alltime: '[[global:header.popular]]',
-	};
-
-	if (!req.loggedIn) {
-		if (anonCache[term] && anonCache[term][page] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) {
-			return res.render('popular', anonCache[term][page]);
-		}
-	}
-	var settings;
 	async.waterfall([
 		function (next) {
-			user.getSettings(req.uid, next);
-		},
-		function (_settings, next) {
-			settings = _settings;
-			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
-			var stop = start + settings.topicsPerPage - 1;
-			topics.getPopularTopics(term, req.uid, start, stop, next);
+			recentController.getData(req, 'popular', 'posts', next);
 		},
-		function (data) {
-			var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
-
-			data.title = meta.config.homePageTitle || '[[pages:home]]';
-			data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
-			data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (req.params.term || 'alltime') + '.rss';
-			data.term = term;
-			data.pagination = pagination.create(page, pageCount, req.query);
-
+		function (data, next) {
+			if (!data) {
+				return next();
+			}
+			var term = helpers.terms[req.query.term] || 'alltime';
 			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/popular') || req.originalUrl.startsWith(nconf.get('relative_path') + '/popular')) {
 				data.title = '[[pages:popular-' + term + ']]';
-				var breadcrumbs = [{ text: termToBreadcrumb[term] }];
-
-				if (req.params.term) {
-					breadcrumbs.unshift({ text: '[[global:header.popular]]', url: '/popular' });
-				}
-
+				var breadcrumbs = [{ text: '[[global:header.popular]]' }];
 				data.breadcrumbs = helpers.buildBreadcrumbs(breadcrumbs);
 			}
-
-			if (!req.loggedIn) {
-				anonCache[term] = anonCache[term] || {};
-				anonCache[term][page] = data;
-				lastUpdateTime = Date.now();
-			}
-
+			data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (req.query.term || 'alltime') + '.rss';
 			res.render('popular', data);
 		},
 	], next);
diff --git a/src/controllers/recent.js b/src/controllers/recent.js
index 1ef041604f..e0d037d0f6 100644
--- a/src/controllers/recent.js
+++ b/src/controllers/recent.js
@@ -3,7 +3,6 @@
 
 var async = require('async');
 var nconf = require('nconf');
-var querystring = require('querystring');
 
 var user = require('../user');
 var topics = require('../topics');
@@ -14,17 +13,33 @@ var pagination = require('../pagination');
 var recentController = module.exports;
 
 recentController.get = function (req, res, next) {
+	async.waterfall([
+		function (next) {
+			recentController.getData(req, 'recent', 'recent', next);
+		},
+		function (data, next) {
+			if (!data) {
+				return next();
+			}
+			res.render('recent', data);
+		},
+	], next);
+};
+
+recentController.getData = function (req, url, sort, callback) {
 	var page = parseInt(req.query.page, 10) || 1;
 	var stop = 0;
+	var term = helpers.terms[req.query.term];
 	var settings;
 	var cid = req.query.cid;
-	var filter = req.params.filter || '';
+	var filter = req.query.filter || '';
 	var categoryData;
 	var rssToken;
 
-	if (!helpers.validFilters[filter]) {
-		return next();
+	if (!helpers.validFilters[filter] || (!term && req.query.term)) {
+		return callback();
 	}
+	term = term || 'alltime';
 
 	async.waterfall([
 		function (next) {
@@ -48,37 +63,47 @@ recentController.get = function (req, res, next) {
 			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
 			stop = start + settings.topicsPerPage - 1;
 
-			topics.getRecentTopics(cid, req.uid, start, stop, filter, next);
+			topics.getSortedTopics({
+				cids: cid,
+				uid: req.uid,
+				start: start,
+				stop: stop,
+				filter: filter,
+				term: term,
+				sort: sort,
+			}, next);
 		},
-		function (data) {
+		function (data, next) {
 			data.categories = categoryData.categories;
+			data.allCategoriesUrl = url + helpers.buildQueryString('', filter, '');
 			data.selectedCategory = categoryData.selectedCategory;
 			data.selectedCids = categoryData.selectedCids;
 			data.nextStart = stop + 1;
-			data.set = 'topics:recent';
 			data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
-			data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
+			data.rssFeedUrl = nconf.get('relative_path') + '/' + url + '.rss';
 			if (req.loggedIn) {
 				data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
 			}
 			data.title = meta.config.homePageTitle || '[[pages:home]]';
-			data.filters = helpers.buildFilters('recent', filter);
 
+			data.filters = helpers.buildFilters(url, filter, req.query);
 			data.selectedFilter = data.filters.find(function (filter) {
 				return filter && filter.selected;
 			});
+			data.terms = helpers.buildTerms(url, term, req.query);
+			data.selectedTerm = data.terms.find(function (term) {
+				return term && term.selected;
+			});
 
 			var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
 			data.pagination = pagination.create(page, pageCount, req.query);
 
-			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/recent') || req.originalUrl.startsWith(nconf.get('relative_path') + '/recent')) {
-				data.title = '[[pages:recent]]';
-				data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[recent:title]]' }]);
+			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/' + url) || req.originalUrl.startsWith(nconf.get('relative_path') + '/' + url)) {
+				data.title = '[[pages:' + url + ']]';
+				data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[' + url + ':title]]' }]);
 			}
 
-			data.querystring = cid ? '?' + querystring.stringify({ cid: cid }) : '';
-
-			res.render('recent', data);
+			next(null, data);
 		},
-	], next);
+	], callback);
 };
diff --git a/src/controllers/top.js b/src/controllers/top.js
index 3be4fb261d..b471acac60 100644
--- a/src/controllers/top.js
+++ b/src/controllers/top.js
@@ -2,82 +2,19 @@
 'use strict';
 
 var async = require('async');
-var nconf = require('nconf');
-var querystring = require('querystring');
-
-var user = require('../user');
-var topics = require('../topics');
-var meta = require('../meta');
-var helpers = require('./helpers');
-var pagination = require('../pagination');
+var recentController = require('./recent');
 
 var topController = module.exports;
 
 topController.get = function (req, res, next) {
-	var page = parseInt(req.query.page, 10) || 1;
-	var stop = 0;
-	var settings;
-	var cid = req.query.cid;
-	var filter = req.params.filter || '';
-	var categoryData;
-	var rssToken;
-
-	if (!helpers.validFilters[filter]) {
-		return next();
-	}
-
 	async.waterfall([
 		function (next) {
-			async.parallel({
-				settings: function (next) {
-					user.getSettings(req.uid, next);
-				},
-				watchedCategories: function (next) {
-					helpers.getWatchedCategories(req.uid, cid, next);
-				},
-				rssToken: function (next) {
-					user.auth.getFeedToken(req.uid, next);
-				},
-			}, next);
-		},
-		function (results, next) {
-			rssToken = results.rssToken;
-			settings = results.settings;
-			categoryData = results.watchedCategories;
-
-			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
-			stop = start + settings.topicsPerPage - 1;
-
-			topics.getTopTopics(cid, req.uid, start, stop, filter, next);
+			recentController.getData(req, 'top', 'votes', next);
 		},
-		function (data) {
-			data.categories = categoryData.categories;
-			data.selectedCategory = categoryData.selectedCategory;
-			data.selectedCids = categoryData.selectedCids;
-			data.nextStart = stop + 1;
-			data.set = 'topics:votes';
-			data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
-			data.rssFeedUrl = nconf.get('relative_path') + '/top.rss';
-			if (req.loggedIn) {
-				data.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
+		function (data, next) {
+			if (!data) {
+				return next();
 			}
-			data.title = meta.config.homePageTitle || '[[pages:home]]';
-			data.filters = helpers.buildFilters('top', filter);
-
-			data.selectedFilter = data.filters.find(function (filter) {
-				return filter && filter.selected;
-			});
-
-			var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
-			data.pagination = pagination.create(page, pageCount, req.query);
-
-			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/top') || req.originalUrl.startsWith(nconf.get('relative_path') + '/top')) {
-				data.title = '[[pages:top]]';
-				data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[top:title]]' }]);
-			}
-
-			data.querystring = cid ? '?' + querystring.stringify({ cid: cid }) : '';
-
 			res.render('top', data);
 		},
 	], next);
diff --git a/src/controllers/unread.js b/src/controllers/unread.js
index d7de3d8e91..500cfe11a6 100644
--- a/src/controllers/unread.js
+++ b/src/controllers/unread.js
@@ -18,7 +18,7 @@ unreadController.get = function (req, res, next) {
 	var page = parseInt(req.query.page, 10) || 1;
 	var results;
 	var cid = req.query.cid;
-	var filter = req.params.filter || '';
+	var filter = req.query.filter || '';
 	var settings;
 
 	async.waterfall([
@@ -71,6 +71,7 @@ unreadController.get = function (req, res, next) {
 			}
 
 			data.categories = results.watchedCategories.categories;
+			data.allCategoriesUrl = 'unread' + helpers.buildQueryString('', filter, '');
 			data.selectedCategory = results.watchedCategories.selectedCategory;
 			data.selectedCids = results.watchedCategories.selectedCids;
 			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/unread') || req.originalUrl.startsWith(nconf.get('relative_path') + '/unread')) {
@@ -78,20 +79,19 @@ unreadController.get = function (req, res, next) {
 				data.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[unread:title]]' }]);
 			}
 
-			data.filters = helpers.buildFilters('unread', filter);
+			data.filters = helpers.buildFilters('unread', filter, req.query);
 
 			data.selectedFilter = data.filters.find(function (filter) {
 				return filter && filter.selected;
 			});
 
-			data.querystring = cid ? '?' + querystring.stringify({ cid: cid }) : '';
 			res.render('unread', data);
 		},
 	], next);
 };
 
 unreadController.unreadTotal = function (req, res, next) {
-	var filter = req.params.filter || '';
+	var filter = req.query.filter || '';
 
 	async.waterfall([
 		function (next) {
diff --git a/src/routes/api.js b/src/routes/api.js
index 425ce815d0..012f0b26f0 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -25,7 +25,7 @@ module.exports = function (app, middleware, controllers) {
 
 	router.get('/categories/:cid/moderators', controllers.api.getModerators);
 	router.get('/recent/posts/:term?', controllers.posts.getRecentPosts);
-	router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal);
+	router.get('/unread/total', middleware.authenticate, controllers.unread.unreadTotal);
 	router.get('/topic/teaser/:topic_id', controllers.topics.teaser);
 	router.get('/topic/pagination/:topic_id', controllers.topics.pagination);
 
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index a60b3b4f3d..f800ab806d 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -197,11 +197,8 @@ function generateForRecent(req, res, next) {
 			}
 		},
 		function (token, next) {
-			next(null, token && token === req.query.token ? req.query.uid : req.uid);
-		},
-		function (uid, next) {
 			generateForTopics({
-				uid: uid,
+				uid: token && token === req.query.token ? req.query.uid : req.uid,
 				title: 'Recently Active Topics',
 				description: 'A list of topics that have been active within the past 24 hours',
 				feed_url: '/recent.rss',
@@ -225,11 +222,8 @@ function generateForTop(req, res, next) {
 			}
 		},
 		function (token, next) {
-			next(null, token && token === req.query.token ? req.query.uid : req.uid);
-		},
-		function (uid, next) {
 			generateForTopics({
-				uid: uid,
+				uid: token && token === req.query.token ? req.query.uid : req.uid,
 				title: 'Top Voted Topics',
 				description: 'A list of topics that have received the most votes',
 				feed_url: '/top.rss',
@@ -250,14 +244,29 @@ function generateForPopular(req, res, next) {
 		alltime: 'alltime',
 	};
 	var term = terms[req.params.term] || 'day';
-
+	var uid;
 	async.waterfall([
 		function (next) {
-			topics.getPopularTopics(term, req.uid, 0, 19, next);
+			if (req.query.token && req.query.uid) {
+				db.getObjectField('user:' + req.query.uid, 'rss_token', next);
+			} else {
+				next(null, null);
+			}
+		},
+		function (token, next) {
+			uid = token && token === req.query.token ? req.query.uid : req.uid;
+
+			topics.getSortedTopics({
+				uid: uid,
+				start: 0,
+				stop: 19,
+				term: term,
+				sort: 'posts',
+			}, next);
 		},
 		function (result, next) {
 			generateTopicsFeed({
-				uid: req.uid,
+				uid: uid,
 				title: 'Popular Topics',
 				description: 'A list of topics that are sorted by post count',
 				feed_url: '/popular/' + (req.params.term || 'daily') + '.rss',
diff --git a/src/routes/index.js b/src/routes/index.js
index ce0a4045e8..0b5b37c7b2 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -63,10 +63,10 @@ function tagRoutes(app, middleware, controllers) {
 
 function categoryRoutes(app, middleware, controllers) {
 	setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
-	setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.get);
-	setupPageRoute(app, '/recent/:filter?', middleware, [], controllers.recent.get);
-	setupPageRoute(app, '/top/:filter?', middleware, [], controllers.top.get);
-	setupPageRoute(app, '/unread/:filter?', middleware, [middleware.authenticate], controllers.unread.get);
+	setupPageRoute(app, '/popular', middleware, [], controllers.popular.get);
+	setupPageRoute(app, '/recent', middleware, [], controllers.recent.get);
+	setupPageRoute(app, '/top', middleware, [], controllers.top.get);
+	setupPageRoute(app, '/unread', middleware, [middleware.authenticate], controllers.unread.get);
 
 	setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.category.get);
 	setupPageRoute(app, '/category/:category_id/:slug?', middleware, [], controllers.category.get);
diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js
index 343bef113b..54d4289e8c 100644
--- a/src/socket.io/topics/infinitescroll.js
+++ b/src/socket.io/topics/infinitescroll.js
@@ -88,30 +88,22 @@ module.exports = function (SocketTopics) {
 	};
 
 	SocketTopics.loadMoreUnreadTopics = function (socket, data, callback) {
-		loadData(data, callback, function (start, stop) {
-			topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback);
-		});
+		loadData(socket.uid, data, 'unread', callback);
 	};
 
 	SocketTopics.loadMoreRecentTopics = function (socket, data, callback) {
-		loadData(data, callback, function (start, stop) {
-			topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback);
-		});
+		loadData(socket.uid, data, 'recent', callback);
 	};
 
 	SocketTopics.loadMorePopularTopics = function (socket, data, callback) {
-		loadData(data, callback, function (start, stop) {
-			topics.getPopularTopics(data.term, socket.uid, start, stop, callback);
-		});
+		loadData(socket.uid, data, 'posts', callback);
 	};
 
 	SocketTopics.loadMoreTopTopics = function (socket, data, callback) {
-		loadData(data, callback, function (start, stop) {
-			topics.getTopTopics(data.cid, socket.uid, start, stop, data.filter, callback);
-		});
+		loadData(socket.uid, data, 'votes', callback);
 	};
 
-	function loadData(data, callback, loadFn) {
+	function loadData(uid, data, sort, callback) {
 		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
 			return callback(new Error('[[error:invalid-data]]'));
 		}
@@ -123,7 +115,18 @@ module.exports = function (SocketTopics) {
 		var stop = start + Math.max(0, itemsPerPage - 1);
 		start = Math.max(0, start);
 		stop = Math.max(0, stop);
-		loadFn(start, stop);
+		if (sort === 'unread') {
+			return topics.getUnreadTopics({ cid: data.cid, uid: uid, start: start, stop: stop, filter: data.filter }, callback);
+		}
+		topics.getSortedTopics({
+			cids: data.cid,
+			uid: uid,
+			start: start,
+			stop: stop,
+			filter: data.filter,
+			sort: sort,
+			term: data.term,
+		}, callback);
 	}
 
 	SocketTopics.loadMoreFromSet = function (socket, data, callback) {
diff --git a/src/topics.js b/src/topics.js
index 8aa00488a0..5214b6a716 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -20,8 +20,6 @@ require('./topics/create')(Topics);
 require('./topics/delete')(Topics);
 require('./topics/unread')(Topics);
 require('./topics/recent')(Topics);
-require('./topics/popular')(Topics);
-require('./topics/top')(Topics);
 require('./topics/user')(Topics);
 require('./topics/fork')(Topics);
 require('./topics/posts')(Topics);
diff --git a/src/topics/popular.js b/src/topics/popular.js
deleted file mode 100644
index c1ff1ddf8a..0000000000
--- a/src/topics/popular.js
+++ /dev/null
@@ -1,14 +0,0 @@
-
-'use strict';
-
-module.exports = function (Topics) {
-	Topics.getPopularTopics = function (term, uid, start, stop, callback) {
-		Topics.getSortedTopics({
-			uid: uid,
-			start: start,
-			stop: stop,
-			term: term,
-			sort: 'posts',
-		}, callback);
-	};
-};
diff --git a/src/topics/top.js b/src/topics/top.js
deleted file mode 100644
index 0e4c3d991e..0000000000
--- a/src/topics/top.js
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-'use strict';
-
-module.exports = function (Topics) {
-	Topics.getTopTopics = function (cid, uid, start, stop, filter, callback) {
-		Topics.getSortedTopics({
-			cids: cid,
-			uid: uid,
-			start: start,
-			stop: stop,
-			filter: filter,
-			sort: 'votes',
-		}, callback);
-	};
-};
diff --git a/src/user/digest.js b/src/user/digest.js
index 3c74fcdfaa..07bc7f1cde 100644
--- a/src/user/digest.js
+++ b/src/user/digest.js
@@ -158,7 +158,13 @@ Digest.send = function (data, callback) {
 	function getTermTopics(term, uid, start, stop, callback) {
 		async.waterfall([
 			function (next) {
-				topics.getPopularTopics(term, uid, start, stop, next);
+				topics.getSortedTopics({
+					uid: uid,
+					start: start,
+					stop: stop,
+					term: term,
+					sort: 'posts',
+				}, next);
 			},
 			function (data, next) {
 				if (!data.topics.length) {
diff --git a/test/controllers.js b/test/controllers.js
index f23c5a2b40..302ddd62a0 100644
--- a/test/controllers.js
+++ b/test/controllers.js
@@ -2037,7 +2037,7 @@ describe('Controllers', function () {
 		});
 
 		it('should 404 if filter is invalid', function (done) {
-			request(nconf.get('url') + '/api/unread/doesnotexist/total', { jar: jar }, function (err, res) {
+			request(nconf.get('url') + '/api/unread/total?filter=doesnotexist', { jar: jar }, function (err, res) {
 				assert.ifError(err);
 				assert.equal(res.statusCode, 404);
 				done();
@@ -2045,7 +2045,7 @@ describe('Controllers', function () {
 		});
 
 		it('should return total unread count', function (done) {
-			request(nconf.get('url') + '/api/unread/new/total', { jar: jar }, function (err, res, body) {
+			request(nconf.get('url') + '/api/unread/total?filter=new', { jar: jar }, function (err, res, body) {
 				assert.ifError(err);
 				assert.equal(res.statusCode, 200);
 				assert.equal(body, 0);
diff --git a/test/topics.js b/test/topics.js
index af7d22f76e..5629fdf27a 100644
--- a/test/topics.js
+++ b/test/topics.js
@@ -1824,11 +1824,18 @@ describe('Topic\'s', function () {
 		});
 	});
 
-	describe('top topics', function () {
-		it('should get top topics in category', function (done) {
+	describe('sorted topics', function () {
+		it('should get sorted topics in category', function (done) {
 			var filters = ['', 'watched', 'unreplied', 'new'];
 			async.map(filters, function (filter, next) {
-				topics.getTopTopics(topic.categoryId, topic.userId, 0, -1, filter, next);
+				topics.getSortedTopics({
+					cids: [topic.categoryId],
+					uid: topic.userId,
+					start: 0,
+					stop: -1,
+					filter: filter,
+					sort: 'votes',
+				}, next);
 			}, function (err, data) {
 				assert.ifError(err);
 				assert(data);