From 3c8109e27f4e7c77f8723fbc70e5d60edbf5e97f Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 10 Nov 2021 20:45:25 +0000 Subject: [PATCH 001/849] chore: update changelog for v1.18.6 --- CHANGELOG.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e339ad70ff..7a3b14d286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,110 @@ +#### v1.18.6 (2021-11-10) + +##### Chores + +* make it a link (a0f0dd02) +* update badges, remove david doesnt work (dad31c8e) +* up themes (b1d6c9ba) +* up mentions (98b98a11) +* up mentions (3e4d477e) +* fix type.yaml example and summary (591424ce) +* incrementing version number - v1.18.5 (1e418f5b) +* update changelog for v1.18.5 (82eda23a) +* remove .opacity() mixin as it is supported cross-browser (28efcb59) +* **deps:** + * update dependency eslint-plugin-import to v2.25.3 (45a0895c) + * update commitlint monorepo to v14 (dc78125a) + * update dependency jsdom to v18.0.1 (7d468e72) +* **i18n:** + * fallback strings for new resources: nodebb.admin-development-info (91676c6c) + * fallback strings for new resources: nodebb.admin-settings-navigation (3727e39f) + * fallback strings for new resources: nodebb.admin-settings-post (46789910) + +##### New Features + +* #9992, hooks.one (96f13e4f) +* use auto-generated meta and link tags in ACP, closes #9991 (1719bff8) +* add node 16 (#9847) (d27c9696) +* #9967, allow dropdowns in navigation (2e623dd2) +* show number of events per type in acp (b916e42f) +* show posts previews if enabled on mouse over (8c670316) + +##### Bug Fixes + +* **deps:** + * update dependency nodebb-theme-slick to v1.4.16 (#9990) (cf30876f) + * update dependency nodebb-plugin-composer-default to v7.0.14 (#9989) (ef02bdc4) + * update dependency nodebb-plugin-composer-default to v7.0.13 (#9988) (654c8e61) + * update dependency nodebb-plugin-mentions to v3.0.2 (1a22b0ec) + * update dependency socket.io to v4.3.2 (98ebc4d9) + * update dependency html-to-text to v8.1.0 (c1f5889f) + * update dependency nodebb-plugin-dbsearch to v5.1.0 (#9983) (4f1ee1fc) + * update dependency nodebb-plugin-composer-default to v7.0.12 (7fee0e32) + * update dependency nodebb-plugin-mentions to v3.0.1 (#9979) (8224a2a9) + * update dependency nodebb-plugin-spam-be-gone to v0.7.11 (91293ecc) + * update dependency nodebb-theme-lavender to v5.3.1 (f7295aaa) + * update dependency nodebb-plugin-mentions to v3 (#9966) (0888aae6) + * update dependency mongodb to v4.1.4 (#9968) (f5993731) + * update dependency nodebb-theme-persona to v11.2.21 (#9969) (8fac8d61) + * update dependency nodebb-plugin-mentions to v2.15.1 (0f8a68c0) + * update dependency validator to v13.7.0 (81c8d70c) + * update dependency autoprefixer to v10.4.0 (755860f1) +* ability to enumerate email via updateProfile method (c1ac2912) +* accidentally not clearing email when said email is confirmed for a different uid (b912a564) +* #9976 (28dd31a8) +* #9976, handle array or object (9bfb6c72) +* dont show previews on mobile (41e02400) +* category load more btn visibility (05468526) +* #9973, ignore if assigning to same parent (66e7cdac) +* #9972 (67cb2491) +* remove tooltip on ajaxify (f728abda) +* don't highlight external nav items (8a88295d) +* don't use # for previews (5a0efd2d) +* events for just topic with main post (3d611ab7) +* #9954, get next post timestamp (89399c0e) +* topic events not rendered in infinitescroll (a7f235db) +* broken post uploads due to 6a976a9db0340e34577961ce8d5d9479c78f7856 (485b6ced) +* #9950, rename account export routes to remove `uid/` prefix (0ee85d5a) +* double invocation of authenticateRequest (60352eca) +* #9945, call authenticateRequest middleware for mount points in /api (6a976a9d) +* hooks is sometimes undefined (74aa12c9) +* typo in flags (bc4b19b4) +* remove unused code (50b2ebf8) +* handle undefined data.query (8f08d9ca) + +##### Performance Improvements + +* only load posts once (9fbb3b11) + +##### Refactors + +* shorter require (41c3eb82) +* deprecate app.alert functions user alerts module directly (0428912c) +* deprecate app.logout (8b4510cc) +* simpler rejoin (61903448) +* deprecate app.openChat/newChat (f352be63) +* move search functions from app.js to search module (1a9b1598) +* move session messages (666fe209) +* move warnings/messages out of app.js (51855254) +* remove jshint (0a7ff208) +* cleanup info, better cpu usage % (4b738c8c) +* acp only uses 3 modes and a single theme (890bf03f) +* display errors from category drag/drop (c1cc35a9) +* use utils.debounce (e8c17fee) + +##### Tests + +* add another assert for random failing test (ae64b9f4) +* socket.emit doesnt exist in tests (61d1f565) +* show body when test fails (e3f5b706) +* lint (3d2398ac) +* fix tpl test (30cce142) +* dbsearch no longer has staticDir (3386893b) +* increase timeout (4ac9270a) +* fix account export test routes (10bb8cf7) +* add test aliases.buildTargets (62ac9a8b) +* empty query params for search (bda5d144) + #### v1.18.5 (2021-10-27) ##### Breaking Changes From 3a78a151345017f3289a4677aac170d27925528b Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 10 Nov 2021 20:45:24 +0000 Subject: [PATCH 002/849] chore: incrementing version number - v1.18.6 (cherry picked from commit 22e74dc0bb9cf296f548ad3b67ba1d297e4a7dc1) Signed-off-by: Misty (Bot) --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 22325d77db..b0b48b5c8f 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "1.18.5", + "version": "1.18.6", "homepage": "http://www.nodebb.org", "repository": { "type": "git", From 80f9963bed22b495c5e3ecac5b31a2930ccc72cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 10 Nov 2021 20:19:13 -0500 Subject: [PATCH 003/849] refactor: remove jshint, remove async.parallel --- public/.jshintrc | 84 -------------------------------- src/socket.io/admin/analytics.js | 41 ++++++---------- 2 files changed, 14 insertions(+), 111 deletions(-) delete mode 100644 public/.jshintrc diff --git a/public/.jshintrc b/public/.jshintrc deleted file mode 100644 index 6e93972469..0000000000 --- a/public/.jshintrc +++ /dev/null @@ -1,84 +0,0 @@ -{ - "maxerr" : 50, // {int} Maximum error before stopping - - "esversion": 9, - - // Enforcing - "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) - "camelcase" : false, // true: Identifiers must be in camelCase - "curly" : true, // true: Require {} for every new block or scope - "eqeqeq" : true, // true: Require triple equals (===) for comparison - "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() - "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` - "indent" : 4, // {int} Number of spaces to use for indentation - "latedef" : false, // true: Require variables/functions to be defined before being used - "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` - "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` - "noempty" : true, // true: Prohibit use of empty blocks - "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) - "plusplus" : false, // true: Prohibit use of `++` & `--` - "quotmark" : false, // Quotation mark consistency: - // false : do nothing (default) - // true : ensure whatever is used is consistent - // "single" : require single quotes - // "double" : require double quotes - "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) - "unused" : true, // true: Require all defined variables be used - "strict" : true, // true: Requires all functions run in ES5 Strict Mode - "trailing" : false, // true: Prohibit trailing whitespaces - "maxparams" : false, // {int} Max number of formal params allowed per function - "maxdepth" : false, // {int} Max depth of nested blocks (within functions) - "maxstatements" : false, // {int} Max number statements per function - "maxcomplexity" : false, // {int} Max cyclomatic complexity per function - "maxlen" : false, // {int} Max number of characters per line - - // Relaxing - "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) - "boss" : false, // true: Tolerate assignments where comparisons would be expected - "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. - "eqnull" : false, // true: Tolerate use of `== null` - "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) - "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) - "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) - // (ex: `for each`, multiple try/catch, function expression…) - "evil" : false, // true: Tolerate use of `eval` and `new Function()` - "expr" : false, // true: Tolerate `ExpressionStatement` as Programs - "funcscope" : false, // true: Tolerate defining variables inside control statements" - "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') - "iterator" : false, // true: Tolerate using the `__iterator__` property - "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block - "laxbreak" : false, // true: Tolerate possibly unsafe line breakings - "laxcomma" : false, // true: Tolerate comma-first style coding - "loopfunc" : false, // true: Tolerate functions being defined in loops - "multistr" : false, // true: Tolerate multi-line strings - "proto" : false, // true: Tolerate using the `__proto__` property - "scripturl" : false, // true: Tolerate script-targeted URLs - "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment - "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` - "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation - "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` - "validthis" : false, // true: Tolerate using this in a non-constructor function - - "globals": { - "app": true, - "io": true, - "socket": true, - "ajaxify": true, - "config": true, - "utils": true, - "overrides": true, - "componentHandler": true, - "templates": true, - "Visibility": true, - "Tinycon": true, - "require": true, - "define": true, - "ace": true, - "Sortable": true, - "Slideout": true, - "NProgress": true - }, - - "jquery": true, - "browser": true -} \ No newline at end of file diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js index 6bb80c9df1..bc084b14f5 100644 --- a/src/socket.io/admin/analytics.js +++ b/src/socket.io/admin/analytics.js @@ -1,13 +1,13 @@ 'use strict'; -const async = require('async'); const analytics = require('../../analytics'); +const utils = require('../../utils'); const Analytics = module.exports; -Analytics.get = function (socket, data, callback) { +Analytics.get = async function (socket, data) { if (!data || !data.graph || !data.units) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } // Default returns views from past 24 hours, by hour @@ -20,30 +20,17 @@ Analytics.get = function (socket, data, callback) { } const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; if (data.graph === 'traffic') { - async.parallel({ - uniqueVisitors: function (next) { - getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); - }, - pageviews: function (next) { - getStats('analytics:pageviews', data.until || Date.now(), data.amount, next); - }, - pageviewsRegistered: function (next) { - getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount, next); - }, - pageviewsGuest: function (next) { - getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount, next); - }, - pageviewsBot: function (next) { - getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount, next); - }, - summary: function (next) { - analytics.getSummary(next); - }, - }, (err, data) => { - data.pastDay = data.pageviews.reduce((a, b) => parseInt(a, 10) + parseInt(b, 10)); - const last = data.pageviews.length - 1; - data.pageviews[last] = parseInt(data.pageviews[last], 10) + analytics.getUnwrittenPageviews(); - callback(err, data); + const result = await utils.promiseParallel({ + uniqueVisitors: getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount), + pageviews: getStats('analytics:pageviews', data.until || Date.now(), data.amount), + pageviewsRegistered: getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount), + pageviewsGuest: getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount), + pageviewsBot: getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount), + summary: analytics.getSummary(), }); + result.pastDay = result.pageviews.reduce((a, b) => parseInt(a, 10) + parseInt(b, 10)); + const last = result.pageviews.length - 1; + result.pageviews[last] = parseInt(result.pageviews[last], 10) + analytics.getUnwrittenPageviews(); + return result; } }; From 8750ee04a6e3afc06fc17e887039404dbc7597f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 10 Nov 2021 20:40:34 -0500 Subject: [PATCH 004/849] refactor: make a single call to set widgets per template --- src/socket.io/admin/widgets.js | 8 +++----- src/widgets/index.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/socket.io/admin/widgets.js b/src/socket.io/admin/widgets.js index 7dfc8e7ab7..b2a4032f49 100644 --- a/src/socket.io/admin/widgets.js +++ b/src/socket.io/admin/widgets.js @@ -1,14 +1,12 @@ 'use strict'; -const async = require('async'); const widgets = require('../../widgets'); const Widgets = module.exports; -Widgets.set = function (socket, data, callback) { +Widgets.set = async function (socket, data) { if (!Array.isArray(data)) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } - - async.eachSeries(data, widgets.setArea, callback); + await widgets.setAreas(data); }; diff --git a/src/widgets/index.js b/src/widgets/index.js index 572121b021..bda9682736 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -167,6 +167,25 @@ widgets.setArea = async function (area) { await db.setObjectField(`widgets:${area.template}`, area.location, JSON.stringify(area.widgets)); }; +widgets.setAreas = async function (areas) { + const templates = {}; + areas.forEach((area) => { + if (!area.location || !area.template) { + throw new Error('Missing location and template data'); + } + templates[area.template] = templates[area.template] || {}; + templates[area.template][area.location] = JSON.stringify(area.widgets); + }); + + const keys = []; + const data = []; + Object.keys(templates).forEach((tpl) => { + keys.push(`widgets:${tpl}`); + data.push(templates[tpl]); + }); + await db.setObjectBulk(keys, data); +}; + widgets.reset = async function () { const defaultAreas = [ { name: 'Draft Zone', template: 'global', location: 'header' }, From d1964095803e162b4020dfa7262f0eb5728a514e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 10 Nov 2021 20:55:06 -0500 Subject: [PATCH 005/849] refactor: remove more async.eachSeries/mapSeries --- src/categories/update.js | 7 +++---- src/groups/join.js | 6 +++--- src/topics/delete.js | 10 +++++----- src/topics/fork.js | 7 +++---- src/topics/merge.js | 12 ++++++------ src/topics/tags.js | 5 +++-- src/topics/teaser.js | 5 ++--- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/categories/update.js b/src/categories/update.js index 04650a4c46..259dd58d06 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -1,7 +1,5 @@ 'use strict'; -const async = require('async'); - const db = require('../database'); const meta = require('../meta'); const utils = require('../utils'); @@ -37,9 +35,10 @@ module.exports = function (Categories) { fields.splice(0, 0, fields.splice(parentCidIndex, 1)[0]); } - await async.eachSeries(fields, async (key) => { + for (const key of fields) { + // eslint-disable-next-line no-await-in-loop await updateCategoryField(cid, key, category[key]); - }); + } plugins.hooks.fire('action:category.update', { cid: cid, modified: category }); } diff --git a/src/groups/join.js b/src/groups/join.js index 7b7a68095e..9fb02ec0bb 100644 --- a/src/groups/join.js +++ b/src/groups/join.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const winston = require('winston'); const db = require('../database'); @@ -75,8 +74,9 @@ module.exports = function (Groups) { return; } - await async.eachSeries(groupsToCreate, async (groupName) => { + for (const groupName of groupsToCreate) { try { + // eslint-disable-next-line no-await-in-loop await Groups.create({ name: groupName, hidden: 1, @@ -87,7 +87,7 @@ module.exports = function (Groups) { throw err; } } - }); + } } async function setGroupTitleIfNotSet(groupNames, uid) { diff --git a/src/topics/delete.js b/src/topics/delete.js index 3e84a29cc8..34d581e49d 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const db = require('../database'); const user = require('../user'); @@ -60,10 +59,11 @@ module.exports = function (Topics) { Topics.purgePostsAndTopic = async function (tid, uid) { const mainPid = await Topics.getTopicField(tid, 'mainPid'); - await batch.processSortedSet(`tid:${tid}:posts`, (pids, next) => { - async.eachSeries(pids, (pid, next) => { - posts.purge(pid, uid, next); - }, next); + await batch.processSortedSet(`tid:${tid}:posts`, async (pids) => { + for (const pid of pids) { + // eslint-disable-next-line no-await-in-loop + await posts.purge(pid, uid); + } }, { alwaysStartAt: 0 }); await posts.purge(mainPid, uid); await Topics.purge(tid, uid); diff --git a/src/topics/fork.js b/src/topics/fork.js index 5005dd1696..aa16168c87 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -1,8 +1,6 @@ 'use strict'; -const async = require('async'); - const db = require('../database'); const posts = require('../posts'); const categories = require('../categories'); @@ -55,13 +53,14 @@ module.exports = function (Topics) { const tid = await Topics.create(result.params); await Topics.updateTopicBookmarks(fromTid, pids); - await async.eachSeries(pids, async (pid) => { + for (const pid of pids) { + /* eslint-disable no-await-in-loop */ const canEdit = await privileges.posts.canEdit(pid, uid); if (!canEdit.flag) { throw new Error(canEdit.message); } await Topics.movePostToTopic(uid, pid, tid, scheduled); - }); + } await Topics.updateLastPostTime(tid, scheduled ? (postData.timestamp + 1) : Date.now()); diff --git a/src/topics/merge.js b/src/topics/merge.js index 33684491ae..1a06adefbb 100644 --- a/src/topics/merge.js +++ b/src/topics/merge.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const plugins = require('../plugins'); const posts = require('../posts'); @@ -24,11 +23,12 @@ module.exports = function (Topics) { const otherTids = tids.sort((a, b) => a - b) .filter(tid => tid && parseInt(tid, 10) !== parseInt(mergeIntoTid, 10)); - await async.eachSeries(otherTids, async (tid) => { + for (const tid of otherTids) { + /* eslint-disable no-await-in-loop */ const pids = await Topics.getPids(tid); - await async.eachSeries(pids, (pid, next) => { - Topics.movePostToTopic(uid, pid, mergeIntoTid, next); - }); + for (const pid of pids) { + await Topics.movePostToTopic(uid, pid, mergeIntoTid); + } await Topics.setTopicField(tid, 'mainPid', 0); await Topics.delete(tid, uid); @@ -37,7 +37,7 @@ module.exports = function (Topics) { mergerUid: uid, mergedTimestamp: Date.now(), }); - }); + } await Promise.all([ posts.updateQueuedPostsTopic(mergeIntoTid, otherTids), diff --git a/src/topics/tags.js b/src/topics/tags.js index f47dc2aad5..b0be277a9d 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -116,9 +116,10 @@ module.exports = function (Topics) { }; Topics.renameTags = async function (data) { - await async.eachSeries(data, async (tagData) => { + for (const tagData of data) { + // eslint-disable-next-line no-await-in-loop await renameTag(tagData.value, tagData.newName); - }); + } }; async function renameTag(tag, newTagName) { diff --git a/src/topics/teaser.js b/src/topics/teaser.js index 11473f4d64..f5815f8deb 100644 --- a/src/topics/teaser.js +++ b/src/topics/teaser.js @@ -1,7 +1,6 @@ 'use strict'; -const async = require('async'); const _ = require('lodash'); const db = require('../database'); @@ -111,12 +110,12 @@ module.exports = function (Topics) { return teasers; } - return await async.mapSeries(teasers, async (postData) => { + return await Promise.all(teasers.map(async (postData) => { if (blockedUids.includes(parseInt(postData.uid, 10))) { return await getPreviousNonBlockedPost(postData, blockedUids); } return postData; - }); + })); } async function getPreviousNonBlockedPost(postData, blockedUids) { From facc10e40f8789058214d0677806e65b0cc7c37d Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Fri, 12 Nov 2021 11:18:44 -0500 Subject: [PATCH 006/849] perf: remove createUserTooltips --- public/src/app.js | 20 ++------------------ public/src/client/account/posts.js | 1 - public/src/client/account/topics.js | 1 - public/src/client/categories.js | 2 -- public/src/client/category.js | 1 - public/src/client/topic/posts.js | 3 --- public/src/modules/chat.js | 1 - public/src/modules/topicList.js | 1 - 8 files changed, 2 insertions(+), 28 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 6154c8b323..e92eb5c8ca 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -209,18 +209,8 @@ app.flags = {}; .addClass('active'); } - app.createUserTooltips = function (els, placement) { - if (isTouchDevice) { - return; - } - els = els || $('body'); - els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').each(function () { - $(this).tooltip({ - placement: placement || $(this).attr('title-placement') || 'top', - title: $(this).attr('title'), - container: '#content', - }); - }); + app.createUserTooltips = function () { + console.warn('[removed] app.creatUserTooltips is removed'); }; app.createStatusTooltips = function () { @@ -234,15 +224,9 @@ app.flags = {}; app.processPage = function () { highlightNavigationLink(); - $('.timeago').timeago(); - utils.makeNumbersHumanReadable($('.human-readable-number')); - utils.addCommasToNumbers($('.formatted-number')); - - app.createUserTooltips($('#content')); - app.createStatusTooltips(); }; diff --git a/public/src/client/account/posts.js b/public/src/client/account/posts.js index 27442e21a5..a0c0a3e92f 100644 --- a/public/src/client/account/posts.js +++ b/public/src/client/account/posts.js @@ -45,7 +45,6 @@ define('forum/account/posts', ['forum/account/header', 'forum/infinitescroll', ' $('[component="posts"]').append(html); html.find('img:not(.not-responsive)').addClass('img-responsive'); html.find('.timeago').timeago(); - app.createUserTooltips(); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:posts.loaded', { posts: posts }); callback(); diff --git a/public/src/client/account/topics.js b/public/src/client/account/topics.js index cba3358275..565bb9b170 100644 --- a/public/src/client/account/topics.js +++ b/public/src/client/account/topics.js @@ -46,7 +46,6 @@ define('forum/account/topics', [ app.parseAndTranslate(template, 'topics', { topics: topics }, function (html) { $('[component="category"]').append(html); html.find('.timeago').timeago(); - app.createUserTooltips(); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:topics.loaded', { topics: topics }); callback(); diff --git a/public/src/client/categories.js b/public/src/client/categories.js index 44025e8b8c..1881941c00 100644 --- a/public/src/client/categories.js +++ b/public/src/client/categories.js @@ -55,8 +55,6 @@ define('forum/categories', ['components', 'categorySelector', 'hooks'], function } html.fadeIn(); - - app.createUserTooltips(); html.find('.timeago').timeago(); if (category.find('[component="category/posts"]').length > parseInt(numRecentReplies, 10)) { diff --git a/public/src/client/category.js b/public/src/client/category.js index da8a6296cf..5e0fccbcbb 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -104,7 +104,6 @@ define('forum/category', [ html.find('.timeago').timeago(); $('[component="category/subcategory/container"]').append(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); - app.createUserTooltips(html); ajaxify.data.nextSubCategoryStart += ajaxify.data.subCategoriesPerPage; ajaxify.data.subCategoriesLeft -= data.length; btn.toggleClass('hidden', ajaxify.data.subCategoriesLeft <= 0) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index b84953e915..9e3f63a657 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -370,9 +370,6 @@ define('forum/topic/posts', [ Posts.onNewPostsAddedToDom = function (posts) { Posts.onTopicPageLoad(posts); - - app.createUserTooltips(posts); - utils.addCommasToNumbers(posts.find('.formatted-number')); utils.makeNumbersHumanReadable(posts.find('.human-readable-number')); posts.find('.timeago').timeago(); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index d57386040a..1300b78c22 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -96,7 +96,6 @@ define('chat', [ app.parseAndTranslate('partials/chats/dropdown', { rooms: rooms }, function (html) { chatsListEl.find('*').not('.navigation-link').remove(); chatsListEl.prepend(html); - app.createUserTooltips(chatsListEl, 'right'); chatsListEl.off('click').on('click', '[data-roomid]', function (ev) { if ($(ev.target).parents('.user-link').length) { return; diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index 1ce4016364..db65c20522 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -262,7 +262,6 @@ define('topicList', [ } html.find('.timeago').timeago(); - app.createUserTooltips(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:topics.loaded', { topics: topics, template: templateName }); callback(); From 09e0c6d503e4267087f3695755e20fd94df312a9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 12 Nov 2021 14:58:02 -0500 Subject: [PATCH 007/849] feat: add feature flag to disable verification emails, closes #9996 --- install/data/defaults.json | 1 + .../language/en-GB/admin/settings/email.json | 3 ++- src/user/email.js | 7 +++++++ src/views/admin/settings/email.tpl | 21 ++++++++++++------- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index 52b7683954..cd01e7644a 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -138,6 +138,7 @@ "disableEmailSubscriptions": 0, "emailConfirmInterval": 10, "removeEmailNotificationImages": 0, + "sendValidationEmail": 1, "includeUnverifiedEmails": 0, "emailPrompt": 1, "inviteExpiration": 7, diff --git a/public/language/en-GB/admin/settings/email.json b/public/language/en-GB/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/en-GB/admin/settings/email.json +++ b/public/language/en-GB/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/src/user/email.js b/src/user/email.js index d6d2d32b4d..a97aa1f38f 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -2,6 +2,7 @@ 'use strict'; const nconf = require('nconf'); +const winston = require('winston'); const user = require('./index'); const utils = require('../utils'); @@ -69,6 +70,11 @@ UserEmail.sendValidationEmail = async function (uid, options) { * - force, sends email even if it is too soon to send another */ + if (meta.config.sendValidationEmail !== 1) { + winston.verbose(`[user/email] Validation email for uid ${uid} not sent due to config settings`); + return; + } + options = options || {}; // Fallback behaviour (email passed in as second argument) @@ -110,6 +116,7 @@ UserEmail.sendValidationEmail = async function (uid, options) { await db.expireAt(`confirm:${confirm_code}`, Math.floor((Date.now() / 1000) + (60 * 60 * 24))); const username = await user.getUserField(uid, 'username'); + winston.verbose(`[user/email] Validation email for uid ${uid} sent to ${options.email}`); events.log({ type: 'email-confirmation-sent', uid, diff --git a/src/views/admin/settings/email.tpl b/src/views/admin/settings/email.tpl index 7f90431b96..e5b3fab134 100644 --- a/src/views/admin/settings/email.tpl +++ b/src/views/admin/settings/email.tpl @@ -20,13 +20,6 @@
-
- -
-

[[admin/settings/email:require-email-address-warning]]

+
+ +
+

[[admin/settings/email:prompt-help]]

+ +
+ +
From 72e1c281d7c2299131cfc3162ca04e8767cdbd03 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 12 Nov 2021 19:59:42 +0000 Subject: [PATCH 008/849] chore(i18n): fallback strings for new resources: nodebb.admin-settings-email --- public/language/ar/admin/settings/email.json | 3 ++- public/language/bg/admin/settings/email.json | 3 ++- public/language/bn/admin/settings/email.json | 3 ++- public/language/cs/admin/settings/email.json | 3 ++- public/language/da/admin/settings/email.json | 3 ++- public/language/de/admin/settings/email.json | 3 ++- public/language/el/admin/settings/email.json | 3 ++- public/language/en-US/admin/settings/email.json | 3 ++- public/language/en-x-pirate/admin/settings/email.json | 3 ++- public/language/es/admin/settings/email.json | 3 ++- public/language/et/admin/settings/email.json | 3 ++- public/language/fa-IR/admin/settings/email.json | 3 ++- public/language/fi/admin/settings/email.json | 3 ++- public/language/fr/admin/settings/email.json | 3 ++- public/language/gl/admin/settings/email.json | 3 ++- public/language/he/admin/settings/email.json | 3 ++- public/language/hr/admin/settings/email.json | 3 ++- public/language/hu/admin/settings/email.json | 3 ++- public/language/id/admin/settings/email.json | 3 ++- public/language/it/admin/settings/email.json | 3 ++- public/language/ja/admin/settings/email.json | 3 ++- public/language/ko/admin/settings/email.json | 3 ++- public/language/lt/admin/settings/email.json | 3 ++- public/language/lv/admin/settings/email.json | 3 ++- public/language/ms/admin/settings/email.json | 3 ++- public/language/nb/admin/settings/email.json | 3 ++- public/language/nl/admin/settings/email.json | 3 ++- public/language/pl/admin/settings/email.json | 3 ++- public/language/pt-BR/admin/settings/email.json | 3 ++- public/language/pt-PT/admin/settings/email.json | 3 ++- public/language/ro/admin/settings/email.json | 3 ++- public/language/ru/admin/settings/email.json | 3 ++- public/language/rw/admin/settings/email.json | 3 ++- public/language/sc/admin/settings/email.json | 3 ++- public/language/sk/admin/settings/email.json | 3 ++- public/language/sl/admin/settings/email.json | 3 ++- public/language/sr/admin/settings/email.json | 3 ++- public/language/sv/admin/settings/email.json | 3 ++- public/language/th/admin/settings/email.json | 3 ++- public/language/tr/admin/settings/email.json | 3 ++- public/language/uk/admin/settings/email.json | 3 ++- public/language/vi/admin/settings/email.json | 3 ++- public/language/zh-CN/admin/settings/email.json | 3 ++- public/language/zh-TW/admin/settings/email.json | 3 ++- 44 files changed, 88 insertions(+), 44 deletions(-) diff --git a/public/language/ar/admin/settings/email.json b/public/language/ar/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/ar/admin/settings/email.json +++ b/public/language/ar/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/bg/admin/settings/email.json b/public/language/bg/admin/settings/email.json index 74d2d26ce1..6898895a82 100644 --- a/public/language/bg/admin/settings/email.json +++ b/public/language/bg/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Моля, въведете число, представляващо часа, в който да се разпращат е-писма с подготвеното резюме (напр.. 0 за полунощ, 17 за 5 следобед). Имайте предвид, че този час е според часовата зона на сървъра и може да не съвпада с часовника на системата Ви.
Приблизителното време на сървъра е:
Изпращането на следващия ежедневен бюлетин е планирано за ", "notifications.remove-images": "Премахване на изображенията от известията по е-поща", "require-email-address": "Новите потребители задължително трябва да предоставят е-поща", - "require-email-address-warning": "По подразбиране потребителите могат да не въвеждат адрес на е-поща. Ако включите това, те задължително ще трябва да предоставят е-поща, за да могат да се регистрират. Това не означава, че потребителят ще въведе съществуваща е-поща, нито че тя ще е негова.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Изпращане на е-писма към получатели, които не са потвърдили изрично е-пощата си", "include-unverified-warning": "За потребителите, които имат свързана е-поща с регистрацията си, тя се смята за потвърдена. Но има ситуации, в които това не е така (например при ползване на регистрация от друга система, но и в други случаи), Включете тази настройка на собствен риск – изпращането на е-писма към непотвърдени адреси може да нарушава определени местни закони против нежеланата поща.", "prompt": "Подсещане на потребителите да въведат или потвърдят е-пощата си", diff --git a/public/language/bn/admin/settings/email.json b/public/language/bn/admin/settings/email.json index 7970d5ec70..df2081c946 100644 --- a/public/language/bn/admin/settings/email.json +++ b/public/language/bn/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/cs/admin/settings/email.json b/public/language/cs/admin/settings/email.json index daf0596406..682a5ad311 100644 --- a/public/language/cs/admin/settings/email.json +++ b/public/language/cs/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Zadejte číslo odpovídající hodině, kdy mají být odeslány přehledové e-maily (tj. 0 pro půlnoc, 17 pro 5:00pm). Mějte na paměti, že tato hodina závisí na hodinách samotného serveru a nemusí tak souhlasit se systémovými hodinami.
Přibližný čas serveru je: .
Další odeslání přehledů je plánováno na .", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/da/admin/settings/email.json b/public/language/da/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/da/admin/settings/email.json +++ b/public/language/da/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/de/admin/settings/email.json b/public/language/de/admin/settings/email.json index 9de1ce0df1..7467e2ca3f 100644 --- a/public/language/de/admin/settings/email.json +++ b/public/language/de/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Bitte geben Sie eine Nummer ein, welche die Stunde repräsentiert zu welcher geplante Emails versandt werden sollen (z.B. 0 für Mitternacht, 17 für 5 Uhr Nachmittags). Beachten Sie, dass die Zeit auf der Serverzeit basiert und daher nicht umbedingt mit ihrer Systemzeit übereinstimmen muss.
Die ungefähre Serverzeit ist:
Die nächste tägliche Sendung ist um geplant", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/el/admin/settings/email.json b/public/language/el/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/el/admin/settings/email.json +++ b/public/language/el/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/en-US/admin/settings/email.json b/public/language/en-US/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/en-US/admin/settings/email.json +++ b/public/language/en-US/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/en-x-pirate/admin/settings/email.json b/public/language/en-x-pirate/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/en-x-pirate/admin/settings/email.json +++ b/public/language/en-x-pirate/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/es/admin/settings/email.json b/public/language/es/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/es/admin/settings/email.json +++ b/public/language/es/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/et/admin/settings/email.json b/public/language/et/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/et/admin/settings/email.json +++ b/public/language/et/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/fa-IR/admin/settings/email.json b/public/language/fa-IR/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/fa-IR/admin/settings/email.json +++ b/public/language/fa-IR/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/fi/admin/settings/email.json b/public/language/fi/admin/settings/email.json index 2dd033e1ad..f712942d72 100644 --- a/public/language/fi/admin/settings/email.json +++ b/public/language/fi/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/fr/admin/settings/email.json b/public/language/fr/admin/settings/email.json index 56956a04ca..c761d4f304 100644 --- a/public/language/fr/admin/settings/email.json +++ b/public/language/fr/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Veuillez entrer un nombre représentant l'heure à laquelle envoyer les lettres d'activités (c'est à dire 0 pour minuit, 17 pour 5:00 pm). Gardez à l'esprit qu'il s'agit de l'heure du serveur, et peut ne pas correspondre à votre heure locale.
L'heure du serveur est :
La prochaine lettre d'activités sera envoyée à ", "notifications.remove-images": "Supprimer les images des notifications par e-mail", "require-email-address": "Exiger une adresse e-mail aux nouveaux utilisateurs ", - "require-email-address-warning": "Par défaut, les utilisateurs peuvent refuser de saisir une adresse e-mail. L'activation de cette option oblige de renseigner une une adresse e-mail lors de l'inscription. Ne garantit pas que l'utilisateur entrera adresse e-mail valide, ni même une adresse qu'il possède.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Envoyer des mails aux destinataires qui n'ont pas explicitement confirmé leurs mails", "include-unverified-warning": "Par défaut, les utilisateurs dont les mails sont associés à leur compte ont déjà été vérifiés, mais il existe des situations où ce n'est pas le cas (par exemple, les connexions SSO, les utilisateurs bénéficiant de droits acquis, etc.). Activez ce paramètre à vos risques et périls – l'envoi de mails à des adresses non vérifiées peut constituer une violation des lois anti-spam régionales.", "prompt": "Inviter les utilisateurs à saisir ou à confirmer leurs emails", diff --git a/public/language/gl/admin/settings/email.json b/public/language/gl/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/gl/admin/settings/email.json +++ b/public/language/gl/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/he/admin/settings/email.json b/public/language/he/admin/settings/email.json index 7c1fa4f51b..e2f6c16059 100644 --- a/public/language/he/admin/settings/email.json +++ b/public/language/he/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "הסר תמונות מהודעות דוא\"ל", "require-email-address": "דרוש ממשתמשים חדשים כתובת אימייל", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/hr/admin/settings/email.json b/public/language/hr/admin/settings/email.json index 48d010cef5..036689b3f3 100644 --- a/public/language/hr/admin/settings/email.json +++ b/public/language/hr/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Unesite broj koji pretstavlja vrijeme kada će se poslati pregled mailom (npr. 0 za ponoć, 17za 5 popodne).Imajte na umu da to vrijeme predstavlja vrijeme servera te ne mora predstavljati vrijeme na Vašem sistemu. Vrijeme servera je:
Sljedeći pregled će biti poslan .", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/hu/admin/settings/email.json b/public/language/hu/admin/settings/email.json index 5206a293fb..63b86ffba4 100644 --- a/public/language/hu/admin/settings/email.json +++ b/public/language/hu/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Kérjük adj meg egy számot, ami azt az órát jelenti, amikor az ütemezett összefoglalókat kiküldi a rendszer (0 az éjfél, 17 a délután 5 óra). Tartsd észben, hogy ez az időpont a szerver idejét veszi figyelembe és előfordulhat, hogy nem egyezik meg a Te gépeden jelzett idővel. A becsült szerver idő jelenleg:
A következő napi összefoglalás tervezett kiküldési ideje ", "notifications.remove-images": "Képek eltávolítása az email értesítésekből", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/id/admin/settings/email.json b/public/language/id/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/id/admin/settings/email.json +++ b/public/language/id/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/it/admin/settings/email.json b/public/language/it/admin/settings/email.json index 2d2eae1612..a5e9c6a1ee 100644 --- a/public/language/it/admin/settings/email.json +++ b/public/language/it/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Si prega di inserire un numero che rappresenta l'ora per l'invio dell'email programmate (es. 0per mezzanotte, 17per le 17: 00). Tieni presente che questa è l'ora secondo il server stesso, e potrebbe non combaciare esattamente al tuo orologio di sistema.
L'orario approssimativo del server è:
La prossima trasmissione giornaliera è prevista alle ", "notifications.remove-images": "Rimuovi le immagini dalle notifiche email", "require-email-address": "Richiedere ai nuovi utenti di specificare un indirizzo email", - "require-email-address-warning": "Per impostazione predefinita, gli utenti possono rinunciare a inserire un indirizzo email. Abilitare questa opzione significa che devono inserire un indirizzo email per procedere con la registrazione. Non assicura che l'utente inserisca un indirizzo email reale, e nemmeno un indirizzo che possiede.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Invia email a destinatari che non hanno confermato esplicitamente le loro email", "include-unverified-warning": "Per impostazione predefinita, gli utenti con email associate al loro account sono già stati verificati, ma ci sono situazioni in cui ciò non è vero (ad esempio accessi SSO, vecchi utenti, ecc.). Abilita questa impostazione a tuo rischio e pericolo – l'invio di email a indirizzi non verificati può essere una violazione delle leggi regionali anti-spam.", "prompt": "Chiedi agli utenti di inserire o confermare le loro email", diff --git a/public/language/ja/admin/settings/email.json b/public/language/ja/admin/settings/email.json index a3bf8a8616..de5faf05d6 100644 --- a/public/language/ja/admin/settings/email.json +++ b/public/language/ja/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "スケジュールされたメールのダイジェストを送信する時間を表す数字を入力してください(深夜は0、午後5:00は17)これはサーバー自体に基づく時間であり、システムの時計と正確に一致しない場合があります。
次の日のダイジェストは", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/ko/admin/settings/email.json b/public/language/ko/admin/settings/email.json index 2b6b3587dd..dd61798ffe 100644 --- a/public/language/ko/admin/settings/email.json +++ b/public/language/ko/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "정기 포럼 메일을 보낼 시간을 입력해주세요. (예: 0은 자정, 17은 오후 5시 입니다. 이 시간은 서버 시간 기준이며, 사용자의 시스템 시간과 일치하지 않을 수 있습니다.
서버 시간은 입니다.
다음 정기 포럼 메일은 에 발송 예정입니다.", "notifications.remove-images": "이메일 알림에서 이미지 제거", "require-email-address": "신규 사용자에게 이메일 주소 설정 요구", - "require-email-address-warning": "기본적으로 사용자들은 이메일 주소 입력을 거부할 수 있습니다. 이 설정을 활성화하면 사용자 등록 시 이메일 주소를 입력해야 진행할 수 있습니다. 하지만 해당 이메일 주소가 유효한 주소인지는 확인하지 않습니다.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "전자 메일을 명시적으로 확인하지 않은 수신자에게 전자 메일 보내기", "include-unverified-warning": "기본적으로 계정과 연결된 전자 메일이 있는 사용자는 이미 확인되었지만 그렇지 않은 경우가 있습니다(예: SSO 로그인, 약관으로부터 제외된 사용자 등). 사용자가 위험을 감수하고 이 설정을 사용하도록 설정합니다. – 확인되지 않은 주소로 이메일을 보내는 것은 지역별 스팸 방지법을 위반하는 것일 수 있습니다.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/lt/admin/settings/email.json b/public/language/lt/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/lt/admin/settings/email.json +++ b/public/language/lt/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/lv/admin/settings/email.json b/public/language/lv/admin/settings/email.json index a170931724..29e0674e8e 100644 --- a/public/language/lv/admin/settings/email.json +++ b/public/language/lv/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Ievadīt skaitli, kas norāda stundu, kurā nosūtītu e-pasta rakstu apkopojumu (piemēram, 0 nozīmē pusnakts, 17 nozīmē plkst.1700). Paturēt prātā, ka šī ir stunda servera laikā, un tā var neatbilst Tavam pulkstenim. Aptuvens servera laiks ir:
Nākamais ikdienas apkopojums tiks nosūtīts ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/ms/admin/settings/email.json b/public/language/ms/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/ms/admin/settings/email.json +++ b/public/language/ms/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/nb/admin/settings/email.json b/public/language/nb/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/nb/admin/settings/email.json +++ b/public/language/nb/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/nl/admin/settings/email.json b/public/language/nl/admin/settings/email.json index 3588f40700..87151dd85b 100644 --- a/public/language/nl/admin/settings/email.json +++ b/public/language/nl/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Voer het nummer in dat het uur representeerd waarop scheduled email digests worden verstuurd (bv. 0 voor middernacht, 17 voor 17:00). Neem er s.v.p. notie van dat dit het uur is van de server self, dit hoeft niet exact overeen te komen met de klok van uw systeem.
De tijd op de server is bij benadering:
De volgende dagelijkse digest staat gepland om ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/pl/admin/settings/email.json b/public/language/pl/admin/settings/email.json index 76895b0536..9fc29057b7 100644 --- a/public/language/pl/admin/settings/email.json +++ b/public/language/pl/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Wprowadź liczbę odpowiadającą godzinie, o której mają być wysyłane regularne e-maile z podsumowaniem (np. 0 dla północy lub 17 dla 17:00). Pamiętaj, że godzina jest godziną serwera i nie musi zgadzać się z czasem lokalnym administratora. Przybliżony czas serwera to:
Wysłanie kolejnego e-maila z podsumowaniem zaplanowano na ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/pt-BR/admin/settings/email.json b/public/language/pt-BR/admin/settings/email.json index ca02b536d0..718ca10450 100644 --- a/public/language/pt-BR/admin/settings/email.json +++ b/public/language/pt-BR/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Por favor, entre um número representando a hora para enviar os resumos agendados via e-mail (por exemplo: 0 para meia-noite, 17 para 5:00pm). Tenha em mente que esta é a hora de acordo com o servidor e pode não combinar exatamente com o relógio do seu sistema.
O horário aproximado do servidor é:
O próximo resumo diário está agendado para ser enviado ", "notifications.remove-images": "Remover imagens de notificações por e-mail", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/pt-PT/admin/settings/email.json b/public/language/pt-PT/admin/settings/email.json index 8e9e871abc..564991683a 100644 --- a/public/language/pt-PT/admin/settings/email.json +++ b/public/language/pt-PT/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/ro/admin/settings/email.json b/public/language/ro/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/ro/admin/settings/email.json +++ b/public/language/ro/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/ru/admin/settings/email.json b/public/language/ru/admin/settings/email.json index 05028e66f2..53f3d2455f 100644 --- a/public/language/ru/admin/settings/email.json +++ b/public/language/ru/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Введите число, соответствующее номеру часа (например, 0 для полуночи, 17 для 17:00). Имейте в виду, что время определяется по часовому поясу сервера.
Текущее время сервера:
Следующая рассылка запланирована на ", "notifications.remove-images": "Удалить изображения из уведомлений по электронной почте", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/rw/admin/settings/email.json b/public/language/rw/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/rw/admin/settings/email.json +++ b/public/language/rw/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/sc/admin/settings/email.json b/public/language/sc/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/sc/admin/settings/email.json +++ b/public/language/sc/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/sk/admin/settings/email.json b/public/language/sk/admin/settings/email.json index 894bd512e3..fad4ad3245 100644 --- a/public/language/sk/admin/settings/email.json +++ b/public/language/sk/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/sl/admin/settings/email.json b/public/language/sl/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/sl/admin/settings/email.json +++ b/public/language/sl/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/sr/admin/settings/email.json b/public/language/sr/admin/settings/email.json index f0a4584fb4..384c8b03d4 100644 --- a/public/language/sr/admin/settings/email.json +++ b/public/language/sr/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Molim unesite broj koji označava satnicu kada da pošalje zakazani sažeti email (nrp. 0 za ponoć, 17 za 5:00 pm). Uzmite u obzir da će se slanje događati po satnici samog servara, i da vrlo verovatno se ne poklapa sa satnicom vašeg sistema.
Trenutno vreme servera je:
Sledeći dnevni sažeti email zakazan je za slanje u ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/sv/admin/settings/email.json b/public/language/sv/admin/settings/email.json index 381ab387df..a06d4b1a48 100644 --- a/public/language/sv/admin/settings/email.json +++ b/public/language/sv/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/th/admin/settings/email.json b/public/language/th/admin/settings/email.json index 65f579d28c..17c60daf69 100644 --- a/public/language/th/admin/settings/email.json +++ b/public/language/th/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/tr/admin/settings/email.json b/public/language/tr/admin/settings/email.json index 0dffd74edf..d3094fb308 100644 --- a/public/language/tr/admin/settings/email.json +++ b/public/language/tr/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. 0 for midnight, 17 for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.
The approximate server time is:
The next daily digest is scheduled to be sent ", "notifications.remove-images": "Görselleri e-posta bildirimlerinden kaldır", "require-email-address": "Yeni kullanıcıların bir e-posta adresi belirtmesini gerektir", - "require-email-address-warning": "Varsayılan olarak kullanıcıların bir e-posta adresi girmesi devre dışıdır. Bu seçeneğin etkinleştirilmesi, kayıt işlemine devam etmek için bir e-posta adresi girmeleri gerektiği anlamına gelir. Elbette bu kullanıcının gerçek bir e-posta adresi veya sahip olduğu bir adresi girmelerini sağlamaz.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "E-postalarını onaylamayan alıcılara onay e-postası gönderin", "include-unverified-warning": "Varsayılan olarak, hesaplarıyla ilişkili e-postaları olan kullanıcılar (Sosyal Login) zaten doğrulanmıştır, ancak durumun böyle olmadığı durumlar vardır (ör. Riski size ait olmak üzere bu ayarı etkinleştirin – doğrulanmamış adreslere e-posta göndermek, bölgesel istenmeyen posta önleme yasalarının ihlali olabilir.", "prompt": "Kullanıcılardan e-postalarını girmelerini veya onaylamalarını isteyin", diff --git a/public/language/uk/admin/settings/email.json b/public/language/uk/admin/settings/email.json index 912ca7528a..86c1d1c540 100644 --- a/public/language/uk/admin/settings/email.json +++ b/public/language/uk/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Вкажіть, будь ласка, годину о котрій кожного дня буде надсилатися дайджест (наприклад 0 — це північ, а 17 — п'ята година вечора). Зверніть увагу, що година визначається згідно налаштувань сервера і може не співпадати з часом вашого комп'ютера.
Приблизний час сервера:
Наступний дайджест заплановано до відправки ", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/vi/admin/settings/email.json b/public/language/vi/admin/settings/email.json index 07682dcf26..ccdc472e3b 100644 --- a/public/language/vi/admin/settings/email.json +++ b/public/language/vi/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "Vui lòng nhập một số đại diện cho giờ để gửi thông báo email đã lên lịch (VD: 0 cho nửa đêm, 17 cho 5h chiều). Hãy nhớ rằng đây là giờ theo chính máy chủ và có thể không khớp chính xác với đồng hồ hệ thống của bạn.
Thời gian máy chủ gần đúng là:
Thông báo hàng ngày kế tiếp được lên lịch để gửi ", "notifications.remove-images": "Xóa hình ảnh khỏi thông báo email", "require-email-address": "Bắt buộc người dùng mới phải điền địa chỉ email", - "require-email-address-warning": "Mặc định, người dùng có thể chọn không nhập địa chỉ email. Bật tùy chọn này nghĩa là họ buộc phải nhập địa chỉ email để đăng ký. Việc này không chắc người dùng sẽ nhập địa chỉ email thực, hoặc không phải địa chỉ mà họ sở hữu.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Gửi email đến những người nhận chưa xác nhận rõ ràng email của họ", "include-unverified-warning": "Theo mặc định, người dùng có email được liên kết với tài khoản của họ đã được xác minh, nhưng có những trường hợp không phải như vậy (ví dụ: đăng nhập SSO, người dùng phổ thông, v.v.). Bạn tự chịu rủi ro khi bật cài đặt này – gửi email đến các địa chỉ chưa được xác minh có thể vi phạm luật chống thư rác trong khu vực.", "prompt": "Nhắc người dùng nhập hoặc xác nhận email của họ", diff --git a/public/language/zh-CN/admin/settings/email.json b/public/language/zh-CN/admin/settings/email.json index 5afd0ca782..d753190123 100644 --- a/public/language/zh-CN/admin/settings/email.json +++ b/public/language/zh-CN/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "请输入一个代表小时的数字来发送计划的电子邮件摘要 (例如,对于午夜,0,对于下午5:00,17)。 请记住,这是根据服务器本身的时间,可能与您的系统时钟不完全匹配。
服务器的大致时间为:
下一个每日摘要被计划在发送", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", diff --git a/public/language/zh-TW/admin/settings/email.json b/public/language/zh-TW/admin/settings/email.json index bddc247003..6e6b0416d4 100644 --- a/public/language/zh-TW/admin/settings/email.json +++ b/public/language/zh-TW/admin/settings/email.json @@ -38,7 +38,8 @@ "subscriptions.hour-help": "請輸入一個代表小時的數字來發送排程的電子郵件摘要 (例如,對於午夜,0,對於下午5:00,17)。 請記住,這是根據伺服器本身的時間,可能與您的系統時鐘不完全符合。
伺服器的大致時間為:
下一個每日摘要被排程在發送", "notifications.remove-images": "Remove images from email notifications", "require-email-address": "Require new users to specify an email address", - "require-email-address-warning": "By default, users can opt-out of entering an email address. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", + "send-validation-email": "Send validation emails when an email is added or changed", "include-unverified-emails": "Send emails to recipients who have not explicitly confirmed their emails", "include-unverified-warning": "By default, users with emails associated with their account have already been verified, but there are situations where this is not the case (e.g. SSO logins, grandfathered users, etc). Enable this setting at your own risk – sending emails to unverified addresses may be a violation of regional anti-spam laws.", "prompt": "Prompt users to enter or confirm their emails", From d412ba441150ed9a58d5ff2dfad50013a2c3a3dc Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Fri, 12 Nov 2021 15:13:36 -0500 Subject: [PATCH 009/849] perf: closes #9994, bulk methods for settings --- src/meta/settings.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/meta/settings.js b/src/meta/settings.js index 5df3a2f8b9..e9ba356eba 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -69,16 +69,21 @@ Settings.set = async function (hash, values, quiet) { await db.deleteAll(deleteKeys); })); - const ops = []; + const sortedSetData = []; + const objectData = { keys: [], data: [] }; sortedLists.forEach((list) => { const arr = sortedListData[list]; arr.forEach((data, order) => { - ops.push(db.sortedSetAdd(`settings:${hash}:sorted-list:${list}`, order, order)); - ops.push(db.setObject(`settings:${hash}:sorted-list:${list}:${order}`, data)); + sortedSetData.push([`settings:${hash}:sorted-list:${list}`, order, order]); + objectData.keys.push(`settings:${hash}:sorted-list:${list}:${order}`); + objectData.data.push(data); }); }); - await Promise.all(ops); + await Promise.all([ + db.sortedSetAddBulk(sortedSetData), + db.setObjectBulk(objectData.keys, objectData.data), + ]); } if (Object.keys(values).length) { From e325aa935a160ef2f8234db3a456fedd3470ebb9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 12 Nov 2021 15:23:41 -0500 Subject: [PATCH 010/849] docs: update readme blurb --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ec072e3a3a..dc72deb2a7 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,11 @@ [![Coverage Status](https://coveralls.io/repos/github/NodeBB/NodeBB/badge.svg?branch=master)](https://coveralls.io/github/NodeBB/NodeBB?branch=master) [![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB) -[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB has many modern features out of the box such as social network integration and streaming discussions, while still making sure to be compatible with older browsers. +[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging. -Additional functionality is enabled through the use of third-party plugins. +NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins. -* [Demo](https://try.nodebb.org) -* [Developer Community](http://community.nodebb.org) -* [Documentation & Installation Instructions](http://docs.nodebb.org) -* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/) -* [NodeBB Blog](http://blog.nodebb.org) -* [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB") -* Unofficial IRC community – channel `#nodebb` on Libera.chat -* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") -* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") +### [Try it now](//try.nodebb.org) ## Screenshots @@ -78,3 +70,15 @@ Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.node NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html). Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org. + +## More Information/Links + +* [Demo](https://try.nodebb.org) +* [Developer Community](http://community.nodebb.org) +* [Documentation & Installation Instructions](http://docs.nodebb.org) +* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/) +* [NodeBB Blog](http://blog.nodebb.org) +* [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB") +* Unofficial IRC community – channel `#nodebb` on Libera.chat +* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") +* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") \ No newline at end of file From 1a85aaad23dd6dc61254284e5afc46f0d0eb7c67 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 12 Nov 2021 15:26:00 -0500 Subject: [PATCH 011/849] docs: add docs link higher up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc72deb2a7..63000c0f80 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins. -### [Try it now](//try.nodebb.org) +### [Try it now](//try.nodebb.org) | [Documentation](//docs.nodebb.org) ## Screenshots From 8379c11b2289179e46dcca983709c77bc0ce5fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 12 Nov 2021 19:51:59 -0500 Subject: [PATCH 012/849] refactor: setObjectBulk to match sortedSetAddBulk --- src/categories/update.js | 3 +- src/database/mongo/hash.js | 22 +++++++++------ src/database/postgres/hash.js | 27 +++++++++--------- src/database/redis/hash.js | 19 +++++++++---- src/meta/settings.js | 7 ++--- src/posts/queue.js | 3 +- src/topics/scheduled.js | 2 +- src/topics/tags.js | 17 ++++------- src/upgrades/1.15.0/topic_poster_count.js | 8 ++---- src/upgrades/1.17.0/topic_thumb_count.js | 3 +- src/upgrades/1.18.0/topic_tags_refactor.js | 3 +- src/widgets/index.js | 10 ++----- test/database/hash.js | 33 ++++++++++------------ 13 files changed, 75 insertions(+), 82 deletions(-) diff --git a/src/categories/update.js b/src/categories/update.js index 259dd58d06..63015684dd 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -121,8 +121,7 @@ module.exports = function (Categories) { ); await db.setObjectBulk( - childrenCids.map(cid => `category:${cid}`), - childrenCids.map((cid, index) => ({ order: index + 1 })) + childrenCids.map((cid, index) => [`category:${cid}`, { order: index + 1 }]) ); cache.del([ diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 67daab7b16..bb6c4d488f 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -35,20 +35,26 @@ module.exports = function (module) { cache.del(key); }; - module.setObjectBulk = async function (keys, data) { - if (!keys.length || !data.length) { + module.setObjectBulk = async function (...args) { + let data = args[0]; + if (!Array.isArray(data) || !data.length) { return; } + if (Array.isArray(args[1])) { + console.warn('[deprecated] db.setObjectBulk(keys, data) usage is deprecated, please use db.setObjectBulk(data)'); + // conver old format to new format for backwards compatibility + data = args[0].map((key, i) => [key, args[1][i]]); + } - const writeData = data.map(helpers.serializeData); try { let bulk; - keys.forEach((key, i) => { - if (Object.keys(writeData[i]).length) { + data.forEach((item) => { + const writeData = helpers.serializeData(item[1]); + if (Object.keys(writeData).length) { if (!bulk) { bulk = module.client.collection('objects').initializeUnorderedBulkOp(); } - bulk.find({ _key: key }).upsert().updateOne({ $set: writeData[i] }); + bulk.find({ _key: item[0] }).upsert().updateOne({ $set: writeData }); } }); if (bulk) { @@ -56,12 +62,12 @@ module.exports = function (module) { } } catch (err) { if (err && err.message.startsWith('E11000 duplicate key error')) { - return await module.setObjectBulk(keys, data); + return await module.setObjectBulk(data); } throw err; } - cache.del(keys); + cache.del(data.map(item => item[0])); }; module.setObjectField = async function (key, field, value) { diff --git a/src/database/postgres/hash.js b/src/database/postgres/hash.js index 015154aa42..519a8e6c0e 100644 --- a/src/database/postgres/hash.js +++ b/src/database/postgres/hash.js @@ -44,29 +44,30 @@ module.exports = function (module) { }); }; - module.setObjectBulk = async function (keys, data) { - if (!keys.length || !data.length) { + module.setObjectBulk = async function (...args) { + let data = args[0]; + if (!Array.isArray(data) || !data.length) { return; } + if (Array.isArray(args[1])) { + console.warn('[deprecated] db.setObjectBulk(keys, data) usage is deprecated, please use db.setObjectBulk(data)'); + // conver old format to new format for backwards compatibility + data = args[0].map((key, i) => [key, args[1][i]]); + } await module.transaction(async (client) => { - keys = keys.slice(); - data = data.filter((d, i) => { - if (d.hasOwnProperty('')) { - delete d['']; - } - const keep = !!Object.keys(d).length; - if (!keep) { - keys.splice(i, 1); + data = data.filter((item) => { + if (item[1].hasOwnProperty('')) { + delete item[1]['']; } - return keep; + return !!Object.keys(item[1]).length; }); - + const keys = data.map(item => item[0]); if (!keys.length) { return; } await helpers.ensureLegacyObjectsType(client, keys, 'hash'); - const dataStrings = data.map(JSON.stringify); + const dataStrings = data.map(item => JSON.stringify(item[1])); await client.query({ name: 'setObjectBulk', text: ` diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 966a36eddd..1afdccd3b1 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -36,18 +36,25 @@ module.exports = function (module) { cache.del(key); }; - module.setObjectBulk = async function (keys, data) { - if (!keys.length || !data.length) { + module.setObjectBulk = async function (...args) { + let data = args[0]; + if (!Array.isArray(data) || !data.length) { return; } + if (Array.isArray(args[1])) { + console.warn('[deprecated] db.setObjectBulk(keys, data) usage is deprecated, please use db.setObjectBulk(data)'); + // conver old format to new format for backwards compatibility + data = args[0].map((key, i) => [key, args[1][i]]); + } + const batch = module.client.batch(); - keys.forEach((k, i) => { - if (Object.keys(data[i]).length) { - batch.hmset(k, data[i]); + data.forEach((item) => { + if (Object.keys(item[1]).length) { + batch.hmset(item[0], item[1]); } }); await helpers.execBatch(batch); - cache.del(keys); + cache.del(data.map(item => item[0])); }; module.setObjectField = async function (key, field, value) { diff --git a/src/meta/settings.js b/src/meta/settings.js index e9ba356eba..badfa791bd 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -70,19 +70,18 @@ Settings.set = async function (hash, values, quiet) { })); const sortedSetData = []; - const objectData = { keys: [], data: [] }; + const objectData = []; sortedLists.forEach((list) => { const arr = sortedListData[list]; arr.forEach((data, order) => { sortedSetData.push([`settings:${hash}:sorted-list:${list}`, order, order]); - objectData.keys.push(`settings:${hash}:sorted-list:${list}:${order}`); - objectData.data.push(data); + objectData.push([`settings:${hash}:sorted-list:${list}:${order}`, data]); }); }); await Promise.all([ db.sortedSetAddBulk(sortedSetData), - db.setObjectBulk(objectData.keys, objectData.data), + db.setObjectBulk(objectData), ]); } diff --git a/src/posts/queue.js b/src/posts/queue.js index 6ed1af9c47..33a64e14be 100644 --- a/src/posts/queue.js +++ b/src/posts/queue.js @@ -343,8 +343,7 @@ module.exports = function (Posts) { post.data.tid = newTid; }); await db.setObjectBulk( - postData.map(p => `post:queue:${p.id}`), - postData.map(p => ({ data: JSON.stringify(p.data) })) + postData.map(p => [`post:queue:${p.id}`, { data: JSON.stringify(p.data) }]), ); cache.del('post-queue'); } diff --git a/src/topics/scheduled.js b/src/topics/scheduled.js index 5d99b1432b..56c363db39 100644 --- a/src/topics/scheduled.js +++ b/src/topics/scheduled.js @@ -124,5 +124,5 @@ async function updateUserLastposttimes(uids, topicsData) { async function shiftPostTimes(tid, timestamp) { const pids = (await posts.getPidsFromSet(`tid:${tid}:posts`, 0, -1, false)); // Leaving other related score values intact, since they reflect post order correctly, and it seems that's good enough - return db.setObjectBulk(pids.map(pid => `post:${pid}`), pids.map((_, idx) => ({ timestamp: timestamp + idx + 1 }))); + return db.setObjectBulk(pids.map((pid, idx) => [`post:${pid}`, { timestamp: timestamp + idx + 1 }])); } diff --git a/src/topics/tags.js b/src/topics/tags.js index b0be277a9d..f78eb8e09e 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -155,8 +155,7 @@ module.exports = function (Topics) { } }); await db.setObjectBulk( - topicData.map(t => `topic:${t.tid}`), - topicData.map(t => ({ tags: t.tags.join(',') })) + topicData.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), ); }, {}); await Topics.deleteTag(tag); @@ -335,14 +334,11 @@ module.exports = function (Topics) { topicTags.push(tag); } }); - bulkSet.push({ tags: topicTags.join(',') }); + bulkSet.push([`topic:${t.tid}`, { tags: topicTags.join(',') }]); }); await Promise.all([ db.sortedSetAddBulk(bulkAdd), - db.setObjectBulk( - topicData.map(t => `topic:${t.tid}`), - bulkSet, - ), + db.setObjectBulk(bulkSet), ]); await Promise.all(tags.map(updateTagCount)); @@ -363,14 +359,11 @@ module.exports = function (Topics) { topicTags.splice(topicTags.indexOf(tag), 1); } }); - bulkSet.push({ tags: topicTags.join(',') }); + bulkSet.push([`topic:${t.tid}`, { tags: topicTags.join(',') }]); }); await Promise.all([ db.sortedSetRemoveBulk(bulkRemove), - db.setObjectBulk( - topicData.map(t => `topic:${t.tid}`), - bulkSet, - ), + db.setObjectBulk(bulkSet), ]); await Promise.all(tags.map(updateTagCount)); diff --git a/src/upgrades/1.15.0/topic_poster_count.js b/src/upgrades/1.15.0/topic_poster_count.js index 91a93ce788..f7d20d4c0d 100644 --- a/src/upgrades/1.15.0/topic_poster_count.js +++ b/src/upgrades/1.15.0/topic_poster_count.js @@ -15,15 +15,13 @@ module.exports = { const keys = tids.map(tid => `tid:${tid}:posters`); await db.sortedSetsRemoveRangeByScore(keys, '-inf', 0); const counts = await db.sortedSetsCard(keys); - const setKeys = []; - const data = []; + const bulkSet = []; for (let i = 0; i < tids.length; i++) { if (counts[i] > 0) { - setKeys.push(`topic:${tids[i]}`); - data.push({ postercount: counts[i] }); + bulkSet.push([`topic:${tids[i]}`, { postercount: counts[i] }]); } } - await db.setObjectBulk(setKeys, data); + await db.setObjectBulk(bulkSet); }, { progress: progress, batchSize: 500, diff --git a/src/upgrades/1.17.0/topic_thumb_count.js b/src/upgrades/1.17.0/topic_thumb_count.js index 7e39b195ea..83762612fa 100644 --- a/src/upgrades/1.17.0/topic_thumb_count.js +++ b/src/upgrades/1.17.0/topic_thumb_count.js @@ -16,8 +16,7 @@ module.exports = { const tidToCount = _.zipObject(tids, counts); const tidsWithThumbs = tids.filter((t, i) => counts[i] > 0); await db.setObjectBulk( - tidsWithThumbs.map(tid => `topic:${tid}`), - tidsWithThumbs.map(tid => ({ numThumbs: tidToCount[tid] })) + tidsWithThumbs.map(tid => [`topic:${tid}`, { numThumbs: tidToCount[tid] }]), ); progress.incr(tids.length); diff --git a/src/upgrades/1.18.0/topic_tags_refactor.js b/src/upgrades/1.18.0/topic_tags_refactor.js index 5fd2218c49..eb895e720b 100644 --- a/src/upgrades/1.18.0/topic_tags_refactor.js +++ b/src/upgrades/1.18.0/topic_tags_refactor.js @@ -25,8 +25,7 @@ module.exports = { }).filter(t => t && t.tags.length); await db.setObjectBulk( - topicsWithTags.map(t => `topic:${t.tid}`), - topicsWithTags.map(t => ({ tags: t.tags.join(',') })) + topicsWithTags.map(t => [`topic:${t.tid}`, { tags: t.tags.join(',') }]), ); await db.deleteAll(tids.map(tid => `topic:${tid}:tags`)); progress.incr(tids.length); diff --git a/src/widgets/index.js b/src/widgets/index.js index bda9682736..686c309e7b 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -177,13 +177,9 @@ widgets.setAreas = async function (areas) { templates[area.template][area.location] = JSON.stringify(area.widgets); }); - const keys = []; - const data = []; - Object.keys(templates).forEach((tpl) => { - keys.push(`widgets:${tpl}`); - data.push(templates[tpl]); - }); - await db.setObjectBulk(keys, data); + await db.setObjectBulk( + Object.keys(templates).map(tpl => [`widgets:${tpl}`, templates[tpl]]) + ); }; widgets.reset = async function () { diff --git a/test/database/hash.js b/test/database/hash.js index e2e2ee7364..294e9f765b 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -73,36 +73,33 @@ describe('Hash methods', () => { }); it('should set multiple keys to different objects', async () => { - const keys = ['bulkKey1', 'bulkKey2']; - const data = [{ foo: '1' }, { baz: 'baz' }]; - - await db.setObjectBulk(keys, data); - const result = await db.getObjects(keys); - assert.deepStrictEqual(result, data); + await db.setObjectBulk([ + ['bulkKey1', { foo: '1' }], + ['bulkKey2', { baz: 'baz' }], + ]); + const result = await db.getObjects(['bulkKey1', 'bulkKey2']); + assert.deepStrictEqual(result, [{ foo: '1' }, { baz: 'baz' }]); }); it('should not error if object is empty', async () => { - const keys = ['bulkKey3', 'bulkKey4']; - const data = [{ foo: '1' }, { }]; - - await db.setObjectBulk(keys, data); - const result = await db.getObjects(keys); + await db.setObjectBulk([ + ['bulkKey3', { foo: '1' }], + ['bulkKey4', { }], + ]); + const result = await db.getObjects(['bulkKey3', 'bulkKey4']); assert.deepStrictEqual(result, [{ foo: '1' }, null]); }); it('should update existing object on second call', async () => { - await db.setObjectBulk(['bulkKey3.5'], [{ foo: '1' }]); - await db.setObjectBulk(['bulkKey3.5'], [{ baz: '2' }]); + await db.setObjectBulk([['bulkKey3.5', { foo: '1' }]]); + await db.setObjectBulk([['bulkKey3.5', { baz: '2' }]]); const result = await db.getObject('bulkKey3.5'); assert.deepStrictEqual(result, { foo: '1', baz: '2' }); }); it('should not error if object is empty', async () => { - const keys = ['bulkKey5']; - const data = [{ }]; - - await db.setObjectBulk(keys, data); - const result = await db.getObjects(keys); + await db.setObjectBulk([['bulkKey5', {}]]); + const result = await db.getObjects(['bulkKey5']); assert.deepStrictEqual(result, [null]); }); From d7c2a311ab420a64f632d06c172a8b0edb9bb29b Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 13 Nov 2021 02:09:54 +0000 Subject: [PATCH 013/849] chore(deps): update dependency jsdom to v18.1.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index b0b48b5c8f..8f7bdd7aa8 100644 --- a/install/package.json +++ b/install/package.json @@ -151,7 +151,7 @@ "grunt": "1.4.1", "grunt-contrib-watch": "1.1.0", "husky": "7.0.4", - "jsdom": "18.0.1", + "jsdom": "18.1.0", "lint-staged": "11.2.6", "mocha": "9.1.3", "mocha-lcov-reporter": "1.3.0", From 7f8783555baf5fa95f67e522ac6ce8cb50fa326d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Nov 2021 20:16:24 -0500 Subject: [PATCH 014/849] Revert "perf: remove createUserTooltips" This reverts commit facc10e40f8789058214d0677806e65b0cc7c37d. --- public/src/app.js | 20 ++++++++++++++++++-- public/src/client/account/posts.js | 1 + public/src/client/account/topics.js | 1 + public/src/client/categories.js | 2 ++ public/src/client/category.js | 1 + public/src/client/topic/posts.js | 3 +++ public/src/modules/chat.js | 1 + public/src/modules/topicList.js | 1 + 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index e92eb5c8ca..6154c8b323 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -209,8 +209,18 @@ app.flags = {}; .addClass('active'); } - app.createUserTooltips = function () { - console.warn('[removed] app.creatUserTooltips is removed'); + app.createUserTooltips = function (els, placement) { + if (isTouchDevice) { + return; + } + els = els || $('body'); + els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').each(function () { + $(this).tooltip({ + placement: placement || $(this).attr('title-placement') || 'top', + title: $(this).attr('title'), + container: '#content', + }); + }); }; app.createStatusTooltips = function () { @@ -224,9 +234,15 @@ app.flags = {}; app.processPage = function () { highlightNavigationLink(); + $('.timeago').timeago(); + utils.makeNumbersHumanReadable($('.human-readable-number')); + utils.addCommasToNumbers($('.formatted-number')); + + app.createUserTooltips($('#content')); + app.createStatusTooltips(); }; diff --git a/public/src/client/account/posts.js b/public/src/client/account/posts.js index a0c0a3e92f..27442e21a5 100644 --- a/public/src/client/account/posts.js +++ b/public/src/client/account/posts.js @@ -45,6 +45,7 @@ define('forum/account/posts', ['forum/account/header', 'forum/infinitescroll', ' $('[component="posts"]').append(html); html.find('img:not(.not-responsive)').addClass('img-responsive'); html.find('.timeago').timeago(); + app.createUserTooltips(); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:posts.loaded', { posts: posts }); callback(); diff --git a/public/src/client/account/topics.js b/public/src/client/account/topics.js index 565bb9b170..cba3358275 100644 --- a/public/src/client/account/topics.js +++ b/public/src/client/account/topics.js @@ -46,6 +46,7 @@ define('forum/account/topics', [ app.parseAndTranslate(template, 'topics', { topics: topics }, function (html) { $('[component="category"]').append(html); html.find('.timeago').timeago(); + app.createUserTooltips(); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:topics.loaded', { topics: topics }); callback(); diff --git a/public/src/client/categories.js b/public/src/client/categories.js index 1881941c00..44025e8b8c 100644 --- a/public/src/client/categories.js +++ b/public/src/client/categories.js @@ -55,6 +55,8 @@ define('forum/categories', ['components', 'categorySelector', 'hooks'], function } html.fadeIn(); + + app.createUserTooltips(); html.find('.timeago').timeago(); if (category.find('[component="category/posts"]').length > parseInt(numRecentReplies, 10)) { diff --git a/public/src/client/category.js b/public/src/client/category.js index 5e0fccbcbb..da8a6296cf 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -104,6 +104,7 @@ define('forum/category', [ html.find('.timeago').timeago(); $('[component="category/subcategory/container"]').append(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); + app.createUserTooltips(html); ajaxify.data.nextSubCategoryStart += ajaxify.data.subCategoriesPerPage; ajaxify.data.subCategoriesLeft -= data.length; btn.toggleClass('hidden', ajaxify.data.subCategoriesLeft <= 0) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 9e3f63a657..b84953e915 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -370,6 +370,9 @@ define('forum/topic/posts', [ Posts.onNewPostsAddedToDom = function (posts) { Posts.onTopicPageLoad(posts); + + app.createUserTooltips(posts); + utils.addCommasToNumbers(posts.find('.formatted-number')); utils.makeNumbersHumanReadable(posts.find('.human-readable-number')); posts.find('.timeago').timeago(); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 1300b78c22..d57386040a 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -96,6 +96,7 @@ define('chat', [ app.parseAndTranslate('partials/chats/dropdown', { rooms: rooms }, function (html) { chatsListEl.find('*').not('.navigation-link').remove(); chatsListEl.prepend(html); + app.createUserTooltips(chatsListEl, 'right'); chatsListEl.off('click').on('click', '[data-roomid]', function (ev) { if ($(ev.target).parents('.user-link').length) { return; diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js index db65c20522..1ce4016364 100644 --- a/public/src/modules/topicList.js +++ b/public/src/modules/topicList.js @@ -262,6 +262,7 @@ define('topicList', [ } html.find('.timeago').timeago(); + app.createUserTooltips(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:topics.loaded', { topics: topics, template: templateName }); callback(); From 231472354e59f6f2c24bb08f84d4540d052ef0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Nov 2021 20:25:09 -0500 Subject: [PATCH 015/849] perf: create user tooltips on demand --- public/src/app.js | 17 ++++++++--------- public/src/client/account/posts.js | 2 +- public/src/client/account/topics.js | 2 +- public/src/client/categories.js | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 6154c8b323..a20aaf0bde 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -214,12 +214,16 @@ app.flags = {}; return; } els = els || $('body'); - els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').each(function () { - $(this).tooltip({ - placement: placement || $(this).attr('title-placement') || 'top', - title: $(this).attr('title'), + els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').one('mouseenter', function (ev) { + const $this = $(this); + // perf: create tooltips on demand + $this.tooltip({ + placement: placement || $this.attr('title-placement') || 'top', + title: $this.attr('title'), container: '#content', }); + // this will cause the tooltip to show up + $this.trigger(ev); }); }; @@ -234,15 +238,10 @@ app.flags = {}; app.processPage = function () { highlightNavigationLink(); - $('.timeago').timeago(); - utils.makeNumbersHumanReadable($('.human-readable-number')); - utils.addCommasToNumbers($('.formatted-number')); - app.createUserTooltips($('#content')); - app.createStatusTooltips(); }; diff --git a/public/src/client/account/posts.js b/public/src/client/account/posts.js index 27442e21a5..a4dbb619e1 100644 --- a/public/src/client/account/posts.js +++ b/public/src/client/account/posts.js @@ -45,7 +45,7 @@ define('forum/account/posts', ['forum/account/header', 'forum/infinitescroll', ' $('[component="posts"]').append(html); html.find('img:not(.not-responsive)').addClass('img-responsive'); html.find('.timeago').timeago(); - app.createUserTooltips(); + app.createUserTooltips(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:posts.loaded', { posts: posts }); callback(); diff --git a/public/src/client/account/topics.js b/public/src/client/account/topics.js index cba3358275..0bebfdac2f 100644 --- a/public/src/client/account/topics.js +++ b/public/src/client/account/topics.js @@ -46,7 +46,7 @@ define('forum/account/topics', [ app.parseAndTranslate(template, 'topics', { topics: topics }, function (html) { $('[component="category"]').append(html); html.find('.timeago').timeago(); - app.createUserTooltips(); + app.createUserTooltips(html); utils.makeNumbersHumanReadable(html.find('.human-readable-number')); hooks.fire('action:topics.loaded', { topics: topics }); callback(); diff --git a/public/src/client/categories.js b/public/src/client/categories.js index 44025e8b8c..04010f5554 100644 --- a/public/src/client/categories.js +++ b/public/src/client/categories.js @@ -56,7 +56,7 @@ define('forum/categories', ['components', 'categorySelector', 'hooks'], function html.fadeIn(); - app.createUserTooltips(); + app.createUserTooltips(html); html.find('.timeago').timeago(); if (category.find('[component="category/posts"]').length > parseInt(numRecentReplies, 10)) { From 2b39dc4dea0900ac4aca7c87931c10f11bea888f Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sun, 14 Nov 2021 09:05:56 +0000 Subject: [PATCH 016/849] Latest translations and fallbacks --- public/language/bg/admin/settings/email.json | 4 ++-- public/language/vi/admin/settings/email.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/bg/admin/settings/email.json b/public/language/bg/admin/settings/email.json index 6898895a82..d6b284551d 100644 --- a/public/language/bg/admin/settings/email.json +++ b/public/language/bg/admin/settings/email.json @@ -38,8 +38,8 @@ "subscriptions.hour-help": "Моля, въведете число, представляващо часа, в който да се разпращат е-писма с подготвеното резюме (напр.. 0 за полунощ, 17 за 5 следобед). Имайте предвид, че този час е според часовата зона на сървъра и може да не съвпада с часовника на системата Ви.
Приблизителното време на сървъра е:
Изпращането на следващия ежедневен бюлетин е планирано за ", "notifications.remove-images": "Премахване на изображенията от известията по е-поща", "require-email-address": "Новите потребители задължително трябва да предоставят е-поща", - "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", - "send-validation-email": "Send validation emails when an email is added or changed", + "require-email-address-warning": "По подразбиране потребителите могат да не въвеждат адрес на е-поща, като оставят полето празно. Ако включите това, те задължително ще трябва да предоставят е-поща, за да могат да се регистрират. Това не означава, че потребителят ще въведе съществуваща е-поща, нито че тя ще е негова.", + "send-validation-email": "Изпращане на е-писма за потвърждение, когато бъде добавена или променена е-поща", "include-unverified-emails": "Изпращане на е-писма към получатели, които не са потвърдили изрично е-пощата си", "include-unverified-warning": "За потребителите, които имат свързана е-поща с регистрацията си, тя се смята за потвърдена. Но има ситуации, в които това не е така (например при ползване на регистрация от друга система, но и в други случаи), Включете тази настройка на собствен риск – изпращането на е-писма към непотвърдени адреси може да нарушава определени местни закони против нежеланата поща.", "prompt": "Подсещане на потребителите да въведат или потвърдят е-пощата си", diff --git a/public/language/vi/admin/settings/email.json b/public/language/vi/admin/settings/email.json index ccdc472e3b..06a5256327 100644 --- a/public/language/vi/admin/settings/email.json +++ b/public/language/vi/admin/settings/email.json @@ -38,8 +38,8 @@ "subscriptions.hour-help": "Vui lòng nhập một số đại diện cho giờ để gửi thông báo email đã lên lịch (VD: 0 cho nửa đêm, 17 cho 5h chiều). Hãy nhớ rằng đây là giờ theo chính máy chủ và có thể không khớp chính xác với đồng hồ hệ thống của bạn.
Thời gian máy chủ gần đúng là:
Thông báo hàng ngày kế tiếp được lên lịch để gửi ", "notifications.remove-images": "Xóa hình ảnh khỏi thông báo email", "require-email-address": "Bắt buộc người dùng mới phải điền địa chỉ email", - "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", - "send-validation-email": "Send validation emails when an email is added or changed", + "require-email-address-warning": "Theo mặc định, người dùng có thể từ chối nhập địa chỉ email bằng cách để trống trường. Bật tùy chọn này có nghĩa là họ phải nhập địa chỉ email để tiến hành đăng ký. Nó không đảm bảo người dùng sẽ nhập địa chỉ email thực, thậm chí cả địa chỉ mà họ sở hữu.", + "send-validation-email": "Gửi email xác thực khi một email được thêm vào hoặc thay đổi", "include-unverified-emails": "Gửi email đến những người nhận chưa xác nhận rõ ràng email của họ", "include-unverified-warning": "Theo mặc định, người dùng có email được liên kết với tài khoản của họ đã được xác minh, nhưng có những trường hợp không phải như vậy (ví dụ: đăng nhập SSO, người dùng phổ thông, v.v.). Bạn tự chịu rủi ro khi bật cài đặt này – gửi email đến các địa chỉ chưa được xác minh có thể vi phạm luật chống thư rác trong khu vực.", "prompt": "Nhắc người dùng nhập hoặc xác nhận email của họ", From 4616253755f1eebc98e5d38754b84a539d997cb6 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 14 Nov 2021 13:08:35 +0000 Subject: [PATCH 017/849] fix(deps): update dependency sharp to v0.29.3 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 8f7bdd7aa8..a76f56c917 100644 --- a/install/package.json +++ b/install/package.json @@ -116,7 +116,7 @@ "sanitize-html": "^2.3.2", "semver": "^7.3.4", "serve-favicon": "^2.5.0", - "sharp": "0.29.2", + "sharp": "0.29.3", "sitemap": "^7.0.0", "slideout": "1.0.1", "socket.io": "4.3.2", From aae7be027e2fa7c4a4e324e7986a9ec71d78f436 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 15 Nov 2021 11:00:49 +0000 Subject: [PATCH 018/849] fix(deps): update dependency @socket.io/redis-adapter to v7.0.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a76f56c917..f9e8f21032 100644 --- a/install/package.json +++ b/install/package.json @@ -122,7 +122,7 @@ "socket.io": "4.3.2", "socket.io-adapter-cluster": "^1.0.1", "socket.io-client": "4.3.2", - "@socket.io/redis-adapter": "7.0.0", + "@socket.io/redis-adapter": "7.0.1", "sortablejs": "1.14.0", "spdx-license-list": "^6.4.0", "spider-detector": "2.0.0", From ea9f2c731c8593f25f02a96b9aa0ac89cc43465a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Nov 2021 04:06:50 +0000 Subject: [PATCH 019/849] chore(deps): bump compare-versions from 3.6.0 to 4.1.1 in /install Bumps [compare-versions](https://github.com/omichelsen/compare-versions) from 3.6.0 to 4.1.1. - [Release notes](https://github.com/omichelsen/compare-versions/releases) - [Changelog](https://github.com/omichelsen/compare-versions/blob/master/CHANGELOG.md) - [Commits](https://github.com/omichelsen/compare-versions/compare/v3.6.0...v4.1.1) --- updated-dependencies: - dependency-name: compare-versions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index f9e8f21032..cb64adf4b5 100644 --- a/install/package.json +++ b/install/package.json @@ -43,7 +43,7 @@ "clipboard": "^2.0.6", "colors": "^1.4.0", "commander": "^7.1.0", - "compare-versions": "3.6.0", + "compare-versions": "4.1.1", "compression": "^1.7.4", "connect-flash": "^0.1.1", "connect-mongo": "4.6.0", From 258f368e32f5619abfc0fac9596c8e3168798e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Nov 2021 18:08:09 -0500 Subject: [PATCH 020/849] refactor: add filter:topic.getPosts this hook only fires when loading the posts of a topic cold load + infinite scroll do not remove posts if they have index =-1 use topics.getTopicPosts instead of getMainPostAndReplies --- public/src/client/topic/posts.js | 10 ++-- src/socket.io/topics/infinitescroll.js | 22 ++++----- src/topics/index.js | 54 +--------------------- src/topics/posts.js | 64 ++++++++++++++++++++++++-- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index b84953e915..1301e7289a 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -183,7 +183,7 @@ define('forum/topic/posts', [ } data.posts = data.posts.filter(function (post) { - return $('[component="post"][data-pid="' + post.pid + '"]').length === 0; + return post.index === -1 || $('[component="post"][data-pid="' + post.pid + '"]').length === 0; }); } @@ -206,9 +206,11 @@ define('forum/topic/posts', [ app.parseAndTranslate('topic', 'posts', Object.assign({}, ajaxify.data, data), function (html) { html = html.filter(function () { - const pid = $(this).attr('data-pid'); - const isPost = $(this).is('[component="post"]'); - return !isPost || (pid && $('[component="post"][data-pid="' + pid + '"]').length === 0); + const $this = $(this); + const pid = $this.attr('data-pid'); + const index = parseInt($this.attr('data-index'), 10); + const isPost = $this.is('[component="post"]'); + return !isPost || index === -1 || (pid && $('[component="post"][data-pid="' + pid + '"]').length === 0); }); if (after) { diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index c20730229f..506a87b6f7 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -16,7 +16,7 @@ module.exports = function (SocketTopics) { const [userPrivileges, topicData] = await Promise.all([ privileges.topics.get(data.tid, socket.uid), - topics.getTopicFields(data.tid, ['postcount', 'deleted', 'scheduled', 'uid']), + topics.getTopicData(data.tid), ]); if (!userPrivileges['topics:read'] || !privileges.topics.canViewDeletedScheduled(topicData, userPrivileges)) { @@ -32,28 +32,22 @@ module.exports = function (SocketTopics) { parseInt(data.count, 10) || meta.config.postsPerPage || 20 )); - if (data.direction === -1) { - start -= (infScrollPostsPerPage + 1); + if (data.direction === 1) { + start += 1; + } else if (data.direction === -1) { + start -= infScrollPostsPerPage; } let stop = start + infScrollPostsPerPage - 1; start = Math.max(0, start); stop = Math.max(0, stop); - - const [mainPost, posts, postSharing] = await Promise.all([ - start > 0 ? null : topics.getMainPost(data.tid, socket.uid), - topics.getTopicPosts(data.tid, set, start, stop, socket.uid, reverse), + const [posts, postSharing] = await Promise.all([ + topics.getTopicPosts(topicData, set, start, stop, socket.uid, reverse), social.getActivePostSharing(), ]); - if (mainPost) { - topicData.mainPost = mainPost; - topicData.posts = [mainPost].concat(posts); - } else { - topicData.posts = posts; - } - + topicData.posts = posts; topicData.privileges = userPrivileges; topicData.postSharing = postSharing; topicData['reputation:disabled'] = meta.config['reputation:disabled'] === 1; diff --git a/src/topics/index.js b/src/topics/index.js index 67614927ae..9584b8d7cf 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -168,7 +168,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev thumbs, events, ] = await Promise.all([ - getMainPostAndReplies(topicData, set, uid, start, stop, reverse), + Topics.getTopicPosts(topicData, set, start, stop, uid, reverse), categories.getCategoryData(topicData.cid), categories.getTagWhitelist([topicData.cid]), plugins.hooks.fire('filter:topic.thread_tools', { topic: topicData, uid: uid, tools: [] }), @@ -211,58 +211,6 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev return result.topic; }; -async function getMainPostAndReplies(topic, set, uid, start, stop, reverse) { - let repliesStart = start; - let repliesStop = stop; - if (stop > 0) { - repliesStop -= 1; - if (start > 0) { - repliesStart -= 1; - } - } - const pids = await posts.getPidsFromSet(set, repliesStart, repliesStop, reverse); - if (!pids.length && !topic.mainPid) { - return []; - } - - if (topic.mainPid && start === 0) { - pids.unshift(topic.mainPid); - } - const postData = await posts.getPostsByPids(pids, uid); - if (!postData.length) { - return []; - } - let replies = postData; - if (topic.mainPid && start === 0) { - postData[0].index = 0; - replies = postData.slice(1); - } - - Topics.calculatePostIndices(replies, repliesStart); - - await Topics.addNextPostTimestamp(postData, set, reverse); - return await Topics.addPostData(postData, uid); -} - -Topics.addNextPostTimestamp = async function (postData, set, reverse) { - if (!postData.length) { - return; - } - postData.forEach((p, index) => { - if (p && postData[index + 1]) { - p.nextPostTimestamp = postData[index + 1].timestamp; - } - }); - const lastPost = postData[postData.length - 1]; - if (lastPost) { - lastPost.nextPostTimestamp = Date.now(); - if (lastPost.index) { - const data = await db[reverse ? 'getSortedSetRevRangeWithScores' : 'getSortedSetRangeWithScores'](set, lastPost.index, lastPost.index); - lastPost.nextPostTimestamp = data.length ? data[0].score : lastPost.nextPostTimestamp; - } - } -}; - async function getDeleter(topicData) { if (!parseInt(topicData.deleterUid, 10)) { return null; diff --git a/src/topics/posts.js b/src/topics/posts.js index dc947c6e67..5185ce4066 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const validator = require('validator'); const nconf = require('nconf'); +const winston = require('winston'); const db = require('../database'); const user = require('../user'); @@ -20,12 +21,67 @@ module.exports = function (Topics) { await Topics.addPostToTopic(postData.tid, postData); }; - Topics.getTopicPosts = async function (tid, set, start, stop, uid, reverse) { - const postData = await posts.getPostsFromSet(set, start, stop, uid, reverse); - Topics.calculatePostIndices(postData, start); + Topics.getTopicPosts = async function (topicOrTid, set, start, stop, uid, reverse) { + if (topicOrTid && typeof topicOrTid !== 'object') { + // TODO: remove in 1.19.0 + winston.warn('[deprecated] Topics.getTopicPosts(tid, ...) usage is deprecated, pass a topic object as first argument!'); + topicOrTid = await Topics.getTopicData(topicOrTid); + } + + let repliesStart = start; + let repliesStop = stop; + if (stop > 0) { + repliesStop -= 1; + if (start > 0) { + repliesStart -= 1; + } + } + const pids = await posts.getPidsFromSet(set, repliesStart, repliesStop, reverse); + if (!pids.length && !topicOrTid.mainPid) { + return []; + } + + if (topicOrTid.mainPid && start === 0) { + pids.unshift(topicOrTid.mainPid); + } + const postData = await posts.getPostsByPids(pids, uid); + if (!postData.length) { + return []; + } + let replies = postData; + if (topicOrTid.mainPid && start === 0) { + postData[0].index = 0; + replies = postData.slice(1); + } + + Topics.calculatePostIndices(replies, repliesStart); await Topics.addNextPostTimestamp(postData, set, reverse); - return await Topics.addPostData(postData, uid); + const result = await plugins.hooks.fire('filter:topic.getPosts', { + topic: topicOrTid, + uid: uid, + posts: await Topics.addPostData(postData, uid), + }); + return result.posts; + }; + + Topics.addNextPostTimestamp = async function (postData, set, reverse) { + if (!postData.length) { + return; + } + postData.forEach((p, index) => { + if (p && postData[index + 1]) { + p.nextPostTimestamp = postData[index + 1].timestamp; + } + }); + const lastPost = postData[postData.length - 1]; + if (lastPost) { + lastPost.nextPostTimestamp = Date.now(); + if (lastPost.index) { + const data = await db[reverse ? 'getSortedSetRevRangeWithScores' : 'getSortedSetRangeWithScores'](set, lastPost.index, lastPost.index); + lastPost.nextPostTimestamp = data.length ? data[0].score : lastPost.nextPostTimestamp; + } + } }; Topics.addPostData = async function (postData, uid) { From f729e51921cdf9f52da11dc7089a0284f77eb8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Nov 2021 18:21:52 -0500 Subject: [PATCH 021/849] refactor: clone before returning --- src/social.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/social.js b/src/social.js index 1ecc9465c1..b4cb54ae22 100644 --- a/src/social.js +++ b/src/social.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const plugins = require('./plugins'); const db = require('./database'); @@ -9,7 +10,7 @@ social.postSharing = null; social.getPostSharing = async function () { if (social.postSharing) { - return social.postSharing; + return _.cloneDeep(social.postSharing); } let networks = [ @@ -31,7 +32,7 @@ social.getPostSharing = async function () { }); social.postSharing = networks; - return networks; + return _.cloneDeep(networks); }; social.getActivePostSharing = async function () { From 6d38eab69a9a950aa241378fc5bb8767d9e4a92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Nov 2021 18:27:22 -0500 Subject: [PATCH 022/849] refactor: update dates --- app.js | 2 +- loader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index a1a7e1be87..f985362fbe 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,7 @@ /* NodeBB - A better forum platform for the modern web https://github.com/NodeBB/NodeBB/ - Copyright (C) 2013-2017 NodeBB Inc. + Copyright (C) 2013-2021 NodeBB Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/loader.js b/loader.js index d9b4b025f2..bd1d463b46 100644 --- a/loader.js +++ b/loader.js @@ -48,7 +48,7 @@ Loader.init = function () { Loader.displayStartupMessages = function () { console.log(''); - console.log(`NodeBB v${pkg.version} Copyright (C) 2013-2014 NodeBB Inc.`); + console.log(`NodeBB v${pkg.version} Copyright (C) 2013-${(new Date()).getFullYear()} NodeBB Inc.`); console.log('This program comes with ABSOLUTELY NO WARRANTY.'); console.log('This is free software, and you are welcome to redistribute it under certain conditions.'); console.log('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html'); From aac0792ab8a9aa0c10aa571172c6a2f62994cf9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 15 Nov 2021 18:31:55 -0500 Subject: [PATCH 023/849] test: mainPost removed from inf scroll --- test/topics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/topics.js b/test/topics.js index a50157cc8d..a8cad19d1c 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1365,7 +1365,6 @@ describe('Topic\'s', () => { it('should infinite load topic posts', (done) => { socketTopics.loadMore({ uid: adminUid }, { tid: tid, after: 0, count: 10 }, (err, data) => { assert.ifError(err); - assert(data.mainPost); assert(data.posts); assert(data.privileges); done(); From c16dad40cf04b134ac4272f430104383c6d5a539 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Tue, 16 Nov 2021 09:07:13 +0000 Subject: [PATCH 024/849] Latest translations and fallbacks --- public/language/de/admin/manage/digest.json | 30 ++++++++++---------- public/language/de/admin/settings/guest.json | 6 ++-- public/language/de/post-queue.json | 14 ++++----- public/language/de/top.json | 4 +-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/public/language/de/admin/manage/digest.json b/public/language/de/admin/manage/digest.json index 2f1212d11f..98b75f8e5c 100644 --- a/public/language/de/admin/manage/digest.json +++ b/public/language/de/admin/manage/digest.json @@ -1,22 +1,22 @@ { - "lead": "A listing of digest delivery stats and times is displayed below.", - "disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.", - "disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as SendGrid.", + "lead": "Nachfolgend ist eine Auflistung der Zustellungsstatistiken und -zeiten zusammengefasst.", + "disclaimer": "Bitte beachten Sie, dass die Zustellung von E-Mails aufgrund der Funktionsweise von E-Mail-Technologien nicht garantiert werden kann. Ob eine E-Mail im Posteingang des Benutzers auf dem an Empfängerserver letztendlich ankommt, hängt von vielen Variablen ab, z. B. von der Reputation des Servers, von IP-Adressen, die auf der schwarzen Liste stehen, und davon, ob DKIM/SPF/DMARC konfiguriert ist.", + "disclaimer-continued": "Eine erfolgreiche Zustellung zeigt an, dass die Nachricht erfolgreich von NodeBB gesendet und vom Empfänger-Server bestätigt wurde. Es bedeutet nicht, dass die E-Mail im Posteingang gelandet ist. Um beste Ergebnisse zu erzielen, empfehlen wir, einen E-Mail-Zustelldienst eines Drittanbieters wie SendGrid zu verwenden.", "user": "Benutzer", - "subscription": "Subscription Type", + "subscription": "Abonnement Typ", "last-delivery": "Letzte erfolgreiche Zustellung", "default": "System Standard", - "default-help": "System default means the user has not explicitly overridden the global forum setting for digests, which is currently: "%1"", - "resend": "Resend Digest", - "resend-all-confirm": "Are you sure you wish to manually execute this digest run?", - "resent-single": "Manual digest resend completed", - "resent-day": "Daily digest resent", - "resent-week": "Weekly digest resent", - "resent-biweek": "Bi-Weekly digest resent", - "resent-month": "Monthly digest resent", - "null": "Never", - "manual-run": "Manual digest run:", + "default-help": "Systemstandard bedeutet, dass der Benutzer die globale Foreneinstellung für Tagesübersichten nicht explizit außer Kraft gesetzt hat, die derzeit wie folgt lautet: "%1"", + "resend": "Tagesübersicht erneut senden", + "resend-all-confirm": "Sind Sie sicher, dass Sie diesen Tagesübersichts-Lauf manuell ausführen möchten?", + "resent-single": "Manuelles Übersichtversenden abgeschlossen", + "resent-day": "Tägliche Übersicht erneut gesendet", + "resent-week": "Wöchentliche Übersicht erneut gesendet", + "resent-biweek": "Zweiwöchentliche Übersicht erneut gesendet", + "resent-month": "Monatliche Übersicht erneut gesendet", + "null": "Niemals", + "manual-run": "Manueller Tagesübersichts-Lauf:", - "no-delivery-data": "No delivery data found" + "no-delivery-data": "Keine Zustelldaten gefunden" } diff --git a/public/language/de/admin/settings/guest.json b/public/language/de/admin/settings/guest.json index 6c0f22ef55..3e9e67f2bf 100644 --- a/public/language/de/admin/settings/guest.json +++ b/public/language/de/admin/settings/guest.json @@ -1,7 +1,7 @@ { - "settings": "Settings", + "settings": "Einstellungen", "handles.enabled": "Gastzugänge erlauben", "handles.enabled-help": "Diese Option offenbart ein neues Feld, welches Gästen erlaubt einen Nutzernamen zu wählen, welcher sie mit jedem Beitrag assoziiert den sie erstellen. Wenn diese Option deaktiviert ist, werden sie einfach \"Gast\" genannt", - "topic-views.enabled": "Allow guests to increase topic view counts", - "reply-notifications.enabled": "Allow guests to generate reply notifications" + "topic-views.enabled": "Gästen erlauben, die gezählte Anzahl der Themenaufrufe zu erhöhen", + "reply-notifications.enabled": "Erlauben Sie Gästen, Antwortbenachrichtigungen zu erstellen" } \ No newline at end of file diff --git a/public/language/de/post-queue.json b/public/language/de/post-queue.json index 2184fbd04b..129eac233c 100644 --- a/public/language/de/post-queue.json +++ b/public/language/de/post-queue.json @@ -8,11 +8,11 @@ "content": "Inhalt", "posted": "Gepostet", "reply-to": "Auf \"%1\" antworten", - "content-editable": "Click on content to edit", - "category-editable": "Click on category to edit", - "title-editable": "Click on title to edit", - "reply": "Reply", - "topic": "Topic", - "accept": "Accept", - "reject": "Reject" + "content-editable": "Inhalt zum Bearbeiten anklicken", + "category-editable": "Kategorie zum Bearbeiten anklicken", + "title-editable": "Titel zum Bearbeiten anklicken", + "reply": "Antworten", + "topic": "Thema", + "accept": "Annehmen", + "reject": "Ablehnen" } \ No newline at end of file diff --git a/public/language/de/top.json b/public/language/de/top.json index b8a05bfa5f..5cbcfc49de 100644 --- a/public/language/de/top.json +++ b/public/language/de/top.json @@ -1,4 +1,4 @@ { - "title": "Top", - "no_top_topics": "No top topics" + "title": "Top-Themen", + "no_top_topics": "Keine Top-Themen" } \ No newline at end of file From 047f031dd7d20f2033682e4bfde6235c6c46d864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 11:57:00 -0500 Subject: [PATCH 025/849] fix: #10006, dont allow new rooms or adding to a room if target is blocked --- src/messaging/index.js | 25 ++++++++++++++----------- src/socket.io/modules.js | 14 +------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/messaging/index.js b/src/messaging/index.js index 2f90270b60..c5cf1d46cd 100644 --- a/src/messaging/index.js +++ b/src/messaging/index.js @@ -187,32 +187,35 @@ Messaging.getLatestUndeletedMessage = async (uid, roomId) => { }; Messaging.canMessageUser = async (uid, toUid) => { - if (meta.config.disableChat || uid <= 0 || uid === toUid) { + if (meta.config.disableChat || uid <= 0) { throw new Error('[[error:chat-disabled]]'); } if (parseInt(uid, 10) === parseInt(toUid, 10)) { - throw new Error('[[error:cant-chat-with-yourself'); + throw new Error('[[error:cant-chat-with-yourself]]'); } + const [exists, canChat] = await Promise.all([ + user.exists(toUid), + privileges.global.can('chat', uid), + ]); - const exists = await user.exists(toUid); if (!exists) { throw new Error('[[error:no-user]]'); } - const canChat = await privileges.global.can('chat', uid); if (!canChat) { throw new Error('[[error:no-privileges]]'); } - const results = await utils.promiseParallel({ - settings: user.getSettings(toUid), - isAdmin: user.isAdministrator(uid), - isModerator: user.isModeratorOfAnyCategory(uid), - isFollowing: user.isFollowing(toUid, uid), - }); + const [settings, isAdmin, isModerator, isFollowing, isBlocked] = await Promise.all([ + user.getSettings(toUid), + user.isAdministrator(uid), + user.isModeratorOfAnyCategory(uid), + user.isFollowing(toUid, uid), + user.blocks.is(uid, toUid), + ]); - if (results.settings.restrictChat && !results.isAdmin && !results.isModerator && !results.isFollowing) { + if (isBlocked || (settings.restrictChat && !isAdmin && !isModerator && !isFollowing)) { throw new Error('[[error:chat-restricted]]'); } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 20c20b2e8e..f2804af9a4 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -146,19 +146,7 @@ SocketModules.chats.addUserToRoom = async function (socket, data) { if (!uid) { throw new Error('[[error:no-user]]'); } - if (socket.uid === parseInt(uid, 10)) { - throw new Error('[[error:cant-chat-with-yourself]]'); - } - const [settings, isAdminOrGlobalMod, isFollowing] = await Promise.all([ - user.getSettings(uid), - user.isAdminOrGlobalMod(socket.uid), - user.isFollowing(uid, socket.uid), - ]); - - if (settings.restrictChat && !isAdminOrGlobalMod && !isFollowing) { - throw new Error('[[error:chat-restricted]]'); - } - + await Messaging.canMessageUser(socket.uid, uid); await Messaging.addUsersToRoom(socket.uid, [uid], data.roomId); }; From 0532c1b2a1d982bdef07b3cc849767205466e090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 16:20:39 -0500 Subject: [PATCH 026/849] feat: #9957, don't remove existing fields form config.json --- src/install.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index 98339c1314..7016662d05 100644 --- a/src/install.js +++ b/src/install.js @@ -527,7 +527,16 @@ install.save = async function (server_conf) { serverConfigPath = path.resolve(__dirname, '../', nconf.get('config')); } - await fs.promises.writeFile(serverConfigPath, JSON.stringify(server_conf, null, 4)); + let currentConfig = {}; + try { + currentConfig = require(serverConfigPath); + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err; + } + } + + await fs.promises.writeFile(serverConfigPath, JSON.stringify({ ...currentConfig, ...server_conf }, null, 4)); console.log('Configuration Saved OK'); nconf.file({ file: serverConfigPath, From 4359e5c97cbb77ea2771b355d94b1ff33860b269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 16:24:17 -0500 Subject: [PATCH 027/849] refactor: remove tabs after declaration --- loader.js | 2 +- public/src/admin/advanced/logs.js | 2 +- public/src/admin/dashboard.js | 12 ++++++------ public/src/admin/manage/categories.js | 2 +- public/src/admin/manage/category-analytics.js | 10 +++++----- public/src/admin/manage/category.js | 2 +- public/src/admin/manage/groups.js | 4 ++-- public/src/admin/manage/tags.js | 2 +- public/src/admin/settings.js | 6 +++--- public/src/admin/settings/notifications.js | 2 +- public/src/client/account/followers.js | 2 +- public/src/client/account/following.js | 2 +- public/src/client/account/settings.js | 2 +- public/src/client/categories.js | 2 +- public/src/client/category.js | 2 +- public/src/client/ip-blacklist.js | 6 +++--- public/src/client/login.js | 2 +- public/src/client/recent.js | 2 +- public/src/client/reset.js | 2 +- public/src/client/search.js | 2 +- public/src/client/top.js | 2 +- public/src/client/users.js | 2 +- public/src/modules/taskbar.js | 8 ++++---- test/database.js | 2 +- test/database/keys.js | 2 +- test/database/list.js | 2 +- test/database/sets.js | 2 +- test/database/sorted.js | 2 +- test/groups.js | 2 +- test/locale-detect.js | 2 +- test/pagination.js | 2 +- test/search.js | 2 +- test/topics.js | 2 +- 33 files changed, 50 insertions(+), 50 deletions(-) diff --git a/loader.js b/loader.js index bd1d463b46..8c3e47e806 100644 --- a/loader.js +++ b/loader.js @@ -209,7 +209,7 @@ fs.open(pathToConfig, 'r', (err) => { if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) { if (file.existsSync(pidFilePath)) { try { - const pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); + const pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' }); process.kill(pid, 0); process.exit(); } catch (e) { diff --git a/public/src/admin/advanced/logs.js b/public/src/admin/advanced/logs.js index 6a236b49e3..f752cfc126 100644 --- a/public/src/admin/advanced/logs.js +++ b/public/src/admin/advanced/logs.js @@ -2,7 +2,7 @@ define('admin/advanced/logs', function () { - const Logs = {}; + const Logs = {}; Logs.init = function () { const logsEl = $('.logs pre'); diff --git a/public/src/admin/dashboard.js b/public/src/admin/dashboard.js index 645023b563..0e3c58f3f0 100644 --- a/public/src/admin/dashboard.js +++ b/public/src/admin/dashboard.js @@ -2,17 +2,17 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], function (Chart, translator, Benchpress, bootbox) { - const Admin = {}; - const intervals = { + const Admin = {}; + const intervals = { rooms: false, graphs: false, }; let isMobile = false; - const graphData = { + const graphData = { rooms: {}, traffic: {}, }; - const currentGraph = { + const currentGraph = { units: 'hours', until: undefined, }; @@ -23,7 +23,7 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func realtimeInterval: 1500, }; - const usedTopicColors = []; + const usedTopicColors = []; $(window).on('action:ajaxify.start', function () { clearInterval(intervals.rooms); @@ -520,7 +520,7 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func let html = ''; topics.forEach(function (t, i) { const link = t.tid ? ' ' + t.title + '' : t.title; - const label = t.count === '0' ? t.title : link; + const label = t.count === '0' ? t.title : link; html += '
  • ' + '
    ' + diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index 23c85892a4..9120a52c23 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -8,7 +8,7 @@ define('admin/manage/categories', [ 'Sortable', 'bootbox', ], function (translator, Benchpress, categorySelector, api, Sortable, bootbox) { - const Categories = {}; + const Categories = {}; let newCategoryId = -1; let sortables; diff --git a/public/src/admin/manage/category-analytics.js b/public/src/admin/manage/category-analytics.js index 6a283ef43f..a4366d07d1 100644 --- a/public/src/admin/manage/category-analytics.js +++ b/public/src/admin/manage/category-analytics.js @@ -6,13 +6,13 @@ define('admin/manage/category-analytics', ['Chart'], function (Chart) { CategoryAnalytics.init = function () { const hourlyCanvas = document.getElementById('pageviews:hourly'); - const dailyCanvas = document.getElementById('pageviews:daily'); - const topicsCanvas = document.getElementById('topics:daily'); - const postsCanvas = document.getElementById('posts:daily'); - const hourlyLabels = utils.getHoursArray().map(function (text, idx) { + const dailyCanvas = document.getElementById('pageviews:daily'); + const topicsCanvas = document.getElementById('topics:daily'); + const postsCanvas = document.getElementById('posts:daily'); + const hourlyLabels = utils.getHoursArray().map(function (text, idx) { return idx % 3 ? '' : text; }); - const dailyLabels = utils.getDaysArray().map(function (text, idx) { + const dailyLabels = utils.getDaysArray().map(function (text, idx) { return idx % 3 ? '' : text; }); diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 10f665ecab..6dc849aee7 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -8,7 +8,7 @@ define('admin/manage/category', [ 'api', 'bootbox', ], function (uploader, iconSelect, categorySelector, Benchpress, api, bootbox) { - const Category = {}; + const Category = {}; let updateHash = {}; Category.init = function () { diff --git a/public/src/admin/manage/groups.js b/public/src/admin/manage/groups.js index 10867bede5..576b7293ce 100644 --- a/public/src/admin/manage/groups.js +++ b/public/src/admin/manage/groups.js @@ -6,10 +6,10 @@ define('admin/manage/groups', [ 'api', 'bootbox', ], function (categorySelector, slugify, api, bootbox) { - const Groups = {}; + const Groups = {}; Groups.init = function () { - const createModal = $('#create-modal'); + const createModal = $('#create-modal'); const createGroupName = $('#create-group-name'); const createModalGo = $('#create-modal-go'); const createModalError = $('#create-modal-error'); diff --git a/public/src/admin/manage/tags.js b/public/src/admin/manage/tags.js index 836c8f7be9..4e30620f15 100644 --- a/public/src/admin/manage/tags.js +++ b/public/src/admin/manage/tags.js @@ -6,7 +6,7 @@ define('admin/manage/tags', [ 'forum/infinitescroll', 'admin/modules/selectable', ], function (bootbox, infinitescroll, selectable) { - const Tags = {}; + const Tags = {}; Tags.init = function () { selectable.enable('.tag-management', '.tag-row'); diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index c7166305a0..6b5a5c717f 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -30,9 +30,9 @@ define('admin/settings', ['uploader', 'mousetrap', 'hooks'], function (uploader, Settings.prepare = function (callback) { // Populate the fields on the page from the config const fields = $('#content [data-field]'); - const numFields = fields.length; - const saveBtn = $('#save'); - const revertBtn = $('#revert'); + const numFields = fields.length; + const saveBtn = $('#save'); + const revertBtn = $('#revert'); let x; let key; let inputType; diff --git a/public/src/admin/settings/notifications.js b/public/src/admin/settings/notifications.js index 9235478482..af86b2ce59 100644 --- a/public/src/admin/settings/notifications.js +++ b/public/src/admin/settings/notifications.js @@ -3,7 +3,7 @@ define('admin/settings/notifications', [ 'autocomplete', ], function (autocomplete) { - const Notifications = {}; + const Notifications = {}; Notifications.init = function () { const searchInput = $('[data-field="welcomeUid"]'); diff --git a/public/src/client/account/followers.js b/public/src/client/account/followers.js index 89b67561d8..b9b78d0f6e 100644 --- a/public/src/client/account/followers.js +++ b/public/src/client/account/followers.js @@ -2,7 +2,7 @@ define('forum/account/followers', ['forum/account/header'], function (header) { - const Followers = {}; + const Followers = {}; Followers.init = function () { header.init(); diff --git a/public/src/client/account/following.js b/public/src/client/account/following.js index 8bc5c92614..083b153c51 100644 --- a/public/src/client/account/following.js +++ b/public/src/client/account/following.js @@ -2,7 +2,7 @@ define('forum/account/following', ['forum/account/header'], function (header) { - const Following = {}; + const Following = {}; Following.init = function () { header.init(); diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 995fbbd4b6..c5f2d8ae75 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -2,7 +2,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'translator', 'api'], function (header, components, translator, api) { - const AccountSettings = {}; + const AccountSettings = {}; // If page skin is changed but not saved, switch the skin back $(window).on('action:ajaxify.start', function () { diff --git a/public/src/client/categories.js b/public/src/client/categories.js index 04010f5554..f07730278f 100644 --- a/public/src/client/categories.js +++ b/public/src/client/categories.js @@ -2,7 +2,7 @@ define('forum/categories', ['components', 'categorySelector', 'hooks'], function (components, categorySelector, hooks) { - const categories = {}; + const categories = {}; $(window).on('action:ajaxify.start', function (ev, data) { if (ajaxify.currentPage !== data.url) { diff --git a/public/src/client/category.js b/public/src/client/category.js index da8a6296cf..f318f6e25b 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -18,7 +18,7 @@ define('forum/category', [ }); Category.init = function () { - const cid = ajaxify.data.cid; + const cid = ajaxify.data.cid; app.enterRoom('category_' + cid); diff --git a/public/src/client/ip-blacklist.js b/public/src/client/ip-blacklist.js index 624fe70ee5..55ddbd42d5 100644 --- a/public/src/client/ip-blacklist.js +++ b/public/src/client/ip-blacklist.js @@ -43,11 +43,11 @@ define('forum/ip-blacklist', ['Chart', 'benchpress', 'bootbox'], function (Chart Blacklist.setupAnalytics = function () { const hourlyCanvas = document.getElementById('blacklist:hourly'); - const dailyCanvas = document.getElementById('blacklist:daily'); - const hourlyLabels = utils.getHoursArray().map(function (text, idx) { + const dailyCanvas = document.getElementById('blacklist:daily'); + const hourlyLabels = utils.getHoursArray().map(function (text, idx) { return idx % 3 ? '' : text; }); - const dailyLabels = utils.getDaysArray().slice(-7).map(function (text, idx) { + const dailyLabels = utils.getDaysArray().slice(-7).map(function (text, idx) { return idx % 3 ? '' : text; }); diff --git a/public/src/client/login.js b/public/src/client/login.js index 9b130a4939..1a09f09e25 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -2,7 +2,7 @@ define('forum/login', ['hooks', 'translator', 'jquery-form'], function (hooks, translator) { - const Login = { + const Login = { _capsState: false, }; diff --git a/public/src/client/recent.js b/public/src/client/recent.js index 9d0a9904a2..af039cd1d2 100644 --- a/public/src/client/recent.js +++ b/public/src/client/recent.js @@ -1,7 +1,7 @@ 'use strict'; define('forum/recent', ['topicList'], function (topicList) { - const Recent = {}; + const Recent = {}; Recent.init = function () { app.enterRoom('recent_topics'); diff --git a/public/src/client/reset.js b/public/src/client/reset.js index 0c2f4d6066..43289da1c8 100644 --- a/public/src/client/reset.js +++ b/public/src/client/reset.js @@ -2,7 +2,7 @@ define('forum/reset', function () { - const ResetPassword = {}; + const ResetPassword = {}; ResetPassword.init = function () { const inputEl = $('#email'); diff --git a/public/src/client/search.js b/public/src/client/search.js index ac0d1ccfb1..8be674432c 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -7,7 +7,7 @@ define('forum/search', [ 'storage', 'hooks', ], function (searchModule, autocomplete, storage, hooks) { - const Search = {}; + const Search = {}; Search.init = function () { const searchQuery = $('#results').attr('data-search-query'); diff --git a/public/src/client/top.js b/public/src/client/top.js index b2eb44e99f..785255d001 100644 --- a/public/src/client/top.js +++ b/public/src/client/top.js @@ -1,7 +1,7 @@ 'use strict'; define('forum/top', ['topicList'], function (topicList) { - const Top = {}; + const Top = {}; Top.init = function () { app.enterRoom('top_topics'); diff --git a/public/src/client/users.js b/public/src/client/users.js index 2b732c7632..4973216a71 100644 --- a/public/src/client/users.js +++ b/public/src/client/users.js @@ -4,7 +4,7 @@ define('forum/users', [ 'translator', 'benchpress', 'api', 'accounts/invite', ], function (translator, Benchpress, api, AccountInvite) { - const Users = {}; + const Users = {}; let searchResultCount = 0; diff --git a/public/src/modules/taskbar.js b/public/src/modules/taskbar.js index ce811bc046..87fc2ad1c6 100644 --- a/public/src/modules/taskbar.js +++ b/public/src/modules/taskbar.js @@ -13,7 +13,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t $(document.body).append(self.taskbar); self.taskbar.on('click', 'li', function () { - const $btn = $(this); + const $btn = $(this); const module = $btn.attr('data-module'); const uuid = $btn.attr('data-uuid'); @@ -121,7 +121,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t }; taskbar.updateActive = function (uuid) { - const tasks = taskbar.tasklist.find('li'); + const tasks = taskbar.tasklist.find('li'); tasks.removeClass('active'); tasks.filter('[data-uuid="' + uuid + '"]').addClass('active'); @@ -135,7 +135,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t }; function update() { - const tasks = taskbar.tasklist.find('li'); + const tasks = taskbar.tasklist.find('li'); if (tasks.length > 0) { taskbar.taskbar.attr('data-active', '1'); @@ -152,7 +152,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t translator.translate(data.options.title, function (taskTitle) { const title = $('
    ').text(taskTitle || 'NodeBB Task').html(); - const taskbarEl = $('
  • ') + const taskbarEl = $('
  • ') .addClass(data.options.className) .html('' + (data.options.icon ? ' ' : '') + diff --git a/test/database.js b/test/database.js index 0d3f73b34e..baede9f72b 100644 --- a/test/database.js +++ b/test/database.js @@ -1,7 +1,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('assert'); const nconf = require('nconf'); const db = require('./mocks/databasemock'); diff --git a/test/database/keys.js b/test/database/keys.js index ef4afc8857..3941edb65a 100644 --- a/test/database/keys.js +++ b/test/database/keys.js @@ -1,7 +1,7 @@ 'use strict'; -const async = require('async'); +const async = require('async'); const assert = require('assert'); const db = require('../mocks/databasemock'); diff --git a/test/database/list.js b/test/database/list.js index f6f500f2f7..663ed7695e 100644 --- a/test/database/list.js +++ b/test/database/list.js @@ -1,7 +1,7 @@ 'use strict'; -const async = require('async'); +const async = require('async'); const assert = require('assert'); const db = require('../mocks/databasemock'); diff --git a/test/database/sets.js b/test/database/sets.js index 35608c018b..eae737c688 100644 --- a/test/database/sets.js +++ b/test/database/sets.js @@ -1,7 +1,7 @@ 'use strict'; -const async = require('async'); +const async = require('async'); const assert = require('assert'); const db = require('../mocks/databasemock'); diff --git a/test/database/sorted.js b/test/database/sorted.js index ede6cc8c5d..8a25818017 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -1,7 +1,7 @@ 'use strict'; -const async = require('async'); +const async = require('async'); const assert = require('assert'); const db = require('../mocks/databasemock'); diff --git a/test/groups.js b/test/groups.js index c4d5149e03..98aadadd98 100644 --- a/test/groups.js +++ b/test/groups.js @@ -752,7 +752,7 @@ describe('Groups', () => { Groups.leaveAllGroups(testUid, (err) => { assert.ifError(err); - const groups = ['Test', 'Hidden']; + const groups = ['Test', 'Hidden']; async.every(groups, (group, next) => { Groups.isMember(testUid, group, (err, isMember) => { next(err, !isMember); diff --git a/test/locale-detect.js b/test/locale-detect.js index 3f6bfaf5db..91c3e94194 100644 --- a/test/locale-detect.js +++ b/test/locale-detect.js @@ -1,6 +1,6 @@ 'use strict'; -const assert = require('assert'); +const assert = require('assert'); const nconf = require('nconf'); const request = require('request'); diff --git a/test/pagination.js b/test/pagination.js index ef86871014..3073728d8d 100644 --- a/test/pagination.js +++ b/test/pagination.js @@ -1,7 +1,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('assert'); const pagination = require('../src/pagination'); describe('Pagination', () => { diff --git a/test/search.js b/test/search.js index ee28dd3956..b58702bb38 100644 --- a/test/search.js +++ b/test/search.js @@ -1,7 +1,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('assert'); const async = require('async'); const request = require('request'); const nconf = require('nconf'); diff --git a/test/topics.js b/test/topics.js index a8cad19d1c..dc48d829c3 100644 --- a/test/topics.js +++ b/test/topics.js @@ -313,7 +313,7 @@ describe('Topic\'s', () => { }); describe('Get methods', () => { - let newTopic; + let newTopic; let newPost; before((done) => { From 27c05448e1532ce466658513af0e2ff65576b410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 17:11:26 -0500 Subject: [PATCH 028/849] refactor: remove another async.series --- src/cli/setup.js | 91 +++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/src/cli/setup.js b/src/cli/setup.js index d4f530f728..ed66cc8bbf 100644 --- a/src/cli/setup.js +++ b/src/cli/setup.js @@ -1,13 +1,12 @@ 'use strict'; const winston = require('winston'); -const async = require('async'); const path = require('path'); const nconf = require('nconf'); -const { install } = require('../../install/web'); +const { webInstall } = require('../../install/web'); -function setup(initConfig) { +async function setup(initConfig) { const { paths } = require('../constants'); const install = require('../install'); const build = require('../meta/build'); @@ -21,59 +20,41 @@ function setup(initConfig) { console.log('Press enter to accept the default setting (shown in brackets).'); install.values = initConfig; - - async.series([ - async function () { - return await install.setup(); - }, - function (next) { - let configFile = paths.config; - if (nconf.get('config')) { - configFile = path.resolve(paths.baseDir, nconf.get('config')); - } - - prestart.loadConfig(configFile); - - if (!nconf.get('skip-build')) { - build.buildAll(next); - } else { - setImmediate(next); - } - }, - ], (err, data) => { - // Disregard build step data - data = data[0]; - - let separator = ' '; - if (process.stdout.columns > 10) { - for (let x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) { - separator += '='; - } + const data = await install.setup(); + let configFile = paths.config; + if (nconf.get('config')) { + configFile = path.resolve(paths.baseDir, nconf.get('config')); + } + + prestart.loadConfig(configFile); + + if (!nconf.get('skip-build')) { + await build.buildAll(); + } + + let separator = ' '; + if (process.stdout.columns > 10) { + for (let x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) { + separator += '='; } - console.log(`\n${separator}\n`); - - if (err) { - winston.error(`There was a problem completing NodeBB setup\n${err.stack}`); - throw err; - } else { - if (data.hasOwnProperty('password')) { - console.log('An administrative user was automatically created for you:'); - console.log(` Username: ${data.username}`); - console.log(` Password: ${data.password}`); - console.log(''); - } - console.log('NodeBB Setup Completed. Run "./nodebb start" to manually start your NodeBB server.'); - - // If I am a child process, notify the parent of the returned data before exiting (useful for notifying - // hosts of auto-generated username/password during headless setups) - if (process.send) { - process.send(data); - } - } - - process.exit(); - }); + } + console.log(`\n${separator}\n`); + + if (data.hasOwnProperty('password')) { + console.log('An administrative user was automatically created for you:'); + console.log(` Username: ${data.username}`); + console.log(` Password: ${data.password}`); + console.log(''); + } + console.log('NodeBB Setup Completed. Run "./nodebb start" to manually start your NodeBB server.'); + + // If I am a child process, notify the parent of the returned data before exiting (useful for notifying + // hosts of auto-generated username/password during headless setups) + if (process.send) { + process.send(data); + } + process.exit(); } exports.setup = setup; -exports.webInstall = install; +exports.webInstall = webInstall; From 51cbeccb080af16c19b85739a62bc5733fda06fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 19:20:18 -0500 Subject: [PATCH 029/849] refactor: clone settings before returning prevents plugins from mistakenly modifying saved settings in cache --- src/meta/settings.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/meta/settings.js b/src/meta/settings.js index badfa791bd..01944dac0e 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -1,5 +1,7 @@ 'use strict'; +const _ = require('lodash'); + const db = require('../database'); const plugins = require('../plugins'); const Meta = require('./index'); @@ -11,29 +13,31 @@ const Settings = module.exports; Settings.get = async function (hash) { const cached = cache.get(`settings:${hash}`); if (cached) { - return cached; + return _.cloneDeep(cached); } - let data = await db.getObject(`settings:${hash}`) || {}; - const sortedLists = await db.getSetMembers(`settings:${hash}:sorted-lists`); - + const [data, sortedLists] = await Promise.all([ + db.getObject(`settings:${hash}`), + db.getSetMembers(`settings:${hash}:sorted-lists`), + ]); + const values = data || {}; await Promise.all(sortedLists.map(async (list) => { const members = await db.getSortedSetRange(`settings:${hash}:sorted-list:${list}`, 0, -1) || []; const keys = []; - data[list] = []; + values[list] = []; for (const order of members) { keys.push(`settings:${hash}:sorted-list:${list}:${order}`); } const objects = await db.getObjects(keys); objects.forEach((obj) => { - data[list].push(obj); + values[list].push(obj); }); })); - ({ values: data } = await plugins.hooks.fire('filter:settings.get', { plugin: hash, values: data })); - cache.set(`settings:${hash}`, data); - return data; + const result = await plugins.hooks.fire('filter:settings.get', { plugin: hash, values: values }); + cache.set(`settings:${hash}`, result.values); + return _.cloneDeep(result.values); }; Settings.getOne = async function (hash, field) { From 190532b3b45d47639cf4f8e0ecf361980d315e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Nov 2021 19:25:40 -0500 Subject: [PATCH 030/849] refactor: shorter meta.settings.get --- src/meta/settings.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/meta/settings.js b/src/meta/settings.js index 01944dac0e..4ac548895f 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -21,13 +21,10 @@ Settings.get = async function (hash) { ]); const values = data || {}; await Promise.all(sortedLists.map(async (list) => { - const members = await db.getSortedSetRange(`settings:${hash}:sorted-list:${list}`, 0, -1) || []; - const keys = []; + const members = await db.getSortedSetRange(`settings:${hash}:sorted-list:${list}`, 0, -1); + const keys = members.map(order => `settings:${hash}:sorted-list:${list}:${order}`); values[list] = []; - for (const order of members) { - keys.push(`settings:${hash}:sorted-list:${list}:${order}`); - } const objects = await db.getObjects(keys); objects.forEach((obj) => { From 500cad78e59c2e03c57a18ac05c63c50cffb3c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Nov 2021 13:07:58 -0500 Subject: [PATCH 031/849] test: up mongodb version --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e8b07af4cd..e8bc48f96e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -69,7 +69,7 @@ jobs: - 6379:6379 mongo: - image: 'mongo:3.2' + image: 'mongo:3.6' ports: # Maps port 27017 on service container to the host - 27017:27017 From af5393ecdc9332f0e443d06ce9ad74211aa5adf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Nov 2021 13:20:07 -0500 Subject: [PATCH 032/849] chore: update readme mongodb version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63000c0f80..f1f953c9e5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Our minimalist "Persona" theme gets you going right away, no coding experience r NodeBB requires the following software to be installed: * A version of Node.js at least 12 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions)) -* MongoDB, version 2.6 or greater **or** Redis, version 2.8.9 or greater +* MongoDB, version 3.6 or greater **or** Redis, version 2.8.9 or greater * If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured. * nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB) @@ -81,4 +81,4 @@ Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive * [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB") * Unofficial IRC community – channel `#nodebb` on Libera.chat * [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") -* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") \ No newline at end of file +* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") From 2378fc84fa4a61d7f42906c2703ef15c82b79a09 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Nov 2021 13:42:10 -0500 Subject: [PATCH 033/849] fix(deps): update dependency mongodb to v4.2.0 (#10011) Co-authored-by: Renovate Bot --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index cb64adf4b5..0004716617 100644 --- a/install/package.json +++ b/install/package.json @@ -78,7 +78,7 @@ "material-design-lite": "^1.3.0", "mime": "^2.5.2", "mkdirp": "^1.0.4", - "mongodb": "4.1.4", + "mongodb": "4.2.0", "morgan": "^1.10.0", "mousetrap": "^1.6.5", "multiparty": "4.2.2", From e368feef51e0766f119c9710fb4db8f64724725c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Nov 2021 22:11:24 -0500 Subject: [PATCH 034/849] refactor: dont expost entire res._locals to client side --- public/src/app.js | 2 +- src/controllers/api.js | 1 + src/middleware/render.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index a20aaf0bde..68cfd9024f 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -353,7 +353,7 @@ app.flags = {}; function registerServiceWorker() { // Do not register for Safari browsers - if (!ajaxify.data._locals.useragent.isSafari && 'serviceWorker' in navigator) { + if (!config.useragent.isSafari && 'serviceWorker' in navigator) { navigator.serviceWorker.register(config.relative_path + '/service-worker.js', { scope: config.relative_path + '/' }) .then(function () { console.info('ServiceWorker registration succeeded.'); diff --git a/src/controllers/api.js b/src/controllers/api.js index 984ea98e42..1194a75399 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -81,6 +81,7 @@ apiController.loadConfig = async function (req) { }, iconBackgrounds: await user.getIconBackgrounds(req.uid), emailPrompt: meta.config.emailPrompt, + useragent: req.useragent, }; let settings = config; diff --git a/src/middleware/render.js b/src/middleware/render.js index 6957f5a6d2..8555ee9314 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -65,7 +65,7 @@ module.exports = function (middleware) { req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); return res.json(options); } - + const optionsString = JSON.stringify(options).replace(/<\//g, '<\\/'); const results = await utils.promiseParallel({ header: renderHeaderFooter('renderHeader', req, res, options), content: renderContent(render, templateToRender, req, res, options), @@ -76,7 +76,7 @@ module.exports = function (middleware) { (res.locals.postHeader || '') + results.content }${ res.locals.preFooter || '' }${results.footer}`; From d5bfd512676850b71b96b07d5bb930f58441294a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 17 Nov 2021 23:34:01 -0500 Subject: [PATCH 035/849] fix: #10010, handle reverse sorting for topic events dont add events to dom if sort is most votes if sorting is reverse add new events after the main post or at the top instead of bottom --- public/src/client/topic/posts.js | 15 ++++++++++++++- public/src/modules/helpers.js | 9 ++++++--- src/controllers/write/topics.js | 2 +- src/topics/events.js | 6 ++++-- src/topics/index.js | 2 +- src/topics/posts.js | 25 +++++++++++++++++-------- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 1301e7289a..3b03b950cb 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -289,9 +289,22 @@ define('forum/topic/posts', [ }; Posts.addTopicEvents = function (events) { + if (config.topicPostSort === 'most_votes') { + return; + } const html = helpers.renderEvents.call(ajaxify.data, events); translator.translate(html, (translated) => { - document.querySelector('[component="topic"]').insertAdjacentHTML('beforeend', translated); + if (config.topicPostSort === 'oldest_to_newest') { + $('[component="topic"]').append(translated); + } else if (config.topicPostSort === 'newest_to_oldest') { + const mainPost = $('[component="topic"] [component="post"][data-index="0"]'); + if (mainPost.length) { + $(translated).insertAfter(mainPost); + } else { + $('[component="topic"]').prepend(translated); + } + } + $('[component="topic/event"] .timeago').timeago(); }); }; diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 34781ba92d..4ec6763e94 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -210,9 +210,12 @@ return ''; } - function renderTopicEvents(index) { - const start = this.posts[index].timestamp; - const end = this.posts[index].nextPostTimestamp; + function renderTopicEvents(index, sort) { + if (sort === 'most_votes') { + return ''; + } + const start = this.posts[index].eventStart; + const end = this.posts[index].eventEnd; const events = this.events.filter(event => event.timestamp >= start && event.timestamp < end); if (!events.length) { return ''; diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index 3cbef43311..1a6b8a342f 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -209,7 +209,7 @@ Topics.getEvents = async (req, res) => { return helpers.formatApiResponse(403, res); } - helpers.formatApiResponse(200, res, await topics.events.get(req.params.tid)); + helpers.formatApiResponse(200, res, await topics.events.get(req.params.tid, req.uid)); }; Topics.deleteEvent = async (req, res) => { diff --git a/src/topics/events.js b/src/topics/events.js index 1d2688e5fd..dcbf414e27 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -66,7 +66,7 @@ Events.init = async () => { Events._types = types; }; -Events.get = async (tid, uid) => { +Events.get = async (tid, uid, reverse = false) => { const topics = require('.'); if (!await topics.exists(tid)) { @@ -79,7 +79,9 @@ Events.get = async (tid, uid) => { eventIds = eventIds.map(obj => obj.value); let events = await db.getObjects(keys); events = await modifyEvent({ tid, uid, eventIds, timestamps, events }); - + if (reverse) { + events.reverse(); + } return events; }; diff --git a/src/topics/index.js b/src/topics/index.js index 9584b8d7cf..56e047dbc5 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -179,7 +179,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev getMerger(topicData), Topics.getRelatedTopics(topicData, uid), Topics.thumbs.load([topicData]), - Topics.events.get(topicData.tid, uid), + Topics.events.get(topicData.tid, uid, reverse), ]); topicData.thumbs = thumbs[0]; diff --git a/src/topics/posts.js b/src/topics/posts.js index 5185ce4066..97c3ebbb87 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -56,7 +56,7 @@ module.exports = function (Topics) { Topics.calculatePostIndices(replies, repliesStart); - await Topics.addNextPostTimestamp(postData, set, reverse); + await addEventStartEnd(postData, set, reverse, topicOrTid); const result = await plugins.hooks.fire('filter:topic.getPosts', { topic: topicOrTid, uid: uid, @@ -65,24 +65,33 @@ module.exports = function (Topics) { return result.posts; }; - Topics.addNextPostTimestamp = async function (postData, set, reverse) { + async function addEventStartEnd(postData, set, reverse, topicData) { if (!postData.length) { return; } postData.forEach((p, index) => { - if (p && postData[index + 1]) { - p.nextPostTimestamp = postData[index + 1].timestamp; + if (p && p.index === 0 && reverse) { + p.eventStart = topicData.lastposttime; + p.eventEnd = Date.now(); + } else if (p && postData[index + 1]) { + p.eventStart = reverse ? postData[index + 1].timestamp : p.timestamp; + p.eventEnd = reverse ? p.timestamp : postData[index + 1].timestamp; } }); const lastPost = postData[postData.length - 1]; if (lastPost) { - lastPost.nextPostTimestamp = Date.now(); + lastPost.eventStart = reverse ? topicData.timestamp : lastPost.timestamp; + lastPost.eventEnd = reverse ? lastPost.timestamp : Date.now(); if (lastPost.index) { - const data = await db[reverse ? 'getSortedSetRevRangeWithScores' : 'getSortedSetRangeWithScores'](set, lastPost.index, lastPost.index); - lastPost.nextPostTimestamp = data.length ? data[0].score : lastPost.nextPostTimestamp; + const nextPost = await db[reverse ? 'getSortedSetRevRangeWithScores' : 'getSortedSetRangeWithScores'](set, lastPost.index, lastPost.index); + if (reverse) { + lastPost.eventStart = nextPost.length ? nextPost[0].score : lastPost.eventStart; + } else { + lastPost.eventEnd = nextPost.length ? nextPost[0].score : lastPost.eventEnd; + } } } - }; + } Topics.addPostData = async function (postData, uid) { if (!Array.isArray(postData) || !postData.length) { From 5ec32c3145ed97e7c6258f0a80a7876d525eed52 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 18 Nov 2021 05:29:43 +0000 Subject: [PATCH 036/849] fix(deps): update dependency nodebb-plugin-mentions to v3.0.3 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0004716617..395a79da51 100644 --- a/install/package.json +++ b/install/package.json @@ -89,7 +89,7 @@ "nodebb-plugin-emoji": "^3.5.0", "nodebb-plugin-emoji-android": "2.0.5", "nodebb-plugin-markdown": "8.14.4", - "nodebb-plugin-mentions": "3.0.2", + "nodebb-plugin-mentions": "3.0.3", "nodebb-plugin-spam-be-gone": "0.7.11", "nodebb-rewards-essentials": "0.2.0", "nodebb-theme-lavender": "5.3.1", From 3eb91a2011440931c4dea9bfa10f9c7047d87904 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 18 Nov 2021 09:11:40 +0000 Subject: [PATCH 037/849] fix(deps): update dependency nodebb-theme-persona to v11.2.22 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 395a79da51..d60978299a 100644 --- a/install/package.json +++ b/install/package.json @@ -93,7 +93,7 @@ "nodebb-plugin-spam-be-gone": "0.7.11", "nodebb-rewards-essentials": "0.2.0", "nodebb-theme-lavender": "5.3.1", - "nodebb-theme-persona": "11.2.21", + "nodebb-theme-persona": "11.2.22", "nodebb-theme-slick": "1.4.16", "nodebb-theme-vanilla": "12.1.9", "nodebb-widget-essentials": "5.0.4", From 68dddbd946257e828d3a5eaace4c8eae347d4020 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 18 Nov 2021 11:15:03 +0000 Subject: [PATCH 038/849] fix(deps): update dependency nodebb-theme-vanilla to v12.1.10 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index d60978299a..a827531fe3 100644 --- a/install/package.json +++ b/install/package.json @@ -95,7 +95,7 @@ "nodebb-theme-lavender": "5.3.1", "nodebb-theme-persona": "11.2.22", "nodebb-theme-slick": "1.4.16", - "nodebb-theme-vanilla": "12.1.9", + "nodebb-theme-vanilla": "12.1.10", "nodebb-widget-essentials": "5.0.4", "nodemailer": "^6.5.0", "nprogress": "0.2.0", From f05d308ac7cd1207194df763173146401a20f79a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 18 Nov 2021 14:43:49 +0000 Subject: [PATCH 039/849] fix(deps): update socket.io packages to v4.4.0 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index a827531fe3..fa778e8d2c 100644 --- a/install/package.json +++ b/install/package.json @@ -119,9 +119,9 @@ "sharp": "0.29.3", "sitemap": "^7.0.0", "slideout": "1.0.1", - "socket.io": "4.3.2", + "socket.io": "4.4.0", "socket.io-adapter-cluster": "^1.0.1", - "socket.io-client": "4.3.2", + "socket.io-client": "4.4.0", "@socket.io/redis-adapter": "7.0.1", "sortablejs": "1.14.0", "spdx-license-list": "^6.4.0", From 697dd37670c20670eac40cabab061de81ca1471d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 18 Nov 2021 13:11:05 -0500 Subject: [PATCH 040/849] refactor: change category feed so it is not updated on every reply allow cid query param for recent/top/popular feeds --- src/routes/feeds.js | 117 +++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/src/routes/feeds.js b/src/routes/feeds.js index 7ace7cf216..c3bda30473 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -120,31 +120,32 @@ async function generateForCategory(req, res, next) { if (meta.config['feeds:disableRSS'] || !parseInt(cid, 10)) { return next(); } - - const [userPrivileges, category] = await Promise.all([ + const uid = req.uid || req.query.uid || 0; + const [userPrivileges, category, tids] = await Promise.all([ privileges.categories.get(cid, req.uid), - categories.getCategoryById({ - cid: cid, - set: `cid:${cid}:tids`, - reverse: true, + categories.getCategoryData(cid), + db.getSortedSetRevIntersect({ + sets: ['topics:tid', `cid:${cid}:tids:lastposttime`], start: 0, stop: 25, - uid: req.uid || req.query.uid || 0, + weights: [1, 0], }), ]); - if (!category) { + if (!category || !category.name) { return next(); } if (await validateTokenIfRequiresLogin(!userPrivileges.read, cid, req, res)) { + let topicsData = await topics.getTopicsByTids(tids, uid); + topicsData = await user.blocks.filter(uid, topicsData); const feed = await generateTopicsFeed({ - uid: req.uid || req.query.uid || 0, + uid: uid, title: category.name, description: category.description, feed_url: `/category/${cid}.rss`, site_url: `/category/${category.cid}`, - }, category.topics); + }, topicsData, 'timestamp'); sendFeed(feed, res); } @@ -169,61 +170,47 @@ async function generateForTopics(req, res, next) { } async function generateForRecent(req, res, next) { - if (meta.config['feeds:disableRSS']) { - return next(); - } - let token = null; - if (req.query.token && req.query.uid) { - token = await db.getObjectField(`user:${req.query.uid}`, 'rss_token'); - } - - await sendTopicsFeed({ - uid: token && token === req.query.token ? req.query.uid : req.uid, + await generateSorted({ title: 'Recently Active Topics', description: 'A list of topics that have been active within the past 24 hours', feed_url: '/recent.rss', site_url: '/recent', - }, 'topics:recent', res); + sort: 'recent', + timestampField: 'lastposttime', + term: 'alltime', + }, req, res, next); } async function generateForTop(req, res, next) { - if (meta.config['feeds:disableRSS']) { - return next(); - } - const term = terms[req.params.term] || 'day'; - - let token = null; - if (req.query.token && req.query.uid) { - token = await db.getObjectField(`user:${req.query.uid}`, 'rss_token'); - } - - const uid = token && token === req.query.token ? req.query.uid : req.uid; - - const result = await topics.getSortedTopics({ - uid: uid, - start: 0, - stop: 19, - term: term, - sort: 'votes', - }); - - const feed = await generateTopicsFeed({ - uid: uid, + await generateSorted({ title: 'Top Voted Topics', description: 'A list of topics that have received the most votes', feed_url: `/top/${req.params.term || 'daily'}.rss`, site_url: `/top/${req.params.term || 'daily'}`, - }, result.topics); - - sendFeed(feed, res); + sort: 'votes', + timestampField: 'timestamp', + term: 'day', + }, req, res, next); } async function generateForPopular(req, res, next) { + await generateSorted({ + title: 'Popular Topics', + description: 'A list of topics that are sorted by post count', + feed_url: `/popular/${req.params.term || 'daily'}.rss`, + site_url: `/popular/${req.params.term || 'daily'}`, + sort: 'posts', + timestampField: 'timestamp', + term: 'day', + }, req, res, next); +} + +async function generateSorted(options, req, res, next) { if (meta.config['feeds:disableRSS']) { return next(); } - const term = terms[req.params.term] || 'day'; + const term = terms[req.params.term] || options.term; let token = null; if (req.query.token && req.query.uid) { @@ -232,33 +219,43 @@ async function generateForPopular(req, res, next) { const uid = token && token === req.query.token ? req.query.uid : req.uid; - const result = await topics.getSortedTopics({ + const params = { uid: uid, start: 0, stop: 19, term: term, - sort: 'posts', - }); + sort: options.sort, + }; + const { cid } = req.query; + if (cid) { + if (!await privileges.categories.can('topics:read', cid, uid)) { + return helpers.notAllowed(req, res); + } + params.cids = [cid]; + } + + const result = await topics.getSortedTopics(params); const feed = await generateTopicsFeed({ uid: uid, - title: 'Popular Topics', - description: 'A list of topics that are sorted by post count', - feed_url: `/popular/${req.params.term || 'daily'}.rss`, - site_url: `/popular/${req.params.term || 'daily'}`, - }, result.topics); + title: options.title, + description: options.description, + feed_url: options.feed_url, + site_url: options.site_url, + }, result.topics, options.timestampField); + sendFeed(feed, res); } -async function sendTopicsFeed(options, set, res) { +async function sendTopicsFeed(options, set, res, timestampField) { const start = options.hasOwnProperty('start') ? options.start : 0; const stop = options.hasOwnProperty('stop') ? options.stop : 19; const topicData = await topics.getTopicsFromSet(set, options.uid, start, stop); - const feed = await generateTopicsFeed(options, topicData.topics); + const feed = await generateTopicsFeed(options, topicData.topics, timestampField); sendFeed(feed, res); } -async function generateTopicsFeed(feedOptions, feedTopics) { +async function generateTopicsFeed(feedOptions, feedTopics, timestampField) { feedOptions.ttl = 60; feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url; feedOptions.site_url = nconf.get('url') + feedOptions.site_url; @@ -268,14 +265,14 @@ async function generateTopicsFeed(feedOptions, feedTopics) { const feed = new rss(feedOptions); if (feedTopics.length > 0) { - feed.pubDate = new Date(feedTopics[0].lastposttime).toUTCString(); + feed.pubDate = new Date(feedTopics[0][timestampField]).toUTCString(); } async function addFeedItem(topicData) { const feedItem = { title: utils.stripHTMLTags(topicData.title, utils.tags), url: `${nconf.get('url')}/topic/${topicData.slug}`, - date: new Date(topicData.lastposttime).toUTCString(), + date: new Date(topicData[timestampField]).toUTCString(), }; if (topicData.deleted) { From c26870d227854d4c23620b351b5e6bbd85fb1ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 18 Nov 2021 13:59:39 -0500 Subject: [PATCH 041/849] feat: #10008, add history entry for note deletion --- src/api/flags.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/flags.js b/src/api/flags.js index 235030c9df..2fe37ea51f 100644 --- a/src/api/flags.js +++ b/src/api/flags.js @@ -71,6 +71,10 @@ flagsApi.deleteNote = async (caller, data) => { } await flags.deleteNote(data.flagId, data.datetime); + await flags.appendHistory(data.flagId, caller.uid, { + notes: '[[flags:note-deleted]]', + datetime: Date.now(), + }); const [notes, history] = await Promise.all([ flags.getNotes(data.flagId), From fb363957d1ff8ac63c0a50aaeeb2dd86975876bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 18 Nov 2021 16:42:18 -0500 Subject: [PATCH 042/849] refactor: tab rules --- install/package.json | 2 +- public/src/admin/manage/privileges.js | 2 +- public/src/admin/manage/users.js | 6 +-- public/src/ajaxify.js | 2 +- public/src/app.js | 4 +- public/src/client/chats/search.js | 2 +- public/src/client/groups/details.js | 4 +- public/src/client/infinitescroll.js | 2 +- public/src/modules/chat.js | 2 +- public/src/modules/helpers.js | 2 +- public/src/modules/hooks.js | 8 ++-- public/src/modules/notifications.js | 2 +- public/src/modules/scrollStop.js | 2 +- public/src/utils.js | 2 +- src/api/posts.js | 2 +- src/api/topics.js | 2 +- src/categories/create.js | 2 +- src/categories/index.js | 2 +- src/categories/recentreplies.js | 2 +- src/cli/index.js | 2 +- src/cli/upgrade-plugins.js | 2 +- src/controllers/accounts/helpers.js | 2 +- src/controllers/accounts/settings.js | 6 +-- src/controllers/admin/dashboard.js | 2 +- src/controllers/admin/events.js | 4 +- src/controllers/admin/privileges.js | 2 +- src/controllers/authentication.js | 10 ++-- src/controllers/mods.js | 2 +- src/controllers/write/topics.js | 6 +-- src/database/mongo/sorted/union.js | 2 +- src/database/postgres/main.js | 2 +- src/flags.js | 14 +++--- src/groups/invite.js | 2 +- src/groups/search.js | 2 +- src/messaging/notifications.js | 2 +- src/meta/blacklist.js | 10 ++-- src/meta/errors.js | 2 +- src/meta/settings.js | 2 +- src/middleware/user.js | 2 +- src/notifications.js | 2 +- src/plugins/hooks.js | 4 +- src/posts/queue.js | 2 +- src/posts/votes.js | 2 +- src/routes/authentication.js | 4 +- src/routes/index.js | 2 +- src/routes/write/files.js | 6 +-- src/routes/write/flags.js | 2 +- src/routes/write/topics.js | 2 +- src/socket.io/flags.js | 2 +- src/socket.io/posts/tools.js | 2 +- src/topics/unread.js | 4 +- .../1.6.0/clear-stale-digest-template.js | 6 +-- src/user/auth.js | 2 +- src/user/email.js | 6 +-- src/user/index.js | 6 +-- src/user/interstitials.js | 4 +- src/user/reset.js | 2 +- src/webserver.js | 2 +- test/api.js | 20 ++++---- test/groups.js | 2 +- test/i18n.js | 2 +- test/messaging.js | 10 ++-- test/meta.js | 6 +-- test/posts.js | 2 +- test/socket.io.js | 2 +- test/topics/thumbs.js | 4 +- test/uploads.js | 48 ------------------- test/user.js | 14 +++--- test/utils.js | 1 + 69 files changed, 128 insertions(+), 175 deletions(-) diff --git a/install/package.json b/install/package.json index fa778e8d2c..fd5979b8e0 100644 --- a/install/package.json +++ b/install/package.json @@ -146,7 +146,7 @@ "@commitlint/config-angular": "14.1.0", "coveralls": "3.1.1", "eslint": "7.32.0", - "eslint-config-nodebb": "0.0.3", + "eslint-config-nodebb": "0.1.1", "eslint-plugin-import": "2.25.3", "grunt": "1.4.1", "grunt-contrib-watch": "1.1.0", diff --git a/public/src/admin/manage/privileges.js b/public/src/admin/manage/privileges.js index afb9c45a6b..89d771a1b1 100644 --- a/public/src/admin/manage/privileges.js +++ b/public/src/admin/manage/privileges.js @@ -84,7 +84,7 @@ define('admin/manage/privileges', [ Privileges.exposeAssumedPrivileges(); checkboxRowSelector.updateAll(); - Privileges.addEvents(); // events with confirmation modals + Privileges.addEvents(); // events with confirmation modals }; Privileges.addEvents = function () { diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 7c6543fa75..5eb6615620 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -145,7 +145,7 @@ define('admin/manage/users', [ const uids = getSelectedUids(); if (!uids.length) { app.alertError('[[error:no-users-selected]]'); - return false; // specifically to keep the menu open + return false; // specifically to keep the menu open } bootbox.confirm((uids.length > 1 ? '[[admin/manage/users:alerts.confirm-ban-multi]]' : '[[admin/manage/users:alerts.confirm-ban]]'), function (confirm) { @@ -163,7 +163,7 @@ define('admin/manage/users', [ const uids = getSelectedUids(); if (!uids.length) { app.alertError('[[error:no-users-selected]]'); - return false; // specifically to keep the menu open + return false; // specifically to keep the menu open } Benchpress.render('admin/partials/temporary-ban', {}).then(function (html) { @@ -207,7 +207,7 @@ define('admin/manage/users', [ const uids = getSelectedUids(); if (!uids.length) { app.alertError('[[error:no-users-selected]]'); - return false; // specifically to keep the menu open + return false; // specifically to keep the menu open } Promise.all(uids.map(function (uid) { diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index d42a0b89d1..300d633635 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -536,7 +536,7 @@ $(document).ready(function () { } // eslint-disable-next-line no-script-url - if (hrefEmpty(this.href) || this.protocol === 'javascript:' || href === '#' || href === '') { + if (hrefEmpty(this.href) || this.protocol === 'javascript:' || href === '#' || href === '') { return e.preventDefault(); } diff --git a/public/src/app.js b/public/src/app.js index 68cfd9024f..4ea7d4ff21 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -37,7 +37,7 @@ app.flags = {}; * e.g. New Topic/Reply, post tools */ if (document.body) { - let earlyQueue = []; // once we can ES6, use Set instead + let earlyQueue = []; // once we can ES6, use Set instead const earlyClick = function (ev) { let btnEl = ev.target.closest('button'); const anchorEl = ev.target.closest('a'); @@ -114,7 +114,7 @@ app.flags = {}; }); }; - app.require = async (modules) => { // allows you to await require.js modules + app.require = async (modules) => { // allows you to await require.js modules const single = !Array.isArray(modules); if (single) { modules = [modules]; diff --git a/public/src/client/chats/search.js b/public/src/client/chats/search.js index fac37f908e..6467b60009 100644 --- a/public/src/client/chats/search.js +++ b/public/src/client/chats/search.js @@ -45,7 +45,7 @@ define('forum/chats/search', ['components', 'api'], function (components, api) { function displayUser(chatsListEl, userObj) { function createUserImage() { return (userObj.picture ? - '' : + '' : '
    ' + userObj['icon:text'] + '
    ') + ' ' + userObj.username; } diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index b0ed482c0c..d4583d0c48 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -102,7 +102,7 @@ define('forum/groups/details', [ Details.deleteGroup(); break; - case 'join': // intentional fall-throughs! + case 'join': // intentional fall-throughs! api.put('/groups/' + ajaxify.data.group.slug + '/membership/' + (uid || app.user.uid), undefined).then(() => ajaxify.refresh()).catch(app.alertError); break; @@ -111,7 +111,7 @@ define('forum/groups/details', [ break; // TODO (14/10/2020): rewrite these to use api module and merge with above 2 case blocks - case 'accept': // intentional fall-throughs! + case 'accept': // intentional fall-throughs! case 'reject': case 'issueInvite': case 'rescindInvite': diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index e734cf455e..20eb4fc592 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -5,7 +5,7 @@ define('forum/infinitescroll', ['hooks'], function (hooks) { const scroll = {}; let callback; let previousScrollTop = 0; - let loadingMore = false; + let loadingMore = false; let container; let scrollTimeout = 0; diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index d57386040a..6a4b0e0fbc 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -1,7 +1,7 @@ 'use strict'; define('chat', [ - 'components', 'taskbar', 'translator', 'hooks', 'bootbox', + 'components', 'taskbar', 'translator', 'hooks', 'bootbox', ], function (components, taskbar, translator, hooks, bootbox) { const module = {}; let newMessage = false; diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 4ec6763e94..a73c251100 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -285,7 +285,7 @@ case 'iPad': icons += ''; break; - case 'iPod': // intentional fall-through + case 'iPod': // intentional fall-through case 'iPhone': icons += ''; break; diff --git a/public/src/modules/hooks.js b/public/src/modules/hooks.js index f324bc706e..f6c2cbe971 100644 --- a/public/src/modules/hooks.js +++ b/public/src/modules/hooks.js @@ -6,10 +6,10 @@ define('hooks', [], () => { temporary: new Set(), runOnce: new Set(), deprecated: { - 'action:script.load': 'filter:script.load', // 👋 @ 1.18.0 - 'action:category.loaded': 'action:topics.loaded', // 👋 @ 1.19.0 - 'action:category.loading': 'action:topics.loading', // 👋 @ 1.19.0 - 'action:composer.check': 'filter:composer.check', // 👋 @ 1.19.0 + 'action:script.load': 'filter:script.load', // 👋 @ 1.18.0 + 'action:category.loaded': 'action:topics.loaded', // 👋 @ 1.19.0 + 'action:category.loading': 'action:topics.loading', // 👋 @ 1.19.0 + 'action:composer.check': 'filter:composer.check', // 👋 @ 1.19.0 }, }; diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index cb8ed86b6b..5dc46daecc 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -142,7 +142,7 @@ define('notifications', [ Tinycon.setBubble(count > 99 ? '99+' : count); } - if (navigator.setAppBadge) { // feature detection + if (navigator.setAppBadge) { // feature detection navigator.setAppBadge(count); } }; diff --git a/public/src/modules/scrollStop.js b/public/src/modules/scrollStop.js index c6134feefa..497a099f71 100644 --- a/public/src/modules/scrollStop.js +++ b/public/src/modules/scrollStop.js @@ -20,7 +20,7 @@ define('scrollStop', function () { if ( (e.originalEvent.deltaY < 0 && scrollTop === 0) || // scroll up - (e.originalEvent.deltaY > 0 && (elementHeight + scrollTop) >= scrollHeight) // scroll down + (e.originalEvent.deltaY > 0 && (elementHeight + scrollTop) >= scrollHeight) // scroll down ) { return false; } diff --git a/public/src/utils.js b/public/src/utils.js index 684dd6b3ea..67c71d3243 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -737,7 +737,7 @@ }, isInternalURI: function (targetLocation, referenceLocation, relative_path) { - return targetLocation.host === '' || // Relative paths are always internal links + return targetLocation.host === '' || // Relative paths are always internal links ( targetLocation.host === referenceLocation.host && // Otherwise need to check if protocol and host match diff --git a/src/api/posts.js b/src/api/posts.js index 3cc62f2383..a49e6eb9ed 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -94,7 +94,7 @@ postsAPI.edit = async function (caller, data) { ]); const uids = _.uniq(_.flatten(memberData).concat(String(caller.uid))); - uids.forEach(uid => websockets.in(`uid_${uid}`).emit('event:post_edited', editResult)); + uids.forEach(uid => websockets.in(`uid_${uid}`).emit('event:post_edited', editResult)); return returnData; }; diff --git a/src/api/topics.js b/src/api/topics.js index 4925a62c7e..75a902f39f 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -71,7 +71,7 @@ topicsAPI.reply = async function (caller, data) { return queueObj; } - const postData = await topics.reply(payload); // postData seems to be a subset of postObj, refactor? + const postData = await topics.reply(payload); // postData seems to be a subset of postObj, refactor? const postObj = await posts.getPostSummaryByPids([postData.pid], caller.uid, {}); const result = { diff --git a/src/categories/create.js b/src/categories/create.js index c72fce2bd4..805583b74f 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -21,7 +21,7 @@ module.exports = function (Categories) { data.name = String(data.name || `Category ${cid}`); const slug = `${cid}/${slugify(data.name)}`; const smallestOrder = firstChild.length ? firstChild[0].score - 1 : 1; - const order = data.order || smallestOrder; // If no order provided, place it at the top + const order = data.order || smallestOrder; // If no order provided, place it at the top const colours = Categories.assignColours(); let category = { diff --git a/src/categories/index.js b/src/categories/index.js index b5ae7cf056..5789c25e37 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -392,7 +392,7 @@ Categories.buildForSelectCategories = function (categories, fields, parentCid) { rootCategories.forEach(category => recursive(category, categoriesData, '', 0)); const pickFields = [ - 'cid', 'name', 'level', 'icon', 'parentCid', + 'cid', 'name', 'level', 'icon', 'parentCid', 'color', 'bgColor', 'backgroundImage', 'imageClass', ]; fields = fields || []; diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index c1d1a540eb..935ef80642 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -153,7 +153,7 @@ module.exports = function (Categories) { function getPostsRecursive(category, posts) { if (Array.isArray(category.posts)) { - category.posts.forEach(p => posts.push(p)); + category.posts.forEach(p => posts.push(p)); } category.children.forEach(child => getPostsRecursive(child, posts)); diff --git a/src/cli/index.js b/src/cli/index.js index b7ef494f36..409b429502 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -100,7 +100,7 @@ nconf.argv(opts).env({ prestart.setupWinston(); // Alternate configuration file support -const configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json'); +const configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json'); const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); prestart.loadConfig(configFile); diff --git a/src/cli/upgrade-plugins.js b/src/cli/upgrade-plugins.js index dfa2d38d0c..388e92bd54 100644 --- a/src/cli/upgrade-plugins.js +++ b/src/cli/upgrade-plugins.js @@ -97,7 +97,7 @@ async function checkPlugins() { const toCheck = Object.keys(plugins); if (!toCheck.length) { process.stdout.write(' OK'.green + ''.reset); - return []; // no extraneous plugins installed + return []; // no extraneous plugins installed } const suggestedModules = await getSuggestedModules(nbbVersion, toCheck); process.stdout.write(' OK'.green + ''.reset); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 21e3e16908..e0462ea2e8 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -78,7 +78,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {}) userData.isSelf = isSelf; userData.isFollowing = results.isFollowing; userData.hasPrivateChat = results.hasPrivateChat; - userData.showHidden = results.canEdit; // remove in v1.19.0 + userData.showHidden = results.canEdit; // remove in v1.19.0 userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : []; userData.disableSignatures = meta.config.disableSignatures === 1; userData['reputation:disabled'] = meta.config['reputation:disabled'] === 1; diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index cc79c10880..1331b79d95 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -60,17 +60,17 @@ settingsController.get = async function (req, res, next) { userData.bootswatchSkinOptions = [ { name: 'Default', value: '' }, { name: 'Cerulean', value: 'cerulean' }, - { name: 'Cosmo', value: 'cosmo' }, + { name: 'Cosmo', value: 'cosmo' }, { name: 'Cyborg', value: 'cyborg' }, { name: 'Darkly', value: 'darkly' }, { name: 'Flatly', value: 'flatly' }, - { name: 'Journal', value: 'journal' }, + { name: 'Journal', value: 'journal' }, { name: 'Lumen', value: 'lumen' }, { name: 'Paper', value: 'paper' }, { name: 'Readable', value: 'readable' }, { name: 'Sandstone', value: 'sandstone' }, { name: 'Simplex', value: 'simplex' }, - { name: 'Slate', value: 'slate' }, + { name: 'Slate', value: 'slate' }, { name: 'Spacelab', value: 'spacelab' }, { name: 'Superhero', value: 'superhero' }, { name: 'United', value: 'united' }, diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index d16d2dbaa6..ac9f4fc130 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -188,7 +188,7 @@ async function getStatsFromAnalytics(set, field) { today: data.slice(-1)[0], lastweek: sum(data.slice(-14)), thisweek: sum(data.slice(-7)), - lastmonth: sum(data.slice(0)), // entire set + lastmonth: sum(data.slice(0)), // entire set thismonth: sum(data.slice(-30)), alltime: await getGlobalField(field), }; diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js index f077972aca..3d59892090 100644 --- a/src/controllers/admin/events.js +++ b/src/controllers/admin/events.js @@ -15,8 +15,8 @@ eventsController.get = async function (req, res) { // Limit by date let from = req.query.start ? new Date(req.query.start) || undefined : undefined; let to = req.query.end ? new Date(req.query.end) || undefined : new Date(); - from = from && from.setHours(0, 0, 0, 0); // setHours returns a unix timestamp (Number, not Date) - to = to && to.setHours(23, 59, 59, 999); // setHours returns a unix timestamp (Number, not Date) + from = from && from.setHours(0, 0, 0, 0); // setHours returns a unix timestamp (Number, not Date) + to = to && to.setHours(23, 59, 59, 999); // setHours returns a unix timestamp (Number, not Date) const currentFilter = req.query.type || ''; diff --git a/src/controllers/admin/privileges.js b/src/controllers/admin/privileges.js index c3ab23b9d6..28833b5562 100644 --- a/src/controllers/admin/privileges.js +++ b/src/controllers/admin/privileges.js @@ -21,7 +21,7 @@ privilegesController.get = async function (req, res) { name: '[[admin/manage/privileges:global]]', icon: 'fa-list', }, { - cid: 'admin', // what do? + cid: 'admin', name: '[[admin/manage/privileges:admin]]', icon: 'fa-lock', }]; diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index e970f895cd..4afd54fb84 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -106,7 +106,7 @@ authenticationController.register = async function (req, res) { user.isPasswordValid(userData.password); - res.locals.processLogin = true; // set it to false in plugin if you wish to just register only + res.locals.processLogin = true; // set it to false in plugin if you wish to just register only await plugins.hooks.fire('filter:register.check', { req: req, res: res, userData: userData }); const data = await registerAndLoginUser(req, res, userData); @@ -151,7 +151,7 @@ authenticationController.registerComplete = async function (req, res) { req.body.files = req.files; if ( (cur.callback.constructor && cur.callback.constructor.name === 'AsyncFunction') || - cur.callback.length === 2 // non-async function w/o callback + cur.callback.length === 2 // non-async function w/o callback ) { memo.push(cur.callback); } else { @@ -187,7 +187,7 @@ authenticationController.registerComplete = async function (req, res) { if (req.session.registration.register === true) { res.locals.processLogin = true; - req.body.noscript = 'true'; // trigger full page load on error + req.body.noscript = 'true'; // trigger full page load on error const data = await registerAndLoginUser(req, res, req.session.registration); if (!data) { @@ -388,7 +388,9 @@ authenticationController.onSuccessfulLogin = async function (req, uid) { version: req.useragent.version, }); await Promise.all([ - new Promise(resolve => req.session.save(resolve)), + new Promise((resolve) => { + req.session.save(resolve); + }), user.auth.addSession(uid, req.sessionID), user.updateLastOnlineTime(uid), user.updateOnlineUsers(uid), diff --git a/src/controllers/mods.js b/src/controllers/mods.js index be875eda76..e058a6a99f 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -114,7 +114,7 @@ modsController.flags.detail = async function (req, res, next) { results.privileges = { ...results.privileges[0], ...results.privileges[1] }; if (!results.flagData || (!(results.isAdminOrGlobalMod || !!results.moderatedCids.length))) { - return next(); // 404 + return next(); // 404 } if (results.flagData.type === 'user') { diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index 1a6b8a342f..d0f860680f 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -50,7 +50,7 @@ Topics.pin = async (req, res) => { if (req.body.expiry) { await topics.tools.setPinExpiry(req.params.tid, req.body.expiry, req.uid); } - await api.topics.pin(req, { tids: [req.params.tid] }); + await api.topics.pin(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; @@ -107,7 +107,7 @@ Topics.deleteTags = async (req, res) => { }; Topics.getThumbs = async (req, res) => { - if (isFinite(req.params.tid)) { // post_uuids can be passed in occasionally, in that case no checks are necessary + if (isFinite(req.params.tid)) { // post_uuids can be passed in occasionally, in that case no checks are necessary const [exists, canRead] = await Promise.all([ topics.exists(req.params.tid), privileges.topics.can('topics:read', req.params.tid, req.uid), @@ -126,7 +126,7 @@ Topics.addThumb = async (req, res) => { return; } - const files = await uploadsController.uploadThumb(req, res); // response is handled here + const files = await uploadsController.uploadThumb(req, res); // response is handled here // Add uploaded files to topic zset if (files && files.length) { diff --git a/src/database/mongo/sorted/union.js b/src/database/mongo/sorted/union.js index 86f86683f3..ea4ad4d8e1 100644 --- a/src/database/mongo/sorted/union.js +++ b/src/database/mongo/sorted/union.js @@ -58,7 +58,7 @@ module.exports = function (module) { if (params.withScores) { project.score = '$totalScore'; } - pipeline.push({ $project: project }); + pipeline.push({ $project: project }); let data = await module.client.collection('objects').aggregate(pipeline).toArray(); if (!params.withScores) { diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index 5e6ba43e61..a3f784960a 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -28,7 +28,7 @@ module.exports = function (module) { }); return key.map(k => res.rows.some(r => r.k === k)); } - const res = await module.pool.query({ + const res = await module.pool.query({ name: 'exists', text: ` SELECT EXISTS(SELECT * diff --git a/src/flags.js b/src/flags.js index e071bf6fa9..0afa88ef34 100644 --- a/src/flags.js +++ b/src/flags.js @@ -64,8 +64,8 @@ Flags.init = async function () { cid: function (sets, orSets, key) { prepareSets(sets, orSets, 'flags:byCid:', key); }, - page: function () { /* noop */ }, - perPage: function () { /* noop */ }, + page: function () { /* noop */ }, + perPage: function () { /* noop */ }, quick: function (sets, orSets, key, uid) { switch (key) { case 'mine': @@ -141,7 +141,7 @@ Flags.getFlagIdsWithFilters = async function ({ filters, uid, query }) { winston.warn(`[flags/list] No flag filter type found: ${type}`); } } - sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default + sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default let flagIds = []; if (sets.length === 1) { @@ -244,7 +244,7 @@ Flags.sort = async function (flagIds, sort) { break; } - case 'upvotes': // fall-through + case 'upvotes': // fall-through case 'downvotes': case 'replies': { flagIds = await filterPosts(flagIds); @@ -426,8 +426,8 @@ Flags.create = async function (type, id, uid, reason, timestamp) { }), Flags.addReport(flagId, type, id, uid, reason, timestamp), db.sortedSetAdd('flags:datetime', timestamp, flagId), // by time, the default - db.sortedSetAdd(`flags:byType:${type}`, timestamp, flagId), // by flag type - db.sortedSetIncrBy('flags:byTarget', 1, [type, id].join(':')), // by flag target (score is count) + db.sortedSetAdd(`flags:byType:${type}`, timestamp, flagId), // by flag type + db.sortedSetIncrBy('flags:byTarget', 1, [type, id].join(':')), // by flag target (score is count) analytics.increment('flags') // some fancy analytics ); @@ -441,7 +441,7 @@ Flags.create = async function (type, id, uid, reason, timestamp) { if (type === 'post') { batched.push( - db.sortedSetAdd(`flags:byPid:${id}`, timestamp, flagId), // by target pid + db.sortedSetAdd(`flags:byPid:${id}`, timestamp, flagId), // by target pid posts.setPostField(id, 'flagId', flagId) ); diff --git a/src/groups/invite.js b/src/groups/invite.js index 784f0f560b..74b7453c4e 100644 --- a/src/groups/invite.js +++ b/src/groups/invite.js @@ -45,7 +45,7 @@ module.exports = function (Groups) { groupNames = [groupNames]; } const sets = []; - groupNames.forEach(groupName => sets.push(`group:${groupName}:pending`, `group:${groupName}:invited`)); + groupNames.forEach(groupName => sets.push(`group:${groupName}:pending`, `group:${groupName}:invited`)); await db.setsRemove(sets, uid); }; diff --git a/src/groups/search.js b/src/groups/search.js index 4bec5085af..9b6b15d5a4 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -42,7 +42,7 @@ module.exports = function (Groups) { groups.sort((a, b) => b.createtime - a.createtime); break; - case 'alpha': // intentional fall-through + case 'alpha': // intentional fall-through default: groups.sort((a, b) => (a.slug > b.slug ? 1 : -1)); } diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js index 895138c7e8..bd03f2dd48 100644 --- a/src/messaging/notifications.js +++ b/src/messaging/notifications.js @@ -7,7 +7,7 @@ const plugins = require('../plugins'); const meta = require('../meta'); module.exports = function (Messaging) { - Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser + Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => { let uids = await Messaging.getUidsInRoom(roomId, 0, -1); diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js index 8fd15bd5be..8e3bc7b1f7 100644 --- a/src/meta/blacklist.js +++ b/src/meta/blacklist.js @@ -45,8 +45,8 @@ Blacklist.get = async function () { Blacklist.test = async function (clientIp) { // Some handy test addresses - // clientIp = '2001:db8:85a3:0:0:8a2e:370:7334'; // IPv6 - // clientIp = '127.0.15.1'; // IPv4 + // clientIp = '2001:db8:85a3:0:0:8a2e:370:7334'; // IPv6 + // clientIp = '127.0.15.1'; // IPv4 // clientIp = '127.0.15.1:3443'; // IPv4 with port strip port to not fail if (!clientIp) { return; @@ -62,15 +62,15 @@ Blacklist.test = async function (clientIp) { } if ( - !Blacklist._rules.ipv4.includes(clientIp) && // not explicitly specified in ipv4 list - !Blacklist._rules.ipv6.includes(clientIp) && // not explicitly specified in ipv6 list + !Blacklist._rules.ipv4.includes(clientIp) && // not explicitly specified in ipv4 list + !Blacklist._rules.ipv6.includes(clientIp) && // not explicitly specified in ipv6 list !Blacklist._rules.cidr.some((subnet) => { const cidr = ipaddr.parseCIDR(subnet); if (addr.kind() !== cidr[0].kind()) { return false; } return addr.match(cidr); - }) // not in a blacklisted IPv4 or IPv6 cidr range + }) // not in a blacklisted IPv4 or IPv6 cidr range ) { try { // To return test failure, pass back an error in callback diff --git a/src/meta/errors.js b/src/meta/errors.js index 843051e310..1d2949c28a 100644 --- a/src/meta/errors.js +++ b/src/meta/errors.js @@ -37,7 +37,7 @@ Errors.log404 = function (route) { if (!route) { return; } - route = route.slice(0, 512).replace(/\/$/, ''); // remove trailing slashes + route = route.slice(0, 512).replace(/\/$/, ''); // remove trailing slashes analytics.increment('errors:404'); counters[route] = counters[route] || 0; counters[route] += 1; diff --git a/src/meta/settings.js b/src/meta/settings.js index 4ac548895f..1207d13734 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -94,7 +94,7 @@ Settings.set = async function (hash, values, quiet) { plugins.hooks.fire('action:settings.set', { plugin: hash, - settings: { ...values, ...sortedListData }, // Add back sorted list data to values hash + settings: { ...values, ...sortedListData }, // Add back sorted list data to values hash }); pubsub.publish(`action:settings.set.${hash}`, values); diff --git a/src/middleware/user.js b/src/middleware/user.js index b9f195477e..c70d4f21d9 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -73,7 +73,7 @@ module.exports = function (middleware) { await plugins.hooks.fire('response:middleware.authenticate', { req: req, res: res, - next: function () {}, // no-op for backwards compatibility + next: function () {}, // no-op for backwards compatibility }); if (!res.headersSent) { diff --git a/src/notifications.js b/src/notifications.js index 57dae2f635..6c9c01f46f 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -371,7 +371,7 @@ Notifications.merge = async function (notifications) { notifications = mergeIds.reduce((notifications, mergeId) => { const isolated = notifications.filter(n => n && n.hasOwnProperty('mergeId') && n.mergeId.split('|')[0] === mergeId); if (isolated.length <= 1) { - return notifications; // Nothing to merge + return notifications; // Nothing to merge } // Each isolated mergeId may have multiple differentiators, so process each separately diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 050da0a628..108b4750ac 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -8,8 +8,8 @@ const utils = require('../utils'); const Hooks = module.exports; Hooks.deprecatedHooks = { - 'filter:email.send': 'static:email.send', // 👋 @ 1.19.0 - 'filter:router.page': 'response:router.page', // 👋 @ 2.0.0 + 'filter:email.send': 'static:email.send', // 👋 @ 1.19.0 + 'filter:router.page': 'response:router.page', // 👋 @ 2.0.0 }; Hooks.internals = { diff --git a/src/posts/queue.js b/src/posts/queue.js index 33a64e14be..7e5790e166 100644 --- a/src/posts/queue.js +++ b/src/posts/queue.js @@ -19,7 +19,7 @@ const socketHelpers = require('../socket.io/helpers'); module.exports = function (Posts) { Posts.getQueuedPosts = async (filter = {}, options = {}) => { - options = { metadata: true, ...options }; // defaults + options = { metadata: true, ...options }; // defaults let postData = _.cloneDeep(cache.get('post-queue')); if (!postData) { const ids = await db.getSortedSetRange('post:queue', 0, -1); diff --git a/src/posts/votes.js b/src/posts/votes.js index c4d976a1ee..8f3f240314 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -206,7 +206,7 @@ module.exports = function (Posts) { let current = voteStatus.upvoted ? 'upvote' : 'downvote'; if (unvote) { // e.g. unvoting, removing a upvote or downvote hook = 'unvote'; - } else { // e.g. User *has not* voted, clicks upvote or downvote + } else { // e.g. User *has not* voted, clicks upvote or downvote current = 'unvote'; } // action:post.upvote diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 6d7a36e037..b2b4f0bf6d 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -137,9 +137,7 @@ Auth.reloadRoutes = async function (params) { res.locals.strategy = strategy; next(); })(req, res, next); - }, - Auth.middleware.validateAuth, - (req, res, next) => { + }, Auth.middleware.validateAuth, (req, res, next) => { async.waterfall([ async.apply(req.login.bind(req), res.locals.user), async.apply(controllers.authentication.onSuccessfulLogin, req, req.uid), diff --git a/src/routes/index.js b/src/routes/index.js index 076c7c36b2..402384b0d3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -203,7 +203,7 @@ function addRemountableRoutes(app, router, middleware, mounts) { const original = mount; mount = mounts[original]; - if (!mount) { // do not mount at all + if (!mount) { // do not mount at all winston.warn(`[router] Not mounting /${original}`); return; } diff --git a/src/routes/write/files.js b/src/routes/write/files.js index 97ddde6d01..873144870c 100644 --- a/src/routes/write/files.js +++ b/src/routes/write/files.js @@ -11,9 +11,9 @@ module.exports = function () { const middlewares = [middleware.ensureLoggedIn, middleware.admin.checkPrivileges]; // setupApiRoute(router, 'put', '/', [ - // ...middlewares, - // middleware.checkRequired.bind(null, ['path']), - // middleware.assert.folder + // ...middlewares, + // middleware.checkRequired.bind(null, ['path']), + // middleware.assert.folder // ], controllers.write.files.upload); setupApiRoute(router, 'delete', '/', [ ...middlewares, diff --git a/src/routes/write/flags.js b/src/routes/write/flags.js index 783fa177d4..c713b24dfa 100644 --- a/src/routes/write/flags.js +++ b/src/routes/write/flags.js @@ -11,7 +11,7 @@ module.exports = function () { const middlewares = [middleware.ensureLoggedIn]; setupApiRoute(router, 'post', '/', [...middlewares], controllers.write.flags.create); - // setupApiRoute(router, 'delete', ...); // does not exist + // setupApiRoute(router, 'delete', ...); // does not exist setupApiRoute(router, 'get', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.get); setupApiRoute(router, 'put', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.update); diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index b62124f95b..3a0c7c306e 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -30,7 +30,7 @@ module.exports = function () { setupApiRoute(router, 'put', '/:tid/follow', [...middlewares, middleware.assert.topic], controllers.write.topics.follow); setupApiRoute(router, 'delete', '/:tid/follow', [...middlewares, middleware.assert.topic], controllers.write.topics.unfollow); setupApiRoute(router, 'put', '/:tid/ignore', [...middlewares, middleware.assert.topic], controllers.write.topics.ignore); - setupApiRoute(router, 'delete', '/:tid/ignore', [...middlewares, middleware.assert.topic], controllers.write.topics.unfollow); // intentional, unignore == unfollow + setupApiRoute(router, 'delete', '/:tid/ignore', [...middlewares, middleware.assert.topic], controllers.write.topics.unfollow); // intentional, unignore == unfollow setupApiRoute(router, 'put', '/:tid/tags', [...middlewares, middleware.checkRequired.bind(null, ['tags']), middleware.assert.topic], controllers.write.topics.addTags); setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags); diff --git a/src/socket.io/flags.js b/src/socket.io/flags.js index ee8bc9ac05..7992aa8541 100644 --- a/src/socket.io/flags.js +++ b/src/socket.io/flags.js @@ -15,7 +15,7 @@ SocketFlags.create = async function (socket, data) { SocketFlags.update = async function (socket, data) { sockets.warnDeprecated(socket, 'PUT /api/v3/flags/:flagId'); - if (!data || !(data.flagId && data.data)) { // check only req'd in socket.io + if (!data || !(data.flagId && data.data)) { // check only req'd in socket.io throw new Error('[[error:invalid-data]]'); } diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index 7128cfa7d6..e9908a919d 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -28,7 +28,7 @@ module.exports = function (SocketPosts) { canDelete: privileges.posts.canDelete(data.pid, socket.uid), canPurge: privileges.posts.canPurge(data.pid, socket.uid), canFlag: privileges.posts.canFlag(data.pid, socket.uid), - flagged: flags.exists('post', data.pid, socket.uid), // specifically, whether THIS calling user flagged + flagged: flags.exists('post', data.pid, socket.uid), // specifically, whether THIS calling user flagged bookmarked: posts.hasBookmarked(data.pid, socket.uid), postSharing: social.getActivePostSharing(), history: posts.diffs.exists(data.pid), diff --git a/src/topics/unread.js b/src/topics/unread.js index 6627fc8074..f0ab2a5c36 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -84,8 +84,8 @@ module.exports = function (Topics) { }; async function getTids(params) { - const counts = { '': 0, new: 0, watched: 0, unreplied: 0 }; - const tidsByFilter = { '': [], new: [], watched: [], unreplied: [] }; + const counts = { '': 0, new: 0, watched: 0, unreplied: 0 }; + const tidsByFilter = { '': [], new: [], watched: [], unreplied: [] }; if (params.uid <= 0) { return { counts: counts, tids: [], tidsByFilter: tidsByFilter }; diff --git a/src/upgrades/1.6.0/clear-stale-digest-template.js b/src/upgrades/1.6.0/clear-stale-digest-template.js index 3bcd11b49c..0eabe804f6 100644 --- a/src/upgrades/1.6.0/clear-stale-digest-template.js +++ b/src/upgrades/1.6.0/clear-stale-digest-template.js @@ -8,9 +8,9 @@ module.exports = { timestamp: Date.UTC(2017, 8, 6), method: async function () { const matches = [ - '112e541b40023d6530dd44df4b0d9c5d', // digest @ 75917e25b3b5ad7bed8ed0c36433fb35c9ab33eb - '110b8805f70395b0282fd10555059e9f', // digest @ 9b02bb8f51f0e47c6e335578f776ffc17bc03537 - '9538e7249edb369b2a25b03f2bd3282b', // digest @ 3314ab4b83138c7ae579ac1f1f463098b8c2d414 + '112e541b40023d6530dd44df4b0d9c5d', // digest @ 75917e25b3b5ad7bed8ed0c36433fb35c9ab33eb + '110b8805f70395b0282fd10555059e9f', // digest @ 9b02bb8f51f0e47c6e335578f776ffc17bc03537 + '9538e7249edb369b2a25b03f2bd3282b', // digest @ 3314ab4b83138c7ae579ac1f1f463098b8c2d414 ]; const fieldset = await meta.configs.getFields(['email:custom:digest']); const hash = fieldset['email:custom:digest'] ? crypto.createHash('md5').update(fieldset['email:custom:digest']).digest('hex') : null; diff --git a/src/user/auth.js b/src/user/auth.js index 9e4738514a..d8113547e6 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -95,7 +95,7 @@ module.exports = function (User) { const sid = uuidMapping[uuid]; const sessionObj = await getSessionFromStore(sid); const expired = !sessionObj || !sessionObj.hasOwnProperty('passport') || - !sessionObj.passport.hasOwnProperty('user') || + !sessionObj.passport.hasOwnProperty('user') || parseInt(sessionObj.passport.user, 10) !== parseInt(uid, 10); if (expired) { expiredUUIDs.push(uuid); diff --git a/src/user/email.js b/src/user/email.js index a97aa1f38f..251ebab944 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -65,9 +65,9 @@ UserEmail.expireValidation = async (uid) => { UserEmail.sendValidationEmail = async function (uid, options) { /* - * Options: - * - email, overrides email retrieval - * - force, sends email even if it is too soon to send another + * Options: + * - email, overrides email retrieval + * - force, sends email even if it is too soon to send another */ if (meta.config.sendValidationEmail !== 1) { diff --git a/src/user/index.js b/src/user/index.js index 16096c892c..6a52886011 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -230,9 +230,9 @@ User.addInterstitials = function (callback) { plugins.hooks.register('core', { hook: 'filter:register.interstitial', method: [ - User.interstitials.email, // Email address (for password reset + digest) - User.interstitials.gdpr, // GDPR information collection/processing consent + email consent - User.interstitials.tou, // Forum Terms of Use + User.interstitials.email, // Email address (for password reset + digest) + User.interstitials.gdpr, // GDPR information collection/processing consent + email consent + User.interstitials.tou, // Forum Terms of Use ], }); diff --git a/src/user/interstitials.js b/src/user/interstitials.js index ac401127d1..101cdd01c8 100644 --- a/src/user/interstitials.js +++ b/src/user/interstitials.js @@ -36,7 +36,7 @@ Interstitials.email = async (data) => { uid: userData.uid, email: formData.email, registration: false, - allowed: true, // change this value to disallow + allowed: true, // change this value to disallow error: '[[error:invalid-email]]', }), ]); @@ -79,7 +79,7 @@ Interstitials.email = async (data) => { uid: null, email: formData.email, registration: true, - allowed: true, // change this value to disallow + allowed: true, // change this value to disallow error: '[[error:invalid-email]]', }); diff --git a/src/user/reset.js b/src/user/reset.js index b6e58e8206..3f83e3812c 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -91,7 +91,7 @@ UserReset.commit = async function (code, password) { // don't verify email if password reset is due to expiry const isPasswordExpired = userData.passwordExpiry && userData.passwordExpiry < Date.now(); if (!isPasswordExpired) { - data['email:confirmed'] = 1; + data['email:confirmed'] = 1; await groups.join('verified-users', uid); await groups.leave('unverified-users', uid); } diff --git a/src/webserver.js b/src/webserver.js index c246272b85..e75c59eb00 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -168,7 +168,7 @@ function setupExpressApp(app) { app.use((req, res, next) => { als.run({ uid: req.uid }, next); }); - app.use(middleware.autoLocale); // must be added after auth middlewares are added + app.use(middleware.autoLocale); // must be added after auth middlewares are added const toobusy = require('toobusy-js'); toobusy.maxLag(meta.config.eventLoopLagThreshold); diff --git a/test/api.js b/test/api.js index fa59ffeda7..f855e157aa 100644 --- a/test/api.js +++ b/test/api.js @@ -33,7 +33,7 @@ describe('API', async () => { let jar; let csrfToken; let setup = false; - const unauthenticatedRoutes = ['/api/login', '/api/register']; // Everything else will be called with the admin user + const unauthenticatedRoutes = ['/api/login', '/api/register']; // Everything else will be called with the admin user const mocks = { head: {}, @@ -73,19 +73,19 @@ describe('API', async () => { { in: 'path', name: 'uuid', - example: '', // to be defined below... + example: '', // to be defined below... }, ], '/posts/{pid}/diffs/{timestamp}': [ { in: 'path', name: 'pid', - example: '', // to be defined below... + example: '', // to be defined below... }, { in: 'path', name: 'timestamp', - example: '', // to be defined below... + example: '', // to be defined below... }, ], }, @@ -116,7 +116,7 @@ describe('API', async () => { for (let x = 0; x < 4; x++) { // eslint-disable-next-line no-await-in-loop - await user.create({ username: 'deleteme', password: '123456' }); // for testing of DELETE /users (uids 5, 6) and DELETE /user/:uid/account (uid 7) + await user.create({ username: 'deleteme', password: '123456' }); // for testing of DELETE /users (uids 5, 6) and DELETE /user/:uid/account (uid 7) } await groups.join('administrators', adminUid); @@ -299,7 +299,7 @@ describe('API', async () => { function generateTests(api, paths, prefix) { // Iterate through all documented paths, make a call to it, // and compare the result body with what is defined in the spec - const pathLib = path; // for calling path module from inside this forEach + const pathLib = path; // for calling path module from inside this forEach paths.forEach((path) => { const context = api.paths[path]; let schema; @@ -393,9 +393,9 @@ describe('API', async () => { method: method, jar: !unauthenticatedRoutes.includes(path) ? jar : undefined, json: true, - followRedirect: false, // all responses are significant (e.g. 302) - simple: false, // don't throw on non-200 (e.g. 302) - resolveWithFullResponse: true, // send full request back (to check statusCode) + followRedirect: false, // all responses are significant (e.g. 302) + simple: false, // don't throw on non-200 (e.g. 302) + resolveWithFullResponse: true, // send full request back (to check statusCode) headers: headers, qs: qs, body: body, @@ -572,7 +572,7 @@ describe('API', async () => { // Compare the response to the schema Object.keys(response).forEach((prop) => { - if (additionalProperties) { // All bets are off + if (additionalProperties) { // All bets are off return; } diff --git a/test/groups.js b/test/groups.js index 98aadadd98..3d8de2f57c 100644 --- a/test/groups.js +++ b/test/groups.js @@ -60,7 +60,7 @@ describe('Groups', () => { // Also create a hidden group await Groups.join('Hidden', 'Test'); // create another group that starts with test for search/sort - await Groups.create({ name: 'Test2', description: 'Foobar!' }); + await Groups.create({ name: 'Test2', description: 'Foobar!' }); testUid = await User.create({ username: 'testuser', diff --git a/test/i18n.js b/test/i18n.js index af066098f9..ed3a86e7ff 100644 --- a/test/i18n.js +++ b/test/i18n.js @@ -19,7 +19,7 @@ describe('i18n', () => { }); it('should contain folders named after the language code', async () => { - const valid = /(?:README.md|^[a-z]{2}(?:-[A-Z]{2})?$|^[a-z]{2}(?:-x-[a-z]+)?$)/; // good luck + const valid = /(?:README.md|^[a-z]{2}(?:-[A-Z]{2})?$|^[a-z]{2}(?:-x-[a-z]+)?$)/; // good luck folders.forEach((folder) => { assert(valid.test(folder)); diff --git a/test/messaging.js b/test/messaging.js index 996ad14bdb..83665b1dc0 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -17,17 +17,17 @@ const helpers = require('./helpers'); const socketModules = require('../src/socket.io/modules'); describe('Messaging Library', () => { - let fooUid; // the admin - let bazUid; // the user with chat restriction enabled + let fooUid; // the admin + let bazUid; // the user with chat restriction enabled let herpUid; let roomId; before((done) => { // Create 3 users: 1 admin, 2 regular async.series([ - async.apply(User.create, { username: 'foo', password: 'barbar' }), // admin - async.apply(User.create, { username: 'baz', password: 'quuxquux' }), // restricted user - async.apply(User.create, { username: 'herp', password: 'derpderp' }), // regular user + async.apply(User.create, { username: 'foo', password: 'barbar' }), // admin + async.apply(User.create, { username: 'baz', password: 'quuxquux' }), // restricted user + async.apply(User.create, { username: 'herp', password: 'derpderp' }), // regular user ], (err, uids) => { if (err) { return done(err); diff --git a/test/meta.js b/test/meta.js index 86ea5a3637..84452561d4 100644 --- a/test/meta.js +++ b/test/meta.js @@ -19,9 +19,9 @@ describe('meta', () => { Groups.cache.reset(); // Create 3 users: 1 admin, 2 regular async.series([ - async.apply(User.create, { username: 'foo', password: 'barbar' }), // admin - async.apply(User.create, { username: 'baz', password: 'quuxquux' }), // restricted user - async.apply(User.create, { username: 'herp', password: 'derpderp' }), // regular user + async.apply(User.create, { username: 'foo', password: 'barbar' }), // admin + async.apply(User.create, { username: 'baz', password: 'quuxquux' }), // restricted user + async.apply(User.create, { username: 'herp', password: 'derpderp' }), // regular user ], (err, uids) => { if (err) { return done(err); diff --git a/test/posts.js b/test/posts.js index e6fa092536..71dfa74a74 100644 --- a/test/posts.js +++ b/test/posts.js @@ -1514,7 +1514,7 @@ describe('Post\'s', () => { const events = await topics.events.get(tid1, 1); assert(events); - assert.strictEqual(events.length, 1); // should still equal 1 + assert.strictEqual(events.length, 1); // should still equal 1 }); it('should not show backlink events if the feature is disabled', async () => { diff --git a/test/socket.io.js b/test/socket.io.js index fad6591185..f709e86178 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -716,7 +716,7 @@ describe('socket.io', () => { event: async.apply(events.getEvents, '', 0, 0), }, (err, data) => { assert.ifError(err); - assert.strictEqual(data.count, 1); // should still equal 1 + assert.strictEqual(data.count, 1); // should still equal 1 // Event validity assert.strictEqual(data.event.length, 1); diff --git a/test/topics/thumbs.js b/test/topics/thumbs.js index 2cdd1d66a9..8020cfd323 100644 --- a/test/topics/thumbs.js +++ b/test/topics/thumbs.js @@ -175,7 +175,7 @@ describe('Topic thumbs', () => { const score = await db.sortedSetScore(`topic:${tid}:thumbs`, relativeThumbPaths[0]); - assert(isFinite(score)); // exists in set + assert(isFinite(score)); // exists in set assert.strictEqual(score, 2); }); @@ -188,7 +188,7 @@ describe('Topic thumbs', () => { const score = await db.sortedSetScore(`topic:${tid}:thumbs`, relativeThumbPaths[0]); - assert(isFinite(score)); // exists in set + assert(isFinite(score)); // exists in set assert.strictEqual(score, 0); }); diff --git a/test/uploads.js b/test/uploads.js index 4def21ed17..7d7186ca02 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -243,54 +243,6 @@ describe('Upload Controllers', () => { }); }); - // it('should fail if topic thumbs are disabled', function (done) { - // helpers.uploadFile( - // nconf.get('url') + '/api/topic/thumb/upload', - // path.join(__dirname, '../test/files/test.png'), - // {}, jar, csrf_token, - // function (err, res, body) { - // assert.ifError(err); - // assert.strictEqual(res.statusCode, 404); - // console.log(body); - // assert(body && body.status && body.status.code); - // assert.strictEqual(body.status.code, '[[error:topic-thumbnails-are-disabled]]'); - // done(); - // } - // ); - // }); - - // it('should fail if file is not image', function (done) { - // meta.config.allowTopicsThumbnail = 1; - // helpers.uploadFile( - // nconf.get('url') + '/api/topic/thumb/upload', - // path.join(__dirname, '../test/files/503.html'), - // {}, jar, csrf_token, - // function (err, res, body) { - // assert.ifError(err); - // assert.equal(res.statusCode, 500); - // assert.equal(body.error, '[[error:invalid-file]]'); - // done(); - // } - // ); - // }); - - // it('should upload topic thumb', function (done) { - // meta.config.allowTopicsThumbnail = 1; - // helpers.uploadFile( - // nconf.get('url') + '/api/topic/thumb/upload', - // path.join(__dirname, '../test/files/test.png'), - // {}, jar, csrf_token, - // function (err, res, body) { - // assert.ifError(err); - // assert.equal(res.statusCode, 200); - // assert(Array.isArray(body)); - // assert(body[0].path); - // assert(body[0].url); - // done(); - // } - // ); - // }); - it('should not allow non image uploads', (done) => { socketUser.updateCover({ uid: 1 }, { uid: 1, file: { path: '../../text.txt' } }, (err) => { assert.equal(err.message, '[[error:invalid-data]]'); diff --git a/test/user.js b/test/user.js index 671cc06d20..308d68b4b8 100644 --- a/test/user.js +++ b/test/user.js @@ -869,7 +869,7 @@ describe('User', () => { assert.ifError(err); Object.keys(data).forEach((key) => { if (key === 'email') { - assert.strictEqual(userData.email, 'just@for.updated'); // email remains the same until confirmed + assert.strictEqual(userData.email, 'just@for.updated'); // email remains the same until confirmed } else if (key !== 'password') { assert.equal(data[key], userData[key]); } else { @@ -1500,9 +1500,9 @@ describe('User', () => { function (next) { User.digest.getSubscribers('day', (err, subs) => { assert.ifError(err); - assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed - assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed - assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed + assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed + assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed + assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed next(); }); @@ -1516,9 +1516,9 @@ describe('User', () => { function (next) { User.digest.getSubscribers('week', (err, subs) => { assert.ifError(err); - assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed - assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed - assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed + assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed + assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed + assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed next(); }); diff --git a/test/utils.js b/test/utils.js index b9806ca4c8..3c13ef186c 100644 --- a/test/utils.js +++ b/test/utils.js @@ -259,6 +259,7 @@ describe('Utility Methods', () => { }); it('should return passed in value if invalid', (done) => { + // eslint-disable-next-line no-loss-of-precision const bigInt = -111111111111111111; const result = utils.toISOString(bigInt); assert.equal(bigInt, result); From 9245f71a66f5e9a4ffdc6ffc8bc2d837af81a8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 18 Nov 2021 20:12:07 -0500 Subject: [PATCH 043/849] fix: search crash --- src/controllers/search.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/controllers/search.js b/src/controllers/search.js index 004fe67e3b..c7036e9393 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -110,12 +110,14 @@ async function recordSearch(data) { clearTimeout(searches[data.uid].timeoutId); } searches[data.uid].timeoutId = setTimeout(async () => { - const copy = searches[data.uid].queries.slice(); - const filtered = searches[data.uid].queries.filter( - q => !copy.find(query => query.startsWith(q) && query.length > q.length) - ); - await Promise.all(filtered.map(query => db.sortedSetIncrBy('searches:all', 1, query))); - delete searches[data.uid]; + if (searches[data.uid] && searches[data.uid].queries) { + const copy = searches[data.uid].queries.slice(); + const filtered = searches[data.uid].queries.filter( + q => !copy.find(query => query.startsWith(q) && query.length > q.length) + ); + delete searches[data.uid]; + await Promise.all(filtered.map(query => db.sortedSetIncrBy('searches:all', 1, query))); + } }, 5000); } } From 449366ca834ad433f0a6f914f6bef045241a80d7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Nov 2021 12:20:48 -0500 Subject: [PATCH 044/849] fix: consolidate plugin reset logic --- src/cli/reset.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/cli/reset.js b/src/cli/reset.js index 290f5bf7a2..7d4c7e9e2a 100644 --- a/src/cli/reset.js +++ b/src/cli/reset.js @@ -119,14 +119,10 @@ async function resetPlugin(pluginId) { const isActive = await db.isSortedSetMember('plugins:active', pluginId); if (isActive) { await db.sortedSetRemove('plugins:active', pluginId); - } - - await events.log({ - type: 'plugin-deactivate', - text: pluginId, - }); - - if (isActive) { + await events.log({ + type: 'plugin-deactivate', + text: pluginId, + }); winston.info('[reset] Plugin `%s` disabled', pluginId); } else { winston.warn('[reset] Plugin `%s` was not active on this forum', pluginId); From 5b42b6b369ca3eb917b1c983ffa51ffa46002eaa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 19 Nov 2021 15:12:13 -0500 Subject: [PATCH 045/849] API route for returning tracked analytics keys (#10019) * feat: track metrics saved by NodeBB (and assoc. plugins), #9949 * feat: route to retrieve analytics keys, closes #9949 --- public/openapi/write.yaml | 2 ++ public/openapi/write/admin/analytics.yaml | 20 ++++++++++++++++++++ src/analytics.js | 18 ++++++++++++++++++ src/controllers/write/admin.js | 14 ++++++++------ src/routes/write/admin.js | 5 +++-- 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 public/openapi/write/admin/analytics.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 734b64ff24..7fade16fab 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -142,6 +142,8 @@ paths: $ref: 'write/flags/flagId/notes/datetime.yaml' /admin/settings/{setting}: $ref: 'write/admin/settings/setting.yaml' + /admin/analytics: + $ref: 'write/admin/analytics.yaml' /admin/analytics/{set}: $ref: 'write/admin/analytics/set.yaml' /files/: diff --git a/public/openapi/write/admin/analytics.yaml b/public/openapi/write/admin/analytics.yaml new file mode 100644 index 0000000000..06f68a3778 --- /dev/null +++ b/public/openapi/write/admin/analytics.yaml @@ -0,0 +1,20 @@ +get: + tags: + - admin + summary: get analytics keys + description: This operation returns the list metrics tracked by NodeBB. It is only accessible to administrators. + responses: + '200': + description: Analytics keys retrieved + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + keys: + type: array \ No newline at end of file diff --git a/src/analytics.js b/src/analytics.js index fa848ad665..c2d0a010e1 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -53,6 +53,8 @@ Analytics.increment = function (keys, callback) { } }; +Analytics.getKeys = async () => db.getSortedSetRange('analyticsKeys', 0, -1); + Analytics.pageView = async function (payload) { pageViews += 1; @@ -90,6 +92,17 @@ Analytics.writeData = async function () { const month = new Date(); const dbQueue = []; + // Build list of metrics that were updated + let metrics = [ + 'pageviews', + 'pageviews:month', + ]; + metrics.forEach((metric) => { + const toAdd = ['registered', 'guest', 'bot'].map(type => `${metric}:${type}`); + metrics = [...metrics, ...toAdd]; + }); + metrics.push('uniquevisitors'); + today.setHours(today.getHours(), 0, 0, 0); month.setMonth(month.getMonth(), 1); month.setHours(0, 0, 0, 0); @@ -130,8 +143,13 @@ Analytics.writeData = async function () { for (const [key, value] of Object.entries(counters)) { dbQueue.push(db.sortedSetIncrBy(`analytics:${key}`, value, today.getTime())); + metrics.push(key); delete counters[key]; } + + // Update list of tracked metrics + dbQueue.push(db.sortedSetAdd('analyticsKeys', metrics.map(() => +Date.now()), metrics)); + try { await Promise.all(dbQueue); } catch (err) { diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js index b667efcbad..8b9faa55ef 100644 --- a/src/controllers/write/admin.js +++ b/src/controllers/write/admin.js @@ -1,6 +1,5 @@ 'use strict'; -const user = require('../../user'); const meta = require('../../meta'); const privileges = require('../../privileges'); const analytics = require('../../analytics'); @@ -20,13 +19,16 @@ Admin.updateSetting = async (req, res) => { helpers.formatApiResponse(200, res); }; -Admin.getAnalytics = async (req, res) => { - const ok = await user.isAdministrator(req.uid); +Admin.getAnalyticsKeys = async (req, res) => { + let keys = await analytics.getKeys(); - if (!ok) { - return helpers.formatApiResponse(403, res); - } + // Sort keys alphabetically + keys = keys.sort((a, b) => (a < b ? -1 : 1)); + + helpers.formatApiResponse(200, res, { keys }); +}; +Admin.getAnalyticsData = async (req, res) => { // Default returns views from past 24 hours, by hour if (!req.query.amount) { if (req.query.units === 'days') { diff --git a/src/routes/write/admin.js b/src/routes/write/admin.js index 96cb386beb..0cda6327fb 100644 --- a/src/routes/write/admin.js +++ b/src/routes/write/admin.js @@ -8,11 +8,12 @@ const routeHelpers = require('../helpers'); const { setupApiRoute } = routeHelpers; module.exports = function () { - const middlewares = [middleware.ensureLoggedIn]; + const middlewares = [middleware.ensureLoggedIn, middleware.admin.checkPrivileges]; setupApiRoute(router, 'put', '/settings/:setting', [...middlewares, middleware.checkRequired.bind(null, ['value'])], controllers.write.admin.updateSetting); - setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalytics); + setupApiRoute(router, 'get', '/analytics', [...middlewares], controllers.write.admin.getAnalyticsKeys); + setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalyticsData); return router; }; From 387f2a07eb721605691ad16d173f47990b9f3c23 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sat, 20 Nov 2021 09:06:23 +0000 Subject: [PATCH 046/849] Latest translations and fallbacks --- .../language/es/admin/manage/categories.json | 38 +++++++++---------- public/language/es/post-queue.json | 16 ++++---- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json index 977744d05e..e069264095 100644 --- a/public/language/es/admin/manage/categories.json +++ b/public/language/es/admin/manage/categories.json @@ -10,30 +10,30 @@ "custom-class": "Clase Personalizada", "num-recent-replies": "# de Respuestas Recientes", "ext-link": "Enlace Externo", - "subcategories-per-page": "Subcategories per page", + "subcategories-per-page": "Subcategorías por página", "is-section": "Tratar esta categoría como una sección", - "post-queue": "Post queue", - "tag-whitelist": "Tag Whitelist", + "post-queue": "Cola de publicación", + "tag-whitelist": "Etiquetas permitidas", "upload-image": "Subir Imagen", "delete-image": "Eliminar", "category-image": "Imagen de Categoría", "parent-category": "Categoría Superior", "optional-parent-category": "(Opcional) Categoría Superior", - "top-level": "Top Level", + "top-level": "Nivel superior", "parent-category-none": "(Ninguna)", - "copy-parent": "Copy Parent", + "copy-parent": "Copiar Padre", "copy-settings": "Copiar Configuración Desde", "optional-clone-settings": "(Opcional) Clonar Configuración De Categoría", - "clone-children": "Clone Children Categories And Settings", + "clone-children": "Clonar Categorías Hijas y Configuraciones", "purge": "Purgar Categoría", "enable": "Activar", "disable": "Desactivar", "edit": "Editar", - "analytics": "Analytics", - "view-category": "View category", - "set-order": "Set order", - "set-order-help": "Setting the order of the category will move this category to that order and update the order of other categories as necessary. Minimum order is 1 which puts the category at the top.", + "analytics": "Analítica", + "view-category": "Ver categoría", + "set-order": "Establecer orden", + "set-order-help": "Configurar el orden de las categorías moverá esta categoría a ese orden y actualizará el orden de las demás categorías tal y como sea necesario. El valor mínimo de orden es 1, esto ubicará la categoría al principio.", "select-category": "Seleccionar Categoría", "set-parent-category": "Fijar Categoría Superior", @@ -50,17 +50,17 @@ "privileges.no-users": "No hay privilegios específicos de usuario en esta categoría.", "privileges.section-group": "Grupo", "privileges.group-private": "Éste grupo es privado", - "privileges.inheritance-exception": "This group does not inherit privileges from registered-users group", - "privileges.banned-user-inheritance": "Banned users inherit privileges from banned-users group", + "privileges.inheritance-exception": "Este grupo no hereda los privilegios del grupo de usuarios registrados", + "privileges.banned-user-inheritance": "Usuarios expulsados heredan privilegios del grupo de usuarios expulsados", "privileges.search-group": "Añadir Grupo", "privileges.copy-to-children": "Copiar a categorías inferiores", "privileges.copy-from-category": "Copiar de Categoría", - "privileges.copy-privileges-to-all-categories": "Copy to All Categories", - "privileges.copy-group-privileges-to-children": "Copy this group's privileges to the children of this category.", - "privileges.copy-group-privileges-to-all-categories": "Copy this group's privileges to all categories.", - "privileges.copy-group-privileges-from": "Copy this group's privileges from another category.", + "privileges.copy-privileges-to-all-categories": "Copiar a todas las Categorías", + "privileges.copy-group-privileges-to-children": "Copiar los privilegios de este grupo a los hijos de esta categoría.", + "privileges.copy-group-privileges-to-all-categories": "Copiar los privilegios de este grupo a todas las categorías.", + "privileges.copy-group-privileges-from": "Copiar los privilegios de este grupo desde otra categoría", "privileges.inherit": "Si al grupo de los usuarios registrados se le otorga un privilegio específico, todos los otros grupos reciben un privilegio implícito , incluso si no están definidos/asignados de forma explícita. Este privilegio implícito se te muestra por que todos los usuarios son parte del grupo de usuarios usuarios registrados y, por tanto, los privilegios para grupos adicionales no deben de ser garantizados explícitamente.", - "privileges.copy-success": "Privileges copied!", + "privileges.copy-success": "Privilegios copiados!", "analytics.back": "Volver a lista de Categorías", "analytics.title": "Analíticas para \"%1\" categoría", @@ -84,9 +84,9 @@ "alert.user-search": "Buscar un usuario aquí...", "alert.find-group": "Encontrar un Grupo", "alert.group-search": "Buscar un grupo aquí...", - "alert.not-enough-whitelisted-tags": "Whitelisted tags are less than minimum tags, you need to create more whitelisted tags!", + "alert.not-enough-whitelisted-tags": "Las etiquetas permitidas son menos de las mínimas permitidas, necesitas crear más etiqutas!", "collapse-all": "Minimizar Todo", "expand-all": "Expandir Todo", "disable-on-create": "Desactivar en crear", - "no-matches": "No matches" + "no-matches": "No hay coincidencias" } \ No newline at end of file diff --git a/public/language/es/post-queue.json b/public/language/es/post-queue.json index 78e2ea1f91..e463ba3a9a 100644 --- a/public/language/es/post-queue.json +++ b/public/language/es/post-queue.json @@ -1,18 +1,18 @@ { "post-queue": "Cola de Mensajes", - "description": "There are no posts in the post queue.
    To enable this feature, go to
    Settings → Post → Post Queue and enable Post Queue.", + "description": "No hay publicaciones en cola.
    Para habilitar esta funcionalidad, ir a Ajustes/Publicar/Cola de Publicacionesy habilitar Cola de publicaciones.", "user": "Usuario", "category": "Categoría", "title": "Título", "content": "Contenido", "posted": "Publicado", "reply-to": "Responder a %1", - "content-editable": "Click on content to edit", - "category-editable": "Click on category to edit", - "title-editable": "Click on title to edit", - "reply": "Reply", - "topic": "Topic", - "accept": "Accept", - "reject": "Reject" + "content-editable": "Click en el contenido para editar", + "category-editable": "Click en la categoría para editar", + "title-editable": "Click en el título para editar", + "reply": "Responder", + "topic": "Tema", + "accept": "Aceptar", + "reject": "Rechazar" } \ No newline at end of file From 34de9608d04924545bd12bac5c2b88b941355aab Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sun, 21 Nov 2021 09:06:13 +0000 Subject: [PATCH 047/849] Latest translations and fallbacks --- public/language/sr/modules.json | 2 +- public/language/sr/notifications.json | 2 +- public/language/sr/user.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index 2478273950..d70f995259 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -23,7 +23,7 @@ "chat.three_months": "3 месеца", "chat.delete_message_confirm": "Да ли сте сигурни да желите да избришете ову поруку?", "chat.retrieving-users": "Преузимање корисника...", - "chat.manage-room": "Управљај собама за ћаскање", + "chat.manage-room": "Управљај собом за ћаскање", "chat.add-user-help": "Потражите кориснике овде. Када буде изабран, корисник ће бити додан у ћаскање. Нови корисник неће бити у могућности да види поруке написане пре него што је додан у преписку. Само власници соба () могу уклонити кориснике из соба за ћаскање.", "chat.confirm-chat-with-dnd-user": "Овај корисник је поставио свој статус на \"Не узнемиравај\". Да ли и даље желите да ћаскате са њим?", "chat.rename-room": "Преименуј собу", diff --git a/public/language/sr/notifications.json b/public/language/sr/notifications.json index e179db5ae1..becda68078 100644 --- a/public/language/sr/notifications.json +++ b/public/language/sr/notifications.json @@ -60,7 +60,7 @@ "notification_and_email": "Обавештење и е-пошта", "notificationType_upvote": "Када неко гласа за вашу поруку", "notificationType_new-topic": "Када неко кога пратите постави тему", - "notificationType_new-reply": "Када је постављен нови одговор у теми коју надгледате", + "notificationType_new-reply": "Када је објављен нови одговор у теми коју надгледате", "notificationType_post-edit": "Када је порука уређена у теми коју надгледате", "notificationType_follow": "Када неко почне да вас прати", "notificationType_new-chat": "Када примите поруку ћаскања", diff --git a/public/language/sr/user.json b/public/language/sr/user.json index 270180a713..c08e2f4674 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -94,12 +94,12 @@ "digest_off": "Искључено", "digest_daily": "Дневно", "digest_weekly": "Седмично", - "digest_biweekly": "Bi-Weekly", + "digest_biweekly": "Двоседмично", "digest_monthly": "Месечно", "has_no_follower": "Овај корисник нема пратиоце :(", "follows_no_one": "Овај корисник не прати никога :(", "has_no_posts": "Овај корисник још ништа није објавио. ", - "has_no_best_posts": "This user does not have any upvoted posts yet.", + "has_no_best_posts": "Овај корисник још увек нема ниједну поруку са позитивним гласом.", "has_no_topics": "Овај корисник још није покренуо ниједну тему.", "has_no_watched_topics": "Овај корисник још не надгледа ниједну тему.", "has_no_ignored_topics": "Овај корисник још није игнорисао ниједну тему.", From 242f8e95ad335546f26eb7183ec9a35b5a50cab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 21 Nov 2021 19:45:04 -0500 Subject: [PATCH 048/849] fix: #10020, /api/post/upload returns v3 style response --- public/src/modules/uploader.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/public/src/modules/uploader.js b/public/src/modules/uploader.js index fb50e431ba..c8d5351e7e 100644 --- a/public/src/modules/uploader.js +++ b/public/src/modules/uploader.js @@ -66,7 +66,6 @@ define('uploader', ['jquery-form'], function () { module.ajaxSubmit = function (uploadModal, callback) { const uploadForm = uploadModal.find('#uploadForm'); - const v3 = uploadForm.attr('action').startsWith(config.relative_path + '/api/v3/'); uploadForm.ajaxSubmit({ headers: { 'x-csrf-token': config.csrf_token, @@ -79,24 +78,14 @@ define('uploader', ['jquery-form'], function () { uploadModal.find('#upload-progress-bar').css('width', percent + '%'); }, success: function (response) { - response = maybeParse(response); + let images = maybeParse(response); // Appropriately handle v3 API responses - if (v3) { - if (response.status.code === 'ok') { - response = response.response.images; - } else { - response = { - error: response.status.code, - }; - } + if (response.hasOwnProperty('response') && response.hasOwnProperty('status') && response.status.code === 'ok') { + images = response.response.images; } - if (response.error) { - return showAlert(uploadModal, 'error', response.error); - } - - callback(response[0].url); + callback(images[0].url); showAlert(uploadModal, 'success', '[[uploads:upload-success]]'); setTimeout(function () { From 87433b79ef98ce01c1bfddc76673f54a5e96bc2c Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 22 Nov 2021 01:48:38 +0000 Subject: [PATCH 049/849] chore(deps): update dependency jsdom to v18.1.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fd5979b8e0..18a3c77d12 100644 --- a/install/package.json +++ b/install/package.json @@ -151,7 +151,7 @@ "grunt": "1.4.1", "grunt-contrib-watch": "1.1.0", "husky": "7.0.4", - "jsdom": "18.1.0", + "jsdom": "18.1.1", "lint-staged": "11.2.6", "mocha": "9.1.3", "mocha-lcov-reporter": "1.3.0", From 6eac500a509eeabf58798fec26b452649568d85d Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Mon, 22 Nov 2021 09:06:27 +0000 Subject: [PATCH 050/849] Latest translations and fallbacks --- public/language/it/admin/settings/email.json | 4 ++-- public/language/sr/email.json | 2 +- public/language/sr/flags.json | 10 +++++----- public/language/sr/global.json | 4 ++-- public/language/sr/notifications.json | 20 ++++++++++---------- public/language/sr/pages.json | 6 +++--- public/language/sr/topic.json | 8 ++++---- public/language/sr/user.json | 16 ++++++++-------- public/language/sr/users.json | 2 +- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/public/language/it/admin/settings/email.json b/public/language/it/admin/settings/email.json index a5e9c6a1ee..7ba3def26e 100644 --- a/public/language/it/admin/settings/email.json +++ b/public/language/it/admin/settings/email.json @@ -38,8 +38,8 @@ "subscriptions.hour-help": "Si prega di inserire un numero che rappresenta l'ora per l'invio dell'email programmate (es. 0per mezzanotte, 17per le 17: 00). Tieni presente che questa è l'ora secondo il server stesso, e potrebbe non combaciare esattamente al tuo orologio di sistema.
    L'orario approssimativo del server è:
    La prossima trasmissione giornaliera è prevista alle ", "notifications.remove-images": "Rimuovi le immagini dalle notifiche email", "require-email-address": "Richiedere ai nuovi utenti di specificare un indirizzo email", - "require-email-address-warning": "By default, users can opt-out of entering an email address by leaving the field blank. Enabling this option means they have to enter an email address in order to proceed with registration. It does not ensure user will enter a real email address, nor even an address they own.", - "send-validation-email": "Send validation emails when an email is added or changed", + "require-email-address-warning": "Per impostazione predefinita, gli utenti possono rinunciare a inserire un indirizzo email lasciando il campo vuoto. Abilitare questa opzione significa che devono inserire un indirizzo email per procedere con la registrazione. Non assicura che l'utente inserisca un indirizzo email reale, e nemmeno un indirizzo che possiede.", + "send-validation-email": "Invia email di convalida quando un'email viene aggiunta o modificata", "include-unverified-emails": "Invia email a destinatari che non hanno confermato esplicitamente le loro email", "include-unverified-warning": "Per impostazione predefinita, gli utenti con email associate al loro account sono già stati verificati, ma ci sono situazioni in cui ciò non è vero (ad esempio accessi SSO, vecchi utenti, ecc.). Abilita questa impostazione a tuo rischio e pericolo – l'invio di email a indirizzi non verificati può essere una violazione delle leggi regionali anti-spam.", "prompt": "Chiedi agli utenti di inserire o confermare le loro email", diff --git a/public/language/sr/email.json b/public/language/sr/email.json index 41aadc9d6a..d3aaa84161 100644 --- a/public/language/sr/email.json +++ b/public/language/sr/email.json @@ -38,7 +38,7 @@ "notif.chat.cta": "Кликните овде да наставите са разговором", "notif.chat.unsub.info": "Ова обавештење о ћаскању вам је послато услед вашег подешавања претплате.", "notif.post.unsub.info": "Ово обавештење вам је послато услед вашег подешавања претплате.", - "notif.post.unsub.one-click": "Алтернативно, одјавите претплату на будуће поруке е-поште, кликом на", + "notif.post.unsub.one-click": "Алтернативно, откажите будуће овакву е-пошту, кликом на", "notif.cta": "Ка форуму", "notif.cta-new-reply": "Погледајте поруку", "notif.cta-new-chat": "Погледајте ћаскање", diff --git a/public/language/sr/flags.json b/public/language/sr/flags.json index fbe3c7e5e4..821bdb4754 100644 --- a/public/language/sr/flags.json +++ b/public/language/sr/flags.json @@ -15,7 +15,7 @@ "filter-reset": "Уклони заставице", "filters": "Опције филтера", "filter-reporterId": "UID извештача", - "filter-targetUid": "UID означеног", + "filter-targetUid": "UID означеног заставицом", "filter-type": "Тип заставице", "filter-type-all": "Сав садржај", "filter-type-post": "Порука", @@ -30,10 +30,10 @@ "fewer-filters": "Мање филтера", "quick-actions": "Брзе радње", - "flagged-user": "Означени корисник", + "flagged-user": "Корисник означен заставицом", "view-profile": "Погледај профил", "start-new-chat": "Започни ново ћаскање", - "go-to-target": "Погледај циљ означавања", + "go-to-target": "Погледај циљ означавања заставицом", "assign-to-me": "Додели мени", "delete-post": "Избриши поруку", "purge-post": "Очисти поруку", @@ -70,13 +70,13 @@ "sort-replies": "Највише одговора", "modal-title": "Извештај о садржају", - "modal-body": "Наведите разлог за означавање %1 %2 за проверу. Алтернативно, користите један од тастера за брзу пријаву ко је применљиво.", + "modal-body": "Наведите разлог за означавање заставицом %1 %2. Алтернативно, користите један од тастера за брзу пријаву ако је примењиво.", "modal-reason-spam": "Непожељно", "modal-reason-offensive": "Увредљиво", "modal-reason-other": "Остало (наведите испод)", "modal-reason-custom": "Разлог за пријаву овог садржаја...", "modal-submit": "Пошаљи извештај", - "modal-submit-success": "Садржај је означен за модерацију.", + "modal-submit-success": "Садржај је означен заставицом за модерацију.", "bulk-actions": "Масовне радње", "bulk-resolve": "Реши заставицу/е", diff --git a/public/language/sr/global.json b/public/language/sr/global.json index 636d636c39..57f8c3dcd0 100644 --- a/public/language/sr/global.json +++ b/public/language/sr/global.json @@ -59,8 +59,8 @@ "votes": "Гласови", "x-votes": "%1 гласа/ова", "voters": "Гласачи", - "upvoters": "Позитивно гласали", - "upvoted": "Позитивно гласано", + "upvoters": "Гласали", + "upvoted": "Гласано", "downvoters": "Негативно гласали", "downvoted": "Негативно гласано", "views": "Прегледи", diff --git a/public/language/sr/notifications.json b/public/language/sr/notifications.json index becda68078..3a3e929f5b 100644 --- a/public/language/sr/notifications.json +++ b/public/language/sr/notifications.json @@ -16,22 +16,22 @@ "chat": "Ћаскања", "group-chat": "Групна ћаскања", "follows": "Праћења", - "upvote": "Позитивни гласови", + "upvote": "Гласови", "new-flags": "Нове заставице", "my-flags": "Заставице додељене мени", "bans": "Забране", "new_message_from": "Нова порука од %1", - "upvoted_your_post_in": "%1 је додао глас вашој поруци у %2", + "upvoted_your_post_in": "%1 је гласао за вашу поруку у %2", "upvoted_your_post_in_dual": "%1 и %2 осталих су гласали за вашу поруку у %3.", "upvoted_your_post_in_multiple": "%1 и %2 осталих су гласали за вашу поруку у %3.", "moved_your_post": "%1 је померио вашу поруку у %2", "moved_your_topic": "%1 је померио %2", - "user_flagged_post_in": "%1 је означио поруку у %2", - "user_flagged_post_in_dual": "%1 и %2 су означили поруку у %3", - "user_flagged_post_in_multiple": "%1 и осталих %2 су означили поруку у %3", - "user_flagged_user": "%1 је означио кориснички профил (%2)", - "user_flagged_user_dual": "%1 и %2 су означили кориснички профил (%3)", - "user_flagged_user_multiple": "%1 и %2 осталих су означили кориснички профил (%3)", + "user_flagged_post_in": "%1 је означио заставицом поруку у %2", + "user_flagged_post_in_dual": "%1 и %2 су означили заставицом поруку у %3", + "user_flagged_post_in_multiple": "%1 и осталих %2 су означили заставицом поруку у %3", + "user_flagged_user": "%1 је означио заставицом кориснички профил (%2)", + "user_flagged_user_dual": "%1 и %2 су означили заставицом кориснички профил (%3)", + "user_flagged_user_multiple": "%1 и %2 осталих су означили заставицом кориснички профил (%3)", "user_posted_to": "%1 је послао нови одговор на: %2", "user_posted_to_dual": "%1 и %2 су одговорили на: %3", "user_posted_to_multiple": "%1 и %2 других су одговорили на: %3", @@ -70,6 +70,6 @@ "notificationType_group-request-membership": "Када неко затражи да се придружи групи коју поседујете", "notificationType_new-register": "Када је неко додат на чекање за регистрацију", "notificationType_post-queue": "Када је нова порука на чекању", - "notificationType_new-post-flag": "Када је порука означена", - "notificationType_new-user-flag": "Када је корисник означен" + "notificationType_new-post-flag": "Када је порука означена заставицом", + "notificationType_new-user-flag": "Када је корисник означен заставицом" } \ No newline at end of file diff --git a/public/language/sr/pages.json b/public/language/sr/pages.json index 7b38ccf2bb..3d1dccba63 100644 --- a/public/language/sr/pages.json +++ b/public/language/sr/pages.json @@ -19,7 +19,7 @@ "users/sort-posts": "Корисници са највише порука", "users/sort-reputation": "Корисници са највећим угледом", "users/banned": "Забрањени корисници", - "users/most-flags": "Најчешће означени корисници", + "users/most-flags": "Корисници најчешће означени заставицом", "users/search": "Претрага корисника", "notifications": "Обавештења", "tags": "Ознаке", @@ -34,7 +34,7 @@ "chats": "Ћаскања", "chat": "Ћаскање са %1", "flags": "Заставице", - "flag-details": "Означи %1 детаље", + "flag-details": "Означи заставицом %1 детаље", "account/edit": "Уређивање \"%1\"", "account/edit/password": "Уређивање лозинке од \"%1\"", "account/edit/username": "Уређивање корисничког имена од \"%1\"", @@ -51,7 +51,7 @@ "account/settings": "Корисничка подешавања", "account/watched": "Теме које надгледа %1", "account/ignored": "Теме које игнорише %1", - "account/upvoted": "Поруке које је позитивно гласао %1", + "account/upvoted": "Поруке које је гласао %1", "account/downvoted": "Поруке које је негативно гласао %1", "account/best": "Најбоље поруке од %1", "account/blocks": "Корисници које је блокирао %1", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index c95851bc0d..8bcc0c8fb6 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -49,9 +49,9 @@ "queued-by": "Порука је на чекању за одобрење →", "backlink": "Referenced by", "bookmark_instructions": "Кликните овде за повратак на последњу прочитану поруку у овој теми.", - "flag-post": "Обележи заставицом поруку", - "flag-user": "Обележи заставицом корисника", - "already-flagged": "Већ је обележено заставицом", + "flag-post": "Означи поруку заставицом", + "flag-user": "Означи корисника заставицом", + "already-flagged": "Већ је означено заставицом", "view-flag-report": "Погледај извештај о заставици", "resolve-flag": "Реши заставицу", "merged_message": "Ова тема је обједињена у %2", @@ -59,7 +59,7 @@ "following_topic.message": "Од сада ће те примати обавештења када неко одговори у овој теми.", "not_following_topic.message": "Видећете ову тему у списку непрочитаних тема али нећете примати обавештења када неко одговори у њој.", "ignoring_topic.message": "Више нећете видети ову тему у списку непрочитаних тема. Бићете обавештени када вас неко спомене или када неко гласа за вашу поруку.", - "login_to_subscribe": "Региструјте се или се пријавите да бисте се претплатили на ову тему.", + "login_to_subscribe": "Региструјте се или се пријавите за праћење ове теме.", "markAsUnreadForAll.success": "Тема је свима означена као непрочитана.", "mark_unread": "Означи као непрочитано", "mark_unread.success": "Тема је означена као непрочитана", diff --git a/public/language/sr/user.json b/public/language/sr/user.json index c08e2f4674..4cf2f25e87 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -36,7 +36,7 @@ "change_all": "Промени све", "watched": "Надгледано", "ignored": "Игнорисано", - "default-category-watch-state": "Стање надгледања подразумеване категорије", + "default-category-watch-state": "Подразумевано стање надгледања категорија", "followers": "Пратиоци", "following": "Праћења", "blocks": "Блокирања", @@ -49,7 +49,7 @@ "chat": "Ђаскање", "chat_with": "Ћаскај са %1", "new_chat_with": "Започни ново ћаскање са %1", - "flag-profile": "Означи профил", + "flag-profile": "Означи профил заставицом", "follow": "Прати", "unfollow": "Не прати", "more": "Више", @@ -89,8 +89,8 @@ "show_email": "Прикажи моју лозинку", "show_fullname": "Прикажи моје пуно име", "restrict_chats": "Дозволи поруке ћаскања само од корисника које пратим", - "digest_label": "Претплата на сажетак", - "digest_description": "Претплати се на ажурирања за овај форум (нова обавештења и теме) према планираном распореду", + "digest_label": "Пријава за сажетак", + "digest_description": "Пријавите се за праћење ажурирања форума (нова обавештења и теме) путем е-поште према одређеном распореду", "digest_off": "Искључено", "digest_daily": "Дневно", "digest_weekly": "Седмично", @@ -99,11 +99,11 @@ "has_no_follower": "Овај корисник нема пратиоце :(", "follows_no_one": "Овај корисник не прати никога :(", "has_no_posts": "Овај корисник још ништа није објавио. ", - "has_no_best_posts": "Овај корисник још увек нема ниједну поруку са позитивним гласом.", + "has_no_best_posts": "Овај корисник још увек нема ниједну поруку за коју се гласало.", "has_no_topics": "Овај корисник још није покренуо ниједну тему.", "has_no_watched_topics": "Овај корисник још не надгледа ниједну тему.", "has_no_ignored_topics": "Овај корисник још није игнорисао ниједну тему.", - "has_no_upvoted_posts": "Овај корисник још увек није позитивно гласао за неку поруку.", + "has_no_upvoted_posts": "Овај корисник још увек није гласао за неку поруку.", "has_no_downvoted_posts": "Овај корисник још увек није негативно гласао за неку поруку.", "has_no_voted_posts": "Овај корисник нема објаве за које се гласало.", "has_no_blocks": "Нисте блокирали ниједног корисника", @@ -145,8 +145,8 @@ "sso.dissociate": "Одвоји", "sso.dissociate-confirm-title": "Потврди одвајање", "sso.dissociate-confirm": "Да ли сте сигурни да желите да одвојите овај налог од %1?", - "info.latest-flags": "Најновије ознаке", - "info.no-flags": "Нема пронађених означених порука", + "info.latest-flags": "Најновији означени заставицом", + "info.no-flags": "Нема пронађених порука означених заставицом", "info.ban-history": "Историја недавно забрањених налога", "info.no-ban-history": "Овај корисник никада није био забрањен", "info.banned-until": "Забрањен до %1", diff --git a/public/language/sr/users.json b/public/language/sr/users.json index 99dc6278c6..14cafa137b 100644 --- a/public/language/sr/users.json +++ b/public/language/sr/users.json @@ -2,7 +2,7 @@ "latest_users": "Најновији корисници", "top_posters": "Највише порука", "most_reputation": "Највећи углед", - "most_flags": "Највише ознака", + "most_flags": "Најчешће означени заставицом", "search": "Претрага", "enter_username": "Унесите корисничко име за претрагу", "load_more": "Учитај више", From a10ea03c3a4effde1687ef842d486bc97cd1136d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Nov 2021 11:14:29 -0500 Subject: [PATCH 051/849] fix: #10023, bump persona --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 18a3c77d12..c3e14ddd2d 100644 --- a/install/package.json +++ b/install/package.json @@ -93,7 +93,7 @@ "nodebb-plugin-spam-be-gone": "0.7.11", "nodebb-rewards-essentials": "0.2.0", "nodebb-theme-lavender": "5.3.1", - "nodebb-theme-persona": "11.2.22", + "nodebb-theme-persona": "11.3.0", "nodebb-theme-slick": "1.4.16", "nodebb-theme-vanilla": "12.1.10", "nodebb-widget-essentials": "5.0.4", @@ -182,4 +182,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} From fdae69911b5e0b7b5b48f4ca36620df20da41b30 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 22 Nov 2021 18:46:13 +0000 Subject: [PATCH 052/849] fix(deps): update dependency nodebb-theme-persona to v11.3.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index c3e14ddd2d..2d2ed9ff43 100644 --- a/install/package.json +++ b/install/package.json @@ -93,7 +93,7 @@ "nodebb-plugin-spam-be-gone": "0.7.11", "nodebb-rewards-essentials": "0.2.0", "nodebb-theme-lavender": "5.3.1", - "nodebb-theme-persona": "11.3.0", + "nodebb-theme-persona": "11.3.1", "nodebb-theme-slick": "1.4.16", "nodebb-theme-vanilla": "12.1.10", "nodebb-widget-essentials": "5.0.4", From f0d192fbfdc9a5e9c2c9581f2ce9673a51ec97c1 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 22 Nov 2021 15:37:32 -0500 Subject: [PATCH 053/849] feat: autocomplete for activate/reset useless features:tm: --- src/cli/manage.js | 3 +++ src/cli/reset.js | 2 ++ src/plugins/install.js | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cli/manage.js b/src/cli/manage.js index 027c808798..326fc22c93 100644 --- a/src/cli/manage.js +++ b/src/cli/manage.js @@ -25,6 +25,9 @@ async function activate(plugin) { // Allow omission of `nodebb-plugin-` plugin = `nodebb-plugin-${plugin}`; } + + plugin = await plugins.autocomplete(plugin); + const isInstalled = await plugins.isInstalled(plugin); if (!isInstalled) { throw new Error('plugin not installed'); diff --git a/src/cli/reset.js b/src/cli/reset.js index 7d4c7e9e2a..75daeeb153 100644 --- a/src/cli/reset.js +++ b/src/cli/reset.js @@ -25,6 +25,7 @@ exports.reset = async function (options) { themeId = `nodebb-theme-${themeId}`; } + themeId = await plugins.autocomplete(themeId); await resetTheme(themeId); } }, @@ -38,6 +39,7 @@ exports.reset = async function (options) { pluginId = `nodebb-plugin-${pluginId}`; } + pluginId = await plugins.autocomplete(pluginId); await resetPlugin(pluginId); } }, diff --git a/src/plugins/install.js b/src/plugins/install.js index 8c0f6c644a..dcfd7a8c16 100644 --- a/src/plugins/install.js +++ b/src/plugins/install.js @@ -2,7 +2,7 @@ const winston = require('winston'); const path = require('path'); -const fs = require('fs'); +const fs = require('fs').promises; const nconf = require('nconf'); const os = require('os'); const cproc = require('child_process'); @@ -137,7 +137,7 @@ module.exports = function (Plugins) { Plugins.isInstalled = async function (id) { const pluginDir = path.join(paths.nodeModules, id); try { - const stats = await fs.promises.stat(pluginDir); + const stats = await fs.stat(pluginDir); return stats.isDirectory(); } catch (err) { return false; @@ -151,4 +151,12 @@ module.exports = function (Plugins) { Plugins.getActive = async function () { return await db.getSortedSetRange('plugins:active', 0, -1); }; + + Plugins.autocomplete = async (fragment) => { + const pluginDir = paths.nodeModules; + const plugins = (await fs.readdir(pluginDir)).filter(filename => filename.startsWith(fragment)); + + // Autocomplete only if single match + return plugins.length === 1 ? plugins.pop() : fragment; + }; }; From c5f08fdc814598c1f8d0956a803f990149c53bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 19:23:51 -0500 Subject: [PATCH 054/849] breaking: remove socket.io/flags.js refactor: helpers.loginUser returns a single object {jar, csrf_token} --- src/routes/write/flags.js | 1 - src/socket.io/flags.js | 52 ------------------ src/socket.io/index.js | 7 +-- test/api.js | 4 +- test/authentication.js | 6 +-- test/controllers-admin.js | 111 ++++++++++++++++++++++---------------- test/controllers.js | 73 ++++++++----------------- test/feeds.js | 8 +-- test/flags.js | 65 +++++++++------------- test/helpers/index.js | 14 ++++- test/messaging.js | 12 ++--- test/posts.js | 10 ++-- test/topics.js | 5 +- test/topics/thumbs.js | 24 +++------ test/uploads.js | 49 +++++------------ test/user.js | 34 +++++------- 16 files changed, 175 insertions(+), 300 deletions(-) delete mode 100644 src/socket.io/flags.js diff --git a/src/routes/write/flags.js b/src/routes/write/flags.js index c713b24dfa..a718f8f3f0 100644 --- a/src/routes/write/flags.js +++ b/src/routes/write/flags.js @@ -11,7 +11,6 @@ module.exports = function () { const middlewares = [middleware.ensureLoggedIn]; setupApiRoute(router, 'post', '/', [...middlewares], controllers.write.flags.create); - // setupApiRoute(router, 'delete', ...); // does not exist setupApiRoute(router, 'get', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.get); setupApiRoute(router, 'put', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.update); diff --git a/src/socket.io/flags.js b/src/socket.io/flags.js deleted file mode 100644 index 7992aa8541..0000000000 --- a/src/socket.io/flags.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const sockets = require('.'); -const api = require('../api'); - -const SocketFlags = module.exports; - -SocketFlags.create = async function (socket, data) { - sockets.warnDeprecated(socket, 'POST /api/v3/flags'); - const response = await api.flags.create(socket, data); - if (response) { - return response.flagId; - } -}; - -SocketFlags.update = async function (socket, data) { - sockets.warnDeprecated(socket, 'PUT /api/v3/flags/:flagId'); - if (!data || !(data.flagId && data.data)) { // check only req'd in socket.io - throw new Error('[[error:invalid-data]]'); - } - - // Old socket method took input directly from .serializeArray(), v3 expects fully-formed obj. - let payload = { - flagId: data.flagId, - }; - payload = data.data.reduce((memo, cur) => { - memo[cur.name] = cur.value; - return memo; - }, payload); - - return await api.flags.update(socket, payload); -}; - -SocketFlags.appendNote = async function (socket, data) { - sockets.warnDeprecated(socket, 'POST /api/v3/flags/:flagId/notes'); - if (!data || !(data.flagId && data.note)) { - throw new Error('[[error:invalid-data]]'); - } - - return await api.flags.appendNote(socket, data); -}; - -SocketFlags.deleteNote = async function (socket, data) { - sockets.warnDeprecated(socket, 'DELETE /api/v3/flags/:flagId/notes/:datetime'); - if (!data || !(data.flagId && data.datetime)) { - throw new Error('[[error:invalid-data]]'); - } - - return await api.flags.deleteNote(socket, data); -}; - -require('../promisify')(SocketFlags); diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 4dd74db26f..6d07cfa991 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -171,9 +171,10 @@ async function onMessage(socket, payload) { } function requireModules() { - const modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', - 'flags', 'uploads', + const modules = [ + 'admin', 'categories', 'groups', 'meta', 'modules', + 'notifications', 'plugins', 'posts', 'topics', 'user', + 'blacklist', 'uploads', ]; modules.forEach((module) => { diff --git a/test/api.js b/test/api.js index f855e157aa..114f9ccfd0 100644 --- a/test/api.js +++ b/test/api.js @@ -208,7 +208,7 @@ describe('API', async () => { }); // All tests run as admin user - jar = await helpers.loginUser('admin', '123456'); + ({ jar } = await helpers.loginUser('admin', '123456')); // Retrieve CSRF token using cookie, to test Write API const config = await request({ @@ -457,7 +457,7 @@ describe('API', async () => { it('should successfully re-login if needed', async () => { const reloginPaths = ['PUT /users/{uid}/password', 'DELETE /users/{uid}/sessions/{uuid}']; if (reloginPaths.includes(`${method.toUpperCase()} ${path}`)) { - jar = await helpers.loginUser('admin', '123456'); + ({ jar } = await helpers.loginUser('admin', '123456')); const sessionUUIDs = await db.getObject('uid:1:sessionUUID:sessionId'); mocks.delete['/users/{uid}/sessions/{uuid}'][1].example = Object.keys(sessionUUIDs).pop(); diff --git a/test/authentication.js b/test/authentication.js index 87d3b9f186..b65c95f2dd 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -187,14 +187,12 @@ describe('authentication', () => { }); it('should regenerate the session identifier on successful login', async () => { - const login = util.promisify(helpers.loginUser); - const logout = util.promisify(helpers.logoutUser); const matchRegexp = /express\.sid=s%3A(.+?);/; const { hostname, path } = url.parse(nconf.get('url')); const sid = String(jar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1]; - await logout(jar); - const newJar = await login('regular', 'regularpwd'); + await helpers.logoutUser(jar); + const newJar = (await helpers.loginUser('regular', 'regularpwd')).jar; const newSid = String(newJar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1]; assert.notStrictEqual(newSid, sid); diff --git a/test/controllers-admin.js b/test/controllers-admin.js index 467652d88a..beb81ccc4c 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -36,7 +36,7 @@ describe('Admin Controllers', () => { user.create({ username: 'admin', password: 'barbar' }, next); }, regularUid: function (next) { - user.create({ username: 'regular' }, next); + user.create({ username: 'regular', password: 'regularpwd' }, next); }, regular2Uid: function (next) { user.create({ username: 'regular2' }, next); @@ -66,9 +66,9 @@ describe('Admin Controllers', () => { }); it('should 403 if user is not admin', (done) => { - helpers.loginUser('admin', 'barbar', (err, _jar) => { + helpers.loginUser('admin', 'barbar', (err, data) => { assert.ifError(err); - jar = _jar; + jar = data.jar; request(`${nconf.get('url')}/admin`, { jar: jar }, (err, res, body) => { assert.ifError(err); assert.equal(res.statusCode, 403); @@ -602,14 +602,11 @@ describe('Admin Controllers', () => { describe('mods page', () => { let moderatorJar; - - before((done) => { - helpers.loginUser('moderator', 'modmod', (err, _jar) => { - assert.ifError(err); - moderatorJar = _jar; - - groups.join(`cid:${cid}:privileges:moderate`, moderatorUid, done); - }); + let regularJar; + before(async () => { + moderatorJar = (await helpers.loginUser('moderator', 'modmod')).jar; + regularJar = (await helpers.loginUser('regular', 'regularpwd')).jar; + await groups.join(`cid:${cid}:privileges:moderate`, moderatorUid); }); it('should error with no privileges', (done) => { @@ -652,42 +649,69 @@ describe('Admin Controllers', () => { }); it('should error when you attempt to flag a privileged user\'s post', async () => { - const socketFlags = require('../src/socket.io/flags'); - const oldValue = meta.config['min:rep:flag']; - try { - await socketFlags.create({ uid: regularUid }, { id: pid, type: 'post', reason: 'spam' }); - } catch (err) { - assert.strictEqual(err.message, '[[error:cant-flag-privileged]]'); - } + const { res, body } = await helpers.request('post', '/api/v3/flags', { + json: true, + jar: regularJar, + form: { + id: pid, + type: 'post', + reason: 'spam', + }, + }); + assert.strictEqual(res.statusCode, 400); + assert.strictEqual(body.status.code, 'bad-request'); + assert.strictEqual(body.status.message, 'You are not allowed to flag the profiles or content of privileged users (moderators/global moderators/admins)'); }); - it('should error with not enough reputation to flag', (done) => { - const socketFlags = require('../src/socket.io/flags'); + it('should error with not enough reputation to flag', async () => { const oldValue = meta.config['min:rep:flag']; meta.config['min:rep:flag'] = 1000; - socketFlags.create({ uid: regularUid }, { id: regularPid, type: 'post', reason: 'spam' }, (err) => { - assert.strictEqual(err.message, '[[error:not-enough-reputation-to-flag]]'); - meta.config['min:rep:flag'] = oldValue; - done(); + const { res, body } = await helpers.request('post', '/api/v3/flags', { + json: true, + jar: regularJar, + form: { + id: regularPid, + type: 'post', + reason: 'spam', + }, }); + assert.strictEqual(res.statusCode, 400); + assert.strictEqual(body.status.code, 'bad-request'); + assert.strictEqual(body.status.message, 'You do not have enough reputation to flag this post'); + + meta.config['min:rep:flag'] = oldValue; }); - it('should return flag details', (done) => { - const socketFlags = require('../src/socket.io/flags'); + it('should return flag details', async () => { const oldValue = meta.config['min:rep:flag']; meta.config['min:rep:flag'] = 0; - socketFlags.create({ uid: regularUid }, { id: regularPid, type: 'post', reason: 'spam' }, (err, flagId) => { - meta.config['min:rep:flag'] = oldValue; - assert.ifError(err); - request(`${nconf.get('url')}/api/flags/${flagId}`, { jar: moderatorJar, json: true }, (err, res, body) => { - assert.ifError(err); - assert(body); - assert(body.reports); - assert(Array.isArray(body.reports)); - assert.strictEqual(body.reports[0].reporter.username, 'regular'); - done(); - }); + const result = await helpers.request('post', '/api/v3/flags', { + json: true, + jar: regularJar, + form: { + id: regularPid, + type: 'post', + reason: 'spam', + }, }); + meta.config['min:rep:flag'] = oldValue; + + const flagsResult = await helpers.request('get', `/api/flags`, { + json: true, + jar: moderatorJar, + }); + + assert(flagsResult.body); + assert(Array.isArray(flagsResult.body.flags)); + const { flagId } = flagsResult.body.flags[0]; + + const { body } = await helpers.request('get', `/api/flags/${flagId}`, { + json: true, + jar: moderatorJar, + }); + assert(body.reports); + assert(Array.isArray(body.reports)); + assert.strictEqual(body.reports[0].reporter.username, 'regular'); }); }); @@ -724,16 +748,9 @@ describe('Admin Controllers', () => { let userJar; let uid; const privileges = require('../src/privileges'); - before((done) => { - user.create({ username: 'regularjoe', password: 'barbar' }, (err, _uid) => { - assert.ifError(err); - uid = _uid; - helpers.loginUser('regularjoe', 'barbar', (err, _jar) => { - assert.ifError(err); - userJar = _jar; - done(); - }); - }); + before(async () => { + uid = await user.create({ username: 'regularjoe', password: 'barbar' }); + userJar = (await helpers.loginUser('regularjoe', 'barbar')).jar; }); it('should allow normal user access to admin pages', async function () { diff --git a/test/controllers.js b/test/controllers.js index 4ac33196d5..e3d0dda050 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -853,17 +853,11 @@ describe('Controllers', () => { let jar; let csrf_token; - before((done) => { - user.create({ username: 'revokeme', password: 'barbar' }, (err, _uid) => { - assert.ifError(err); - uid = _uid; - helpers.loginUser('revokeme', 'barbar', (err, _jar, _csrf_token) => { - assert.ifError(err); - jar = _jar; - csrf_token = _csrf_token; - done(); - }); - }); + before(async () => { + uid = await user.create({ username: 'revokeme', password: 'barbar' }); + const login = await helpers.loginUser('revokeme', 'barbar'); + jar = login.jar; + csrf_token = login.csrf_token; }); it('should fail to revoke session with missing uuid', (done) => { @@ -1081,12 +1075,8 @@ describe('Controllers', () => { describe('account pages', () => { let jar; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('foo', 'barbar')); }); it('should redirect to account page with logged in user', (done) => { @@ -1449,8 +1439,9 @@ describe('Controllers', () => { it('should return false if user can not edit user', (done) => { user.create({ username: 'regularJoe', password: 'barbar' }, (err) => { assert.ifError(err); - helpers.loginUser('regularJoe', 'barbar', (err, jar) => { + helpers.loginUser('regularJoe', 'barbar', (err, data) => { assert.ifError(err); + const { jar } = data; request(`${nconf.get('url')}/api/user/foo/info`, { jar: jar, json: true }, (err, res) => { assert.ifError(err); assert.equal(res.statusCode, 403); @@ -1518,8 +1509,9 @@ describe('Controllers', () => { }); it('should increase profile view', (done) => { - helpers.loginUser('regularJoe', 'barbar', (err, jar) => { + helpers.loginUser('regularJoe', 'barbar', (err, data) => { assert.ifError(err); + const { jar } = data; request(`${nconf.get('url')}/api/user/foo`, { jar: jar }, (err, res) => { assert.ifError(err); assert.equal(res.statusCode, 200); @@ -1706,12 +1698,8 @@ describe('Controllers', () => { describe('post redirect', () => { let jar; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('foo', 'barbar')); }); it('should 404 for invalid pid', (done) => { @@ -1966,12 +1954,8 @@ describe('Controllers', () => { describe('category', () => { let jar; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('foo', 'barbar')); }); it('should return 404 if cid is not a number', (done) => { @@ -2238,12 +2222,8 @@ describe('Controllers', () => { describe('unread', () => { let jar; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('foo', 'barbar')); }); it('should load unread page', (done) => { @@ -2305,21 +2285,10 @@ describe('Controllers', () => { let csrf_token; let jar; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - - request({ - url: `${nconf.get('url')}/api/config`, - json: true, - jar: jar, - }, (err, response, body) => { - assert.ifError(err); - csrf_token = body.csrf_token; - done(); - }); - }); + before(async () => { + const login = await helpers.loginUser('foo', 'barbar'); + jar = login.jar; + csrf_token = login.csrf_token; }); it('should load the composer route', (done) => { diff --git a/test/feeds.js b/test/feeds.js index 3e3780b210..6f1c9d0b08 100644 --- a/test/feeds.js +++ b/test/feeds.js @@ -127,12 +127,8 @@ describe('feeds', () => { describe('private feeds and tokens', () => { let jar; let rssToken; - before((done) => { - helpers.loginUser('foo', 'barbar', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('foo', 'barbar')); }); it('should load feed if its not private', (done) => { diff --git a/test/flags.js b/test/flags.js index d812a9d939..9e07974bc4 100644 --- a/test/flags.js +++ b/test/flags.js @@ -451,20 +451,20 @@ describe('Flags', () => { }); it('should rescind notification if flag is resolved', async () => { - const SocketFlags = require('../src/socket.io/flags'); + const flagsAPI = require('../src/api/flags'); const result = await Topics.post({ cid: category.cid, uid: uid3, title: 'Topic to flag', content: 'This is flaggable content', }); - const flagId = await SocketFlags.create({ uid: uid1 }, { type: 'post', id: result.postData.pid, reason: 'spam' }); + const flagObj = await flagsAPI.create({ uid: uid1 }, { type: 'post', id: result.postData.pid, reason: 'spam' }); await sleep(2000); let userNotifs = await User.notifications.getAll(adminUid); assert(userNotifs.includes(`flag:post:${result.postData.pid}`)); - await Flags.update(flagId, adminUid, { + await Flags.update(flagObj.flagId, adminUid, { state: 'resolved', }); @@ -554,34 +554,22 @@ describe('Flags', () => { }); }); - it('should not error if user blocked target', (done) => { - const SocketFlags = require('../src/socket.io/flags'); - let reporterUid; - let reporteeUid; - async.waterfall([ - function (next) { - User.create({ username: 'reporter' }, next); - }, - function (uid, next) { - reporterUid = uid; - User.create({ username: 'reportee' }, next); - }, - function (uid, next) { - reporteeUid = uid; - User.blocks.add(reporteeUid, reporterUid, next); - }, - function (next) { - Topics.post({ - cid: 1, - uid: reporteeUid, - title: 'Another topic', - content: 'This is flaggable content', - }, next); - }, - function (data, next) { - SocketFlags.create({ uid: reporterUid }, { type: 'post', id: data.postData.pid, reason: 'spam' }, next); - }, - ], done); + it('should not error if user blocked target', async () => { + const apiFlags = require('../src/api/flags'); + const reporterUid = await User.create({ username: 'reporter' }); + const reporteeUid = await User.create({ username: 'reportee' }); + await User.blocks.add(reporteeUid, reporterUid); + const data = await Topics.post({ + cid: 1, + uid: reporteeUid, + title: 'Another topic', + content: 'This is flaggable content', + }); + await apiFlags.create({ uid: reporterUid }, { + type: 'post', + id: data.postData.pid, + reason: 'spam', + }); }); it('should send back error if reporter does not exist', (done) => { @@ -704,20 +692,14 @@ describe('Flags', () => { }); describe('(v3 API)', () => { - const SocketFlags = require('../src/socket.io/flags'); let pid; let tid; let jar; let csrfToken; before(async () => { - const login = util.promisify(helpers.loginUser); - jar = await login('testUser2', 'abcdef'); - const config = await request({ - url: `${nconf.get('url')}/api/config`, - json: true, - jar: jar, - }); - csrfToken = config.csrf_token; + const login = await helpers.loginUser('testUser2', 'abcdef'); + jar = login.jar; + csrfToken = login.csrf_token; const result = await Topics.post({ cid: 1, @@ -787,7 +769,8 @@ describe('Flags', () => { title: 'private topic', content: 'private post', }); - const jar3 = await util.promisify(helpers.loginUser)('unprivileged', 'abcdef'); + const login = await helpers.loginUser('unprivileged', 'abcdef'); + const jar3 = login.jar; const config = await request({ url: `${nconf.get('url')}/api/config`, json: true, diff --git a/test/helpers/index.js b/test/helpers/index.js index 79a0e9e41e..800e3b7d0b 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -20,6 +20,18 @@ helpers.getCsrfToken = async (jar) => { return token; }; +helpers.request = async function (method, uri, options) { + const csrf_token = await helpers.getCsrfToken(options.jar); + return new Promise((resolve, reject) => { + options.headers = options.headers || {}; + options.headers['x-csrf-token'] = csrf_token; + request[method](`${nconf.get('url')}${uri}`, options, (err, res, body) => { + if (err) reject(err); + else resolve({ res, body }); + }); + }); +}; + helpers.loginUser = function (username, password, callback) { const jar = request.jar(); @@ -46,7 +58,7 @@ helpers.loginUser = function (username, password, callback) { if (err || res.statusCode !== 200) { return callback(err || new Error('[[error:invalid-response]]')); } - callback(null, jar, body.csrf_token); + callback(null, { jar, csrf_token: body.csrf_token }); }); }); }; diff --git a/test/messaging.js b/test/messaging.js index 83665b1dc0..7cabd94cb2 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -793,12 +793,8 @@ describe('Messaging Library', () => { describe('logged in chat controller', () => { let jar; - before((done) => { - helpers.loginUser('herp', 'derpderp', (err, _jar) => { - assert.ifError(err); - jar = _jar; - done(); - }); + before(async () => { + ({ jar } = await helpers.loginUser('herp', 'derpderp')); }); it('should return chats page data', (done) => { @@ -833,9 +829,9 @@ describe('Messaging Library', () => { }); it('should return 404 if user is not in room', (done) => { - helpers.loginUser('baz', 'quuxquux', (err, jar) => { + helpers.loginUser('baz', 'quuxquux', (err, data) => { assert.ifError(err); - request(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, { json: true, jar: jar }, (err, response) => { + request(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, { json: true, jar: data.jar }, (err, response) => { assert.ifError(err); assert.equal(response.statusCode, 404); done(); diff --git a/test/posts.js b/test/posts.js index 71dfa74a74..268082ffb0 100644 --- a/test/posts.js +++ b/test/posts.js @@ -390,11 +390,9 @@ describe('Post\'s', () => { privileges.categories.rescind(['groups:posts:view_deleted'], cid, 'Global Moderators', next); }, function (next) { - helpers.loginUser('global mod', '123456', (err, _jar) => { + helpers.loginUser('global mod', '123456', (err, data) => { assert.ifError(err); - const jar = _jar; - - request(`${nconf.get('url')}/api/topic/${tid}`, { jar: jar, json: true }, (err, res, body) => { + request(`${nconf.get('url')}/api/topic/${tid}`, { jar: data.jar, json: true }, (err, res, body) => { assert.ifError(err); assert.equal(body.posts[1].content, '[[topic:post_is_deleted]]'); privileges.categories.give(['groups:posts:view_deleted'], cid, 'Global Moderators', next); @@ -1050,8 +1048,8 @@ describe('Post\'s', () => { }); it('should load queued posts', (done) => { - helpers.loginUser('globalmod', 'globalmodpwd', (err, _jar) => { - jar = _jar; + helpers.loginUser('globalmod', 'globalmodpwd', (err, data) => { + jar = data.jar; assert.ifError(err); request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true }, (err, res, body) => { assert.ifError(err); diff --git a/test/topics.js b/test/topics.js index dc48d829c3..53720c5045 100644 --- a/test/topics.js +++ b/test/topics.js @@ -38,8 +38,9 @@ describe('Topic\'s', () => { adminUid = await User.create({ username: 'admin', password: '123456' }); fooUid = await User.create({ username: 'foo' }); await groups.join('administrators', adminUid); - adminJar = await helpers.loginUser('admin', '123456'); - csrf_token = (await requestType('get', `${nconf.get('url')}/api/config`, { json: true, jar: adminJar })).body.csrf_token; + const adminLogin = await helpers.loginUser('admin', '123456'); + adminJar = adminLogin.jar; + csrf_token = adminLogin.csrf_token; categoryObj = await categories.create({ name: 'Test Category', diff --git a/test/topics/thumbs.js b/test/topics/thumbs.js index 8020cfd323..a03ad058f6 100644 --- a/test/topics/thumbs.js +++ b/test/topics/thumbs.js @@ -47,24 +47,12 @@ describe('Topic thumbs', () => { adminUid = await user.create({ username: 'admin', password: '123456' }); fooUid = await user.create({ username: 'foo', password: '123456' }); await groups.join('administrators', adminUid); - ({ adminJar, adminCSRF } = await new Promise((resolve, reject) => { - helpers.loginUser('admin', '123456', (err, adminJar, adminCSRF) => { - if (err) { - return reject(err); - } - - resolve({ adminJar, adminCSRF }); - }); - })); - ({ fooJar, fooCSRF } = await new Promise((resolve, reject) => { - helpers.loginUser('foo', '123456', (err, fooJar, fooCSRF) => { - if (err) { - return reject(err); - } - - resolve({ fooJar, fooCSRF }); - }); - })); + const adminLogin = await helpers.loginUser('admin', '123456'); + adminJar = adminLogin.jar; + adminCSRF = adminLogin.csrf_token; + const fooLogin = await helpers.loginUser('foo', '123456'); + fooJar = fooLogin.jar; + fooCSRF = fooLogin.csrf_token; categoryObj = await categories.create({ name: 'Test Category', diff --git a/test/uploads.js b/test/uploads.js index 7d7186ca02..831c466458 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -68,13 +68,9 @@ describe('Upload Controllers', () => { let jar; let csrf_token; - before((done) => { - helpers.loginUser('malicioususer', 'herpderp', (err, _jar, _csrf_token) => { - assert.ifError(err); - jar = _jar; - csrf_token = _csrf_token; - privileges.global.give(['groups:upload:post:file'], 'registered-users', done); - }); + before(async () => { + ({ jar, csrf_token } = await helpers.loginUser('malicioususer', 'herpderp')); + await privileges.global.give(['groups:upload:post:file'], 'registered-users'); }); it('should fail if the user exceeds the upload rate limit threshold', (done) => { @@ -110,14 +106,10 @@ describe('Upload Controllers', () => { let jar; let csrf_token; - before((done) => { + before(async () => { meta.config.uploadRateLimitThreshold = 1000; - helpers.loginUser('regular', 'zugzug', (err, _jar, _csrf_token) => { - assert.ifError(err); - jar = _jar; - csrf_token = _csrf_token; - privileges.global.give(['groups:upload:post:file'], 'registered-users', done); - }); + ({ jar, csrf_token } = await helpers.loginUser('regular', 'zugzug')); + await privileges.global.give(['groups:upload:post:file'], 'registered-users'); }); it('should upload an image to a post', (done) => { @@ -286,7 +278,6 @@ describe('Upload Controllers', () => { }); it('should delete users uploads if account is deleted', (done) => { - let jar; let uid; let url; const file = require('../src/file'); @@ -299,8 +290,8 @@ describe('Upload Controllers', () => { uid = _uid; helpers.loginUser('uploader', 'barbar', next); }, - function (jar, csrf_token, next) { - helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, next); + function (data, next) { + helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, data.jar, data.csrf_token, next); }, function (res, body, next) { assert(body && body.status && body.response && body.response.images); @@ -328,25 +319,11 @@ describe('Upload Controllers', () => { let regularJar; let regular_csrf_token; - before((done) => { - async.parallel([ - function (next) { - helpers.loginUser('admin', 'barbar', (err, _jar, _csrf_token) => { - assert.ifError(err); - jar = _jar; - csrf_token = _csrf_token; - next(); - }); - }, - function (next) { - helpers.loginUser('regular', 'zugzug', (err, _jar, _csrf_token) => { - assert.ifError(err); - regularJar = _jar; - regular_csrf_token = _csrf_token; - next(); - }); - }, - ], done); + before(async () => { + ({ jar, csrf_token} = await helpers.loginUser('admin', 'barbar')); + const regularLogin = await helpers.loginUser('regular', 'zugzug'); + regularJar = regularLogin.jar; + regular_csrf_token = regularLogin.csrf_token; }); it('should upload site logo', (done) => { diff --git a/test/user.js b/test/user.js index 308d68b4b8..d1893cf788 100644 --- a/test/user.js +++ b/test/user.js @@ -820,8 +820,7 @@ describe('User', () => { await User.email.confirmByUid(uid); - const _jar = await helpers.loginUser('updateprofile', '123456'); - jar = _jar; + ({ jar } = await helpers.loginUser('updateprofile', '123456')); }); it('should return error if data is invalid', (done) => { @@ -1948,9 +1947,9 @@ describe('User', () => { gdpr_consent: true, }, (err) => { assert.ifError(err); - helpers.loginUser('admin', '123456', (err, jar) => { + helpers.loginUser('admin', '123456', (err, data) => { assert.ifError(err); - request(`${nconf.get('url')}/api/admin/manage/registration`, { jar: jar, json: true }, (err, res, body) => { + request(`${nconf.get('url')}/api/admin/manage/registration`, { jar: data.jar, json: true }, (err, res, body) => { assert.ifError(err); assert.equal(body.users[0].username, 'rejectme'); assert.equal(body.users[0].email, '<script>alert("ok")<script>reject@me.com'); @@ -2080,9 +2079,9 @@ describe('User', () => { let jar; before((done) => { - helpers.loginUser('notAnInviter', COMMON_PW, (err, _jar) => { + helpers.loginUser('notAnInviter', COMMON_PW, (err, data) => { assert.ifError(err); - jar = _jar; + jar = data.jar; request({ url: `${nconf.get('url')}/api/config`, @@ -2116,9 +2115,9 @@ describe('User', () => { let jar; before((done) => { - helpers.loginUser('inviter', COMMON_PW, (err, _jar) => { + helpers.loginUser('inviter', COMMON_PW, (err, data) => { assert.ifError(err); - jar = _jar; + jar = data.jar; request({ url: `${nconf.get('url')}/api/config`, @@ -2218,9 +2217,9 @@ describe('User', () => { let jar; before((done) => { - helpers.loginUser('adminInvite', COMMON_PW, (err, _jar) => { + helpers.loginUser('adminInvite', COMMON_PW, (err, data) => { assert.ifError(err); - jar = _jar; + jar = data.jar; request({ url: `${nconf.get('url')}/api/config`, @@ -2369,9 +2368,9 @@ describe('User', () => { let jar; before((done) => { - helpers.loginUser('inviter', COMMON_PW, (err, _jar) => { + helpers.loginUser('inviter', COMMON_PW, (err, data) => { assert.ifError(err); - jar = _jar; + jar = data.jar; request({ url: `${nconf.get('url')}/api/config`, @@ -2518,14 +2517,7 @@ describe('User', () => { username: 'regularUser', password: COMMON_PW, }); - jar = await new Promise((resolve, reject) => { - helpers.loginUser('regularUser', COMMON_PW, async (err, _jar) => { - if (err) { - reject(err); - } - resolve(_jar); - }); - }); + ({ jar } = await helpers.loginUser('regularUser', COMMON_PW)); }); after((done) => { @@ -2818,7 +2810,7 @@ describe('User', () => { assert.ifError(err); const oldValue = meta.config.minimumPasswordStrength; meta.config.minimumPasswordStrength = 3; - helpers.loginUser('weakpwd', '123456', (err, jar, csrfs_token) => { + helpers.loginUser('weakpwd', '123456', (err) => { assert.ifError(err); meta.config.minimumPasswordStrength = oldValue; done(); From fa1ac04dc6d3171a183e25b4f00b82908e13978b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 19:26:18 -0500 Subject: [PATCH 055/849] lint: fix --- test/uploads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/uploads.js b/test/uploads.js index 831c466458..09b56fe64a 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -320,7 +320,7 @@ describe('Upload Controllers', () => { let regular_csrf_token; before(async () => { - ({ jar, csrf_token} = await helpers.loginUser('admin', 'barbar')); + ({ jar, csrf_token } = await helpers.loginUser('admin', 'barbar')); const regularLogin = await helpers.loginUser('regular', 'zugzug'); regularJar = regularLogin.jar; regular_csrf_token = regularLogin.csrf_token; From 29b3587d919c15c358988d22f9ca68c0436d4619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 19:56:12 -0500 Subject: [PATCH 056/849] test: middleware/expose.js --- src/middleware/expose.js | 9 ++-- src/plugins/hooks.js | 2 +- test/middleware.js | 96 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/middleware.js diff --git a/src/middleware/expose.js b/src/middleware/expose.js index 3642465c01..b4ad1d7be4 100644 --- a/src/middleware/expose.js +++ b/src/middleware/expose.js @@ -18,9 +18,8 @@ module.exports = function (middleware) { return next(); } - const isAdmin = await user.isAdministrator(req.user.uid); - res.locals.isAdmin = isAdmin; - return next(); + res.locals.isAdmin = await user.isAdministrator(req.user.uid); + next(); }; middleware.exposePrivileges = async (req, res, next) => { @@ -36,7 +35,7 @@ module.exports = function (middleware) { } res.locals.privileges = hash; - return next(); + next(); }; middleware.exposePrivilegeSet = async (req, res, next) => { @@ -45,6 +44,6 @@ module.exports = function (middleware) { ...await privileges.global.get(req.user.uid), ...await privileges.admin.get(req.user.uid), }; - return next(); + next(); }; }; diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 108b4750ac..04603b486d 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -109,7 +109,7 @@ Hooks.fire = async function (hook, params) { Hooks.fire('action:plugins.firehook', payload); } if (result !== undefined) { - if (deleteCaller && result && result.caller) { + if (deleteCaller && result && result.hasOwnProperty('caller')) { delete result.caller; } return result; diff --git a/test/middleware.js b/test/middleware.js new file mode 100644 index 0000000000..767d71b08b --- /dev/null +++ b/test/middleware.js @@ -0,0 +1,96 @@ +'use strict'; + +const assert = require('assert'); +const db = require('./mocks/databasemock'); +const middleware = require('../src/middleware'); +const user = require('../src/user'); +const groups = require('../src/groups'); + +describe('Middlewares', () => { + let adminUid; + before(async () => { + adminUid = await user.create({ username: 'admin', password: '123456' }); + await groups.join('administrators', adminUid); + }); + describe('expose', () => { + it('should expose res.locals.isAdmin = false', (done) => { + const resMock = { locals: {} }; + middleware.exposeAdmin({}, resMock, () => { + assert.strictEqual(resMock.locals.isAdmin, false); + done(); + }); + }); + + it('should expose res.locals.isAdmin = true', (done) => { + const reqMock = { user: { uid: adminUid } }; + const resMock = { locals: {} }; + middleware.exposeAdmin(reqMock, resMock, () => { + assert.strictEqual(resMock.locals.isAdmin, true); + done(); + }); + }); + + it('should expose privileges in res.locals.privileges and isSelf=true', (done) => { + const reqMock = { user: { uid: adminUid }, params: { uid: adminUid } }; + const resMock = { locals: {} }; + middleware.exposePrivileges(reqMock, resMock, () => { + assert(resMock.locals.privileges); + assert.strictEqual(resMock.locals.privileges.isAdmin, true); + assert.strictEqual(resMock.locals.privileges.isGmod, false); + assert.strictEqual(resMock.locals.privileges.isPrivileged, true); + assert.strictEqual(resMock.locals.privileges.isSelf, true); + done(); + }); + }); + + it('should expose privileges in res.locals.privileges and isSelf=false', (done) => { + const reqMock = { user: { uid: 0 }, params: { uid: adminUid } }; + const resMock = { locals: {} }; + middleware.exposePrivileges(reqMock, resMock, () => { + assert(resMock.locals.privileges); + assert.strictEqual(resMock.locals.privileges.isAdmin, false); + assert.strictEqual(resMock.locals.privileges.isGmod, false); + assert.strictEqual(resMock.locals.privileges.isPrivileged, false); + assert.strictEqual(resMock.locals.privileges.isSelf, false); + done(); + }); + }); + + it('should expose privilege set', (done) => { + const reqMock = { user: { uid: adminUid } }; + const resMock = { locals: {} }; + middleware.exposePrivilegeSet(reqMock, resMock, () => { + assert(resMock.locals.privileges); + console.log(resMock.locals.privileges); + assert.deepStrictEqual(resMock.locals.privileges, { + chat: true, + 'upload:post:image': true, + 'upload:post:file': true, + signature: true, + invite: true, + 'group:create': true, + 'search:content': true, + 'search:users': true, + 'search:tags': true, + 'view:users': true, + 'view:tags': true, + 'view:groups': true, + 'local:login': true, + ban: true, + 'view:users:info': true, + 'admin:dashboard': true, + 'admin:categories': true, + 'admin:privileges': true, + 'admin:admins-mods': true, + 'admin:users': true, + 'admin:groups': true, + 'admin:tags': true, + 'admin:settings': true, + superadmin: true, + }); + done(); + }); + }); + }); +}); + From d7c32ccbc24e87ddea529c7fa53619fa530e6b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 20:01:47 -0500 Subject: [PATCH 057/849] test: remove log --- test/middleware.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/middleware.js b/test/middleware.js index 767d71b08b..7ac48616a0 100644 --- a/test/middleware.js +++ b/test/middleware.js @@ -61,7 +61,6 @@ describe('Middlewares', () => { const resMock = { locals: {} }; middleware.exposePrivilegeSet(reqMock, resMock, () => { assert(resMock.locals.privileges); - console.log(resMock.locals.privileges); assert.deepStrictEqual(resMock.locals.privileges, { chat: true, 'upload:post:image': true, From d375dcb873bcf21e4e7ae5ee26c928238ecdf0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 21:10:12 -0500 Subject: [PATCH 058/849] test: submitUsage --- src/plugins/usage.js | 11 ++++++++--- test/plugins.js | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/plugins/usage.js b/src/plugins/usage.js index b9a6757361..43a66f2b54 100644 --- a/src/plugins/usage.js +++ b/src/plugins/usage.js @@ -17,9 +17,10 @@ module.exports = function (Plugins) { }), null, true); }; - Plugins.submitUsageData = function () { + Plugins.submitUsageData = function (callback) { + callback = callback || function () {}; if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || global.env !== 'production') { - return; + return callback(); } const hash = crypto.createHash('sha256'); @@ -33,10 +34,14 @@ module.exports = function (Plugins) { timeout: 5000, }, (err, res, body) => { if (err) { - return winston.error(err.stack); + winston.error(err.stack); + return callback(err); } if (res.statusCode !== 200) { winston.error(`[plugins.submitUsageData] received ${res.statusCode} ${body}`); + callback(new Error(`[[error:nbbpm-${res.statusCode}]]`)); + } else { + callback(); } }); }; diff --git a/test/plugins.js b/test/plugins.js index 1b9864a920..c717deec30 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -210,6 +210,13 @@ describe('Plugins', () => { }); }); + it('should submit usage info', (done) => { + plugins.submitUsage((err) => { + assert.ifError(err); + done(); + }); + }); + describe('install/activate/uninstall', () => { let latest; const pluginName = 'nodebb-plugin-imgur'; From 0e72512509e97aefc202930908aa988c6185bd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 21:17:54 -0500 Subject: [PATCH 059/849] test: fix function name --- test/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins.js b/test/plugins.js index c717deec30..e7676990d2 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -211,7 +211,7 @@ describe('Plugins', () => { }); it('should submit usage info', (done) => { - plugins.submitUsage((err) => { + plugins.submitUsageData((err) => { assert.ifError(err); done(); }); From f11bc33ac5c51ae8ee13f2be29e493133dc7c26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 21:22:44 -0500 Subject: [PATCH 060/849] test: digest --- test/user.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/user.js b/test/user.js index d1893cf788..372d334bf6 100644 --- a/test/user.js +++ b/test/user.js @@ -1459,7 +1459,7 @@ describe('User', () => { }); }); - describe('Digest.getSubscribers', (done) => { + describe('Digest.getSubscribers', () => { const uidIndex = {}; before((done) => { @@ -1561,8 +1561,11 @@ describe('User', () => { }); it('should send digests', (done) => { + const oldValue = meta.config.includeUnverifiedEmails; + meta.config.includeUnverifiedEmails = true; User.digest.execute({ interval: 'day' }, (err) => { assert.ifError(err); + meta.config.includeUnverifiedEmails = oldValue; done(); }); }); @@ -1574,6 +1577,12 @@ describe('User', () => { }); }); + it('should get delivery times', async () => { + const data = await User.digest.getDeliveryTimes(0, -1); + const users = data.users.filter(u => u.username === 'digestuser'); + assert.strictEqual(users[0].setting, 'day'); + }); + describe('unsubscribe via POST', () => { it('should unsubscribe from digest if one-click unsubscribe is POSTed', (done) => { const token = jwt.sign({ From 71e34be5656cc645b3acb11bd70698dfae550bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 22:20:01 -0500 Subject: [PATCH 061/849] fix: cli password reset --- src/cli/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/user.js b/src/cli/user.js index eda3502133..097ff8bd3b 100644 --- a/src/cli/user.js +++ b/src/cli/user.js @@ -216,6 +216,7 @@ ${pwGenerated ? ` Generated password: ${password}` : ''}`); const adminUid = await getAdminUidOrFail(); if (password) { + await user.setUserField(uid, 'password', ''); await user.changePassword(adminUid, { newPassword: password, uid, From 2473d5d873e1658837a1e51b40f595bf93f244e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 23:20:31 -0500 Subject: [PATCH 062/849] fix: #10027, properly auto confirm first user --- src/user/create.js | 13 ++-- test/authentication.js | 157 +++++++++++++++++++---------------------- test/flags.js | 4 +- test/helpers/index.js | 10 +-- 4 files changed, 86 insertions(+), 98 deletions(-) diff --git a/src/user/create.js b/src/user/create.js index 869db6b162..a278297db7 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -77,9 +77,6 @@ module.exports = function (User) { const isFirstUser = uid === 1; userData.uid = uid; - if (isFirstUser) { - userData['email:confirmed'] = 1; - } await db.setObject(`user:${uid}`, userData); const bulkAdd = [ @@ -97,20 +94,20 @@ module.exports = function (User) { bulkAdd.push(['fullname:sorted', 0, `${userData.fullname.toLowerCase()}:${userData.uid}`]); } - const groupsToJoin = ['registered-users'].concat( - isFirstUser ? 'verified-users' : 'unverified-users' - ); - await Promise.all([ db.incrObjectField('global', 'userCount'), analytics.increment('registrations'), db.sortedSetAddBulk(bulkAdd), - groups.join(groupsToJoin, userData.uid), + groups.join(['registered-users', 'unverified-users'], userData.uid), User.notifications.sendWelcomeNotification(userData.uid), storePassword(userData.uid, data.password), User.updateDigestSetting(userData.uid, meta.config.dailyDigestFreq), ]); + if (userData.email && isFirstUser) { + await User.email.confirmByUid(userData.uid); + } + if (userData.email && userData.uid > 1) { User.email.sendValidationEmail(userData.uid, { email: userData.email, diff --git a/test/authentication.js b/test/authentication.js index b65c95f2dd..1b796f5797 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -16,44 +16,35 @@ const privileges = require('../src/privileges'); const helpers = require('./helpers'); describe('authentication', () => { - function loginUser(username, password, callback) { - const jar = request.jar(); - request({ - url: `${nconf.get('url')}/api/config`, - json: true, - jar: jar, - }, (err, response, body) => { - if (err) { - return callback(err); - } - - request.post(`${nconf.get('url')}/login`, { - form: { - username: username, - password: password, - }, - json: true, - jar: jar, - headers: { - 'x-csrf-token': body.csrf_token, - }, - }, (err, response, body) => { - callback(err, response, body, jar); - }); - }); - } - const loginUserPromisified = util.promisify(loginUser); - const jar = request.jar(); let regularUid; before((done) => { user.create({ username: 'regular', password: 'regularpwd', email: 'regular@nodebb.org' }, (err, uid) => { assert.ifError(err); regularUid = uid; + assert.strictEqual(uid, 1); done(); }); }); + it('should allow login with email for uid 1', async () => { + const oldValue = meta.config.allowLoginWith; + meta.config.allowLoginWith = 'email'; + const { res } = await helpers.loginUser('regular@nodebb.org', 'regularpwd'); + assert.strictEqual(res.statusCode, 200); + meta.config.allowLoginWith = oldValue; + }); + + it('second user should fail to login with email since email is not confirmed', async () => { + const oldValue = meta.config.allowLoginWith; + meta.config.allowLoginWith = 'email'; + const uid = await user.create({ username: '2nduser', password: '2ndpassword', email: '2nduser@nodebb.org' }); + const { res, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword'); + assert.strictEqual(res.statusCode, 403); + assert.strictEqual(body, '[[error:invalid-login-credentials]]'); + meta.config.allowLoginWith = oldValue; + }); + it('should fail to create user if username is too short', (done) => { helpers.registerUser({ username: 'a', @@ -164,13 +155,13 @@ describe('authentication', () => { }); it('should login a user', (done) => { - loginUser('regular', 'regularpwd', (err, response, body, jar) => { + helpers.loginUser('regular', 'regularpwd', (err, data) => { assert.ifError(err); - assert(body); + assert(data.body); request({ url: `${nconf.get('url')}/api/self`, json: true, - jar: jar, + jar: data.jar, }, (err, response, body) => { assert.ifError(err); assert(body); @@ -245,37 +236,37 @@ describe('authentication', () => { }); it('should fail to login if user does not exist', (done) => { - loginUser('doesnotexist', 'nopassword', (err, response, body) => { + helpers.loginUser('doesnotexist', 'nopassword', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:invalid-login-credentials]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:invalid-login-credentials]]'); done(); }); }); it('should fail to login if username is empty', (done) => { - loginUser('', 'some password', (err, response, body) => { + helpers.loginUser('', 'some password', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:invalid-username-or-password]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:invalid-username-or-password]]'); done(); }); }); it('should fail to login if password is empty', (done) => { - loginUser('someuser', '', (err, response, body) => { + helpers.loginUser('someuser', '', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:invalid-username-or-password]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:invalid-username-or-password]]'); done(); }); }); it('should fail to login if username and password are empty', (done) => { - loginUser('', '', (err, response, body) => { + helpers.loginUser('', '', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:invalid-username-or-password]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:invalid-username-or-password]]'); done(); }); }); @@ -283,10 +274,10 @@ describe('authentication', () => { it('should fail to login if user does not have password field in db', (done) => { user.create({ username: 'hasnopassword', email: 'no@pass.org' }, (err, uid) => { assert.ifError(err); - loginUser('hasnopassword', 'doesntmatter', (err, response, body) => { + helpers.loginUser('hasnopassword', 'doesntmatter', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:invalid-login-credentials]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:invalid-login-credentials]]'); done(); }); }); @@ -297,10 +288,10 @@ describe('authentication', () => { for (let i = 0; i < 5000; i++) { longPassword += 'a'; } - loginUser('someuser', longPassword, (err, response, body) => { + helpers.loginUser('someuser', longPassword, (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:password-too-long]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:password-too-long]]'); done(); }); }); @@ -308,10 +299,10 @@ describe('authentication', () => { it('should fail to login if local login is disabled', (done) => { privileges.global.rescind(['groups:local:login'], 'registered-users', (err) => { assert.ifError(err); - loginUser('regular', 'regularpwd', (err, response, body) => { + helpers.loginUser('regular', 'regularpwd', (err, data) => { assert.ifError(err); - assert.equal(response.statusCode, 403); - assert.equal(body, '[[error:local-login-disabled]]'); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:local-login-disabled]]'); privileges.global.give(['groups:local:login'], 'registered-users', done); }); }); @@ -396,17 +387,17 @@ describe('authentication', () => { it('should be able to login with email', async () => { const uid = await user.create({ username: 'ginger', password: '123456', email: 'ginger@nodebb.org' }); await user.email.confirmByUid(uid); - const response = await loginUserPromisified('ginger@nodebb.org', '123456'); - assert.equal(response.statusCode, 200); + const { res } = await helpers.loginUser('ginger@nodebb.org', '123456'); + assert.equal(res.statusCode, 200); }); it('should fail to login if login type is username and an email is sent', (done) => { meta.config.allowLoginWith = 'username'; - loginUser('ginger@nodebb.org', '123456', (err, response, body) => { + helpers.loginUser('ginger@nodebb.org', '123456', (err, data) => { meta.config.allowLoginWith = 'username-email'; assert.ifError(err); - assert.equal(response.statusCode, 400); - assert.equal(body, '[[error:wrong-login-type-username]]'); + assert.equal(data.res.statusCode, 400); + assert.equal(data.body, '[[error:wrong-login-type-username]]'); done(); }); }); @@ -450,28 +441,28 @@ describe('authentication', () => { it('should prevent banned user from logging in', (done) => { user.bans.ban(bannedUser.uid, 0, 'spammer', (err) => { assert.ifError(err); - loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => { + helpers.loginUser(bannedUser.username, bannedUser.pw, (err, data) => { assert.ifError(err); - assert.equal(res.statusCode, 403); - delete body.timestamp; - assert.deepStrictEqual(body, { + assert.equal(data.res.statusCode, 403); + delete data.body.timestamp; + assert.deepStrictEqual(data.body, { banned_until: 0, banned_until_readable: '', expiry: 0, expiry_readable: '', reason: 'spammer', - uid: 6, + uid: bannedUser.uid, }); user.bans.unban(bannedUser.uid, (err) => { assert.ifError(err); const expiry = Date.now() + 10000; user.bans.ban(bannedUser.uid, expiry, '', (err) => { assert.ifError(err); - loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => { + helpers.loginUser(bannedUser.username, bannedUser.pw, (err, data) => { assert.ifError(err); - assert.equal(res.statusCode, 403); - assert(body.banned_until); - assert(body.reason, '[[user:info.banned-no-reason]]'); + assert.equal(data.res.statusCode, 403); + assert(data.body.banned_until); + assert(data.body.reason, '[[user:info.banned-no-reason]]'); done(); }); }); @@ -482,14 +473,14 @@ describe('authentication', () => { it('should allow banned user to log in if the "banned-users" group has "local-login" privilege', async () => { await privileges.global.give(['groups:local:login'], 'banned-users'); - const res = await loginUserPromisified(bannedUser.username, bannedUser.pw); + const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw); assert.strictEqual(res.statusCode, 200); }); it('should allow banned user to log in if the user herself has "local-login" privilege', async () => { await privileges.global.rescind(['groups:local:login'], 'banned-users'); await privileges.categories.give(['local:login'], 0, bannedUser.uid); - const res = await loginUserPromisified(bannedUser.username, bannedUser.pw); + const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw); assert.strictEqual(res.statusCode, 200); }); }); @@ -503,26 +494,26 @@ describe('authentication', () => { }, function (_uid, next) { uid = _uid; - loginUser('lockme', 'abcdef', next); + helpers.loginUser('lockme', 'abcdef', next); }, - function (res, body, jar, next) { - loginUser('lockme', 'abcdef', next); + function (data, next) { + helpers.loginUser('lockme', 'abcdef', next); }, - function (res, body, jar, next) { - loginUser('lockme', 'abcdef', next); + function (data, next) { + helpers.loginUser('lockme', 'abcdef', next); }, - function (res, body, jar, next) { - loginUser('lockme', 'abcdef', next); + function (data, next) { + helpers.loginUser('lockme', 'abcdef', next); }, - function (res, body, jar, next) { + function (data, next) { meta.config.loginAttempts = 5; - assert.equal(res.statusCode, 403); - assert.equal(body, '[[error:account-locked]]'); - loginUser('lockme', 'abcdef', next); + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:account-locked]]'); + helpers.loginUser('lockme', 'abcdef', next); }, - function (res, body, jar, next) { - assert.equal(res.statusCode, 403); - assert.equal(body, '[[error:account-locked]]'); + function (data, next) { + assert.equal(data.res.statusCode, 403); + assert.equal(data.body, '[[error:account-locked]]'); db.exists(`lockout:${uid}`, next); }, function (locked, next) { @@ -534,7 +525,7 @@ describe('authentication', () => { it('should clear all reset tokens upon successful login', async () => { const code = await user.reset.generate(regularUid); - await loginUserPromisified('regular', 'regularpwd'); + await helpers.loginUser('regular', 'regularpwd'); const valid = await user.reset.validate(code); assert.strictEqual(valid, false); }); diff --git a/test/flags.js b/test/flags.js index 9e07974bc4..784af0630f 100644 --- a/test/flags.js +++ b/test/flags.js @@ -673,8 +673,8 @@ describe('Flags', () => { throw err; } - // 1 for the new event appended, 1 for username change (email not changed immediately) - assert.strictEqual(entries + 2, history.length); + // 1 for the new event appended, 2 for username/email change + assert.strictEqual(entries + 3, history.length); done(); }); }); diff --git a/test/helpers/index.js b/test/helpers/index.js index 800e3b7d0b..eab17d186e 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -43,7 +43,7 @@ helpers.loginUser = function (username, password, callback) { if (err || res.statusCode !== 200) { return callback(err || new Error('[[error:invalid-response]]')); } - + const { csrf_token } = body; request.post(`${nconf.get('url')}/login`, { form: { username: username, @@ -52,13 +52,13 @@ helpers.loginUser = function (username, password, callback) { json: true, jar: jar, headers: { - 'x-csrf-token': body.csrf_token, + 'x-csrf-token': csrf_token, }, - }, (err, res) => { - if (err || res.statusCode !== 200) { + }, (err, res, body) => { + if (err) { return callback(err || new Error('[[error:invalid-response]]')); } - callback(null, { jar, csrf_token: body.csrf_token }); + callback(null, { jar, res, body, csrf_token: csrf_token }); }); }); }; From 1280d9ae85786d23bdfd0bd30d421ddb4157fe54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 23:28:04 -0500 Subject: [PATCH 063/849] test: add digest route test --- test/controllers-admin.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/controllers-admin.js b/test/controllers-admin.js index beb81ccc4c..62a8d6401c 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -126,6 +126,15 @@ describe('Admin Controllers', () => { }); }); + it('should load manage digests', (done) => { + request(`${nconf.get('url')}/admin/manage/digest`, { jar: jar }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + it('should load manage uploads', (done) => { request(`${nconf.get('url')}/admin/manage/uploads`, { jar: jar }, (err, res, body) => { assert.ifError(err); From 754cdab896793c94b262c5967ac6da16d6cbbca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 23:38:04 -0500 Subject: [PATCH 064/849] test: debug routes in dev --- test/controllers.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/controllers.js b/test/controllers.js index e3d0dda050..077ef196b5 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -2406,6 +2406,46 @@ describe('Controllers', () => { }); }); + describe('test routes', () => { + if (process.env.NODE_ENV === 'development') { + it('should load debug route', (done) => { + request(`${nconf.get('url')}/debug/test`, {}, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 404); + assert(body); + done(); + }); + }); + + it('should load redoc read route', (done) => { + request(`${nconf.get('url')}/debug/spec/read`, {}, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should load redoc write route', (done) => { + request(`${nconf.get('url')}/debug/spec/write`, {}, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should load 404 for invalid type', (done) => { + request(`${nconf.get('url')}/debug/spec/doesnotexist`, {}, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 404); + assert(body); + done(); + }); + }); + } + }); + after((done) => { const analytics = require('../src/analytics'); analytics.writeData(done); From 6d186ff10dd93b44695de3a12dabb1d1f91eb519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 22 Nov 2021 23:53:13 -0500 Subject: [PATCH 065/849] test: add mising email.test tpls --- test/socket.io.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/socket.io.js b/test/socket.io.js index f709e86178..5c890809f1 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -244,7 +244,6 @@ describe('socket.io', () => { }); describe('validation emails', () => { - const meta = require('../src/meta'); const plugins = require('../src/plugins'); async function dummyEmailerHook(data) { @@ -567,11 +566,16 @@ describe('socket.io', () => { }); }); - it('should send test email', (done) => { - socketAdmin.email.test({ uid: adminUid }, { template: 'digest.tpl' }, (err) => { + it('should send test email', async () => { + const tpls = ['digest', 'test', 'verify', 'welcome', 'notification', 'invitation']; + try { + for (const tpl of tpls) { + // eslint-disable-next-line no-await-in-loop + await socketAdmin.email.test({ uid: adminUid }, { template: tpl }); + } + } catch (err) { assert.ifError(err); - done(); - }); + } }); it('should not error when resending digests', async () => { From edf7c647e84bf5bda93fc348c967fde563e06922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 00:04:10 -0500 Subject: [PATCH 066/849] test: fix tpl name --- test/socket.io.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/socket.io.js b/test/socket.io.js index 5c890809f1..2f7463cb78 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -567,7 +567,7 @@ describe('socket.io', () => { }); it('should send test email', async () => { - const tpls = ['digest', 'test', 'verify', 'welcome', 'notification', 'invitation']; + const tpls = ['digest', 'banned', 'verify', 'welcome', 'notification', 'invitation']; try { for (const tpl of tpls) { // eslint-disable-next-line no-await-in-loop From bc120dba68ec50fabc5ce828a6e8358a44b02f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 00:13:54 -0500 Subject: [PATCH 067/849] test: add missing controllers --- test/controllers-admin.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/test/controllers-admin.js b/test/controllers-admin.js index 62a8d6401c..356767a5d2 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -81,12 +81,29 @@ describe('Admin Controllers', () => { it('should load admin dashboard', (done) => { groups.join('administrators', adminUid, (err) => { assert.ifError(err); - request(`${nconf.get('url')}/admin`, { jar: jar }, (err, res, body) => { - assert.ifError(err); - assert.equal(res.statusCode, 200); - assert(body); - done(); - }); + const dashboards = [ + '/admin', '/admin/dashboard/logins', '/admin/dashboard/users', '/admin/dashboard/topics', '/admin/dashboard/searches', + ]; + async.each(dashboards, (url, next) => { + request(`${nconf.get('url')}${url}`, { jar: jar }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200, url); + assert(body); + + next(); + }); + }, done); + }); + }); + + it('should load admin analytics', (done) => { + request(`${nconf.get('url')}/api/admin/analytics?units=hours`, { jar: jar, json: true }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + assert(body.query); + assert(body.result); + done(); }); }); From 9fdbfe677914cd1d69b82122a88d118f6256bbbe Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Tue, 23 Nov 2021 09:07:43 +0000 Subject: [PATCH 068/849] Latest translations and fallbacks --- public/language/he/admin/advanced/errors.json | 2 +- public/language/he/admin/extend/widgets.json | 2 +- public/language/he/admin/manage/categories.json | 2 +- public/language/he/admin/manage/registration.json | 6 +++--- public/language/he/admin/settings/user.json | 4 ++-- public/language/he/flags.json | 2 +- public/language/sr/flags.json | 2 +- public/language/sr/modules.json | 2 +- public/language/sr/topic.json | 8 ++++---- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/public/language/he/admin/advanced/errors.json b/public/language/he/admin/advanced/errors.json index 42f7047575..ef5e1d870e 100644 --- a/public/language/he/admin/advanced/errors.json +++ b/public/language/he/admin/advanced/errors.json @@ -8,7 +8,7 @@ "clear-error-log": "נקה רישום שגיאות", "route": "נתיב", "count": "ספירה", - "no-routes-not-found": "מופלטה! אין שגיאות 404!", + "no-routes-not-found": "הידד! אין שגיאות 404!", "clear404-confirm": "האם אתה בטוח שאתה רוצה לנקות את רישום שגיאות 404?", "clear404-success": "שגיאות \"404 לא נמצא\" נוקו" } \ No newline at end of file diff --git a/public/language/he/admin/extend/widgets.json b/public/language/he/admin/extend/widgets.json index 49ad01a667..eb710c3543 100644 --- a/public/language/he/admin/extend/widgets.json +++ b/public/language/he/admin/extend/widgets.json @@ -26,5 +26,5 @@ "container.placeholder": "גרור ושחרר גורם מכיל (container) או הזן HTML כאן.", "show-to-groups": "יוצג בקבוצות", "hide-from-groups": "יוסתר מקבוצות", - "hide-on-mobile": " הסתר במובייל" + "hide-on-mobile": "הסתר במובייל" } \ No newline at end of file diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json index 07eca8f15e..1afd0a7e48 100644 --- a/public/language/he/admin/manage/categories.json +++ b/public/language/he/admin/manage/categories.json @@ -59,7 +59,7 @@ "privileges.copy-group-privileges-to-children": "העתק הרשאות קבוצה זו לצאצאי קטגוריה זו.", "privileges.copy-group-privileges-to-all-categories": "העתק הרשאות קבוצה זו לכל הקטגוריות (זהירות!).", "privileges.copy-group-privileges-from": "העתק הרשאות קבוצה זו מקטגוריה אחרת.", - "privileges.inherit": "אם קבוצת משתמשים רשומים מקבלים הרשאה ספציפית, יסומן הרשאה אוטומטיתלכל הקבוצות האחרות. הרשאה אוטומטית זו יוגדר גם אם לא תסמן אותה במפורש. מכיוון שכל המשתמשים הם חלק מקבוצת המשתמשים משתמשים רשומים , ולכן, אין צורך להעניק במפורש הרשאות עבור קבוצות נוספות.", + "privileges.inherit": "אם קבוצת משתמשים רשומים מקבלים הרשאה ספציפית, יסומן הרשאה אוטומטית לכל הקבוצות האחרות. הרשאה אוטומטית זו יוגדר גם אם לא תסמן אותה במפורש. מכיוון שכל המשתמשים הם חלק מקבוצת המשתמשים משתמשים רשומים , ולכן, אין צורך להעניק במפורש הרשאות עבור קבוצות נוספות.", "privileges.copy-success": "הרשאות הועתקו!", "analytics.back": "חזור לרשימת קטגוריות", diff --git a/public/language/he/admin/manage/registration.json b/public/language/he/admin/manage/registration.json index bec1292366..a9b24a6bce 100644 --- a/public/language/he/admin/manage/registration.json +++ b/public/language/he/admin/manage/registration.json @@ -6,9 +6,9 @@ "list.email": "אימייל", "list.ip": "IP", "list.time": "זמן", - "list.username-spam": "Frequency: %1 Appears: %2 Confidence: %3", - "list.email-spam": "Frequency: %1 Appears: %2", - "list.ip-spam": "Frequency: %1 Appears: %2", + "list.username-spam": "תדירות: %1 מופיע: %2 אמון: %3", + "list.email-spam": "תדירות: %1 מופיע: %2", + "list.ip-spam": "תדירות: %1 מופיע: %2", "invitations": "הזמנות", "invitations.description": "להלן רשימה של הזמנות שנשלחו. השתמש ב- Ctrl+F כדי לחפש בתוך הרשימה על פי אימייל או שם משתמש.

    שם המשתמש יוצג בצד ימין של האימייל למשתמשים שממשו את הזמנתם.", diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index 519cbaaa7e..04acac53ad 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -1,8 +1,8 @@ { "authentication": "אימות", "require-email-confirmation": "דורש אישור על ידי כתובת מייל", - "email-confirm-interval": "המשתמש לא יכול לשלוח הודעת אישור מייל עד", - "email-confirm-email2": "דקות חלפו", + "email-confirm-interval": "המשתמש לא יוכל לשלוח הודעת אישור מייל עד שיחלוף", + "email-confirm-email2": "דקות", "allow-login-with": "אפשר התחברות עם", "allow-login-with.username-email": "שם משתמש או סיסמא", "allow-login-with.username": "שם משתמש בלבד", diff --git a/public/language/he/flags.json b/public/language/he/flags.json index 1dfc565d7a..701f07b137 100644 --- a/public/language/he/flags.json +++ b/public/language/he/flags.json @@ -2,7 +2,7 @@ "state": "מצב", "reports": "דוחות", "first-reported": "דווח ראשון", - "no-flags": "מופלטה! לא נמצאו סימונים.", + "no-flags": "הידד! לא נמצאו סימונים.", "assignee": "מוקצה", "update": "עדכון", "updated": "עודכן", diff --git a/public/language/sr/flags.json b/public/language/sr/flags.json index 821bdb4754..98a13227ee 100644 --- a/public/language/sr/flags.json +++ b/public/language/sr/flags.json @@ -75,7 +75,7 @@ "modal-reason-offensive": "Увредљиво", "modal-reason-other": "Остало (наведите испод)", "modal-reason-custom": "Разлог за пријаву овог садржаја...", - "modal-submit": "Пошаљи извештај", + "modal-submit": "Поднеси извештај", "modal-submit-success": "Садржај је означен заставицом за модерацију.", "bulk-actions": "Масовне радње", diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json index d70f995259..36a482724f 100644 --- a/public/language/sr/modules.json +++ b/public/language/sr/modules.json @@ -45,7 +45,7 @@ "composer.user_said_in": "%1 је рекао у %2", "composer.user_said": "%1 је рекао:", "composer.discard": "Желите ли да одбаците ову поруку?", - "composer.submit_and_lock": "Пошаљи и закључај", + "composer.submit_and_lock": "Проследи и закључај", "composer.toggle_dropdown": "Подесите \"Dropdown\"", "composer.uploading": "Отпремање %1", "composer.formatting.bold": "Подебљано", diff --git a/public/language/sr/topic.json b/public/language/sr/topic.json index 8bcc0c8fb6..141e4711f7 100644 --- a/public/language/sr/topic.json +++ b/public/language/sr/topic.json @@ -15,7 +15,7 @@ "replies_to_this_post": "Одговора: %1", "one_reply_to_this_post": "1 одговор", "last_reply_time": "Последњи одговор", - "reply-as-topic": "Постави одговор као тему", + "reply-as-topic": "Објави одговор као тему", "guest-login-reply": "Пријавите се да бисте одговорили", "login-to-view": "🔒 Пријавите се да бисте прегледали", "edit": "Уреди", @@ -74,7 +74,7 @@ "watching.description": "Обавести ме о новим одговорима.
    Прикажи тему у непрочитаним", "not-watching.description": "Немој ме обавештавати о новим одговорима.
    Прикажи тему у непрочитаним ако категорија није игнорисана.", "ignoring.description": "Немој ме обавештавати о новим одговорима.
    Не приказуј тему у непрочитаним", - "thread_tools.title": "Алатке теме", + "thread_tools.title": "Алати теме", "thread_tools.markAsUnreadForAll": "Означи као непрочитано за све", "thread_tools.pin": "Закачи тему", "thread_tools.unpin": "Откачи тему", @@ -135,10 +135,10 @@ "topic-id": "ID теме", "move_posts_instruction": "Кликните на поруке које желите да преместите, а затим унесите ID теме или идите на циљну тему", "change_owner_instruction": "Кликните на поруке које желите да доделите другом кориснику", - "composer.title_placeholder": "Овде унесите назив теме...", + "composer.title_placeholder": "Овде унесите наслов теме...", "composer.handle_placeholder": "Унесите ваше име/идентитет овде", "composer.discard": "Одбаци", - "composer.submit": "Пошаљи", + "composer.submit": "Проследи", "composer.additional-options": "Additional Options", "composer.schedule": "Испланирај", "composer.replying_to": "Писање одговора на %1", From 9966a00fbe49ef3f3763766a0c42662ea8b8d084 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 23 Nov 2021 10:09:21 +0000 Subject: [PATCH 069/849] fix(deps): update dependency ioredis to v4.28.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 2d2ed9ff43..50dba8e436 100644 --- a/install/package.json +++ b/install/package.json @@ -107,7 +107,7 @@ "postcss": "8.3.11", "postcss-clean": "1.2.0", "prompt": "^1.1.0", - "ioredis": "4.28.0", + "ioredis": "4.28.1", "request": "2.88.2", "request-promise-native": "^1.0.9", "requirejs": "2.3.6", From 79de48c57f09ad6f8ed8b68c778549277e3bbe02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 13:21:18 -0500 Subject: [PATCH 070/849] breaking: remove deprecated methods --- src/socket.io/topics/infinitescroll.js | 48 ------------------------ test/socket.io.js | 8 ---- test/topics.js | 52 -------------------------- 3 files changed, 108 deletions(-) diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 506a87b6f7..d2cb0132a7 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -1,7 +1,5 @@ 'use strict'; -const winston = require('winston'); - const topics = require('../../topics'); const privileges = require('../../privileges'); const meta = require('../../meta'); @@ -56,50 +54,4 @@ module.exports = function (SocketTopics) { topics.modifyPostsByPrivilege(topicData, userPrivileges); return topicData; }; - - SocketTopics.loadMoreSortedTopics = async function (socket, data) { - winston.warn('[deprecated] SocketTopics.loadMoreSortedTopics use infinitescroll.loadMoreXhr'); // TODO: remove in 1.19.0 - if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - throw new Error('[[error:invalid-data]]'); - } - const { start, stop } = calculateStartStop(data); - const params = { - uid: socket.uid, - start: start, - stop: stop, - filter: data.filter, - query: data.query, - }; - if (data.sort === 'unread') { - params.cid = data.cid; - return await topics.getUnreadTopics(params); - } - params.cids = data.cid; - params.tags = data.tags; - params.sort = data.sort; - params.term = data.term; - return await topics.getSortedTopics(params); - }; - - SocketTopics.loadMoreFromSet = async function (socket, data) { - winston.warn('[deprecated] SocketTopics.loadMoreFromSet use infinitescroll.loadMoreXhr'); // TODO: remove in 1.19.0 - if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) { - throw new Error('[[error:invalid-data]]'); - } - const { start, stop } = calculateStartStop(data); - return await topics.getTopicsFromSet(data.set, socket.uid, start, stop); - }; - - function calculateStartStop(data) { - const itemsPerPage = Math.min( - meta.config.topicsPerPage || 20, - parseInt(data.count, 10) || meta.config.topicsPerPage || 20 - ); - let start = Math.max(0, parseInt(data.after, 10)); - if (data.direction === -1) { - start -= itemsPerPage; - } - const stop = start + Math.max(0, itemsPerPage - 1); - return { start: Math.max(0, start), stop: Math.max(0, stop) }; - } }; diff --git a/test/socket.io.js b/test/socket.io.js index 2f7463cb78..4dadb62d1e 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -131,14 +131,6 @@ describe('socket.io', () => { }); }); - it('should get more unread topics', (done) => { - io.emit('topics.loadMoreSortedTopics', { after: 0, count: 10, direction: 1, sort: 'unread' }, (err, result) => { - assert.ifError(err); - assert(Array.isArray(result.topics)); - done(); - }); - }); - it('should ban a user', (done) => { const socketUser = require('../src/socket.io/user'); socketUser.banUsers({ uid: adminUid }, { uids: [regularUid], reason: 'spammer' }, (err) => { diff --git a/test/topics.js b/test/topics.js index 53720c5045..65ae549427 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1371,58 +1371,6 @@ describe('Topic\'s', () => { done(); }); }); - - it('should error with invalid data', (done) => { - socketTopics.loadMoreSortedTopics({ uid: adminUid }, { after: 'invalid' }, (err) => { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); - }); - }); - - it('should load more unread topics', (done) => { - socketTopics.markUnread({ uid: adminUid }, tid, (err) => { - assert.ifError(err); - socketTopics.loadMoreSortedTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10, sort: 'unread' }, (err, data) => { - assert.ifError(err); - assert(data); - assert(Array.isArray(data.topics)); - done(); - }); - }); - }); - - it('should error with invalid data', (done) => { - socketTopics.loadMoreSortedTopics({ uid: adminUid }, { after: 'invalid' }, (err) => { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); - }); - }); - - - it('should load more recent topics', (done) => { - socketTopics.loadMoreSortedTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10, sort: 'recent' }, (err, data) => { - assert.ifError(err); - assert(data); - assert(Array.isArray(data.topics)); - done(); - }); - }); - - it('should error with invalid data', (done) => { - socketTopics.loadMoreFromSet({ uid: adminUid }, { after: 'invalid' }, (err) => { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); - }); - }); - - it('should load more from custom set', (done) => { - socketTopics.loadMoreFromSet({ uid: adminUid }, { set: `uid:${adminUid}:topics`, after: 0, count: 10 }, (err, data) => { - assert.ifError(err); - assert(data); - assert(Array.isArray(data.topics)); - done(); - }); - }); }); describe('suggested topics', () => { From 01bd8a8694b09f232c170234d9b142f6703842a6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Nov 2021 14:46:24 -0500 Subject: [PATCH 071/849] remove email only login (#10030) * feat: remove ACP option for email-only logins * feat: remove email-only login, upgrade script to fix config --- public/language/en-GB/admin/settings/user.json | 1 - public/language/en-GB/login.json | 1 - src/controllers/index.js | 5 +++-- src/upgrades/1.19.0/reenable-username-login.js | 16 ++++++++++++++++ src/views/admin/settings/user.tpl | 1 - 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/upgrades/1.19.0/reenable-username-login.js diff --git a/public/language/en-GB/admin/settings/user.json b/public/language/en-GB/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/en-GB/admin/settings/user.json +++ b/public/language/en-GB/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/en-GB/login.json b/public/language/en-GB/login.json index c0ae9b7624..5421ccc307 100644 --- a/public/language/en-GB/login.json +++ b/public/language/en-GB/login.json @@ -1,7 +1,6 @@ { "username-email": "Username / Email", "username": "Username", - "email": "Email", "remember_me": "Remember Me?", "forgot_password": "Forgot Password?", "alternative_logins": "Alternative Logins", diff --git a/src/controllers/index.js b/src/controllers/index.js index dc23e6918f..896a4a8e5f 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -130,9 +130,10 @@ Controllers.login = async function (req, res) { return helpers.redirect(res, { external: data.authentication[0].url }); } + // Re-auth challenge, pre-fill username if (req.loggedIn) { - const userData = await user.getUserFields(req.uid, ['username', 'email']); - data.username = allowLoginWith === 'email' ? userData.email : userData.username; + const userData = await user.getUserFields(req.uid, ['username']); + data.username = userData.username; data.alternate_logins = false; } res.render('login', data); diff --git a/src/upgrades/1.19.0/reenable-username-login.js b/src/upgrades/1.19.0/reenable-username-login.js new file mode 100644 index 0000000000..197b4730cd --- /dev/null +++ b/src/upgrades/1.19.0/reenable-username-login.js @@ -0,0 +1,16 @@ +'use strict'; + +const db = require('../../database'); +const meta = require('../../meta'); + +module.exports = { + name: 'Re-enable username login', + timestamp: Date.UTC(2021, 10, 23), + method: async () => { + const setting = await meta.config.allowLoginWith; + + if (setting === 'email') { + await meta.configs.set('allowLoginWith', 'username-email'); + } + }, +}; diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl index bc879b9d4c..d49788c68b 100644 --- a/src/views/admin/settings/user.tpl +++ b/src/views/admin/settings/user.tpl @@ -16,7 +16,6 @@ From eecd02fbee4c526a7aef510a2c30ef9e0d6ae765 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Tue, 23 Nov 2021 19:47:37 +0000 Subject: [PATCH 072/849] chore(i18n): fallback strings for new resources: nodebb.admin-settings-user, nodebb.login --- public/language/ar/admin/settings/user.json | 1 - public/language/ar/login.json | 1 - public/language/bg/admin/settings/user.json | 1 - public/language/bg/login.json | 1 - public/language/bn/admin/settings/user.json | 1 - public/language/bn/login.json | 1 - public/language/cs/admin/settings/user.json | 1 - public/language/cs/login.json | 1 - public/language/da/admin/settings/user.json | 1 - public/language/da/login.json | 1 - public/language/de/admin/settings/user.json | 1 - public/language/de/login.json | 1 - public/language/el/admin/settings/user.json | 1 - public/language/el/login.json | 1 - public/language/en-US/admin/settings/user.json | 1 - public/language/en-US/login.json | 1 - public/language/en-x-pirate/admin/settings/user.json | 1 - public/language/en-x-pirate/login.json | 1 - public/language/es/admin/settings/user.json | 1 - public/language/es/login.json | 1 - public/language/et/admin/settings/user.json | 1 - public/language/et/login.json | 1 - public/language/fa-IR/admin/settings/user.json | 1 - public/language/fa-IR/login.json | 1 - public/language/fi/admin/settings/user.json | 1 - public/language/fi/login.json | 1 - public/language/fr/admin/settings/user.json | 1 - public/language/fr/login.json | 1 - public/language/gl/admin/settings/user.json | 1 - public/language/gl/login.json | 1 - public/language/he/admin/settings/user.json | 1 - public/language/he/login.json | 1 - public/language/hr/admin/settings/user.json | 1 - public/language/hr/login.json | 1 - public/language/hu/admin/settings/user.json | 1 - public/language/hu/login.json | 1 - public/language/id/admin/settings/user.json | 1 - public/language/id/login.json | 1 - public/language/it/admin/settings/user.json | 1 - public/language/it/login.json | 1 - public/language/ja/admin/settings/user.json | 1 - public/language/ja/login.json | 1 - public/language/ko/admin/settings/user.json | 1 - public/language/ko/login.json | 1 - public/language/lt/admin/settings/user.json | 1 - public/language/lt/login.json | 1 - public/language/lv/admin/settings/user.json | 1 - public/language/lv/login.json | 1 - public/language/ms/admin/settings/user.json | 1 - public/language/ms/login.json | 1 - public/language/nb/admin/settings/user.json | 1 - public/language/nb/login.json | 1 - public/language/nl/admin/settings/user.json | 1 - public/language/nl/login.json | 1 - public/language/pl/admin/settings/user.json | 1 - public/language/pl/login.json | 1 - public/language/pt-BR/admin/settings/user.json | 1 - public/language/pt-BR/login.json | 1 - public/language/pt-PT/admin/settings/user.json | 1 - public/language/pt-PT/login.json | 1 - public/language/ro/admin/settings/user.json | 1 - public/language/ro/login.json | 1 - public/language/ru/admin/settings/user.json | 1 - public/language/ru/login.json | 1 - public/language/rw/admin/settings/user.json | 1 - public/language/rw/login.json | 1 - public/language/sc/admin/settings/user.json | 1 - public/language/sc/login.json | 1 - public/language/sk/admin/settings/user.json | 1 - public/language/sk/login.json | 1 - public/language/sl/admin/settings/user.json | 1 - public/language/sl/login.json | 1 - public/language/sr/admin/settings/user.json | 1 - public/language/sr/login.json | 1 - public/language/sv/admin/settings/user.json | 1 - public/language/sv/login.json | 1 - public/language/th/admin/settings/user.json | 1 - public/language/th/login.json | 1 - public/language/tr/admin/settings/user.json | 1 - public/language/tr/login.json | 1 - public/language/uk/admin/settings/user.json | 1 - public/language/uk/login.json | 1 - public/language/vi/admin/settings/user.json | 1 - public/language/vi/login.json | 1 - public/language/zh-CN/admin/settings/user.json | 1 - public/language/zh-CN/login.json | 1 - public/language/zh-TW/admin/settings/user.json | 1 - public/language/zh-TW/login.json | 1 - 88 files changed, 88 deletions(-) diff --git a/public/language/ar/admin/settings/user.json b/public/language/ar/admin/settings/user.json index 074655c026..ecff8a7ee4 100644 --- a/public/language/ar/admin/settings/user.json +++ b/public/language/ar/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "السماح بتسجيل الدخول باستخدام", "allow-login-with.username-email": "اسم المستخدم أو البريد الالكتروني", "allow-login-with.username": "اسم المستخدم فقط", - "allow-login-with.email": "البريد الالكتروني فقط", "account-settings": "إعدادت الحساب", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/ar/login.json b/public/language/ar/login.json index 8943235d6f..2912e45b88 100644 --- a/public/language/ar/login.json +++ b/public/language/ar/login.json @@ -1,7 +1,6 @@ { "username-email": "اسم المستخدم / البريد الإلكتروني", "username": "اسم المستخدم", - "email": "البريد الإلكتروني", "remember_me": "تذكرني؟", "forgot_password": "نسيت كلمة المرور؟", "alternative_logins": "تسجيلات الدخول البديلة", diff --git a/public/language/bg/admin/settings/user.json b/public/language/bg/admin/settings/user.json index e04b10dea6..8224155268 100644 --- a/public/language/bg/admin/settings/user.json +++ b/public/language/bg/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Позволяване на вписването чрез", "allow-login-with.username-email": "Потребителско име или е-поща", "allow-login-with.username": "Само потребителско име", - "allow-login-with.email": "Само е-поща", "account-settings": "Настройки на акаунта", "gdpr_enabled": "Включване на искането за съгласие с ОРЗД", "gdpr_enabled_help": "Ако това е включено, всички новорегистрирани потребители ще бъдат задължени изрично да дадат съгласието си за събирането на данни и статистики за потреблението според Общия регламент относно защитата на данните (ОРЗД). Забележка: Включването на ОРЗД не задължава съществуващите потребители да дадат съгласието си. Ако искате това, ще трябва да инсталирате добавката за ОРЗД (GDPR).", diff --git a/public/language/bg/login.json b/public/language/bg/login.json index a619382915..efe62d3a5c 100644 --- a/public/language/bg/login.json +++ b/public/language/bg/login.json @@ -1,7 +1,6 @@ { "username-email": "Потребителско име / е-поща", "username": "Потребителско име", - "email": "Е-поща", "remember_me": "Запомнете ме?", "forgot_password": "Забравена парола?", "alternative_logins": "Други начини за вписване", diff --git a/public/language/bn/admin/settings/user.json b/public/language/bn/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/bn/admin/settings/user.json +++ b/public/language/bn/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/bn/login.json b/public/language/bn/login.json index 36339fb659..b3d7c945d6 100644 --- a/public/language/bn/login.json +++ b/public/language/bn/login.json @@ -1,7 +1,6 @@ { "username-email": "ইউজারনেম / ইমেইল", "username": "ইউজারনেম", - "email": "ইমেইল", "remember_me": "মনে রাখুন", "forgot_password": "পাসওয়ার্ড ভুলে গিয়েছেন?", "alternative_logins": "বিকল্প প্রবেশ", diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json index b4725da73d..fd2768b59e 100644 --- a/public/language/cs/admin/settings/user.json +++ b/public/language/cs/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Povolit přihlášení pomocí", "allow-login-with.username-email": "Uživatelské jméno nebo e-mail", "allow-login-with.username": "Pouze uživatelské jméno", - "allow-login-with.email": "Pouze e-mail", "account-settings": "Nastavení účtu", "gdpr_enabled": "Povolit souhlas s GDPR", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/cs/login.json b/public/language/cs/login.json index 894cd0f36c..49463eae9d 100644 --- a/public/language/cs/login.json +++ b/public/language/cs/login.json @@ -1,7 +1,6 @@ { "username-email": "Uživatelské jméno / e-mail", "username": "Uživatel", - "email": "E-mail", "remember_me": "Zapamatovat si mě?", "forgot_password": "Zapomněli jste heslo?", "alternative_logins": "Další způsoby přihlášení", diff --git a/public/language/da/admin/settings/user.json b/public/language/da/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/da/admin/settings/user.json +++ b/public/language/da/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/da/login.json b/public/language/da/login.json index 97662169d2..26d4ad2143 100644 --- a/public/language/da/login.json +++ b/public/language/da/login.json @@ -1,7 +1,6 @@ { "username-email": "Brugernavn / Email", "username": "Brugernavn", - "email": "Email", "remember_me": "Husk mig?", "forgot_password": "Glemt kodeord?", "alternative_logins": "alternative logins", diff --git a/public/language/de/admin/settings/user.json b/public/language/de/admin/settings/user.json index 795bf4a4b0..1d6593d8b7 100644 --- a/public/language/de/admin/settings/user.json +++ b/public/language/de/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Erlaube Login mit", "allow-login-with.username-email": "Benutzername oder E-Mail", "allow-login-with.username": "Nur Benutzername", - "allow-login-with.email": "Nur E-Mail", "account-settings": "Kontoeinstellungen", "gdpr_enabled": "Aktivieren Sie die DSGVO-Zustimmungserfassung", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/de/login.json b/public/language/de/login.json index f2cccf28e4..7f44c64f6e 100644 --- a/public/language/de/login.json +++ b/public/language/de/login.json @@ -1,7 +1,6 @@ { "username-email": "Benutzername / E-Mail-Adresse", "username": "Benutzername", - "email": "E-Mail", "remember_me": "Eingeloggt bleiben?", "forgot_password": "Passwort vergessen?", "alternative_logins": "Alternative Logins", diff --git a/public/language/el/admin/settings/user.json b/public/language/el/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/el/admin/settings/user.json +++ b/public/language/el/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/el/login.json b/public/language/el/login.json index 91811dffbf..fd1297f017 100644 --- a/public/language/el/login.json +++ b/public/language/el/login.json @@ -1,7 +1,6 @@ { "username-email": "Όνομα χρήστη / Email", "username": "Όνομα Χρήστη", - "email": "Email", "remember_me": "Απομνημόνευση;", "forgot_password": "Ξέχασες τον κωδικό σου;", "alternative_logins": "Εναλλακτικά Login", diff --git a/public/language/en-US/admin/settings/user.json b/public/language/en-US/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/en-US/admin/settings/user.json +++ b/public/language/en-US/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/en-US/login.json b/public/language/en-US/login.json index af5b41eaa5..161982bdfa 100644 --- a/public/language/en-US/login.json +++ b/public/language/en-US/login.json @@ -1,7 +1,6 @@ { "username-email": "Username / Email", "username": "Username", - "email": "Email", "remember_me": "Remember Me?", "forgot_password": "Forgot Password?", "alternative_logins": "Alternative Logins", diff --git a/public/language/en-x-pirate/admin/settings/user.json b/public/language/en-x-pirate/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/en-x-pirate/admin/settings/user.json +++ b/public/language/en-x-pirate/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/en-x-pirate/login.json b/public/language/en-x-pirate/login.json index 5cdb8a5488..6327a0b815 100644 --- a/public/language/en-x-pirate/login.json +++ b/public/language/en-x-pirate/login.json @@ -1,7 +1,6 @@ { "username-email": "Username / Email", "username": "Username", - "email": "Email", "remember_me": "Remember Me?", "forgot_password": "My mind be a scatt'rbrain, help a matey out!", "alternative_logins": "Oth'r gangplanks", diff --git a/public/language/es/admin/settings/user.json b/public/language/es/admin/settings/user.json index a520898501..0da1f353e7 100644 --- a/public/language/es/admin/settings/user.json +++ b/public/language/es/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Permitir login con", "allow-login-with.username-email": "Nombre de usuario o Email", "allow-login-with.username": "Solo Nombre de Usuario", - "allow-login-with.email": "Solo Email", "account-settings": "Configuración de la Cuenta", "gdpr_enabled": "Habilitar la recolección del consentimiento GDPR", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/es/login.json b/public/language/es/login.json index 18512ad400..6596ea35f0 100644 --- a/public/language/es/login.json +++ b/public/language/es/login.json @@ -1,7 +1,6 @@ { "username-email": "Usuario / Email", "username": "Usuario", - "email": "Correo Electrónico", "remember_me": "¿Recordarme?", "forgot_password": "¿Olvidaste tu contraseña?", "alternative_logins": "Accesos alternativos", diff --git a/public/language/et/admin/settings/user.json b/public/language/et/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/et/admin/settings/user.json +++ b/public/language/et/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/et/login.json b/public/language/et/login.json index 4941681d68..331701929b 100644 --- a/public/language/et/login.json +++ b/public/language/et/login.json @@ -1,7 +1,6 @@ { "username-email": "Kasutajanimi / E-mail", "username": "Kasutajanimi", - "email": "E-mail.", "remember_me": "Mäleta mind?", "forgot_password": "Unustasid parooli?", "alternative_logins": "Alternatiivsed sisse logimise võimalused", diff --git a/public/language/fa-IR/admin/settings/user.json b/public/language/fa-IR/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/fa-IR/admin/settings/user.json +++ b/public/language/fa-IR/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/fa-IR/login.json b/public/language/fa-IR/login.json index ebf0a1c42b..50a0d06f2a 100644 --- a/public/language/fa-IR/login.json +++ b/public/language/fa-IR/login.json @@ -1,7 +1,6 @@ { "username-email": "نام کاربری / ایمیل", "username": "نام کاربری", - "email": "ایمیل", "remember_me": "مرا به یاد بسپار؟", "forgot_password": "رمز عبور را فراموش کرده‌اید؟", "alternative_logins": "روش‌های ثبت نام جایگزین", diff --git a/public/language/fi/admin/settings/user.json b/public/language/fi/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/fi/admin/settings/user.json +++ b/public/language/fi/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/fi/login.json b/public/language/fi/login.json index 8fd00c586e..ac5163d08f 100644 --- a/public/language/fi/login.json +++ b/public/language/fi/login.json @@ -1,7 +1,6 @@ { "username-email": "Käyttäjätunnus / Sähköposti", "username": "Käyttäjätunnus", - "email": "Sähköposti", "remember_me": "Muista minut?", "forgot_password": "Unohditko salasanasi?", "alternative_logins": "Vaihtoehtoiset kirjautumistavat", diff --git a/public/language/fr/admin/settings/user.json b/public/language/fr/admin/settings/user.json index 0a736319e6..2f1af27cae 100644 --- a/public/language/fr/admin/settings/user.json +++ b/public/language/fr/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Autoriser l'identification avec", "allow-login-with.username-email": "Nom d'utilisateur ou e-mail", "allow-login-with.username": "Nom d'utilisateur uniquement", - "allow-login-with.email": "E-mail uniquement", "account-settings": "Paramètres du compte", "gdpr_enabled": "Activer le consentement GRPD", "gdpr_enabled_help": "Une fois activé, tous les nouveaux inscrits seront tenus de donner explicitement leur consentement pour la collecte et l'utilisation des données en vertu du Règlement Général sur la Protection des Données (RGPD). Remarque: l'activation du RGPD n'oblige pas les utilisateurs préexistants à donner leur consentement. Pour ce faire, vous devrez installer le plugin GDPR.", diff --git a/public/language/fr/login.json b/public/language/fr/login.json index 4bb0bd2da4..330bba51ed 100644 --- a/public/language/fr/login.json +++ b/public/language/fr/login.json @@ -1,7 +1,6 @@ { "username-email": "Identifiant ou email", "username": "Identifiant", - "email": "Email", "remember_me": "Se souvenir de moi ?", "forgot_password": "Mot de passe oublié ?", "alternative_logins": "Autres méthodes de connexion", diff --git a/public/language/gl/admin/settings/user.json b/public/language/gl/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/gl/admin/settings/user.json +++ b/public/language/gl/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/gl/login.json b/public/language/gl/login.json index 58fcc8c43a..b1508c9cda 100644 --- a/public/language/gl/login.json +++ b/public/language/gl/login.json @@ -1,7 +1,6 @@ { "username-email": "Usuario / Correo electrónico", "username": "Usuario", - "email": "Correo Electrónico", "remember_me": "Lembrarme?", "forgot_password": "Esqueciches o teu contrasinal?", "alternative_logins": "Métodos alternativos", diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json index 04acac53ad..0320d0e856 100644 --- a/public/language/he/admin/settings/user.json +++ b/public/language/he/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "אפשר התחברות עם", "allow-login-with.username-email": "שם משתמש או סיסמא", "allow-login-with.username": "שם משתמש בלבד", - "allow-login-with.email": "כתובת מייל בלבד", "account-settings": "הגדרות חשבון", "gdpr_enabled": "אפשר הסכמת איסוף נתונים GDPR", "gdpr_enabled_help": "כאשר תאפשר, כל המשתמשים החדשים יידרשו לתת הסכמה באופן מפורש לאיסוף ושימוש בנתונים תחת ה התקנה הכללית להגנת נתונים (GDPR). הערה: הפעלת GDPR לא יכריח משתמשים קיימים לאשר הסכמה. כדי לעשות זאת, תצטרכו להתקין את התוסף GDPR.", diff --git a/public/language/he/login.json b/public/language/he/login.json index 3e72a5f159..f8b86ca460 100644 --- a/public/language/he/login.json +++ b/public/language/he/login.json @@ -1,7 +1,6 @@ { "username-email": "שם משתמש/אימייל", "username": "שם משתמש", - "email": "אימייל", "remember_me": "זכור אותי?", "forgot_password": "שכחת סיסמתך?", "alternative_logins": "התחבר באמצעות...", diff --git a/public/language/hr/admin/settings/user.json b/public/language/hr/admin/settings/user.json index e2f9d018fa..13a6fabd04 100644 --- a/public/language/hr/admin/settings/user.json +++ b/public/language/hr/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Dozvoli prijavu sa", "allow-login-with.username-email": "Korisničko ime ili Email", "allow-login-with.username": "Korisničko ime", - "allow-login-with.email": "Samo email", "account-settings": "Postavke računa", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/hr/login.json b/public/language/hr/login.json index aeb975e7c2..46d817fdb4 100644 --- a/public/language/hr/login.json +++ b/public/language/hr/login.json @@ -1,7 +1,6 @@ { "username-email": "Korisničko ime / Email", "username": "Korisničko ime", - "email": "Email", "remember_me": "Zapamti me?", "forgot_password": "Zaboravljena lozinka?", "alternative_logins": "Alternativne prijave", diff --git a/public/language/hu/admin/settings/user.json b/public/language/hu/admin/settings/user.json index d0b96ca525..c207b6ade9 100644 --- a/public/language/hu/admin/settings/user.json +++ b/public/language/hu/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Bejelentkezés engedélyezése ezzel:", "allow-login-with.username-email": "Felhasználónév vagy email cím", "allow-login-with.username": "Csak felhasználónév", - "allow-login-with.email": "Csak email cím", "account-settings": "Fiók beállítások", "gdpr_enabled": "GDPR hozzájárulás gyűjtésének engedélyezése", "gdpr_enabled_help": "Ha engedélyezett, minden új regisztrációnál hozzájárulását kell adnia a felhasználónak az adatai mentéséhez és felhasználásához az Általános adatvédelmi rendelet(GDPR) értelmében. Megjegyzés: A GDPR engedélyezése nem kéri a már meglévő felhasználóktól, hogy fogadják el az adatgyűjtést és felhasználást. Ehhez telepítened kell a GDPR beépülőt.", diff --git a/public/language/hu/login.json b/public/language/hu/login.json index b48dce9942..d9874de691 100644 --- a/public/language/hu/login.json +++ b/public/language/hu/login.json @@ -1,7 +1,6 @@ { "username-email": "Felhasználónév / E-mail", "username": "Felhasználónév", - "email": "E-mail", "remember_me": "Emlékezzen rám?", "forgot_password": "Elfelejtetted a jelszót?", "alternative_logins": "Alternatív belépés", diff --git a/public/language/id/admin/settings/user.json b/public/language/id/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/id/admin/settings/user.json +++ b/public/language/id/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/id/login.json b/public/language/id/login.json index 4ccf372590..15c4c5c63a 100644 --- a/public/language/id/login.json +++ b/public/language/id/login.json @@ -1,7 +1,6 @@ { "username-email": "Username / Email", "username": "Username", - "email": "Email", "remember_me": "Ingin Diingat?", "forgot_password": "Lupa Password?", "alternative_logins": "Login Alternatif", diff --git a/public/language/it/admin/settings/user.json b/public/language/it/admin/settings/user.json index ed79c8ebc3..0b244ffba9 100644 --- a/public/language/it/admin/settings/user.json +++ b/public/language/it/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Consenti l'accesso con", "allow-login-with.username-email": "Username o Email", "allow-login-with.username": "Solo Username", - "allow-login-with.email": "Solo Email", "account-settings": "Impostazioni Account", "gdpr_enabled": "Abilita la raccolta di consensi GDPR", "gdpr_enabled_help": "Quando è abilitato, tutti i nuovi registranti dovranno dare il loro consenso esplicito per la raccolta e l'utilizzo dei dati ai sensi del regolamento generale sulla protezione dei dati (GDPR). Nota: L'abilitazione del GDPR non obbliga gli utenti preesistenti a fornire il consenso. Per farlo, è necessario installare il plugin GDPR.", diff --git a/public/language/it/login.json b/public/language/it/login.json index dcfac5694d..e4907ff99a 100644 --- a/public/language/it/login.json +++ b/public/language/it/login.json @@ -1,7 +1,6 @@ { "username-email": "Nome utente / Email", "username": "Nome utente", - "email": "Email", "remember_me": "Ricordami?", "forgot_password": "Password dimenticata?", "alternative_logins": "Accessi alternativi", diff --git a/public/language/ja/admin/settings/user.json b/public/language/ja/admin/settings/user.json index 41762e6abf..08fadd8c04 100644 --- a/public/language/ja/admin/settings/user.json +++ b/public/language/ja/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "ログインを許可", "allow-login-with.username-email": "ユーザー名または電子メール", "allow-login-with.username": "ユーザー名のみ", - "allow-login-with.email": "メールのみ", "account-settings": "アカウント設定", "gdpr_enabled": "GDPR同意収集を有効にする", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/ja/login.json b/public/language/ja/login.json index 316f0f5e35..3cbfb4beee 100644 --- a/public/language/ja/login.json +++ b/public/language/ja/login.json @@ -1,7 +1,6 @@ { "username-email": "ユーザー名またはメールアドレス", "username": "ユーザー名", - "email": "メール", "remember_me": "ログイン情報を記憶", "forgot_password": "パスワードを忘れましたか?", "alternative_logins": "ほかのログイン方法", diff --git a/public/language/ko/admin/settings/user.json b/public/language/ko/admin/settings/user.json index feb1658be6..23266010b6 100644 --- a/public/language/ko/admin/settings/user.json +++ b/public/language/ko/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "로그인 허용 수단", "allow-login-with.username-email": "사용자명 또는 이메일", "allow-login-with.username": "사용자명", - "allow-login-with.email": "이메일", "account-settings": "계정 관리", "gdpr_enabled": "GDPR 동의 수집 활성화", "gdpr_enabled_help": "활성화되면 모든 신규 등록자는 General Data Protection Regulation (GDPR)에 따라 데이터 수집 및 사용에 대해 명시적으로 동의해야 합니다. 참고: GDPR을 활성화해도 기존 사용자가 동의하지 않을 수 있습니다. 동의를 강제하려면 GDPR 플러그인을 설치해야 합니다.", diff --git a/public/language/ko/login.json b/public/language/ko/login.json index c2515f8ae3..4a3f9a875b 100644 --- a/public/language/ko/login.json +++ b/public/language/ko/login.json @@ -1,7 +1,6 @@ { "username-email": "사용자명 / 이메일", "username": "사용자명", - "email": "이메일", "remember_me": "로그인 유지", "forgot_password": "비밀번호 초기화", "alternative_logins": "다른 방법으로 로그인", diff --git a/public/language/lt/admin/settings/user.json b/public/language/lt/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/lt/admin/settings/user.json +++ b/public/language/lt/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/lt/login.json b/public/language/lt/login.json index 26ea6efe6c..b51df6c453 100644 --- a/public/language/lt/login.json +++ b/public/language/lt/login.json @@ -1,7 +1,6 @@ { "username-email": "Vartotojo vardas / El. paštas", "username": "Vartotojo vardas", - "email": "El. paštas", "remember_me": "Prisiminti?", "forgot_password": "Užmiršote slaptažodį?", "alternative_logins": "Alternatyvūs prisijungimo būdai", diff --git a/public/language/lv/admin/settings/user.json b/public/language/lv/admin/settings/user.json index e70445837c..aae6d86d40 100644 --- a/public/language/lv/admin/settings/user.json +++ b/public/language/lv/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Ielogoties", "allow-login-with.username-email": "Ar lietotājvārdu vai e-pasta adresi", "allow-login-with.username": "Tikai ar lietotājvārdu", - "allow-login-with.email": "Tikai ar e-pasta adresi", "account-settings": "Kontu iestatījumi", "gdpr_enabled": "Iespējot VDAR piekrišanas vākšanu", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/lv/login.json b/public/language/lv/login.json index 79cd4fdbb8..26b7bc57df 100644 --- a/public/language/lv/login.json +++ b/public/language/lv/login.json @@ -1,7 +1,6 @@ { "username-email": "Lietotājvārds / e-pasta adrese", "username": "Lietotājvārds", - "email": "E-pasta adrese", "remember_me": "Atcerēties mani?", "forgot_password": "Aizmirsi paroli?", "alternative_logins": "Alternatīvie lietotājvārdi", diff --git a/public/language/ms/admin/settings/user.json b/public/language/ms/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/ms/admin/settings/user.json +++ b/public/language/ms/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/ms/login.json b/public/language/ms/login.json index 75c94ab47e..5e719043e0 100644 --- a/public/language/ms/login.json +++ b/public/language/ms/login.json @@ -1,7 +1,6 @@ { "username-email": "Nama pengguna / Emel", "username": "Nama pengguna", - "email": "Emel", "remember_me": "Ingatkan Saya", "forgot_password": "Lupa Kata Laluan?", "alternative_logins": "Log Masuk Alternatif", diff --git a/public/language/nb/admin/settings/user.json b/public/language/nb/admin/settings/user.json index f218395cb3..e285224022 100644 --- a/public/language/nb/admin/settings/user.json +++ b/public/language/nb/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Tillat innlogging med", "allow-login-with.username-email": "Brukernavn eller e-post", "allow-login-with.username": "Kun brukernavn", - "allow-login-with.email": "Kun e-post", "account-settings": "Kontoinnstillinger ", "gdpr_enabled": "Aktiver innhenting av GDPR-samtykke", "gdpr_enabled_help": "Når aktivert, vil alle nye registranter være pålagt å eksplisitt gi samtykke til datainnsamling og behandling under Personvernforordningen (GDPR). Merk: Aktivering av GDPR tvinger ikke eksisterende brukere til å gi samtykke. For å gjøre dette, må du installere GDPR-programutvidelse. ", diff --git a/public/language/nb/login.json b/public/language/nb/login.json index ef7d3a0926..b63adf2b7c 100644 --- a/public/language/nb/login.json +++ b/public/language/nb/login.json @@ -1,7 +1,6 @@ { "username-email": "Brukernavn / E-post", "username": "Brukernavn", - "email": "E-post", "remember_me": "Husk meg?", "forgot_password": "Glemt passord?", "alternative_logins": "Alternativ innlogging", diff --git a/public/language/nl/admin/settings/user.json b/public/language/nl/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/nl/admin/settings/user.json +++ b/public/language/nl/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/nl/login.json b/public/language/nl/login.json index adad019683..931d2e5d7f 100644 --- a/public/language/nl/login.json +++ b/public/language/nl/login.json @@ -1,7 +1,6 @@ { "username-email": "Gebruikersnaam / Email", "username": "Gebruikersnaam", - "email": "E-mail", "remember_me": "Aangemeld blijven?", "forgot_password": "Wachtwoord vergeten?", "alternative_logins": "Andere manieren van aanmelden", diff --git a/public/language/pl/admin/settings/user.json b/public/language/pl/admin/settings/user.json index 3de21179b2..c42d455435 100644 --- a/public/language/pl/admin/settings/user.json +++ b/public/language/pl/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Zezwalaj na logowanie przy użyciu", "allow-login-with.username-email": "Nazwy użytkownika lub adresu email", "allow-login-with.username": "Tylko nazwy użytkownika", - "allow-login-with.email": "Tylko adresu email", "account-settings": "Ustawienia konta", "gdpr_enabled": "Włącz gromadzenie danych (RODO)", "gdpr_enabled_help": "Po włączeniu wszyscy nowi użytkownicy będą musieli jednoznacznie wyrazić zgodę na gromadzenie i wykorzystanie danych na mocy ogólnego rozporządzenia o ochronie danych (RODO). Uwaga: włączenie RODO nie zmusza istniejących użytkowników do wyrażenia zgody. Aby to zrobić, musisz zainstalować wtyczkę GDPR.", diff --git a/public/language/pl/login.json b/public/language/pl/login.json index 8261cbfafe..6de924a5bf 100644 --- a/public/language/pl/login.json +++ b/public/language/pl/login.json @@ -1,7 +1,6 @@ { "username-email": "Nazwa użytkownika lub adres e-mail", "username": "Nazwa użytkownika", - "email": "Adres e-mail", "remember_me": "Zapamiętaj mnie", "forgot_password": "Nie pamiętasz hasła?", "alternative_logins": "Alternatywne logowanie", diff --git a/public/language/pt-BR/admin/settings/user.json b/public/language/pt-BR/admin/settings/user.json index ecf98a2b12..a8db053cef 100644 --- a/public/language/pt-BR/admin/settings/user.json +++ b/public/language/pt-BR/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Permitir login com", "allow-login-with.username-email": "Nome de Usuário ou E-mail", "allow-login-with.username": "Apenas Nome de Usuário", - "allow-login-with.email": "Apenas E-mail", "account-settings": "Configurações de Conta", "gdpr_enabled": "Ativar coleta de consentimento do GDPR", "gdpr_enabled_help": "Quando ativado, todos os novos registrantes serão obrigados a dar consentimento explícito para a coleta de dados e uso sob o General Data Protection Regulation (GDPR). Nota: Ativar o GDPR não força usuários pré-existentes a fornecer consentimento. Para fazer isso, você precisará instalar o plug-in GDPR.", diff --git a/public/language/pt-BR/login.json b/public/language/pt-BR/login.json index 3d7dafb7af..d4ad3be6d0 100644 --- a/public/language/pt-BR/login.json +++ b/public/language/pt-BR/login.json @@ -1,7 +1,6 @@ { "username-email": "Nome de usuário / Email", "username": "Nome de usuário", - "email": "Email", "remember_me": "Lembrar de Mim?", "forgot_password": "Esqueceu a Senha?", "alternative_logins": "Logins Alternativos", diff --git a/public/language/pt-PT/admin/settings/user.json b/public/language/pt-PT/admin/settings/user.json index eb7ee976c1..f989d486a8 100644 --- a/public/language/pt-PT/admin/settings/user.json +++ b/public/language/pt-PT/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Permitir início de sessão com", "allow-login-with.username-email": "Nome de Utilizador ou E-mail", "allow-login-with.username": "Nome de Utilizador Apenas", - "allow-login-with.email": "Apenas E-mail", "account-settings": "Definições de Conta", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/pt-PT/login.json b/public/language/pt-PT/login.json index e11772dae2..7f0f1c509c 100644 --- a/public/language/pt-PT/login.json +++ b/public/language/pt-PT/login.json @@ -1,7 +1,6 @@ { "username-email": "Nome de utilizador / E-mail", "username": "Nome de utilizador", - "email": "E-mail", "remember_me": "Lembrar-me", "forgot_password": "Esqueceste-te da palavra-passe?", "alternative_logins": "Inícios de sessão alternativos", diff --git a/public/language/ro/admin/settings/user.json b/public/language/ro/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/ro/admin/settings/user.json +++ b/public/language/ro/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/ro/login.json b/public/language/ro/login.json index 531f2642d4..2d71d22e51 100644 --- a/public/language/ro/login.json +++ b/public/language/ro/login.json @@ -1,7 +1,6 @@ { "username-email": "Utilizator/Email", "username": "Utilizator", - "email": "Email", "remember_me": "Autentifică-mă automat la fiecare vizită", "forgot_password": "Ai uitat parola?", "alternative_logins": "Autentificare Alternativă", diff --git a/public/language/ru/admin/settings/user.json b/public/language/ru/admin/settings/user.json index 9dfd0f7d5d..b259a3d941 100644 --- a/public/language/ru/admin/settings/user.json +++ b/public/language/ru/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Разрешить вход с помощью", "allow-login-with.username-email": "Имени пользователя или адреса электронной почты", "allow-login-with.username": "Только имени пользователя", - "allow-login-with.email": "Только адреса электронной почты", "account-settings": "Настройки учётной записи", "gdpr_enabled": "Запрашивать согласие на сбор и обработку персональных данных", "gdpr_enabled_help": "Включите, чтобы при регистрации новых пользователей запрашивать согласие на сбор и обработку данных в соответствии с законом о General Data Protection Regulation (GDPR). Примечание: эта настойка не повлияет на уже зарегистрированных пользователей. Чтобы запросить их согласие, установите плагин GDPR.", diff --git a/public/language/ru/login.json b/public/language/ru/login.json index c3d9643cf1..e3890407f0 100644 --- a/public/language/ru/login.json +++ b/public/language/ru/login.json @@ -1,7 +1,6 @@ { "username-email": "Имя пользователя / Email", "username": "Имя пользователя", - "email": "Email", "remember_me": "Запомнить меня", "forgot_password": "Забыли пароль?", "alternative_logins": "Войти через", diff --git a/public/language/rw/admin/settings/user.json b/public/language/rw/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/rw/admin/settings/user.json +++ b/public/language/rw/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/rw/login.json b/public/language/rw/login.json index b1082e1279..b5856a55d4 100644 --- a/public/language/rw/login.json +++ b/public/language/rw/login.json @@ -1,7 +1,6 @@ { "username-email": "Izina / Email", "username": "Izina ", - "email": "Email", "remember_me": "Wibukwe?", "forgot_password": "Wibagiwe ijambobanga?", "alternative_logins": "Ukundi Wakwinjiramo", diff --git a/public/language/sc/admin/settings/user.json b/public/language/sc/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/sc/admin/settings/user.json +++ b/public/language/sc/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/sc/login.json b/public/language/sc/login.json index 15fc544631..f58314c709 100644 --- a/public/language/sc/login.json +++ b/public/language/sc/login.json @@ -1,7 +1,6 @@ { "username-email": "Username / Email", "username": "Username", - "email": "Email", "remember_me": "Regorda·mi?", "forgot_password": "Password Iscarèssida?", "alternative_logins": "Intradas Alternativas", diff --git a/public/language/sk/admin/settings/user.json b/public/language/sk/admin/settings/user.json index eac0739010..552344d140 100644 --- a/public/language/sk/admin/settings/user.json +++ b/public/language/sk/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Povoliť prihlásenie pomocou", "allow-login-with.username-email": "Používateľské meno alebo e-mail", "allow-login-with.username": "Iba používateľské meno", - "allow-login-with.email": "Iba e-mail", "account-settings": "Nastavenia účtu", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/sk/login.json b/public/language/sk/login.json index 89220079c0..dd2521d2d0 100644 --- a/public/language/sk/login.json +++ b/public/language/sk/login.json @@ -1,7 +1,6 @@ { "username-email": "Uživateľské meno / E-mail", "username": "Používateľské meno", - "email": "E-mail", "remember_me": "Zapamätať si ma?", "forgot_password": "Zabudli ste heslo?", "alternative_logins": "Ďalšie spôsoby prihlásenia", diff --git a/public/language/sl/admin/settings/user.json b/public/language/sl/admin/settings/user.json index d680faf1ad..c4da62d241 100644 --- a/public/language/sl/admin/settings/user.json +++ b/public/language/sl/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Dovoli prijavo z", "allow-login-with.username-email": "Uporabniško ime ali e-poštni naslov", "allow-login-with.username": "Samo uporabniško ime", - "allow-login-with.email": "Samo e-poštni naslov", "account-settings": "Nastavitve računa", "gdpr_enabled": "Omogoči zbiranje GDPR soglasij", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/sl/login.json b/public/language/sl/login.json index d4d135b3ea..f8454f27ec 100644 --- a/public/language/sl/login.json +++ b/public/language/sl/login.json @@ -1,7 +1,6 @@ { "username-email": "Uporabniško ime/E-pošta", "username": "Uporabniško ime", - "email": "E-pošta", "remember_me": "Zapomni si me.", "forgot_password": "Ste pozabili geslo?", "alternative_logins": "Alternativne prijave", diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json index a0327e470c..44533c4a62 100644 --- a/public/language/sr/admin/settings/user.json +++ b/public/language/sr/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Dozvoli login sa", "allow-login-with.username-email": "Korisničko ime ili Email", "allow-login-with.username": "Samo korisničko ime", - "allow-login-with.email": "Samo Email", "account-settings": "Podešavanje naloga", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/sr/login.json b/public/language/sr/login.json index e17ba474a6..74eda37de6 100644 --- a/public/language/sr/login.json +++ b/public/language/sr/login.json @@ -1,7 +1,6 @@ { "username-email": "Корисничко име / Е-пошта", "username": "Корисничко име", - "email": "Е-пошта", "remember_me": "Запамти ме?", "forgot_password": "Заборављена лозинка?", "alternative_logins": "Алтернативна пријављивања", diff --git a/public/language/sv/admin/settings/user.json b/public/language/sv/admin/settings/user.json index 7923bf8cbe..7695bdddae 100644 --- a/public/language/sv/admin/settings/user.json +++ b/public/language/sv/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/sv/login.json b/public/language/sv/login.json index 81890556ff..3dc899fded 100644 --- a/public/language/sv/login.json +++ b/public/language/sv/login.json @@ -1,7 +1,6 @@ { "username-email": "Namn / Epost", "username": "Namn", - "email": "Epost", "remember_me": "Kom ihåg mig?", "forgot_password": "Glömt lösenord?", "alternative_logins": "Alternativa inloggningssätt", diff --git a/public/language/th/admin/settings/user.json b/public/language/th/admin/settings/user.json index a6c888499c..83281ffeca 100644 --- a/public/language/th/admin/settings/user.json +++ b/public/language/th/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Allow login with", "allow-login-with.username-email": "Username or Email", "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", "account-settings": "Account Settings", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/th/login.json b/public/language/th/login.json index ef50ef13a6..e8c17a34a2 100644 --- a/public/language/th/login.json +++ b/public/language/th/login.json @@ -1,7 +1,6 @@ { "username-email": "ชื่อผู้ใช้ / อีเมล", "username": "ชื่อผู้ใช้", - "email": "อีเมล", "remember_me": "จำไว้ในระบบ?", "forgot_password": "ลืมรหัสผ่าน?", "alternative_logins": "เข้าสู่ระบบโดยทางอื่น", diff --git a/public/language/tr/admin/settings/user.json b/public/language/tr/admin/settings/user.json index ed09675de3..2f63d49d2f 100644 --- a/public/language/tr/admin/settings/user.json +++ b/public/language/tr/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Girişe izin ver:", "allow-login-with.username-email": "Kullanıcı Adı veya E-posta", "allow-login-with.username": "Sadece kullanıcı adı", - "allow-login-with.email": "Sadece E-posta", "account-settings": "Hesap Ayarları", "gdpr_enabled": "GDPR veri toplamayı etkinleştir", "gdpr_enabled_help": "Etkinleştirilirse, yeni kayıt olan kullanıcılar General Data Protection Regulation (GDPR) düzenlemeleri altında veri toplanılması ve kullanılması için açık bir şekilde izin vermiş olurlar. Not: GDPR özelliğini aktifleştirmek halihazırda kayıtlı olan kişileri etkilemez. Şu anki kayıtlı kullanıcıların bu düzenlemeleri kabul etmesi için GDPR eklentisini yüklemelisiniz.", diff --git a/public/language/tr/login.json b/public/language/tr/login.json index 6d4a8b8762..40167b5d61 100644 --- a/public/language/tr/login.json +++ b/public/language/tr/login.json @@ -1,7 +1,6 @@ { "username-email": "Kullanıcı Adı / E-posta Adresi", "username": "Kullanıcı Adı", - "email": "E-posta Adresi", "remember_me": "Beni Hatırla!", "forgot_password": "Şifrenizi mi unuttunuz?", "alternative_logins": "Alternatif Girişler", diff --git a/public/language/uk/admin/settings/user.json b/public/language/uk/admin/settings/user.json index e2036b2820..d1ddd7a5d5 100644 --- a/public/language/uk/admin/settings/user.json +++ b/public/language/uk/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Дозволити вхід використовуючи", "allow-login-with.username-email": "Ім'я користувача або електронну пошту", "allow-login-with.username": "Тільки ім'я користувача", - "allow-login-with.email": "Тільки електронну пошту", "account-settings": "Налаштування акаунту", "gdpr_enabled": "Enable GDPR consent collection", "gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the General Data Protection Regulation (GDPR). Note: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.", diff --git a/public/language/uk/login.json b/public/language/uk/login.json index 1d163ba42d..8185742cad 100644 --- a/public/language/uk/login.json +++ b/public/language/uk/login.json @@ -1,7 +1,6 @@ { "username-email": "Ім'я / Пошта", "username": "Ім'я користувача", - "email": "Пошта", "remember_me": "Запам'ятати мене?", "forgot_password": "Забули пароль?", "alternative_logins": "Альтернативний вхід", diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json index 9074776f44..04f4a5986c 100644 --- a/public/language/vi/admin/settings/user.json +++ b/public/language/vi/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "Cho phép đăng nhập với", "allow-login-with.username-email": "Tên Đăng Nhập hoặc Email", "allow-login-with.username": "Chỉ Tên Đăng Nhập", - "allow-login-with.email": "Chỉ Email", "account-settings": "Cài Đặt Tài Khoản", "gdpr_enabled": "Bật đồng ý thu thâp GDPR", "gdpr_enabled_help": "Khi được bật, tất cả những người đăng ký mới sẽ được yêu cầu đồng ý rõ ràng cho việc thu thập và sử dụng dữ liệu theo Quy định chung về bảo vệ dữ liệu (GDPR). Ghi chú: Bật GDPR không buộc người dùng đã có từ trước phải đồng ý. Để làm như vậy, bạn sẽ cần cài đặt plugin GDPR.", diff --git a/public/language/vi/login.json b/public/language/vi/login.json index 89bf6ed6f6..545104c9ba 100644 --- a/public/language/vi/login.json +++ b/public/language/vi/login.json @@ -1,7 +1,6 @@ { "username-email": "Tên đăng nhập / Email", "username": "Tên đăng nhập", - "email": "Thư điện tử", "remember_me": "Ghi Nhớ Tôi?", "forgot_password": "Quên mật khẩu?", "alternative_logins": "Đăng Nhập Thay Thế", diff --git a/public/language/zh-CN/admin/settings/user.json b/public/language/zh-CN/admin/settings/user.json index baae37bbfc..636c8779b2 100644 --- a/public/language/zh-CN/admin/settings/user.json +++ b/public/language/zh-CN/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "允许使用何种登录名", "allow-login-with.username-email": "用户名或者邮箱", "allow-login-with.username": "仅限用户名", - "allow-login-with.email": "仅限邮箱", "account-settings": "用户设置", "gdpr_enabled": "启用通用数据保护条例(GDPR)许可的个人信息收集", "gdpr_enabled_help": "当启用时,所有的新注册用户需要明确同意允许数据采集和在通用数据保护协议(GDPR)保护下的使用。注意:开启GDPR不一定要之前已经存在的用户同意。在这之前,您需要去安装GDPR插件。", diff --git a/public/language/zh-CN/login.json b/public/language/zh-CN/login.json index 8af149ecbf..11133d6b2d 100644 --- a/public/language/zh-CN/login.json +++ b/public/language/zh-CN/login.json @@ -1,7 +1,6 @@ { "username-email": "用户名 / 邮箱", "username": "用户名", - "email": "邮件", "remember_me": "保持登录信息", "forgot_password": "忘记密码?", "alternative_logins": "使用合作网站帐号登录", diff --git a/public/language/zh-TW/admin/settings/user.json b/public/language/zh-TW/admin/settings/user.json index 2e73dd1945..e393171fa8 100644 --- a/public/language/zh-TW/admin/settings/user.json +++ b/public/language/zh-TW/admin/settings/user.json @@ -6,7 +6,6 @@ "allow-login-with": "允許使用何種登入名", "allow-login-with.username-email": "使用者名或者電子信箱", "allow-login-with.username": "僅限使用者名", - "allow-login-with.email": "僅限電子信箱", "account-settings": "使用者設定", "gdpr_enabled": "啟用通用資料保護條例許可的個人資料收集", "gdpr_enabled_help": "當啟用時,所有新註冊使用者需要明確同意允許資料收集和在 通用資料保護法 (GDPR)範圍內的使用。 注意: 開啟GDPR不一定要之前已經存在的用戶同意。在這之前,你需要去安裝GDPR插件。", diff --git a/public/language/zh-TW/login.json b/public/language/zh-TW/login.json index fffd16349f..9763c5e1b0 100644 --- a/public/language/zh-TW/login.json +++ b/public/language/zh-TW/login.json @@ -1,7 +1,6 @@ { "username-email": "使用者名 / 電子信箱", "username": "使用者名", - "email": "電子信箱", "remember_me": "保持登入?", "forgot_password": "忘記密碼?", "alternative_logins": "使用合作網站帳戶登錄", From e0caa5e0c457a19a5f24f0bda68d4669d3d2c167 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 23 Nov 2021 14:53:30 -0500 Subject: [PATCH 073/849] fix: removed unused var --- src/upgrades/1.19.0/reenable-username-login.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/upgrades/1.19.0/reenable-username-login.js b/src/upgrades/1.19.0/reenable-username-login.js index 197b4730cd..d4d88abd5b 100644 --- a/src/upgrades/1.19.0/reenable-username-login.js +++ b/src/upgrades/1.19.0/reenable-username-login.js @@ -1,6 +1,5 @@ 'use strict'; -const db = require('../../database'); const meta = require('../../meta'); module.exports = { From c93d7fdbdd4645230920fb9c0cdc2bd53b3b20fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 15:16:41 -0500 Subject: [PATCH 074/849] breaking: remove deprecated uploads.delete --- src/socket.io/admin.js | 1 - src/socket.io/admin/uploads.js | 20 -------------------- test/authentication.js | 4 ++-- 3 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 src/socket.io/admin/uploads.js diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index cc523043a0..ff03929dcf 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -30,7 +30,6 @@ SocketAdmin.email = require('./admin/email'); SocketAdmin.analytics = require('./admin/analytics'); SocketAdmin.logs = require('./admin/logs'); SocketAdmin.errors = require('./admin/errors'); -SocketAdmin.uploads = require('./admin/uploads'); SocketAdmin.digest = require('./admin/digest'); SocketAdmin.cache = require('./admin/cache'); diff --git a/src/socket.io/admin/uploads.js b/src/socket.io/admin/uploads.js deleted file mode 100644 index 32e1e334d3..0000000000 --- a/src/socket.io/admin/uploads.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const nconf = require('nconf'); - -const sockets = require('..'); - -const Uploads = module.exports; - -Uploads.delete = function (socket, pathToFile, callback) { - sockets.warnDeprecated(socket, 'DELETE /api/v3/files'); - - pathToFile = path.join(nconf.get('upload_path'), pathToFile); - if (!pathToFile.startsWith(nconf.get('upload_path'))) { - return callback(new Error('[[error:invalid-path]]')); - } - - fs.unlink(pathToFile, callback); -}; diff --git a/test/authentication.js b/test/authentication.js index 1b796f5797..a152c2239c 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -29,7 +29,7 @@ describe('authentication', () => { it('should allow login with email for uid 1', async () => { const oldValue = meta.config.allowLoginWith; - meta.config.allowLoginWith = 'email'; + meta.config.allowLoginWith = 'username-email'; const { res } = await helpers.loginUser('regular@nodebb.org', 'regularpwd'); assert.strictEqual(res.statusCode, 200); meta.config.allowLoginWith = oldValue; @@ -37,7 +37,7 @@ describe('authentication', () => { it('second user should fail to login with email since email is not confirmed', async () => { const oldValue = meta.config.allowLoginWith; - meta.config.allowLoginWith = 'email'; + meta.config.allowLoginWith = 'username-email'; const uid = await user.create({ username: '2nduser', password: '2ndpassword', email: '2nduser@nodebb.org' }); const { res, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword'); assert.strictEqual(res.statusCode, 403); From 217aae4c81419883ace0892ea57b9ce56c0d4427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 15:20:44 -0500 Subject: [PATCH 075/849] test: cache dump test --- test/controllers-admin.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/controllers-admin.js b/test/controllers-admin.js index 356767a5d2..e4a38c7c2b 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -406,6 +406,24 @@ describe('Admin Controllers', () => { }); }); + it('should load /api/admin/advanced/cache/dump and 404 with no query param', (done) => { + request(`${nconf.get('url')}/api/admin/advanced/cache/dump`, { jar: jar, json: true }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 404); + assert(body); + done(); + }); + }); + + it('should load /api/admin/advanced/cache/dump', (done) => { + request(`${nconf.get('url')}/api/admin/advanced/cache/dump?name=post`, { jar: jar }, (err, res, body) => { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + it('should load /admin/advanced/errors', (done) => { request(`${nconf.get('url')}/api/admin/advanced/errors`, { jar: jar, json: true }, (err, res, body) => { assert.ifError(err); From a7d1dfb65c2de04042711402053ae4c704ab7081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 18:02:44 -0500 Subject: [PATCH 076/849] breaking: remove deprecated socket user create/delete functions add missing tests --- src/privileges/admin.js | 3 -- src/socket.io/admin/user.js | 25 ----------- test/socket.io.js | 86 +++++++++++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/privileges/admin.js b/src/privileges/admin.js index 65e8cb2627..602b3fa371 100644 --- a/src/privileges/admin.js +++ b/src/privileges/admin.js @@ -99,9 +99,6 @@ privsAdmin.socketMap = { 'admin.user.sendValidationEmail': 'admin:users', 'admin.user.sendPasswordResetEmail': 'admin:users', 'admin.user.forcePasswordReset': 'admin:users', - 'admin.user.deleteUsers': 'admin:users', - 'admin.user.deleteUsersAndContent': 'admin:users', - 'admin.user.createUser': 'admin:users', 'admin.user.invite': 'admin:users', 'admin.tags.create': 'admin:tags', diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index abb12cceed..00c0a57f12 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -4,7 +4,6 @@ const async = require('async'); const winston = require('winston'); const db = require('../../database'); -const api = require('../../api'); const groups = require('../../groups'); const user = require('../../user'); const events = require('../../events'); @@ -53,11 +52,6 @@ User.removeAdmins = async function (socket, uids) { } }; -User.createUser = async function (socket, userData) { - sockets.warnDeprecated(socket, 'POST /api/v3/users'); - return await api.users.create(socket, userData); -}; - User.resetLockouts = async function (socket, uids) { if (!Array.isArray(uids)) { throw new Error('[[error:invalid-data]]'); @@ -126,25 +120,6 @@ User.forcePasswordReset = async function (socket, uids) { uids.forEach(uid => sockets.in(`uid_${uid}`).emit('event:logout')); }; -User.deleteUsers = async function (socket, uids) { - sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/account'); - await Promise.all(uids.map(async (uid) => { - await api.users.deleteAccount(socket, { uid }); - })); -}; - -User.deleteUsersContent = async function (socket, uids) { - sockets.warnDeprecated(socket, 'DELETE /api/v3/users/:uid/content'); - await Promise.all(uids.map(async (uid) => { - await api.users.deleteContent(socket, { uid }); - })); -}; - -User.deleteUsersAndContent = async function (socket, uids) { - sockets.warnDeprecated(socket, 'DELETE /api/v3/users or DELETE /api/v3/users/:uid'); - await api.users.deleteMany(socket, { uids }); -}; - User.restartJobs = async function () { user.startJobs(); }; diff --git a/test/socket.io.js b/test/socket.io.js index 4dadb62d1e..16bd133ef5 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -191,43 +191,56 @@ describe('socket.io', () => { describe('user create/delete', () => { let uid; + const apiUsers = require('../src/api/users'); it('should create a user', async () => { - const userData = await socketAdmin.user.createUser({ uid: adminUid }, { username: 'foo1' }); + const userData = await apiUsers.create({ uid: adminUid }, { username: 'foo1' }); uid = userData.uid; const isMember = await groups.isMember(userData.uid, 'registered-users'); assert(isMember); }); it('should delete users', async () => { - await socketAdmin.user.deleteUsers({ uid: adminUid }, [uid]); + await apiUsers.delete({ uid: adminUid }, { uid }); await sleep(500); const isMember = await groups.isMember(uid, 'registered-users'); assert(!isMember); }); - it('should error if user does not exist', (done) => { - socketAdmin.user.deleteUsersAndContent({ uid: adminUid }, [uid], (err) => { - assert.strictEqual(err.message, '[[error:no-user]]'); - done(); - }); + it('should error if user does not exist', async () => { + let err; + try { + await apiUsers.deleteMany({ uid: adminUid }, { uids: [uid] }); + } catch (_err) { + err = _err; + } + assert.strictEqual(err.message, '[[error:no-user]]'); }); it('should delete users and their content', async () => { - const userData = await socketAdmin.user.createUser({ uid: adminUid }, { username: 'foo2' }); - await socketAdmin.user.deleteUsersAndContent({ uid: adminUid }, [userData.uid]); + const userData = await apiUsers.create({ uid: adminUid }, { username: 'foo2' }); + await apiUsers.deleteMany({ uid: adminUid }, { uids: [userData.uid] }); await sleep(500); const isMember = await groups.isMember(userData.uid, 'registered-users'); assert(!isMember); }); - }); - it('should error with invalid data', (done) => { - socketAdmin.user.createUser({ uid: adminUid }, null, (err) => { - assert.equal(err.message, '[[error:invalid-data]]'); - done(); + it('should error with invalid data', async () => { + let err; + try { + await apiUsers.create({ uid: adminUid }, null); + } catch (_err) { + err = _err; + } + assert.strictEqual(err.message, '[[error:invalid-data]]'); }); }); + it('should load user groups', async () => { + const { users } = await socketAdmin.user.loadGroups({ uid: adminUid }, [adminUid]); + assert.strictEqual(users[0].username, 'admin'); + assert(Array.isArray(users[0].groups)); + }); + it('should reset lockouts', (done) => { socketAdmin.user.resetLockouts({ uid: adminUid }, [regularUid], (err) => { assert.ifError(err); @@ -566,7 +579,9 @@ describe('socket.io', () => { await socketAdmin.email.test({ uid: adminUid }, { template: tpl }); } } catch (err) { - assert.ifError(err); + if (err.message !== '[[error:sendmail-not-found]]') { + assert.ifError(err); + } } }); @@ -681,6 +696,41 @@ describe('socket.io', () => { describe('password reset', () => { const socketUser = require('../src/socket.io/user'); + it('should error if uids is not array', (done) => { + socketAdmin.user.sendPasswordResetEmail({ uid: adminUid }, null, (err) => { + assert.strictEqual(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error if uid doesnt have email', (done) => { + socketAdmin.user.sendPasswordResetEmail({ uid: adminUid }, [adminUid], (err) => { + assert.strictEqual(err.message, '[[error:user-doesnt-have-email, admin]]'); + done(); + }); + }); + + it('should send password reset email', async () => { + await user.setUserField(adminUid, 'email', 'admin_test@nodebb.org'); + await user.email.confirmByUid(adminUid); + await socketAdmin.user.sendPasswordResetEmail({ uid: adminUid }, [adminUid]); + }); + + it('should error if uids is not array', (done) => { + socketAdmin.user.forcePasswordReset({ uid: adminUid }, null, (err) => { + assert.strictEqual(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should for password reset', async () => { + const then = Date.now(); + const uid = await user.create({ username: 'forceme', password: '123345' }); + await socketAdmin.user.forcePasswordReset({ uid: adminUid }, [uid]); + const pwExpiry = await user.getUserField(uid, 'passwordExpiry'); + assert(pwExpiry > then && pwExpiry < Date.now()); + }); + it('should not error on valid email', (done) => { socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => { assert.ifError(err); @@ -690,7 +740,7 @@ describe('socket.io', () => { event: async.apply(events.getEvents, '', 0, 0), }, (err, data) => { assert.ifError(err); - assert.strictEqual(data.count, 1); + assert.strictEqual(data.count, 2); // Event validity assert.strictEqual(data.event.length, 1); @@ -712,7 +762,7 @@ describe('socket.io', () => { event: async.apply(events.getEvents, '', 0, 0), }, (err, data) => { assert.ifError(err); - assert.strictEqual(data.count, 1); // should still equal 1 + assert.strictEqual(data.count, 2); // Event validity assert.strictEqual(data.event.length, 1); @@ -731,7 +781,7 @@ describe('socket.io', () => { db.sortedSetCount('reset:issueDate', 0, Date.now(), (err, count) => { assert.ifError(err); - assert.strictEqual(count, 1); + assert.strictEqual(count, 2); done(); }); }); From c17ec996e07f4cacececad8040aa74573ad82364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 18:18:02 -0500 Subject: [PATCH 077/849] test: add missing acp root category test --- test/controllers-admin.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/controllers-admin.js b/test/controllers-admin.js index e4a38c7c2b..cee00076c7 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -519,6 +519,20 @@ describe('Admin Controllers', () => { }); }); + it('should load /admin/manage/catgories?cid=', async () => { + const { cid: rootCid } = await categories.create({ name: 'parent category' }); + const { cid: childCid } = await categories.create({ name: 'child category', parentCid: rootCid }); + const { res, body } = await helpers.request('get', `/api/admin/manage/categories?cid=${rootCid}`, { + jar: jar, + json: true, + }); + assert.strictEqual(res.statusCode, 200); + assert.strictEqual(body.categoriesTree[0].cid, rootCid); + assert.strictEqual(body.categoriesTree[0].children[0].cid, childCid); + assert.strictEqual(body.breadcrumbs[0].text, '[[admin/manage/categories:top-level]]'); + assert.strictEqual(body.breadcrumbs[1].text, 'parent category'); + }); + it('should load /admin/manage/categories/1/analytics', (done) => { request(`${nconf.get('url')}/api/admin/manage/categories/1/analytics`, { jar: jar, json: true }, (err, res, body) => { assert.ifError(err); From a998cc1c473f2be011ea59a5ea7d2e29ece5cf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 Nov 2021 18:25:01 -0500 Subject: [PATCH 078/849] chore: right dropdown --- src/views/admin/manage/users.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index 068ed4ea8f..b3d6e2319f 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -5,7 +5,7 @@
    -
    -

    [[admin/settings/email:prompt-help]]

    +
    + +
    +
    +
    + + +