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
JavaScript
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));
}