From 3c611d85ed0cbed19295632eef2c8d44437dd953 Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Tue, 6 Nov 2018 13:34:29 -0500 Subject: [PATCH] closes #6912 - on category setParent dialog do not show children of current category - break recursion if category parentCid is equal to child cid to prevent infinite loop - dont allow setting the parentCid of a category to one of it's children --- public/src/admin/manage/category.js | 7 +++++- src/categories/index.js | 34 ++++++++++++++++++++++++++++- src/categories/update.js | 6 +++++ test/categories.js | 25 +++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index 7f14e02d5c..31d0b0a6ef 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -240,8 +240,13 @@ define('admin/manage/category', [ } Category.launchParentSelector = function () { + var parents = [parseInt(ajaxify.data.category.cid, 10)]; var categories = ajaxify.data.allCategories.filter(function (category) { - return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10); + var isChild = parents.includes(parseInt(category.parentCid, 10)); + if (isChild) { + parents.push(parseInt(category.cid, 10)); + } + return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10) && !isChild; }); categorySelector.modal(categories, function (parentCid) { diff --git a/src/categories/index.js b/src/categories/index.js index 4140bf154f..15ac168aab 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -2,6 +2,7 @@ 'use strict'; var async = require('async'); +var _ = require('lodash'); var db = require('../database'); var user = require('../user'); @@ -219,7 +220,13 @@ Categories.getChildren = function (cids, uid, callback) { }); async.each(categories, function (category, next) { - getChildrenRecursive(category, uid, next); + Categories.getCategoryField(category.cid, 'parentCid', function (err, parentCid) { + if (err) { + return next(err); + } + category.parentCid = parentCid; + getChildrenRecursive(category, uid, next); + }); }, function (err) { callback(err, categories.map(function (c) { return c && c.children; @@ -263,12 +270,37 @@ function getChildrenRecursive(category, uid, callback) { }, function (next) { async.each(category.children, function (child, next) { + if (parseInt(category.parentCid, 10) === parseInt(child.cid, 10)) { + return next(); + } getChildrenRecursive(child, uid, next); }, next); }, ], callback); } +Categories.getChildrenCids = function (rootCid, callback) { + function recursive(currentCid, callback) { + db.getSortedSetRange('cid:' + currentCid + ':children', 0, -1, function (err, childrenCids) { + if (err) { + return callback(err); + } + + if (!childrenCids.length) { + return callback(); + } + async.eachSeries(childrenCids, function (childCid, next) { + allCids.push(parseInt(childCid, 10)); + recursive(childCid, next); + }, callback); + }); + } + var allCids = []; + recursive(rootCid, function (err) { + callback(err, _.uniq(allCids)); + }); +} + Categories.flattenCategories = function (allCategories, categoryData) { categoryData.forEach(function (category) { if (category) { diff --git a/src/categories/update.js b/src/categories/update.js index f1b1ec60f7..c3d89ce67d 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -92,6 +92,12 @@ module.exports = function (Categories) { } async.waterfall([ function (next) { + Categories.getChildrenCids(cid, next); + }, + function (childrenCids, next) { + if (childrenCids.includes(parseInt(newParent, 10))) { + return next(new Error('[[error:cant-set-child-as-parent]]')); + } Categories.getCategoryField(cid, 'parentCid', next); }, function (oldParent, next) { diff --git a/test/categories.js b/test/categories.js index 70e24343b5..c608eca3d5 100644 --- a/test/categories.js +++ b/test/categories.js @@ -338,6 +338,31 @@ describe('Categories', function () { }); }); + it('should error if you try to set child as parent', function (done) { + var child1Cid; + var parentCid; + async.waterfall([ + function (next) { + Categories.create({ name: 'parent 1', description: 'poor parent' }, next); + }, + function (category, next) { + parentCid = category.cid; + Categories.create({ name: 'child1', description: 'wanna be parent', parentCid: parentCid }, next); + }, + function (category, next) { + child1Cid = category.cid; + var updateData = {}; + updateData[parentCid] = { + parentCid: child1Cid, + }; + socketCategories.update({ uid: adminUid }, updateData, function (err) { + assert.equal(err.message, '[[error:cant-set-child-as-parent]]'); + next(); + }); + }, + ], done); + }); + it('should update category data', function (done) { var updateData = {}; updateData[cid] = {