sheercms
Version:
Sheer Cliff CMS is a simple and powerful content management system (CMS) for Node JS.
1,074 lines (998 loc) • 48.5 kB
JavaScript
var logger = require('../logger');
var models = require('./packages-models');
var api = require('../api');
var mongoose = require('mongoose');
var S = require('string');
var util = require('../utilities');
var async = require('async');
var NodeZip = new require('node-zip');
var fs = require('fs');
var path_mod = require('path');
var config = require("../config");
var cms = require('sheercms');
/*
callback: (err, package)
*/
module.exports.createPackage = function(name, callback) {
try {
var p = new models.Package({
name: name,
filename: util.formatSafeName(name),
overwrite: 'default',
created: new Date(),
items: null
});
p.save(function (err) {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else {
logger.info("Package created: " + p._id);
if (callback) callback(null, mapPackage(p));
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err, package)
*/
module.exports.updatePackage = function(pkg, callback) {
try {
var id = util.toObjectId(pkg._id);
models.Package.findById(id, function (err, result) {
try {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else if (result === null) {
var e = new Error("Package not found: " + pkg._id);
logger.error(e);
if (callback) callback(e);
}
else {
//update the properties
result.name = pkg.name;
result.description = pkg.description;
result.overwrite = pkg.overwrite;
result.filename = pkg.filename;
result.modified = new Date();
result.items = pkg.items;
result.markModified('items');
result.markModified('settings');
result.save(function (err) {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else {
logger.info("Package updated: " + result._id);
if (callback) callback(null, mapPackage(result));
}
});
}
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err)
*/
module.exports.deletePackage = function(pkgId, callback) {
try {
var id = util.toObjectId(pkgId);
models.Package.findByIdAndRemove(id, function (err) {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else {
if (callback) callback(null);
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err, package)
*/
module.exports.getPackage = function(pkgId, callback) {
try {
if (callback) {
var id = util.toObjectId(pkgId);
models.Package.findOne({_id: id}).lean().exec(function (err, result) {
if (err) {
logger.error(err);
callback(err);
}
else
callback(null, result);
});
}
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err, packages)
*/
module.exports.getPackages = function(callback) {
try {
if (callback) {
models.Package.find().select("name created modified description").sort("name").lean().exec(function (err, result) {
if (err) {
logger.error(err);
callback(err);
}
else
callback(err, result);
});
}
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err, result)
result: {valid, message, invalidItems}
*/
module.exports.validatePackage = function(pkgId, callback) {
try {
if (callback) {
logger.info("Validating package: " + pkgId);
module.exports.getPackage(pkgId, function (err, pkg) {
if (err) {
callback(err);
}
else {
if (pkg && pkg.items && pkg.items.length > 0) {
logger.debug("Checking each package item for existence.");
//make sure that all of the items exist
var missingItems = [];
async.eachSeries(pkg.items, function (item, done) {
try {
logger.debug("Checking package item: " + item.itemId + " - " + item.name);
if (item.itemType === 'content' || item.itemType === 'media') {
api.content.getContent({contentId: item.itemId}, function (err, content) {
if (err)
done(err);
else if (content) {
logger.debug("Package item valid: " + item.itemId);
done();
}
else {
//missing content item
logger.warn("Package item missing: " + item.itemId + " - " + item.name);
missingItems.push(item);
done();
}
});
}
else if (item.itemType === 'group') {
api.groups.getGroup(item.itemId, function(err, group) {
if (err)
done(err);
else if (group) {
logger.debug("Package item valid: " + item.itemId);
done();
}
else {
//missing group
logger.warn("Package item missing: " + item.itemId + " - " + item.name);
missingItems.push(item);
done();
}
});
}
else if (item.itemType === 'template') {
api.templates.getTemplate(item.itemId, function (err, template) {
if (err)
done(err);
else if (template) {
logger.debug("Package item valid: " + item.itemId);
done();
}
else {
//missing template
logger.warn("Package item missing: " + item.itemId + " - " + item.name);
missingItems.push(item);
done();
}
});
}
else if (item.itemType === 'type') {
api.contentTypes.getContentType(item.itemId, function(err, type) {
if (err)
done(err);
else if (type) {
logger.debug("Package item valid: " + item.itemId);
done();
}
else {
//missing content type
logger.warn("Package item missing: " + item.itemId + " - " + item.name);
missingItems.push(item);
done();
}
});
}
else {
var e = new Error('Invalid item type: ' + item.itemType);
logger.error(e);
done(e);
}
}
catch(e) {
logger.error(e);
done(e);
}
},
function(err) {
if (err) {
callback(err);
}
else {
var result = {
valid: missingItems.length == 0,
message: missingItems.length == 0 ? 'This package is valid.' : 'This package is not valid.',
invalidItems: missingItems
};
callback(null, result);
}
});
}
else if (pkg) {
callback(null, {valid: true, message: 'This package is valid but does not contain any items.'});
}
else {
var msg = 'The package was not found: ' + pkgId;
var e = new Error(msg);
logger.error(e);
callback(e);
}
}
});
}
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
/*
callback: (err, result)
*/
module.exports.buildPackage = function(pkgId, callback) {
try {
module.exports.getPackage(pkgId, function(err, pkg) {
if (err) {
if (callback) callback(err);
}
else {
if (pkg && pkg.items && pkg.items.length > 0) {
createPackageZip(pkg, function (err, result) {
if (callback) callback(err, result);
});
}
else {
var msg = pkg ? 'This package does not contain items.' : 'The package was not found.';
var e = new Error(msg);
logger.error(e);
if (callback) callback(e);
}
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
module.exports.getPackageSettings = function() {
var options = config.settings.packages;
if (options.path && options.path.indexOf("~/") == 0) {
options.path = path_mod.join(cms.getRoot(), options.path.substr(2));
}
return options;
};
/*
options: {name, path, username}
onProgress: (value, total)
callback: (err, package)
statuses: 0 = none, 1 = success, 2 = skipped, 3 = error
*/
module.exports.installPackage = function(options, onProgress, callback) {
try {
logger.info("Installing package: " + options.name);
//first copy the file to the packages folder
var settings = module.exports.getPackageSettings();
var newPath = path_mod.join(settings.path, options.name);
logger.debug("Moving uploaded file to packages folder: " + newPath);
fs.rename(options.path, newPath, function (err) {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else {
try {
logger.debug("Reading the package zip file: " + newPath);
//read the zip file
fs.readFile(newPath, function (err, data) {
if (err) {
logger.error(err);
if (callback) callback(err);
}
else {
try {
logger.debug("Unzipping the package: " + options.name);
//unzip the package
var zipOptions = {
base64: false, //binary
checkCRC32: false
};
var zip = new NodeZip(data, zipOptions);
//get the package file
logger.debug("Reading the package.json file from: " + options.name);
var pkgJson = zip.file('package.json').asText();
if (!pkgJson)
throw new Error("The package.json file was not found in the package. Package=" + newPath);
var pkg = JSON.parse(pkgJson);
if (!pkg || !pkg.items)
throw new Error("The package json is null or does not contain items. Package=" + newPath);
if (onProgress) onProgress(new InstallProgress(pkg, null, 0, pkg.items.length, 0, 'Package opened'));
var count = 0;
//install the items in a certain order: templates, content types, groups, items
var pkgItems = {
templates: [],
types: [],
groups: [],
items: []
};
for(var i = 0; i < pkg.items.length; i++) {
var p = pkg.items[i];
if (p.itemType === 'content' || p.itemType === 'media')
pkgItems.items.push(p);
else if (p.itemType === 'group')
pkgItems.groups.push(p);
else if (p.itemType === 'template')
pkgItems.templates.push(p);
else if (p.itemType === 'type')
pkgItems.types.push(p);
else {
var e = new Error("Invalid package item type. Package=" + pkg.name + "; ItemType=" + p.itemType + "; Item=" + p.itemId);
logger.error(e);
}
}
logger.debug("Begin processing package items from: " + options.name);
async.series([
//TEMPLATES
function(complete){
async.eachSeries(pkgItems.templates, function (item, done) {
try {
logger.info("Installing TEMPLATE=" + item.itemId);
count++;
var itemObj = getPackageZipEntry(zip, item);
if (itemObj) {
installTemplate(zip, pkg, options, item, itemObj, count, onProgress, done);
}
else {
done(new Error('Error processing package entry: ' + item.itemId));
}
}
catch(e) {
logger.error(e);
done(e);
}
}, function(err) {
//reload the templates
api.templates.loadTemplates(function(err) {
complete(err);
});
});
},
//CONTENT TYPES
function(complete){
async.eachSeries(pkgItems.types, function (item, done) {
try {
logger.info("Installing CONTENT TYPE=" + item.itemId);
count++;
var itemObj = getPackageZipEntry(zip, item);
if (itemObj) {
installContentType(zip, pkg, options, item, itemObj, count, onProgress, done);
}
else {
done(new Error('Error processing package entry: ' + item.itemId));
}
}
catch(e) {
logger.error(e);
done(e);
}
}, function(err) {
if (err) {
complete(err);
}
else {
//reload the content types
api.contentTypes.loadContentTypes(function (err) {
complete(err);
});
}
});
},
//ITEM GROUPS
function(complete){
async.eachSeries(pkgItems.groups, function (item, done) {
try {
logger.info("Installing ITEM GROUP=" + item.itemId);
count++;
var itemObj = getPackageZipEntry(zip, item);
if (itemObj) {
installGroup(zip, pkg, options, item, itemObj, count, onProgress, done);
}
else {
done(new Error('Error processing package entry: ' + item.itemId));
}
}
catch(e) {
logger.error(e);
done(e);
}
}, function(err) {
complete(err);
});
},
//CONTENT ITEMS
function(complete){
async.eachSeries(pkgItems.items, function (item, done) {
try {
logger.info("Installing CONTENT ITEM=" + item.itemId);
count++;
var itemObj = getPackageZipEntry(zip, item);
if (itemObj) {
installContentItem(zip, pkg, options, item, itemObj, count, onProgress, done);
}
else {
done(new Error('Error processing package entry: ' + item.itemId));
}
}
catch(e) {
logger.error(e);
done(e);
}
}, function(err) {
complete(err);
});
}
],
//callback after all items have been processed
function(err){
//clean up tasks
async.series([
//RECALCULATE ALL GROUP COUNTS
function(complete) {
api.groups.recalculateAllGroups(function(err) {
complete(err);
})
},
//CLEAR CACHE
function(complete) {
api.cache.removeAll(null, function(err) {
complete(err);
});
}
],
//end of clean up
function(err) {
callback(err);
});
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
}
});
}
catch(e) {
logger.error(e);
if (callback) callback(e);
}
};
//callback: err, itemObj
function getPackageZipEntry(zip, item) {
try {
//get the json file for this item from the zip file
var entryName = item.itemType + '/' + item.itemId + '.json';
logger.debug("Reading package entry: " + entryName);
var itemJson = zip.file(entryName).asText();
if (!itemJson)
throw new Error('Could not find item JSON in package at: ' + entryName);
logger.debug("Parsing package entry to JSON object: " + entryName);
var itemObj = JSON.parse(itemJson);
if (!itemObj)
throw new Error('Could not parse the item JSON in package at: ' + entryName);
return itemObj;
}
catch(e) {
logger.error(e);
return null;
}
}
function installContentType(zip, pkg, options, item, itemObj, count, onProgress, done) {
//validate that the type id matches
if (item.itemId != itemObj.id)
throw new Error('The item JSON does not match the item settings in the package.');
//see if this content type already exists...
logger.debug("Looking for existing content type with id: " + item.itemId);
api.contentTypes.getContentType(item.itemId, function(err, type) {
try {
if (err) {
done(err);
}
else if (!type || item.overwrite === 'yes' || (item.overwrite === 'default' && (pkg.overwrite === 'yes' || pkg.overwrite === 'default'))) {
//import the item type
api.contentTypes.importContentType(itemObj, options.username, false, function (err, savedType) {
try {
if (err) {
done(err);
}
else {
var msg = "Package item successfully installed: " + item.itemId + " - " + savedType.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
else {
logger.info("Skipping package item because it already exists and overwrite is false: " + item.itemId + " - " + content.name);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 2, 'Item skipped')); //2 = skip
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
function installTemplate(zip, pkg, options, item, itemObj, count, onProgress, done) {
//validate that the template id matches
if (item.itemId != itemObj.id)
throw new Error('The item JSON does not match the item settings in the package.');
//see if this template already exists...
logger.debug("Looking for existing template with id: " + item.itemId);
api.templates.getTemplate(item.itemId, function(err, template) {
try {
if (err) {
done(err);
}
else if (!template || item.overwrite === 'yes' || (item.overwrite === 'default' && (pkg.overwrite === 'yes' || pkg.overwrite === 'default'))) {
//import the template
api.templates.importTemplate(itemObj, options.username, false, function (err, savedTemplate) {
try {
if (err) {
done(err);
}
else {
var msg = "Package item successfully installed: " + item.itemId + " - " + savedTemplate.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
else {
logger.info("Skipping package item because it already exists and overwrite is false: " + item.itemId + " - " + content.name);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 2, 'Item skipped')); //2 = skip
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
function installGroup(zip, pkg, options, item, itemObj, count, onProgress, done) {
//validate that the id matches
if (item.itemId != itemObj._id)
throw new Error('The item JSON does not match the item settings in the package.');
//see if this group already exists...
logger.debug("Looking for existing group with id: " + item.itemId);
api.groups.getGroup(item.itemId, function(err, group) {
try {
if (err) {
done(err);
}
else if (!group || item.overwrite === 'yes' || (item.overwrite === 'default' && (pkg.overwrite === 'yes' || pkg.overwrite === 'default'))) {
//import the group
api.groups.importGroup(itemObj, function (err, savedGroup) {
try {
if (err) {
done(err);
}
else {
var msg = "Package item successfully installed: " + item.itemId + " - " + savedGroup.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
else {
logger.info("Skipping package item because it already exists and overwrite is false: " + item.itemId + " - " + content.name);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 2, 'Item skipped')); //2 = skip
done();
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
function installContentItem(zip, pkg, options, item, itemObj, count, onProgress, done) {
logger.debug("Content or media item found. Validating the content id...");
//validate that the contentId matches
if (item.itemId != itemObj.contentId)
throw new Error('The item JSON does not match the item settings in the package.');
//first see if this item exists already
logger.debug("Looking for existing content item with contentId: " + item.itemId);
api.content.getContent({contentId: item.itemId}, function (err, content) {
try {
if (err)
done(err);
else {
if (!content || item.overwrite === 'yes' || (item.overwrite === 'default' && pkg.overwrite === 'yes')) {
api.content.importContent(itemObj, function (err, savedContent) {
try {
if (err) {
logger.error("Error installing package item: " + item.itemId);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 3, err.message)); //3 = error
done(err);
}
else {
if (item.itemType === 'media' && item.fileEntry && item.fileEntry.length > 0) {
//extract the file entry and write to the temp directory
var fileBuffer = zip.file(item.fileEntry).asNodeBuffer();
var filename = path_mod.join(util.getTempDir(), path_mod.basename(item.fileEntry));
fs.writeFile(filename, fileBuffer, function (err) {
try {
if (err) {
logger.error(err);
done(err);
}
else {
//get the media size
fs.stat(filename, function (err, stats) {
if (err) {
logger.error(err);
done(err);
}
else {
api.content.updateMedia({contentId: item.itemId, path: filename, size: stats.size, username: options.username}, function (err) {
try {
if (err) {
done(err);
}
else {
if (item.publish === true) {
logger.debug('Publishing package item: ' + item.itemId);
api.content.publishContent(item.itemId, options.username, function(err) {
if (err) {
logger.error("Error publishing package item: " + item.itemId);
done(err);
}
else {
//installed the non-media item
var msg = "Package item successfully installed: " + item.itemId + " - " + savedContent.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
});
}
else {
//installed the non-media item
var msg = "Package item successfully installed: " + item.itemId + " - " + savedContent.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
});
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
else {
if (item.publish === true) {
logger.debug('Publishing package item: ' + item.itemId);
api.content.publishContent(item.itemId, options.username, function(err) {
if (err) {
logger.error("Error publishing package item: " + item.itemId);
done(err);
}
else {
//installed the non-media item
var msg = "Package item successfully installed: " + item.itemId + " - " + savedContent.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
});
}
else {
//installed the non-media item
var msg = "Package item successfully installed: " + item.itemId + " - " + savedContent.name;
logger.info(msg);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 1, msg)); //1 = success
done();
}
}
}
}
catch (e) {
logger.error(e);
done(e);
}
});
}
else {
logger.info("Skipping package item because it already exists and overwrite is false: " + item.itemId + " - " + content.name);
if (onProgress) onProgress(new InstallProgress(pkg, item, count, pkg.items.length, 2, 'Item skipped')); //2 = skip
done();
}
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
function mapPackage(pkg) {
var p = {
id: pkg._id.toString(),
_id: pkg._id.toString(),
name: pkg.name,
description: pkg.description,
filename: pkg.filename,
created: pkg.created,
modified: pkg.modified,
items: pkg.items,
overwrite: pkg.overwrite
};
return p;
}
function createPackageZip(pkg, callback)
{
try {
var zip = new NodeZip();
var settings = module.exports.getPackageSettings();
//start processing each item
async.eachSeries(pkg.items, function (key, done) {
logger.info("Adding item to package zip: Package=" + pkg.name + "; Type=" + key.itemType + "; Item=" + key.itemId);
if (key.itemType === 'content' || key.itemType === 'media') {
api.content.getContent({contentId: key.itemId}, function(err, content) {
try {
if (err) {
var e = new Error('Error adding the item to the package zip. Package=' + pkg.name + '; Item=' + key.itemId + '; Error=' + err);
logger.error(e);
done(e);
}
else if (content) {
var entryName = key.itemType + '/' + content.contentId + '.json';
logger.info('Adding package zip entry: ' + entryName);
var json = JSON.stringify(content);
zip.file(entryName, json);
if (key.itemType === 'media') {
//need to zip the file too
api.content.readMedia(content, function (err, data) {
if (err)
done(err);
else {
//get the file extension
var ext = path_mod.extname(content.filePath);
ext = ext ? ext.replace('.', '') : '';
entryName = 'media/files/' + content.contentId + '.' + ext;
logger.info('Adding package zip entry: ' + entryName);
zip.file(entryName, data);
//tell the package item that it has an associated file
key.fileEntry = entryName;
done();
}
});
}
else
done();
}
else {
//content item doesn't exist anymore
var msg = 'Content item not found: ' + key.name + ' (' + key.itemId + ')';
logger.warn(msg);
var e = new Error(msg);
done(e);
}
}
catch(e) {
logger.error(e);
done(e);
}
});
}
else if (key.itemType === 'group') {
api.groups.getGroup(key.itemId, function(err, group) {
if (err) {
var e = new Error('Error adding the group to the package zip. Package=' + pkg.name + '; Group=' + key.itemId + '; Error=' + err);
logger.error(e);
done(e);
}
else if (group) {
var entryName = key.itemType + '/' + group._id + '.json';
logger.info('Adding package zip entry: ' + entryName);
var json = JSON.stringify(group);
zip.file(entryName, json);
done();
}
else {
//missing group
var msg = 'Group not found: ' + key.name + ' (' + key.itemId + ')';
var e = new Error(msg);
logger.warn(msg);
done(e);
}
});
}
else if (key.itemType === 'template') {
api.templates.getTemplate(key.itemId, function(err, template) {
if (err) {
var e = new Error('Error adding the template to the package zip. Package=' + pkg.name + '; Template=' + key.itemId + '; Error=' + err);
logger.error(e);
done(e);
}
else if (template) {
var entryName = key.itemType + '/' + template.id + '.json';
logger.info('Adding package zip entry: ' + entryName);
var json = JSON.stringify(template);
zip.file(entryName, json);
done();
}
else {
var msg = 'Template not found: ' + key.name + ' (' + key.itemId + ')';
var e = new Error(msg);
logger.warn(msg);
done(e);
}
});
}
else if (key.itemType === 'type') {
api.contentTypes.getContentType(key.itemId, function(err, type) {
if (err)
done(err);
else if (type) {
var entryName = key.itemType + '/' + type.id + '.json';
logger.info('Adding package zip entry: ' + entryName);
var json = JSON.stringify(type);
zip.file(entryName, json);
done();
}
else {
var msg = 'Content Type not found: ' + key.name + ' (' + key.itemId + ')';
var e = new Error(msg);
logger.warn(msg);
done(e);
}
});
}
else {
var e = new Error("Item type not supported in package zip: " + key.itemType);
logger.error(e);
done(e);
}
}, function (err) {
if (err) {
callback(err);
}
else {
//add the package JSON to the zip
logger.info('Adding package JSON to zip file: ' + pkg.name);
zip.file('package.json', JSON.stringify(pkg));
//now save the file to the packages folder
var filename = (pkg.filename && pkg.filename.length > 0) ? pkg.filename : util.formatSafeName(pkg.name);
var path = path_mod.join(settings.path, filename.replace('.zip', '') + '.zip');
var options = {
type: 'nodebuffer',
compression: 'DEFLATE',
comment: pkg.description
};
var buffer = zip.generate(options);
fs.writeFile(path, buffer, function(err) {
if (err) {
logger.error(err);
callback(err);
}
else {
logger.info('Package zip created successfully: ' + pkg.name);
var result = {
success: true,
path: path,
package: pkg
};
callback(null, result);
}
});
}
});
}
catch(e)
{
logger.error(e);
callback(e);
}
}
function InstallProgress(pkg, item, value, total, status, msg) {
var self = this;
self.package = pkg;
self.item = item;
self.value = value;
self.total = total;
self.status = status;
self.message = msg;
}