Merge branch 'master' into develop

This commit is contained in:
Barış Soner Uşaklı
2025-02-21 12:57:13 -05:00
12 changed files with 53 additions and 9 deletions

View File

@@ -108,7 +108,7 @@
"nodebb-plugin-spam-be-gone": "2.3.1",
"nodebb-plugin-web-push": "0.7.3",
"nodebb-rewards-essentials": "1.0.1",
"nodebb-theme-harmony": "2.0.29",
"nodebb-theme-harmony": "2.0.30",
"nodebb-theme-lavender": "7.1.17",
"nodebb-theme-peace": "2.2.39",
"nodebb-theme-persona": "14.0.15",

View File

@@ -268,6 +268,7 @@
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",

View File

@@ -98,6 +98,8 @@ get:
type: boolean
jobsDisabled:
type: boolean
acpPluginInstallDisabled:
type: boolean
git:
type: object
properties:

View File

@@ -85,12 +85,24 @@ define('forum/topic/threadTools', [
topicContainer.on('click', '[component="topic/event/delete"]', function () {
const eventId = $(this).attr('data-topic-event-id');
const eventEl = $(this).parents('[component="topic/event"]');
const eventEl = $(this).parents('[data-topic-event-id]');
bootbox.confirm('[[topic:delete-event-confirm]]', (ok) => {
if (ok) {
api.del(`/topics/${tid}/events/${eventId}`, {})
.then(function () {
const itemsParent = eventEl.parents('[component="topic/event/items"]');
eventEl.remove();
if (itemsParent.length) {
const childrenCount = itemsParent.children().length;
const eventParent = itemsParent.parents('[component="topic/event"]');
if (!childrenCount) {
eventParent.remove();
} else {
eventParent
.find('[data-bs-toggle]')
.translateText(`[[topic:announcers-x, ${childrenCount}]]`);
}
}
})
.catch(alerts.error);
}

View File

@@ -1,6 +1,6 @@
'use strict';
define('logout', ['hooks'], function (hooks) {
define('logout', ['hooks', 'alerts'], function (hooks, alerts) {
return function logout(redirect) {
redirect = redirect === undefined ? true : redirect;
hooks.fire('action:app.logout');
@@ -23,6 +23,9 @@ define('logout', ['hooks'], function (hooks) {
}
}
},
error: function (jqXHR) {
alerts.error(String(jqXHR.responseText || '[[error:logout-error]]'));
},
});
};
});

View File

@@ -88,6 +88,7 @@ async function getNodeInfo() {
isPrimary: nconf.get('isPrimary'),
runJobs: nconf.get('runJobs'),
jobsDisabled: nconf.get('jobsDisabled'),
acpPluginInstallDisabled: nconf.get('acpPluginInstallDisabled'),
},
};

View File

@@ -199,7 +199,7 @@ async function loadUserInfo(callerUid, uids) {
const confirmObj = confirmObjs[index];
user['email:expired'] = !confirmObj.expires || Date.now() >= confirmObj.expires;
user['email:pending'] = confirmObj.expires && Date.now() < confirmObj.expires;
user.emailToConfirm = confirmObj.email;
user.emailToConfirm = validator.escape(String(confirmObj.email));
}
}
});

View File

@@ -459,7 +459,7 @@ authenticationController.localLogin = async function (req, username, password, n
}
};
authenticationController.logout = async function (req, res, next) {
authenticationController.logout = async function (req, res) {
if (!req.loggedIn || !req.sessionID) {
res.clearCookie(nconf.get('sessionKey'), meta.configs.cookie.get());
return res.status(200).send('not-logged-in');
@@ -475,21 +475,22 @@ authenticationController.logout = async function (req, res, next) {
await user.setUserField(uid, 'lastonline', Date.now() - (meta.config.onlineCutoff * 60000));
await db.sortedSetAdd('users:online', Date.now() - (meta.config.onlineCutoff * 60000), uid);
await plugins.hooks.fire('static:user.loggedOut', { req: req, res: res, uid: uid, sessionID: sessionID });
await plugins.hooks.fire('static:user.loggedOut', { req, res, uid, sessionID });
// Force session check for all connected socket.io clients with the same session id
sockets.in(`sess_${sessionID}`).emit('checkSession', 0);
const payload = {
next: `${nconf.get('relative_path')}/`,
};
plugins.hooks.fire('filter:user.logout', payload);
await plugins.hooks.fire('filter:user.logout', payload);
if (req.body.noscript === 'true') {
return res.redirect(payload.next);
}
res.status(200).send(payload);
} catch (err) {
next(err);
winston.error(`${req.method} ${req.originalUrl}\n${err.stack}`);
res.status(500).send(err.message);
}
};

View File

@@ -58,6 +58,7 @@ function loadConfig(configFile) {
isCluster: false,
isPrimary: true,
jobsDisabled: false,
acpPluginInstallDisabled: false,
fontawesome: {
pro: false,
styles: '*',
@@ -65,7 +66,7 @@ function loadConfig(configFile) {
});
// Explicitly cast as Bool, loader.js passes in isCluster as string 'true'/'false'
const castAsBool = ['isCluster', 'isPrimary', 'jobsDisabled'];
const castAsBool = ['isCluster', 'isPrimary', 'jobsDisabled', 'acpPluginInstallDisabled'];
nconf.stores.env.readOnly = false;
castAsBool.forEach((prop) => {
const value = nconf.get(prop);

View File

@@ -22,6 +22,10 @@ Plugins.toggleActive = async function (socket, plugin_id) {
};
Plugins.toggleInstall = async function (socket, data) {
const isInstalled = await plugins.isInstalled(data.id);
if (nconf.get('acpPluginInstallDisabled') && !isInstalled) {
throw new Error('[[error:plugin-installation-via-acp-disabled]]');
}
postsCache.reset();
await plugins.checkWhitelist(data.id, data.version);
const pluginData = await plugins.toggleInstall(data.id, data.version);

View File

@@ -198,6 +198,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
);
p.eventStart = undefined;
p.eventEnd = undefined;
p.events = mergeConsecutiveShareEvents(p.events);
});
topicData.category = category;
@@ -230,6 +231,23 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
return result.topic;
};
function mergeConsecutiveShareEvents(arr) {
return arr.reduce((acc, curr) => {
const last = acc[acc.length - 1];
if (last && last.type === curr.type && last.type === 'share') {
if (!last.items) {
last.items = [{ ...last }];
['user', 'text', 'timestamp', 'timestampISO'].forEach(field => delete last[field]);
}
last.items.push(curr);
} else {
acc.push(curr);
}
return acc;
}, []);
}
async function getDeleter(topicData) {
if (!parseInt(topicData.deleterUid, 10)) {
return null;

View File

@@ -144,6 +144,7 @@ before(async function () {
nconf.set('version', packageInfo.version);
nconf.set('runJobs', false);
nconf.set('jobsDisabled', false);
nconf.set('acpPluginInstallDisabled', false);
await db.init();