UNPKG

sheercms

Version:

Sheer Cliff CMS is a simple and powerful content management system (CMS) for Node JS.

1,170 lines (1,081 loc) 91.8 kB
var logger = require('../logger'); var models = require('./content-models'); var api = require('../api'); var config = require('../config'); var util = require('../utilities'); var S = require('string'); var path_mod = require('path'); var fs = require('fs'); var mongoose = require('mongoose'); var async = require("async"); var cms = require("sheercms"); module.exports.generateContentId = function() { return mongoose.Types.ObjectId(); }; /* data: { name, groupId, templateId, typeId, username, startDate, endDate, category } callback: (err, item) */ module.exports.createContent = function(data, callback) { try { var dt = new Date(); async.parallel([ function(done) { //find the user api.users.getUser(data.username, function(err, result) { done(err, result); }); }, function(done){ //find the group api.groups.getGroup(data.groupId, function(err, result) { done(err, result); }); }, function(done){ //find the content type api.contentTypes.getContentType(data.typeId, function(err, result) { done(err, result); }); }, function(done) { //find the template (if not null) if (data.templateId && data.templateId.length > 0) { api.templates.getTemplate(data.templateId, function(err, result) { done(err, result); }); } else done(null, null); } ], function(err, results) { if (err) { if (callback) callback(err); } else { var user = results[0]; var group = results[1]; var type = results[2]; var template = results.length > 3 ? results[3] : null; if (!user) { var e = new Error('User not found: ' + data.username); logger.error(e); if (callback) callback(e); } else if (!group) { var e = new Error('Group not found: ' + data.groupId); logger.error(e); if (callback) callback(e); } else if (!type) { var e = new Error('Content type not found: ' + data.typeId); logger.error(e); if (callback) callback(e); } else { //validate that the user has create rights in this group var rights = api.groups.getGroupRights(group, user); if (rights && rights.create === true) { //create the item var cId = exports.generateContentId(); var url = formatUrl(group.urlPattern, cId, data.name, dt); logger.info("Formatted URL: " + url); var tId = template ? util.toObjectId(template._id) : type.defaultTemplateId; var item = new models.Draft({ contentId: cId, name: data.name, url: url, branch: group.branch, created: dt, startDate: data.startDate || dt, endDate: data.endDate || dt, createdBy: data.username, author: data.username, language: "en", //TODO status: 'N', templateId: tId, typeId: util.toObjectId(data.typeId), groupId: util.toObjectId(data.groupId), category: data.category }); item.save(function (err) { try { if (err) { logger.error(err); if (callback) callback(err); } else { api.groups.updateItemCount(item.groupId, 1); if (callback) callback(err, mapItem(item, true)); } } catch (e) { logger.error(e); if (callback) callback(e); } }); } else { var e = new Error("User does not have 'create' rights in this group: group=" + data.groupId + "; user=" + data.username); logger.error(e); if (callback) callback(e); } } } }); } catch(e) { logger.error(e); if (callback) callback(e); } }; /* callback: (err, item) */ module.exports.updateContent = function(item, username, callback) { //this should be an existing content item if (item.contentId) { logger.info("Updating content item: " + item.contentId); cId = util.toObjectId(item.contentId); //existing item models.Draft.findOne({contentId: cId}).exec(function (err, result) { if (err) { if (callback) callback(err); } else if (result == null) { var err2 = new Error("Content item not found: " + item.contentId); logger.error(err2); if (callback) callback(err2); } else { var oldGroupId = result.groupId; var newGroupId = util.toObjectId(item.groupId); //load objects async.parallel([ function(done) { //find the user api.users.getUser(username, function(err, result) { done(err, result); }); }, function(done){ //find the old group api.groups.getGroup(oldGroupId, function(err, result) { done(err, result); }) }, function(done){ //find the new group (if different) if (oldGroupId == newGroupId) done(err, null); else { api.groups.getGroup(newGroupId, function (err, result) { done(err, result); }); } } ], function(err, results) { if (err) { if (callback) callback(err); } else { var user = results[0]; var oldGroup = results[1]; var newGroup = results.length > 2 ? results[2] : null; //verify objects were found if (!user) { var e = new Error('User not found: ' + username); logger.error(e); if (callback) callback(e); } else if (!oldGroup) { var e = new Error('Current item group not found: ' + result.groupId); logger.error(e); if (callback) callback(e); } else { //check the user's permissions in the old (current) group var rights = api.groups.getGroupRights(oldGroup, user); if (rights.update !== true) { var e = new Error('User does not have update rights in group: group=' + result.groupId + '; user=' + username); logger.error(e); if (callback) callback(e); } else { //if the group is changing, then verify user has create rights in new group if (newGroup) { var rightsNew = api.groups.getGroupRights(newGroup, user); if (rightsNew.create !== true) { var e = new Error('User does not have create rights in group: group=' + result.groupId + '; user=' + username); logger.error(e); if (callback) callback(e); return; } } //user passed all permission checks, so update the content item result.name = item.name; result.url = item.url; result.modified = new Date(); result.modifiedBy = username, result.startDate = item.startDate; result.endDate = item.endDate; result.author = item.author; result.language = item.language; result.fields = item.fields; result.filePath = item.filePath; result.groupId = newGroupId; result.category = item.category; if (item.typeId) result.typeId = util.toObjectId(item.typeId); if (item.templateId) result.templateId = util.toObjectId(item.templateId); else result.templateId = null; //compare the tags to see if we need to update the tags sysdoc var updateTags = false; if (item.tags) { if (result.tags == null) updateTags = true; else if (item.tags.length != result.tags.length) updateTags = true; else { for(var i = 0; i < item.tags.length; i++) { var found = false; for(var j = 0; j < result.tags.length; j++) { if (item.tags[i] == result.tags[j]) { found = true; break; } } if (!found) { updateTags = true; break; } } } } result.tags = item.tags; result.markModified('fields'); //update the status if (result.status == 'P' || result.status == 'M') //published or modified result.status = 'M'; //modified else result.status = 'S'; //saved try { result.save(function (err) { try { if (err) { logger.error(err); if (callback) callback(err); } else { logger.info("Content item updated: " + result.contentId); if (oldGroupId == null || oldGroupId.toString() != item.groupId) { //need to redo the group counts if (oldGroupId) api.groups.updateItemCount(oldGroupId, -1); api.groups.updateItemCount(item.groupId, 1); } if (updateTags) { api.tags.addTags(item.tags, function (err) { if (callback) callback(err, mapItem(result, true)); //still resolve because the item was saved. }); } else { if (callback) callback(null, mapItem(result, true)); } } } catch(e) { logger.error(e); if (callback) callback(e); } }); } catch(e) { logger.error(e); if (callback) callback(e); } } } } }); } }); } else { var e = new Error("Invalid or null content ID."); if (callback) callback(e); } }; /* callback: (err, item) */ module.exports.importContent = function(item, callback) { //this should be an existing content item if (!item.contentId) { var e = new Error('The contentId property is required during import.'); logger.error(e); if (callback) callback(e); return; } logger.info("Importing content item: " + item.contentId); cId = util.toObjectId(item.contentId); models.Draft.findOne({contentId: cId}).exec(function (err, result) { try { if (err) { if (callback) callback(err); } else { if (result == null) { result = new models.Draft(); result.contentId = cId; } var unpublish = false; //update ALL of the properties and save result.name = item.name; result.url = item.url; result.branch = item.branch; result.created = item.created; result.modified = new Date(); result.createdBy = item.createdBy; result.modifiedBy = item.modifiedBy; result.startDate = item.startDate; result.endDate = item.endDate; result.author = item.author; result.language = item.language; result.filePath = item.filePath; result.fields = item.fields; result.groupId = util.toObjectId(item.groupId); result.typeId = util.toObjectId(item.typeId); result.category = item.category; if (item.templateId) result.templateId = util.toObjectId(item.templateId); //compare the tags to see if we need to update the tags sysdoc var updateTags = false; if (item.tags) { if (result.tags == null) updateTags = true; else if (item.tags.length != result.tags.length) updateTags = true; else { for (var i = 0; i < item.tags.length; i++) { var found = false; for (var j = 0; j < result.tags.length; j++) { if (item.tags[i] == result.tags[j]) { found = true; break; } } if (!found) { updateTags = true; break; } } } } result.tags = item.tags; result.markModified('fields'); //update the status property if (item.status === 'D' && (result.status === 'P' || result.status === 'M')) { //need to delete from publisheds collection unpublish = true; result.status = 'D'; } else if (item.status !== 'D') { result.status = (result.status === 'M' || result.status === 'P') ? 'M' : 'S'; } //START OF ASYNC async.series([ function (done) { //save the content item try { result.save(function (err) { if (err) { logger.error(err); done(err); } else { logger.info("Content item updated: " + result.contentId); done(); } }); } catch (e) { logger.error(e); done(e); } }, function (done) { //unpublish if needed if (unpublish) { models.Published.remove({'contentId': result.contentId}, function (err) { if (err) { logger.error(err); done(err); } else { logger.info("Content item removed from publisheds collection: " + result.contentId); done(); } }); } else done(); }, function (done) { //update tags if (updateTags) { api.tags.addTags(item.tags, function (err) { if (err) done(err); else done(); }); } else done(); } ], function (err, results) { if (callback) callback(err, mapItem(result, true)); }); //END OF ASYNC } } catch(e) { logger.error(e); if (callback) callback(e); } }); }; /* options: contentId, url, language (currently not supported), collection (drafts, published, backups), cache (default = true - only for 'publisheds' collection) callback: (err, item) */ module.exports.getContent = function(options, callback) { if (callback) { var doCache = options.cache || true; var collection = getCollection(options.collection); var cacheKey = "item:"; var obj = {}; if (options.contentId) { obj.contentId = util.toObjectId(options.contentId); cacheKey += obj.contentId.toString(); } else if (options.url && options.url.length > 0) { obj.url = options.url; cacheKey += obj.url; } else { var e = new Error('Invalid option parameters.'); logger.error(e); callback(e); return; } //only cache the published items if (doCache && isPubCollection(options.collection)) { cacheKey = cacheKey.toLowerCase(); api.cache.get(cacheKey, function (err, result) { if (err) { callback(err); } else if (result) { logger.debug("content-api.getContent - item found in cache: " + result.contentId); callback(null, result); } else { logger.debug("content-api.getContent - item not found in cache...checking database."); collection.findOne(obj).lean().exec(function (err, result) { if (err) { callback(err); } else { var item = mapItem(result, true); api.cache.set(cacheKey, item, config.settings.data_cache_seconds, function (err) { callback(null, item); }); } }); } }); } else { logger.debug("content-api.getContent - cache option false...checking database."); collection.findOne(obj).lean().exec(function (err, result) { if (err) { callback(err); } else { var item = mapItem(result, true); callback(null, item); } }); } } }; /* options: contentId, url, language (currently not supported) callback: (err, item) */ module.exports.getPublishedContent = function(options, callback) { if (callback) { if (!options) options = {}; options.collection = "published"; module.exports.getContent(options, function(err, item) { if (err) callback(err); else callback(null, item); }); } }; /* Returns the items within a group options: groupId, pageNumber, pageSize, startDate, endDate, collection (defaults to drafts) callback: (err, items, total) */ module.exports.getItems = function(options, callback) { if (callback) { try { var collName = options.collection || "drafts"; var collection = getCollection(collName); var pageIndex = (options.pageNumber || 1) - 1; var pageSize = options.pageSize; var groupId = options.groupId ? util.toObjectId(options.groupId) : null; var start = options.startDate; //start date var end = options.endDate; //end date if (!pageSize) { if (start || end) pageSize = config.settings.calendar_page_size; else pageSize = config.settings.content_page_size; } if (pageIndex < 0) pageIndex = 0; var crit = { groupId: groupId }; if (options.category) crit.category = options.category; if (start && end) crit.startDate = {"$gte": start, "$lt": end}; else if (start) crit.startDate = {"$gte": start}; else if (end) crit.startDate = {"$lt": end}; //first check the cache var key = isPubCollection(options.collection) ? 'getItems:' + JSON.stringify(options) : null; api.cache.get(key, function(err, result) { try { if (err) { callback(err); } else if (result) { logger.debug("getItems: results found in cache."); callback(null, result.items, result.total); } else { logger.debug("getItems: results not found in cache...querying database."); //first get the item group api.groups.getGroup(groupId, function(err, group) { try { if (err) { callback(err); } else if (group) { collection.find(crit).count(function (err, count) { try { if (count > 0) { //use the group to determine the sorting var sorting = {}; var sortBy = group.sortBy || 'name'; var sortDir = group.sortDir === 'desc' ? -1 : 1; if (sortBy === 'created') sorting.created = sortDir; else if (sortBy === 'modified') sorting.modified = sortDir; else if (sortBy === 'start' || sortBy === 'startDate' || sortBy === 'startdate') sorting.startDate = sortDir; else sorting.name = sortDir; collection.find(crit).skip(pageIndex * pageSize).limit(pageSize).sort(sorting).lean().exec('find', function (err, docs) { try { if (err) callback(err); else { var items = mapItems(docs, false); //put in cache api.cache.set(key, { items: items, total: count }, config.settings.data_cache_seconds, function (err) { callback(null, items, count); }); } } catch (e) { logger.error(e); callback(e); } }); } else callback(null, [], 0); //no results found } catch(e) { logger.error(e); callback(e); } }); } else { var e = new Error("Group not found: " + groupId); logger.error(e); callback(e); } } catch(e) { logger.error(e); callback(e); } }); } } catch(e) { logger.error(e); callback(e); } }); } catch (e) { logger.error(e); callback(e); } } }; /* Returns the recent draft items within a group callback: (err, items) */ module.exports.getRecentItems = function(groupId, count, callback) { if (callback) { try { var collection = getCollection("drafts"); var groupId = util.toObjectId(groupId); if (!groupId) { var e = new Error("The groupId parameter is required."); logger.error(e); callback(e); return; } collection.find({groupId: groupId}).limit(count).sort({_id: -1}).lean().exec('find', function (err, docs) { try { if (err) { callback(err); } else { var items = mapItems(docs, false); callback(null, items); } } catch(e) { logger.error(e); callback(e); } }); } catch (e) { logger.error(e); callback(e); } } }; /* Returns the items for the given ID array callback: (err, items) */ module.exports.getItemList = function(ids, collectionName, callback) { if (callback) { try { var collName = collectionName || "drafts"; var collection = getCollection(collName); //first check the cache var key = isPubCollection(collName) ? 'getItemList:' + collName + '-' + ids.join() : null; api.cache.get(key, function(err, result) { try { if (err) { callback(err); } else if (result) { logger.debug("getItemList: results found in cache."); callback(null, result.items); } else { logger.debug("getItemList: results not found in cache...querying database."); var objectIds = []; for (var i = 0; i < ids.length; i++) objectIds.push(util.toObjectId(ids[i])); var crit = { 'contentId': { $in: objectIds } }; collection.find(crit).lean().exec('find', function (err, docs) { if (err) callback(err); else { var items = mapItems(docs, false); //put in cache api.cache.set(key, { items: items }, config.settings.data_cache_seconds, function (err) { callback(null, items); }); } }); } } catch(e) { logger.error(e); callback(e); } }); } catch (e) { logger.error(e); callback(e); } } }; /* Used by controllers to display content lists on the website. options: tag, groupId, pageNumber, pageSize, collection (defaults to published), sort {name: 1}, full (bool to include all fields), facets: {dates: bool, tags: bool}, filters: {start, end, tag}, custom: { custom filter here } callback: (err, items, total, facets) */ module.exports.findContent = function(options, callback) { logger.debug("Entering content.findContent"); if (!callback) { return; } try { var collName = options.collection || "published"; var collection = getCollection(collName); var groupId = options.groupId ? util.toObjectId(options.groupId) : null; var pageIndex = (options.pageNumber || 1) - 1; var pageSize = (options.pageSize || config.settings.content_page_size); var full = options.full || false; if (pageIndex < 0) pageIndex = 0; var sort = options.sort || {name: 1}; var filter = options.custom || {}; if (groupId) filter.groupId = groupId; if (options.filters) { if (options.filters.start && options.filters.end) { filter.startDate = { $gte: options.filters.start, $lt: options.filters.end }; } if (options.filters.category) filter.category = options.filters.category; } //first check the cache var key = isPubCollection(collName) ? JSON.stringify(options) : null; api.cache.get(key, function(err, result) { if (err) { callback(err); } else if (result) { logger.debug("getContentList: results found in cache."); callback(null, result.items, result.total, result.facets); } else { logger.debug("getContentList: results not found in cache...querying database."); collection.find(filter).count(function (err, count) { try { if (count > 0) { collection.find(filter).skip(pageIndex * pageSize).limit(pageSize).sort(sort).lean().exec('find', function (err, docs) { try { if (err) callback(err); else { var items = mapItems(docs, full); if (items) logger.debug("Found " + items.length + " matching items."); else logger.debug("Found 0 matching items."); if (options.facets) { var opt = { groupId: groupId, dates: options.facets.dates || false, tags: options.facets.tags || false, categories: options.facets.categories || false, collection: collName }; module.exports.getFacets(opt, function (err, facets) { if (err) callback(err); else { //put in cache api.cache.set(key, { items: items, total: count, facets: facets}, config.settings.data_cache_seconds, function (err) { callback(null, items, count, facets); }); } }); } else { //put in cache api.cache.set(key, { items: items, total: count }, config.settings.data_cache_seconds, function (err) { callback(null, items, count); }); } } } catch (e) { logger.error(e); callback(e); } }); } else { //put in cache api.cache.set(key, { items: [], total: 0 }, config.settings.data_cache_seconds, function (err) { callback(null, [], 0); }); } } catch(e) { logger.error(e); callback(e); } }); } }); } catch(e) { logger.error(e); callback(e); } }; /* options: groupId, dates (bool), tags (bool), categories (bool), collection (defaults to published) callback: (err, facets) */ module.exports.getFacets = function(options, callback) { if (!callback) { return; } try { if (options.groupId == null) throw new Error("The groupId parameter is required for facets."); var groupId = util.toObjectId(options.groupId); var bDates = options.dates || false; var bTags = options.tags || false; var bCats = options.categories || false; var collName = options.collection || "published"; var collection = getCollection(collName); var facets = {}; async.parallel([ function(complete) { //get the data facets if (bDates === true) { collection.aggregate([ { $match: { groupId: groupId, startDate:{$ne:null} } }, { $group : { _id : { month: { $month: "$startDate" }, year: { $year: "$startDate" } }, count: { $sum: 1 } } } ], function(err, results) { if (err) { logger.error(err); complete(err); } else { var arr = []; //re-map the facet results to friendly objects for(var i = 0; i < results.length; i++) { var date = results[i]; arr.push({ month: date._id.month, year: date._id.year, count: date.count }); } facets.dates = arr; complete(); } }); } else complete(); }, function(complete) { //get the category facets if (bCats === true) { collection.aggregate([ { $match: { groupId: groupId, startDate:{$ne:null} } }, { $group : { _id : "$category", count: { $sum: 1 } } } ], function(err, results) { if (err) { logger.error(err); complete(err); } else { var arr = []; //re-map the facet results to friendly objects for(var i = 0; i < results.length; i++) { var result = results[i]; arr.push({ name: result._id, count: result.count }); } facets.categories = arr; complete(); } }); } else complete(); } ], function(err) { if (callback) { callback(err, facets); } }); } catch(e) { logger.error(e); callback(e); } }; /* options: contentId, username, forever callback: (err, item) - item is null if deleted forever */ module.exports.deleteContent = function(options, callback) { var id = util.toObjectId(options.contentId); //first verify that the user has the correct rights module.exports.getContentRights(options.contentId, options.username, function(err, rights) { if (err) { if (callback) callback(err); } else if (rights && rights.delete === true) { if (options.forever === true) { models.Backup.remove({'contentId': id}, function (err) { if (err) { logger.error(err); if (callback) callback(err); } else { models.Published.remove({'contentId': id}, function (err) { if (err) { logger.error(err); if (callback) callback(err); } else { //get the item models.Draft.findOne({'contentId': id}).exec(function (err, item) { try { if (err) { logger.error(err); if (callback) callback(err); } else if (item) { //delete the media if (item.branch === 'media') { var media = api.plugins.get('media'); if (!media) throw new Error('media plugin not installed.'); media.deleteMedia(item, function (err) { if (err) { if (callback) callback(err); } else { //now delete the draft record item.remove(function (err) { if (err) { logger.error(err); if (callback) callback(err); } else { //update the item count of the group api.groups.updateItemCount(item.groupId, -1); if (callback) callback(null, null); } }); } }); } else { //delete the draft record item.remove(function (err) { try { if (err) { logger.error(err); if (callback) callback(err); } else { //update the item count of the group if (item.groupId) api.groups.updateItemCount(item.groupId, -1); if (callback) callback(null, null); } } catch (e) { logger.error(e); if (callback) callback(e); } }); } } else if (callback) callback(null, null); } catch(e) { logger.error(e); if (callback) callback(e); } }); } }); } }); } else { models.Published.remove({'contentId': id}, function (err) { if (err) { logger.error(err); if (callback) callback(err); } else { models.Draft.findOne({contentId: id}).exec(function (err, result) { if (err) { logger.error(err); if (callback) callback(err); } else if (result == null) { var e = new Error("Content item not found to delete: " + options.contentId); logger.error(e); if (callback) callback(e); } else { logger.info("Updating content item status to 'deleted': " + options.contentId); //update the status result.status = 'D'; //deleted result.modified = new Date(); if (options.username) result.modifiedBy = options.username; else result.modifiedBy = null; try { result.save(function (err) { if (err) { logger.error(err); if (callback) callback(err); } else { if (callback) callback(null, mapItem(result, false)); }