mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: #7743, finish post module
This commit is contained in:
		| @@ -1,6 +1,5 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
| var url = require('url'); | ||||
| var winston = require('winston'); | ||||
| @@ -21,37 +20,31 @@ module.exports = function (Posts) { | ||||
| 		length: 5, | ||||
| 	}; | ||||
|  | ||||
| 	Posts.parsePost = function (postData, callback) { | ||||
| 	Posts.parsePost = async function (postData) { | ||||
| 		if (!postData) { | ||||
| 			return setImmediate(callback, null, postData); | ||||
| 			return postData; | ||||
| 		} | ||||
| 		postData.content = String(postData.content || ''); | ||||
| 		var cache = require('./cache'); | ||||
| 		if (postData.pid && cache.has(String(postData.pid))) { | ||||
| 			postData.content = cache.get(String(postData.pid)); | ||||
| 		const cache = require('./cache'); | ||||
| 		const pid = String(postData.pid); | ||||
| 		const cachedContent = cache.get(pid); | ||||
| 		if (postData.pid && cachedContent !== undefined) { | ||||
| 			postData.content = cachedContent; | ||||
| 			cache.hits += 1; | ||||
| 			return callback(null, postData); | ||||
| 			return postData; | ||||
| 		} | ||||
| 		cache.misses += 1; | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				plugins.fireHook('filter:parse.post', { postData: postData }, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				data.postData.content = translator.escape(data.postData.content); | ||||
|  | ||||
| 				if (global.env === 'production' && data.postData.pid) { | ||||
| 					cache.set(String(data.postData.pid), data.postData.content); | ||||
| 				} | ||||
| 				next(null, data.postData); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		const data = await plugins.fireHook('filter:parse.post', { postData: postData }); | ||||
| 		data.postData.content = translator.escape(data.postData.content); | ||||
| 		if (global.env === 'production' && data.postData.pid) { | ||||
| 			cache.set(pid, data.postData.content); | ||||
| 		} | ||||
| 		return data.postData; | ||||
| 	}; | ||||
|  | ||||
| 	Posts.parseSignature = function (userData, uid, callback) { | ||||
| 	Posts.parseSignature = async function (userData, uid) { | ||||
| 		userData.signature = sanitizeSignature(userData.signature || ''); | ||||
| 		plugins.fireHook('filter:parse.signature', { userData: userData, uid: uid }, callback); | ||||
| 		return await plugins.fireHook('filter:parse.signature', { userData: userData, uid: uid }); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.relativeToAbsolute = function (content, regex) { | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| const _ = require('lodash'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| @@ -15,296 +14,169 @@ var plugins = require('../plugins'); | ||||
| var socketHelpers = require('../socket.io/helpers'); | ||||
|  | ||||
| module.exports = function (Posts) { | ||||
| 	Posts.shouldQueue = function (uid, data, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				user.getUserFields(uid, ['uid', 'reputation', 'postcount'], next); | ||||
| 			}, | ||||
| 			function (userData, next) { | ||||
| 				const shouldQueue = meta.config.postQueue && (!userData.uid || userData.reputation < meta.config.newbiePostDelayThreshold || userData.postcount <= 0); | ||||
| 				plugins.fireHook('filter:post.shouldQueue', { | ||||
| 					shouldQueue: shouldQueue, | ||||
| 					uid: uid, | ||||
| 					data: data, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (result, next) { | ||||
| 				next(null, result.shouldQueue); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.shouldQueue = async function (uid, data) { | ||||
| 		const userData = await user.getUserFields(uid, ['uid', 'reputation', 'postcount']); | ||||
| 		const shouldQueue = meta.config.postQueue && (!userData.uid || userData.reputation < meta.config.newbiePostDelayThreshold || userData.postcount <= 0); | ||||
| 		const result = await plugins.fireHook('filter:post.shouldQueue', { | ||||
| 			shouldQueue: !!shouldQueue, | ||||
| 			uid: uid, | ||||
| 			data: data, | ||||
| 		}); | ||||
| 		return result.shouldQueue; | ||||
| 	}; | ||||
|  | ||||
| 	function removeQueueNotification(id, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				notifications.rescind('post-queue-' + id, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				getParsedObject(id, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				if (!data) { | ||||
| 					return callback(); | ||||
| 				} | ||||
| 				getCid(data.type, data, next); | ||||
| 			}, | ||||
| 			function (cid, next) { | ||||
| 				getNotificationUids(cid, next); | ||||
| 			}, | ||||
| 			function (uids, next) { | ||||
| 				uids.forEach(uid => user.notifications.pushCount(uid)); | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function removeQueueNotification(id) { | ||||
| 		await notifications.rescind('post-queue-' + id); | ||||
| 		const data = await getParsedObject(id); | ||||
| 		if (!data) { | ||||
| 			return; | ||||
| 		} | ||||
| 		const cid = await getCid(data.type, data); | ||||
| 		const uids = await getNotificationUids(cid); | ||||
| 		uids.forEach(uid => user.notifications.pushCount(uid)); | ||||
| 	} | ||||
|  | ||||
| 	function getNotificationUids(cid, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				async.parallel([ | ||||
| 					async.apply(groups.getMembersOfGroups, ['administrators', 'Global Moderators']), | ||||
| 					async.apply(categories.getModeratorUids, [cid]), | ||||
| 				], next); | ||||
| 			}, | ||||
| 			function (results, next) { | ||||
| 				next(null, _.uniq(_.flattenDeep(results))); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function getNotificationUids(cid) { | ||||
| 		const results = await Promise.all([ | ||||
| 			groups.getMembersOfGroups(['administrators', 'Global Moderators']), | ||||
| 			categories.getModeratorUids([cid]), | ||||
| 		]); | ||||
| 		return _.uniq(_.flattenDeep(results)); | ||||
| 	} | ||||
|  | ||||
| 	Posts.addToQueue = function (data, callback) { | ||||
| 		var type = data.title ? 'topic' : 'reply'; | ||||
| 		var id = type + '-' + Date.now(); | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				canPost(type, data, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				db.sortedSetAdd('post:queue', Date.now(), id, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				db.setObject('post:queue:' + id, { | ||||
| 					id: id, | ||||
| 					uid: data.uid, | ||||
| 					type: type, | ||||
| 					data: JSON.stringify(data), | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				user.setUserField(data.uid, 'lastqueuetime', Date.now(), next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				async.parallel({ | ||||
| 					notification: function (next) { | ||||
| 						notifications.create({ | ||||
| 							type: 'post-queue', | ||||
| 							nid: 'post-queue-' + id, | ||||
| 							mergeId: 'post-queue', | ||||
| 							bodyShort: '[[notifications:post_awaiting_review]]', | ||||
| 							bodyLong: data.content, | ||||
| 							path: '/post-queue', | ||||
| 						}, next); | ||||
| 					}, | ||||
| 					uids: function (next) { | ||||
| 						async.waterfall([ | ||||
| 							function (next) { | ||||
| 								getCid(type, data, next); | ||||
| 							}, | ||||
| 							function (cid, next) { | ||||
| 								getNotificationUids(cid, next); | ||||
| 							}, | ||||
| 						], next); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (results, next) { | ||||
| 				if (results.notification) { | ||||
| 					notifications.push(results.notification, results.uids, next); | ||||
| 				} else { | ||||
| 					next(); | ||||
| 				} | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				next(null, { | ||||
| 					id: id, | ||||
| 					type: type, | ||||
| 					queued: true, | ||||
| 					message: '[[success:post-queued]]', | ||||
| 				}); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.addToQueue = async function (data) { | ||||
| 		const type = data.title ? 'topic' : 'reply'; | ||||
| 		const now = Date.now(); | ||||
| 		const id = type + '-' + now; | ||||
| 		await canPost(type, data); | ||||
| 		await db.sortedSetAdd('post:queue', now, id); | ||||
| 		await db.setObject('post:queue:' + id, { | ||||
| 			id: id, | ||||
| 			uid: data.uid, | ||||
| 			type: type, | ||||
| 			data: JSON.stringify(data), | ||||
| 		}); | ||||
| 		await user.setUserField(data.uid, 'lastqueuetime', now); | ||||
|  | ||||
| 		const cid = await getCid(type, data); | ||||
| 		const uids = await getNotificationUids(cid); | ||||
| 		const notifObj = await notifications.create({ | ||||
| 			type: 'post-queue', | ||||
| 			nid: 'post-queue-' + id, | ||||
| 			mergeId: 'post-queue', | ||||
| 			bodyShort: '[[notifications:post_awaiting_review]]', | ||||
| 			bodyLong: data.content, | ||||
| 			path: '/post-queue', | ||||
| 		}); | ||||
| 		await notifications.push(notifObj, uids); | ||||
| 		return { | ||||
| 			id: id, | ||||
| 			type: type, | ||||
| 			queued: true, | ||||
| 			message: '[[success:post-queued]]', | ||||
| 		}; | ||||
| 	}; | ||||
|  | ||||
| 	function getCid(type, data, callback) { | ||||
| 	async function getCid(type, data) { | ||||
| 		if (type === 'topic') { | ||||
| 			return setImmediate(callback, null, data.cid); | ||||
| 			return data.cid; | ||||
| 		} else if (type === 'reply') { | ||||
| 			topics.getTopicField(data.tid, 'cid', callback); | ||||
| 		} else { | ||||
| 			return setImmediate(callback, null, null); | ||||
| 			return await topics.getTopicField(data.tid, 'cid'); | ||||
| 		} | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	async function canPost(type, data) { | ||||
| 		const cid = await getCid(type, data); | ||||
| 		const typeToPrivilege = { | ||||
| 			topic: 'topics:create', | ||||
| 			reply: 'topics:reply', | ||||
| 		}; | ||||
|  | ||||
| 		const [canPost] = await Promise.all([ | ||||
| 			privileges.categories.can(typeToPrivilege[type], cid, data.uid), | ||||
| 			user.isReadyToQueue(data.uid, cid), | ||||
| 		]); | ||||
| 		if (!canPost) { | ||||
| 			throw new Error('[[error:no-privileges]]'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function canPost(type, data, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				getCid(type, data, next); | ||||
| 			}, | ||||
| 			function (cid, next) { | ||||
| 				async.parallel({ | ||||
| 					canPost: function (next) { | ||||
| 						if (type === 'topic') { | ||||
| 							privileges.categories.can('topics:create', cid, data.uid, next); | ||||
| 						} else if (type === 'reply') { | ||||
| 							privileges.categories.can('topics:reply', cid, data.uid, next); | ||||
| 						} | ||||
| 					}, | ||||
| 					isReadyToQueue: function (next) { | ||||
| 						user.isReadyToQueue(data.uid, cid, next); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (results, next) { | ||||
| 				if (!results.canPost) { | ||||
| 					return next(new Error('[[error:no-privileges]]')); | ||||
| 				} | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	} | ||||
|  | ||||
| 	Posts.removeFromQueue = function (id, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				removeQueueNotification(id, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				db.sortedSetRemove('post:queue', id, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				db.delete('post:queue:' + id, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.removeFromQueue = async function (id) { | ||||
| 		await removeQueueNotification(id); | ||||
| 		await db.sortedSetRemove('post:queue', id); | ||||
| 		await db.delete('post:queue:' + id); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.submitFromQueue = function (id, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				getParsedObject(id, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				if (!data) { | ||||
| 					return callback(); | ||||
| 				} | ||||
| 				if (data.type === 'topic') { | ||||
| 					createTopic(data.data, next); | ||||
| 				} else if (data.type === 'reply') { | ||||
| 					createReply(data.data, next); | ||||
| 				} | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				Posts.removeFromQueue(id, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.submitFromQueue = async function (id) { | ||||
| 		const data = await getParsedObject(id); | ||||
| 		if (!data) { | ||||
| 			return; | ||||
| 		} | ||||
| 		if (data.type === 'topic') { | ||||
| 			await createTopic(data.data); | ||||
| 		} else if (data.type === 'reply') { | ||||
| 			await createReply(data.data); | ||||
| 		} | ||||
| 		await Posts.removeFromQueue(id); | ||||
| 	}; | ||||
|  | ||||
| 	function getParsedObject(id, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getObject('post:queue:' + id, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				if (!data) { | ||||
| 					return callback(null, null); | ||||
| 				} | ||||
| 				try { | ||||
| 					data.data = JSON.parse(data.data); | ||||
| 				} catch (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
| 				next(null, data); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function getParsedObject(id) { | ||||
| 		const data = await db.getObject('post:queue:' + id); | ||||
| 		if (!data) { | ||||
| 			return null; | ||||
| 		} | ||||
| 		data.data = JSON.parse(data.data); | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| 	function createTopic(data, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				topics.post(data, next); | ||||
| 			}, | ||||
| 			function (result, next) { | ||||
| 				socketHelpers.notifyNew(data.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function createTopic(data) { | ||||
| 		const result = await topics.post(data); | ||||
| 		socketHelpers.notifyNew(data.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); | ||||
| 	} | ||||
|  | ||||
| 	function createReply(data, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				topics.reply(data, next); | ||||
| 			}, | ||||
| 			function (postData, next) { | ||||
| 				var result = { | ||||
| 					posts: [postData], | ||||
| 					'reputation:disabled': !!meta.config['reputation:disabled'], | ||||
| 					'downvote:disabled': !!meta.config['downvote:disabled'], | ||||
| 				}; | ||||
| 				socketHelpers.notifyNew(data.uid, 'newPost', result); | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function createReply(data) { | ||||
| 		const postData = await topics.reply(data); | ||||
| 		const result = { | ||||
| 			posts: [postData], | ||||
| 			'reputation:disabled': !!meta.config['reputation:disabled'], | ||||
| 			'downvote:disabled': !!meta.config['downvote:disabled'], | ||||
| 		}; | ||||
| 		socketHelpers.notifyNew(data.uid, 'newPost', result); | ||||
| 	} | ||||
|  | ||||
| 	Posts.editQueuedContent = function (uid, id, content, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				Posts.canEditQueue(uid, id, next); | ||||
| 			}, | ||||
| 			function (canEditQueue, next) { | ||||
| 				if (!canEditQueue) { | ||||
| 					return callback(new Error('[[error:no-privileges]]')); | ||||
| 				} | ||||
| 				getParsedObject(id, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				if (!data) { | ||||
| 					return callback(); | ||||
| 				} | ||||
| 				data.data.content = content; | ||||
| 				db.setObjectField('post:queue:' + id, 'data', JSON.stringify(data.data), next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.editQueuedContent = async function (uid, id, content) { | ||||
| 		const canEditQueue = await Posts.canEditQueue(uid, id); | ||||
| 		if (!canEditQueue) { | ||||
| 			throw new Error('[[error:no-privileges]]'); | ||||
| 		} | ||||
| 		const data = await getParsedObject(id); | ||||
| 		if (!data) { | ||||
| 			return; | ||||
| 		} | ||||
| 		data.data.content = content; | ||||
| 		await db.setObjectField('post:queue:' + id, 'data', JSON.stringify(data.data)); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.canEditQueue = function (uid, id, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				async.parallel({ | ||||
| 					isAdminOrGlobalMod: function (next) { | ||||
| 						user.isAdminOrGlobalMod(uid, next); | ||||
| 					}, | ||||
| 					data: function (next) { | ||||
| 						getParsedObject(id, next); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (results, next) { | ||||
| 				if (results.isAdminOrGlobalMod) { | ||||
| 					return callback(null, true); | ||||
| 				} | ||||
| 				if (!results.data) { | ||||
| 					return callback(null, false); | ||||
| 				} | ||||
| 				if (results.data.type === 'topic') { | ||||
| 					next(null, results.data.data.cid); | ||||
| 				} else if (results.data.type === 'reply') { | ||||
| 					topics.getTopicField(results.data.data.tid, 'cid', next); | ||||
| 				} | ||||
| 			}, | ||||
| 			function (cid, next) { | ||||
| 				user.isModerator(uid, cid, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.canEditQueue = async function (uid, id) { | ||||
| 		const [isAdminOrGlobalMod, data] = await Promise.all([ | ||||
| 			user.isAdminOrGlobalMod(uid), | ||||
| 			getParsedObject(id), | ||||
| 		]); | ||||
| 		if (!data) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if (isAdminOrGlobalMod) { | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		let cid; | ||||
| 		if (data.type === 'topic') { | ||||
| 			cid = data.data.cid; | ||||
| 		} else if (data.type === 'reply') { | ||||
| 			cid = await topics.getTopicField(data.data.tid, 'cid'); | ||||
| 		} | ||||
| 		return await user.isModerator(uid, cid); | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -1,53 +1,33 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var _ = require('lodash'); | ||||
| const _ = require('lodash'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| var privileges = require('../privileges'); | ||||
| const db = require('../database'); | ||||
| const privileges = require('../privileges'); | ||||
|  | ||||
|  | ||||
| module.exports = function (Posts) { | ||||
| 	var terms = { | ||||
| 	const terms = { | ||||
| 		day: 86400000, | ||||
| 		week: 604800000, | ||||
| 		month: 2592000000, | ||||
| 	}; | ||||
|  | ||||
| 	Posts.getRecentPosts = function (uid, start, stop, term, callback) { | ||||
| 		var min = 0; | ||||
| 	Posts.getRecentPosts = async function (uid, start, stop, term) { | ||||
| 		let min = 0; | ||||
| 		if (terms[term]) { | ||||
| 			min = Date.now() - terms[term]; | ||||
| 		} | ||||
|  | ||||
| 		var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getSortedSetRevRangeByScore('posts:pid', start, count, '+inf', min, next); | ||||
| 			}, | ||||
| 			function (pids, next) { | ||||
| 				privileges.posts.filter('topics:read', pids, uid, next); | ||||
| 			}, | ||||
| 			function (pids, next) { | ||||
| 				Posts.getPostSummaryByPids(pids, uid, { stripTags: true }, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		const count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; | ||||
| 		let pids = await db.getSortedSetRevRangeByScore('posts:pid', start, count, '+inf', min); | ||||
| 		pids = await privileges.posts.filter('topics:read', pids, uid); | ||||
| 		return await Posts.getPostSummaryByPids(pids, uid, { stripTags: true }); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.getRecentPosterUids = function (start, stop, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getSortedSetRevRange('posts:pid', start, stop, next); | ||||
| 			}, | ||||
| 			function (pids, next) { | ||||
| 				Posts.getPostsFields(pids, ['uid'], next); | ||||
| 			}, | ||||
| 			function (postData, next) { | ||||
| 				var uids = _.uniq(postData.map(post => post && post.uid).filter(uid => parseInt(uid, 10))); | ||||
|  | ||||
| 				next(null, uids); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.getRecentPosterUids = async function (start, stop) { | ||||
| 		const pids = await db.getSortedSetRevRange('posts:pid', start, stop); | ||||
| 		const postData = await Posts.getPostsFields(pids, ['uid']); | ||||
| 		return _.uniq(postData.map(p => p && p.uid).filter(uid => parseInt(uid, 10))); | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -1,123 +1,83 @@ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var validator = require('validator'); | ||||
| var _ = require('lodash'); | ||||
| const validator = require('validator'); | ||||
| const _ = require('lodash'); | ||||
|  | ||||
| var topics = require('../topics'); | ||||
| var user = require('../user'); | ||||
| var plugins = require('../plugins'); | ||||
| var categories = require('../categories'); | ||||
| var utils = require('../utils'); | ||||
| const topics = require('../topics'); | ||||
| const user = require('../user'); | ||||
| const plugins = require('../plugins'); | ||||
| const categories = require('../categories'); | ||||
| const utils = require('../utils'); | ||||
|  | ||||
| module.exports = function (Posts) { | ||||
| 	Posts.getPostSummaryByPids = function (pids, uid, options, callback) { | ||||
| 	Posts.getPostSummaryByPids = async function (pids, uid, options) { | ||||
| 		if (!Array.isArray(pids) || !pids.length) { | ||||
| 			return callback(null, []); | ||||
| 			return []; | ||||
| 		} | ||||
|  | ||||
| 		options.stripTags = options.hasOwnProperty('stripTags') ? options.stripTags : false; | ||||
| 		options.parse = options.hasOwnProperty('parse') ? options.parse : true; | ||||
| 		options.extraFields = options.hasOwnProperty('extraFields') ? options.extraFields : []; | ||||
|  | ||||
| 		var fields = ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes'].concat(options.extraFields); | ||||
| 		const fields = ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes'].concat(options.extraFields); | ||||
|  | ||||
| 		var posts; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				Posts.getPostsFields(pids, fields, next); | ||||
| 			}, | ||||
| 			function (_posts, next) { | ||||
| 				posts = _posts.filter(Boolean); | ||||
| 				user.blocks.filter(uid, posts, next); | ||||
| 			}, | ||||
| 			function (_posts, next) { | ||||
| 				posts = _posts; | ||||
| 				var uids = {}; | ||||
| 				var topicKeys = {}; | ||||
| 		let posts = await Posts.getPostsFields(pids, fields); | ||||
| 		posts = posts.filter(Boolean); | ||||
| 		posts = await user.blocks.filter(uid, posts); | ||||
|  | ||||
| 				posts.forEach(function (post) { | ||||
| 					uids[post.uid] = 1; | ||||
| 					topicKeys[post.tid] = 1; | ||||
| 				}); | ||||
| 				async.parallel({ | ||||
| 					users: function (next) { | ||||
| 						user.getUsersFields(Object.keys(uids), ['uid', 'username', 'userslug', 'picture', 'status'], next); | ||||
| 					}, | ||||
| 					topicsAndCategories: function (next) { | ||||
| 						getTopicAndCategories(Object.keys(topicKeys), next); | ||||
| 					}, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 			function (results, next) { | ||||
| 				results.users = toObject('uid', results.users); | ||||
| 				results.topics = toObject('tid', results.topicsAndCategories.topics); | ||||
| 				results.categories = toObject('cid', results.topicsAndCategories.categories); | ||||
| 		const uids = _.uniq(posts.map(p => p && p.uid)); | ||||
| 		const tids = _.uniq(posts.map(p => p && p.tid)); | ||||
|  | ||||
| 				posts.forEach(function (post) { | ||||
| 					// If the post author isn't represented in the retrieved users' data, then it means they were deleted, assume guest. | ||||
| 					if (!results.users.hasOwnProperty(post.uid)) { | ||||
| 						post.uid = 0; | ||||
| 					} | ||||
| 					post.user = results.users[post.uid]; | ||||
| 					post.topic = results.topics[post.tid]; | ||||
| 					post.category = post.topic && results.categories[post.topic.cid]; | ||||
| 					post.isMainPost = post.topic && post.pid === post.topic.mainPid; | ||||
| 					post.deleted = post.deleted === 1; | ||||
| 					post.timestampISO = utils.toISOString(post.timestamp); | ||||
| 				}); | ||||
| 		const [users, topicsAndCategories] = await Promise.all([ | ||||
| 			user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status']), | ||||
| 			getTopicAndCategories(tids), | ||||
| 		]); | ||||
|  | ||||
| 				posts = posts.filter(function (post) { | ||||
| 					return results.topics[post.tid]; | ||||
| 				}); | ||||
| 		const uidToUser = toObject('uid', users); | ||||
| 		const tidToTopic = toObject('tid', topicsAndCategories.topics); | ||||
| 		const cidToCategory = toObject('cid', topicsAndCategories.categories); | ||||
|  | ||||
| 				parsePosts(posts, options, next); | ||||
| 			}, | ||||
| 			function (posts, next) { | ||||
| 				plugins.fireHook('filter:post.getPostSummaryByPids', { posts: posts, uid: uid }, next); | ||||
| 			}, | ||||
| 			function (data, next) { | ||||
| 				next(null, data.posts); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		posts.forEach(function (post) { | ||||
| 			// If the post author isn't represented in the retrieved users' data, then it means they were deleted, assume guest. | ||||
| 			if (!uidToUser.hasOwnProperty(post.uid)) { | ||||
| 				post.uid = 0; | ||||
| 			} | ||||
| 			post.user = uidToUser[post.uid]; | ||||
| 			post.topic = tidToTopic[post.tid]; | ||||
| 			post.category = post.topic && cidToCategory[post.topic.cid]; | ||||
| 			post.isMainPost = post.topic && post.pid === post.topic.mainPid; | ||||
| 			post.deleted = post.deleted === 1; | ||||
| 			post.timestampISO = utils.toISOString(post.timestamp); | ||||
| 		}); | ||||
|  | ||||
| 		posts = posts.filter(post => tidToTopic[post.tid]); | ||||
|  | ||||
| 		posts = await parsePosts(posts, options); | ||||
| 		const result = await plugins.fireHook('filter:post.getPostSummaryByPids', { posts: posts, uid: uid }); | ||||
| 		return result.posts; | ||||
| 	}; | ||||
|  | ||||
| 	function parsePosts(posts, options, callback) { | ||||
| 		async.map(posts, function (post, next) { | ||||
| 			async.waterfall([ | ||||
| 				function (next) { | ||||
| 					if (!post.content || !options.parse) { | ||||
| 						post.content = post.content ? validator.escape(String(post.content)) : post.content; | ||||
| 						return next(null, post); | ||||
| 					} | ||||
| 					Posts.parsePost(post, next); | ||||
| 				}, | ||||
| 				function (post, next) { | ||||
| 					if (options.stripTags) { | ||||
| 						post.content = stripTags(post.content); | ||||
| 					} | ||||
| 					next(null, post); | ||||
| 				}, | ||||
| 			], next); | ||||
| 		}, callback); | ||||
| 	async function parsePosts(posts, options) { | ||||
| 		async function parse(post) { | ||||
| 			if (!post.content || !options.parse) { | ||||
| 				post.content = post.content ? validator.escape(String(post.content)) : post.content; | ||||
| 				return post; | ||||
| 			} | ||||
| 			post = await Posts.parsePost(post); | ||||
| 			if (options.stripTags) { | ||||
| 				post.content = stripTags(post.content); | ||||
| 			} | ||||
| 			return post; | ||||
| 		} | ||||
| 		return await Promise.all(posts.map(p => parse(p))); | ||||
| 	} | ||||
|  | ||||
| 	function getTopicAndCategories(tids, callback) { | ||||
| 		var topicsData; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid'], next); | ||||
| 			}, | ||||
| 			function (_topicsData, next) { | ||||
| 				topicsData = _topicsData; | ||||
| 				var cids = _.uniq(topicsData.map(topic => topic && topic.cid)); | ||||
| 				categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color'], next); | ||||
| 			}, | ||||
| 			function (categoriesData, next) { | ||||
| 				next(null, { topics: topicsData, categories: categoriesData }); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	async function getTopicAndCategories(tids) { | ||||
| 		const topicsData = await topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid']); | ||||
| 		const cids = _.uniq(topicsData.map(topic => topic && topic.cid)); | ||||
| 		const categoriesData = await categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color']); | ||||
| 		return { topics: topicsData, categories: categoriesData }; | ||||
| 	} | ||||
|  | ||||
| 	function toObject(key, data) { | ||||
|   | ||||
| @@ -1,72 +1,53 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
|  | ||||
| var privileges = require('../privileges'); | ||||
| const privileges = require('../privileges'); | ||||
|  | ||||
| module.exports = function (Posts) { | ||||
| 	Posts.tools = {}; | ||||
|  | ||||
| 	Posts.tools.delete = function (uid, pid, callback) { | ||||
| 		togglePostDelete(uid, pid, true, callback); | ||||
| 	Posts.tools.delete = async function (uid, pid) { | ||||
| 		return await togglePostDelete(uid, pid, true); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.tools.restore = function (uid, pid, callback) { | ||||
| 		togglePostDelete(uid, pid, false, callback); | ||||
| 	Posts.tools.restore = async function (uid, pid) { | ||||
| 		return await togglePostDelete(uid, pid, false); | ||||
| 	}; | ||||
|  | ||||
| 	function togglePostDelete(uid, pid, isDelete, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				Posts.exists(pid, next); | ||||
| 			}, | ||||
| 			function (exists, next) { | ||||
| 				if (!exists) { | ||||
| 					return next(new Error('[[error:no-post]]')); | ||||
| 				} | ||||
| 				Posts.getPostField(pid, 'deleted', next); | ||||
| 			}, | ||||
| 			function (deleted, next) { | ||||
| 				if (deleted && isDelete) { | ||||
| 					return next(new Error('[[error:post-already-deleted]]')); | ||||
| 				} else if (!deleted && !isDelete) { | ||||
| 					return next(new Error('[[error:post-already-restored]]')); | ||||
| 				} | ||||
| 	async function togglePostDelete(uid, pid, isDelete) { | ||||
| 		const [postData, canDelete] = await Promise.all([ | ||||
| 			Posts.getPostData(pid), | ||||
| 			privileges.posts.canDelete(pid, uid), | ||||
| 		]); | ||||
| 		if (!postData) { | ||||
| 			throw new Error('[[error:no-post]]'); | ||||
| 		} | ||||
|  | ||||
| 				privileges.posts.canDelete(pid, uid, next); | ||||
| 			}, | ||||
| 			function (canDelete, next) { | ||||
| 				if (!canDelete.flag) { | ||||
| 					return next(new Error(canDelete.message)); | ||||
| 				} | ||||
| 		if (postData.deleted && isDelete) { | ||||
| 			throw new Error('[[error:post-already-deleted]]'); | ||||
| 		} else if (!postData.deleted && !isDelete) { | ||||
| 			throw new Error('[[error:post-already-restored]]'); | ||||
| 		} | ||||
|  | ||||
| 				if (isDelete) { | ||||
| 					require('./cache').del(pid); | ||||
| 					Posts.delete(pid, uid, next); | ||||
| 				} else { | ||||
| 					Posts.restore(pid, uid, function (err, postData) { | ||||
| 						if (err) { | ||||
| 							return next(err); | ||||
| 						} | ||||
| 						Posts.parsePost(postData, next); | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		if (!canDelete.flag) { | ||||
| 			throw new Error(canDelete.message); | ||||
| 		} | ||||
| 		let post; | ||||
| 		if (isDelete) { | ||||
| 			require('./cache').del(pid); | ||||
| 			post = await Posts.delete(pid, uid); | ||||
| 		} else { | ||||
| 			post = await Posts.restore(pid, uid); | ||||
| 			post = await Posts.parsePost(post); | ||||
| 		} | ||||
| 		return post; | ||||
| 	} | ||||
|  | ||||
| 	Posts.tools.purge = function (uid, pid, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				privileges.posts.canPurge(pid, uid, next); | ||||
| 			}, | ||||
| 			function (canPurge, next) { | ||||
| 				if (!canPurge) { | ||||
| 					return next(new Error('[[error:no-privileges]]')); | ||||
| 				} | ||||
| 				require('./cache').del(pid); | ||||
| 				Posts.purge(pid, uid, next); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 	Posts.tools.purge = async function (uid, pid) { | ||||
| 		const canPurge = await privileges.posts.canPurge(pid, uid); | ||||
| 		if (!canPurge) { | ||||
| 			throw new Error('[[error:no-privileges]]'); | ||||
| 		} | ||||
| 		require('./cache').del(pid); | ||||
| 		await Posts.purge(pid, uid); | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ var nconf = require('nconf'); | ||||
| var crypto = require('crypto'); | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var util = require('util'); | ||||
| var winston = require('winston'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| @@ -18,42 +17,33 @@ module.exports = function (Posts) { | ||||
| 	const pathPrefix = path.join(nconf.get('upload_path'), 'files'); | ||||
| 	const searchRegex = /\/assets\/uploads\/files\/([^\s")]+\.?[\w]*)/g; | ||||
|  | ||||
| 	Posts.uploads.sync = function (pid, callback) { | ||||
| 		// Scans a post and updates sorted set of uploads | ||||
| 	Posts.uploads.sync = async function (pid) { | ||||
| 		// Scans a post's content and updates sorted set of uploads | ||||
|  | ||||
| 		async.parallel({ | ||||
| 			content: async.apply(Posts.getPostField, pid, 'content'), | ||||
| 			uploads: async.apply(Posts.uploads.list, pid), | ||||
| 		}, function (err, data) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
| 		const [content, currentUploads] = await Promise.all([ | ||||
| 			Posts.getPostField(pid, 'content'), | ||||
| 			Posts.uploads.list(pid), | ||||
| 		]); | ||||
|  | ||||
| 			// Extract upload file paths from post content | ||||
| 			let match = searchRegex.exec(data.content); | ||||
| 			const uploads = []; | ||||
| 			while (match) { | ||||
| 				uploads.push(match[1].replace('-resized', '')); | ||||
| 				match = searchRegex.exec(data.content); | ||||
| 			} | ||||
| 		// Extract upload file paths from post content | ||||
| 		let match = searchRegex.exec(content); | ||||
| 		const uploads = []; | ||||
| 		while (match) { | ||||
| 			uploads.push(match[1].replace('-resized', '')); | ||||
| 			match = searchRegex.exec(content); | ||||
| 		} | ||||
|  | ||||
| 			// Create add/remove sets | ||||
| 			const add = uploads.filter(path => !data.uploads.includes(path)); | ||||
| 			const remove = data.uploads.filter(path => !uploads.includes(path)); | ||||
|  | ||||
| 			async.parallel([ | ||||
| 				async.apply(Posts.uploads.associate, pid, add), | ||||
| 				async.apply(Posts.uploads.dissociate, pid, remove), | ||||
| 			], function (err) { | ||||
| 				// Strictly return only err | ||||
| 				callback(err); | ||||
| 			}); | ||||
| 		}); | ||||
| 		// Create add/remove sets | ||||
| 		const add = uploads.filter(path => !currentUploads.includes(path)); | ||||
| 		const remove = currentUploads.filter(path => !uploads.includes(path)); | ||||
| 		await Promise.all([ | ||||
| 			Posts.uploads.associate(pid, add), | ||||
| 			Posts.uploads.dissociate(pid, remove), | ||||
| 		]); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.list = function (pid, callback) { | ||||
| 		// Returns array of this post's uploads | ||||
| 		db.getSortedSetRange('post:' + pid + ':uploads', 0, -1, callback); | ||||
| 	Posts.uploads.list = async function (pid) { | ||||
| 		return await db.getSortedSetRange('post:' + pid + ':uploads', 0, -1); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.listWithSizes = async function (pid) { | ||||
| @@ -66,98 +56,70 @@ module.exports = function (Posts) { | ||||
| 		})); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.isOrphan = function (filePath, callback) { | ||||
| 		// Returns bool indicating whether a file is still CURRENTLY included in any posts | ||||
| 		db.sortedSetCard('upload:' + md5(filePath) + ':pids', function (err, length) { | ||||
| 			callback(err, length === 0); | ||||
| 		}); | ||||
| 	Posts.uploads.isOrphan = async function (filePath) { | ||||
| 		const length = await db.sortedSetCard('upload:' + md5(filePath) + ':pids'); | ||||
| 		return length === 0; | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.getUsage = function (filePaths, callback) { | ||||
| 	Posts.uploads.getUsage = async function (filePaths) { | ||||
| 		// Given an array of file names, determines which pids they are used in | ||||
| 		if (!Array.isArray(filePaths)) { | ||||
| 			filePaths = [filePaths]; | ||||
| 		} | ||||
|  | ||||
| 		const keys = filePaths.map(fileObj => 'upload:' + md5(fileObj.name.replace('-resized', '')) + ':pids'); | ||||
| 		async.map(keys, function (key, next) { | ||||
| 			db.getSortedSetRange(key, 0, -1, next); | ||||
| 		}, callback); | ||||
| 		return await Promise.all(keys.map(k => db.getSortedSetRange(k, 0, -1))); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.associate = function (pid, filePaths, callback) { | ||||
| 	Posts.uploads.associate = async function (pid, filePaths) { | ||||
| 		// Adds an upload to a post's sorted set of uploads | ||||
| 		filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths; | ||||
| 		if (!filePaths.length) { | ||||
| 			return setImmediate(callback); | ||||
| 			return; | ||||
| 		} | ||||
| 		async.filter(filePaths, function (filePath, next) { | ||||
| 		filePaths = await async.filter(filePaths, function (filePath, next) { | ||||
| 			// Only process files that exist | ||||
| 			fs.access(path.join(pathPrefix, filePath), fs.constants.F_OK | fs.constants.R_OK, function (err) { | ||||
| 				next(null, !err); | ||||
| 			}); | ||||
| 		}, function (err, filePaths) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
| 			const now = Date.now(); | ||||
| 			const scores = filePaths.map(() => now); | ||||
| 			let methods = [async.apply(db.sortedSetAdd.bind(db), 'post:' + pid + ':uploads', scores, filePaths)]; | ||||
|  | ||||
| 			methods = methods.concat(filePaths.map(path => async.apply(db.sortedSetAdd.bind(db), 'upload:' + md5(path) + ':pids', now, pid))); | ||||
| 			methods = methods.concat(async function () { | ||||
| 				await Posts.uploads.saveSize(filePaths); | ||||
| 			}); | ||||
| 			async.parallel(methods, function (err) { | ||||
| 				// Strictly return only err | ||||
| 				callback(err); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		const now = Date.now(); | ||||
| 		const scores = filePaths.map(() => now); | ||||
| 		const bulkAdd = filePaths.map(path => ['upload:' + md5(path) + ':pids', now, pid]); | ||||
| 		await Promise.all([ | ||||
| 			db.sortedSetAdd('post:' + pid + ':uploads', scores, filePaths), | ||||
| 			db.sortedSetAddBulk(bulkAdd), | ||||
| 			Posts.uploads.saveSize(filePaths), | ||||
| 		]); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.dissociate = function (pid, filePaths, callback) { | ||||
| 	Posts.uploads.dissociate = async function (pid, filePaths) { | ||||
| 		// Removes an upload from a post's sorted set of uploads | ||||
| 		filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths; | ||||
| 		if (!filePaths.length) { | ||||
| 			return setImmediate(callback); | ||||
| 			return; | ||||
| 		} | ||||
| 		let methods = [async.apply(db.sortedSetRemove.bind(db), 'post:' + pid + ':uploads', filePaths)]; | ||||
| 		methods = methods.concat(filePaths.map(path => async.apply(db.sortedSetRemove.bind(db), 'upload:' + md5(path) + ':pids', pid))); | ||||
|  | ||||
| 		async.parallel(methods, function (err) { | ||||
| 			// Strictly return only err | ||||
| 			callback(err); | ||||
| 		}); | ||||
| 		const bulkRemove = filePaths.map(path => ['upload:' + md5(path) + ':pids', pid]); | ||||
| 		await Promise.all([ | ||||
| 			db.sortedSetRemove('post:' + pid + ':uploads', filePaths), | ||||
| 			db.sortedSetRemoveBulk(bulkRemove), | ||||
| 		]); | ||||
| 	}; | ||||
|  | ||||
| 	Posts.uploads.saveSize = async (filePaths) => { | ||||
| 		const getSize = util.promisify(image.size); | ||||
| 		const sizes = await Promise.all(filePaths.map(async function (fileName) { | ||||
| 		await Promise.all(filePaths.map(async function (fileName) { | ||||
| 			try { | ||||
| 				return await getSize(path.join(pathPrefix, fileName)); | ||||
| 			} catch (e) { | ||||
| 				// Error returned by getSize, do not save size in database | ||||
| 				return null; | ||||
| 				const size = await image.size(path.join(pathPrefix, fileName)); | ||||
| 				winston.verbose('[posts/uploads/' + fileName + '] Saving size'); | ||||
| 				await db.setObject('upload:' + md5(fileName), { | ||||
| 					width: size.width, | ||||
| 					height: size.height, | ||||
| 				}); | ||||
| 			} catch (err) { | ||||
| 				winston.error('[posts/uploads] Error while saving post upload sizes (' + fileName + '): ' + err.message); | ||||
| 			} | ||||
| 		})); | ||||
|  | ||||
| 		const methods = filePaths.map((filePath, idx) => { | ||||
| 			if (!sizes[idx]) { | ||||
| 				return null; | ||||
| 			} | ||||
|  | ||||
| 			winston.verbose('[posts/uploads/' + filePath + '] Saving size'); | ||||
| 			return async.apply(db.setObject, 'upload:' + md5(filePath), { | ||||
| 				width: sizes[idx].width, | ||||
| 				height: sizes[idx].height, | ||||
| 			}); | ||||
| 		}).filter(Boolean); | ||||
| 		async.parallel(methods, function (err) { | ||||
| 			if (err) { | ||||
| 				winston.error('[posts/uploads] Error while saving post upload sizes: ', err.message); | ||||
| 			} else { | ||||
| 				winston.verbose('[posts/uploads] Finished saving post upload sizes.'); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -20,7 +20,56 @@ module.exports = function (Posts) { | ||||
| 			privileges.global.can('signature', uid), | ||||
| 		]); | ||||
|  | ||||
| 		var groupTitles = _.uniq(_.flatten(userData.map(u => u && u.groupTitleArray))); | ||||
| 		const groupsMap = await getGroupsMap(userData); | ||||
|  | ||||
| 		userData.forEach(function (userData, index) { | ||||
| 			userData.signature = validator.escape(String(userData.signature || '')); | ||||
| 			userData.fullname = userSettings[index].showfullname ? validator.escape(String(userData.fullname || '')) : undefined; | ||||
| 			userData.selectedGroups = []; | ||||
|  | ||||
| 			if (meta.config.hideFullname) { | ||||
| 				userData.fullname = undefined; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		return await Promise.all(userData.map(async function (userData) { | ||||
| 			const [isMemberOfGroups, signature, customProfileInfo] = await Promise.all([ | ||||
| 				checkGroupMembership(userData.uid, userData.groupTitleArray), | ||||
| 				parseSignature(userData, uid, canUseSignature), | ||||
| 				plugins.fireHook('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }), | ||||
| 			]); | ||||
|  | ||||
| 			if (isMemberOfGroups && userData.groupTitleArray) { | ||||
| 				userData.groupTitleArray.forEach(function (userGroup, index) { | ||||
| 					if (isMemberOfGroups[index] && groupsMap[userGroup]) { | ||||
| 						userData.selectedGroups.push(groupsMap[userGroup]); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 			userData.signature = signature; | ||||
| 			userData.custom_profile_info = customProfileInfo.profile; | ||||
|  | ||||
| 			return await plugins.fireHook('filter:posts.modifyUserInfo', userData); | ||||
| 		})); | ||||
| 	}; | ||||
|  | ||||
| 	async function checkGroupMembership(uid, groupTitleArray) { | ||||
| 		if (!Array.isArray(groupTitleArray) || !groupTitleArray.length) { | ||||
| 			return null; | ||||
| 		} | ||||
| 		return await groups.isMemberOfGroups(uid, groupTitleArray); | ||||
| 	} | ||||
|  | ||||
| 	async function parseSignature(userData, uid, canUseSignature) { | ||||
| 		if (!userData.signature || !canUseSignature || meta.config.disableSignatures) { | ||||
| 			return ''; | ||||
| 		} | ||||
| 		const result = await Posts.parseSignature(userData, uid); | ||||
| 		return result.userData.signature; | ||||
| 	} | ||||
|  | ||||
| 	async function getGroupsMap(userData) { | ||||
| 		const groupTitles = _.uniq(_.flatten(userData.map(u => u && u.groupTitleArray))); | ||||
| 		const groupsMap = {}; | ||||
| 		const groupsData = await groups.getGroupsData(groupTitles); | ||||
| 		groupsData.forEach(function (group) { | ||||
| @@ -35,59 +84,8 @@ module.exports = function (Posts) { | ||||
| 				}; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		userData.forEach(function (userData, index) { | ||||
| 			userData.uid = userData.uid || 0; | ||||
| 			userData.username = userData.username || '[[global:guest]]'; | ||||
| 			userData.userslug = userData.userslug || ''; | ||||
| 			userData.reputation = userData.reputation || 0; | ||||
| 			userData.postcount = userData.postcount || 0; | ||||
| 			userData.banned = userData.banned === 1; | ||||
| 			userData.picture = userData.picture || ''; | ||||
| 			userData.status = user.getStatus(userData); | ||||
| 			userData.signature = validator.escape(String(userData.signature || '')); | ||||
| 			userData.fullname = userSettings[index].showfullname ? validator.escape(String(userData.fullname || '')) : undefined; | ||||
| 			userData.selectedGroups = []; | ||||
|  | ||||
| 			if (meta.config.hideFullname) { | ||||
| 				userData.fullname = undefined; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		return await async.map(userData, async function (userData) { | ||||
| 			const results = await async.parallel({ | ||||
| 				isMemberOfGroups: function (next) { | ||||
| 					if (!Array.isArray(userData.groupTitleArray) || !userData.groupTitleArray.length) { | ||||
| 						return next(); | ||||
| 					} | ||||
| 					groups.isMemberOfGroups(userData.uid, userData.groupTitleArray, next); | ||||
| 				}, | ||||
| 				signature: function (next) { | ||||
| 					if (!userData.signature || !canUseSignature || meta.config.disableSignatures) { | ||||
| 						userData.signature = ''; | ||||
| 						return next(); | ||||
| 					} | ||||
| 					Posts.parseSignature(userData, uid, next); | ||||
| 				}, | ||||
| 				customProfileInfo: function (next) { | ||||
| 					plugins.fireHook('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }, next); | ||||
| 				}, | ||||
| 			}); | ||||
|  | ||||
| 			if (results.isMemberOfGroups && userData.groupTitleArray) { | ||||
| 				userData.groupTitleArray.forEach(function (userGroup, index) { | ||||
| 					if (results.isMemberOfGroups[index] && groupsMap[userGroup]) { | ||||
| 						userData.selectedGroups.push(groupsMap[userGroup]); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			userData.custom_profile_info = results.customProfileInfo.profile; | ||||
|  | ||||
| 			const result = await plugins.fireHook('filter:posts.modifyUserInfo', userData); | ||||
| 			return result; | ||||
| 		}); | ||||
| 	}; | ||||
| 		return groupsMap; | ||||
| 	} | ||||
|  | ||||
| 	async function getUserData(uids, uid) { | ||||
| 		const fields = [ | ||||
|   | ||||
| @@ -1022,7 +1022,7 @@ describe('Post\'s', function () { | ||||
|  | ||||
| 		it('should not crash if id does not exist', function (done) { | ||||
| 			socketPosts.reject({ uid: globalModUid }, { id: '123123123' }, function (err) { | ||||
| 				assert.ifError(err); | ||||
| 				assert.equal(err.message, '[[error:no-privileges]]'); | ||||
| 				done(); | ||||
| 			}); | ||||
| 		}); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user