mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
3ded75ca4f
Closes #418. * Moved the app config behind the promise wall (I couldn't reliably assign generated uuid to the ghost object AND have access to an automatically created db from fixtures AND not have circular reference (try including api in ghost.js ;) )) * Added new functionality to `ghost.init()`, which is responsible for the first run bit (I'm thinking plopping a filter or an action in there for future devs) * Modified `.gitignore` so the `.png`s casper generates aren't added * Fixed ambiguity and typos here and there, see code
317 lines
10 KiB
JavaScript
317 lines
10 KiB
JavaScript
// # Ghost Module
|
||
// Defines core methods required to build the application
|
||
|
||
// Module dependencies
|
||
var config = require('./../config'),
|
||
when = require('when'),
|
||
express = require('express'),
|
||
errors = require('./server/errorHandling'),
|
||
fs = require('fs'),
|
||
path = require('path'),
|
||
hbs = require('express-hbs'),
|
||
nodefn = require('when/node/function'),
|
||
_ = require('underscore'),
|
||
Polyglot = require('node-polyglot'),
|
||
models = require('./server/models'),
|
||
plugins = require('./server/plugins'),
|
||
requireTree = require('./server/require-tree'),
|
||
permissions = require('./server/permissions'),
|
||
uuid = require('node-uuid'),
|
||
|
||
// Variables
|
||
appRoot = path.resolve(__dirname, '../'),
|
||
themePath = path.resolve(appRoot + '/content/themes'),
|
||
pluginPath = path.resolve(appRoot + '/content/plugins'),
|
||
themeDirectories = requireTree(themePath),
|
||
pluginDirectories = requireTree(pluginPath),
|
||
|
||
Ghost,
|
||
instance,
|
||
defaults,
|
||
statuses;
|
||
|
||
// ## Default values
|
||
/**
|
||
* A hash of default values to use instead of 'magic' numbers/strings.
|
||
* @type {Object}
|
||
*/
|
||
defaults = {
|
||
filterPriority: 5,
|
||
maxPriority: 9
|
||
};
|
||
|
||
// ## Article Statuses
|
||
/**
|
||
* A list of atricle status types
|
||
* @type {Object}
|
||
*/
|
||
statuses = {
|
||
'draft': 'draft',
|
||
'complete': 'complete',
|
||
'approved': 'approved',
|
||
'scheduled': 'scheduled',
|
||
'published': 'published'
|
||
};
|
||
|
||
// ## Module Methods
|
||
/**
|
||
* @method Ghost
|
||
* @returns {*}
|
||
* @constructor
|
||
*/
|
||
Ghost = function () {
|
||
var app,
|
||
polyglot;
|
||
|
||
if (!instance) {
|
||
instance = this;
|
||
|
||
// Holds the filters
|
||
instance.filterCallbacks = [];
|
||
|
||
// Holds the filter hooks (that are built in to Ghost Core)
|
||
instance.filters = [];
|
||
|
||
// Holds the theme directories temporarily
|
||
instance.themeDirectories = {};
|
||
|
||
// Holds the plugin directories temporarily
|
||
instance.pluginDirectories = {};
|
||
|
||
// Holds the persistent notifications
|
||
instance.notifications = [];
|
||
|
||
// Holds the available plugins
|
||
instance.availablePlugins = {};
|
||
|
||
// Holds the dbhash (mainly used for cookie secret)
|
||
instance.dbHash = undefined;
|
||
|
||
app = express();
|
||
polyglot = new Polyglot();
|
||
|
||
_.extend(instance, {
|
||
app: function () { return app; },
|
||
config: function () { return config; },
|
||
|
||
// there's no management here to be sure this has loaded
|
||
settings: function () { return instance.settingsCache; },
|
||
dataProvider: models,
|
||
statuses: function () { return statuses; },
|
||
polyglot: function () { return polyglot; },
|
||
getPaths: function () {
|
||
return when.all([themeDirectories, pluginDirectories]).then(function (paths) {
|
||
instance.themeDirectories = paths[0];
|
||
instance.pluginDirectories = paths[1];
|
||
return;
|
||
});
|
||
},
|
||
paths: function () {
|
||
return {
|
||
'appRoot': appRoot,
|
||
'themePath': themePath,
|
||
'pluginPath': pluginPath,
|
||
'activeTheme': path.join(themePath, config.activeTheme),
|
||
'adminViews': path.join(appRoot, '/core/server/views/'),
|
||
'helperTemplates': path.join(appRoot, '/core/server/helpers/tpl/'),
|
||
'lang': path.join(appRoot, '/core/shared/lang/'),
|
||
'availableThemes': instance.themeDirectories,
|
||
'availablePlugins': instance.pluginDirectories
|
||
};
|
||
}
|
||
});
|
||
}
|
||
return instance;
|
||
};
|
||
|
||
// Initialise the application
|
||
Ghost.prototype.init = function () {
|
||
var self = this;
|
||
|
||
return when.join(instance.dataProvider.init(), instance.getPaths()).then(function () {
|
||
// Initialize plugins
|
||
return self.initPlugins();
|
||
}).then(function () {
|
||
// Initialize the settings cache
|
||
return self.updateSettingsCache();
|
||
}).then(function () {
|
||
// Initialize the permissions actions and objects
|
||
return permissions.init();
|
||
}).then(function () {
|
||
// get the settings and whatnot
|
||
return when(models.Settings.read('dbHash')).then(function (dbhash) {
|
||
// we already ran this, chill
|
||
self.dbHash = dbhash.attributes.value;
|
||
return dbhash.attributes.value;
|
||
}).otherwise(function (error) {
|
||
// this is where all the "first run" functionality should go
|
||
var dbhash = uuid.v4();
|
||
return when(models.Settings.add({key: 'dbHash', value: dbhash})).then(function (returned) {
|
||
self.dbHash = dbhash;
|
||
return dbhash;
|
||
});
|
||
});
|
||
}, errors.logAndThrowError);
|
||
};
|
||
|
||
// Maintain the internal cache of the settings object
|
||
Ghost.prototype.updateSettingsCache = function (settings) {
|
||
var self = this;
|
||
|
||
settings = settings || {};
|
||
|
||
if (!_.isEmpty(settings)) {
|
||
self.settingsCache = settings;
|
||
} else {
|
||
// TODO: this should use api.browse
|
||
return models.Settings.findAll().then(function (result) {
|
||
var settings = {};
|
||
_.map(result.models, function (member) {
|
||
if (!settings.hasOwnProperty(member.attributes.key)) {
|
||
settings[member.attributes.key] = member.attributes.value;
|
||
}
|
||
});
|
||
|
||
self.settingsCache = settings;
|
||
}, errors.logAndThrowError);
|
||
}
|
||
};
|
||
|
||
// ## Template utils
|
||
|
||
// Compile a template for a handlebars helper
|
||
Ghost.prototype.compileTemplate = function (templatePath) {
|
||
return nodefn.call(fs.readFile, templatePath).then(function (templateContents) {
|
||
return hbs.handlebars.compile(templateContents.toString());
|
||
}, errors.logAndThrowError);
|
||
};
|
||
|
||
// Load a template for a handlebars helper
|
||
Ghost.prototype.loadTemplate = function (name) {
|
||
var self = this,
|
||
templateFileName = name + '.hbs',
|
||
// Check for theme specific version first
|
||
templatePath = path.join(this.paths().activeTheme, "partials", templateFileName),
|
||
deferred = when.defer();
|
||
|
||
// Can't use nodefn here because exists just returns one parameter, true or false
|
||
|
||
fs.exists(templatePath, function (exists) {
|
||
if (!exists) {
|
||
// Fall back to helpers templates location
|
||
templatePath = path.join(self.paths().helperTemplates, templateFileName);
|
||
}
|
||
|
||
self.compileTemplate(templatePath).then(deferred.resolve, deferred.reject);
|
||
});
|
||
|
||
return deferred.promise;
|
||
};
|
||
|
||
// Register a handlebars helper for themes
|
||
Ghost.prototype.registerThemeHelper = function (name, fn) {
|
||
hbs.registerHelper(name, fn);
|
||
};
|
||
|
||
// Register a new filter callback function
|
||
Ghost.prototype.registerFilter = function (name, priority, fn) {
|
||
// Curry the priority optional parameter to a default of 5
|
||
if (_.isFunction(priority)) {
|
||
fn = priority;
|
||
priority = defaults.filterPriority;
|
||
}
|
||
|
||
this.filterCallbacks[name] = this.filterCallbacks[name] || {};
|
||
this.filterCallbacks[name][priority] = this.filterCallbacks[name][priority] || [];
|
||
|
||
this.filterCallbacks[name][priority].push(fn);
|
||
};
|
||
|
||
// Unregister a filter callback function
|
||
Ghost.prototype.unregisterFilter = function (name, priority, fn) {
|
||
// Curry the priority optional parameter to a default of 5
|
||
if (_.isFunction(priority)) {
|
||
fn = priority;
|
||
priority = defaults.filterPriority;
|
||
}
|
||
|
||
// Check if it even exists
|
||
if (this.filterCallbacks[name] && this.filterCallbacks[name][priority]) {
|
||
// Remove the function from the list of filter funcs
|
||
this.filterCallbacks[name][priority] = _.without(this.filterCallbacks[name][priority], fn);
|
||
}
|
||
};
|
||
|
||
// Execute filter functions in priority order
|
||
Ghost.prototype.doFilter = function (name, args, callback) {
|
||
var callbacks = this.filterCallbacks[name];
|
||
|
||
// Bug out early if no callbacks by that name
|
||
if (!callbacks) {
|
||
return callback(args);
|
||
}
|
||
|
||
_.times(defaults.maxPriority + 1, function (priority) {
|
||
// Bug out if no handlers on this priority
|
||
if (!_.isArray(callbacks[priority])) {
|
||
return;
|
||
}
|
||
|
||
// Call each handler for this priority level
|
||
_.each(callbacks[priority], function (filterHandler) {
|
||
try {
|
||
args = filterHandler(args);
|
||
} catch (e) {
|
||
// If a filter causes an error, we log it so that it can be debugged, but do not throw the error
|
||
errors.logError(e);
|
||
}
|
||
});
|
||
});
|
||
|
||
callback(args);
|
||
};
|
||
|
||
// Initialise plugins. Will load from config.activePlugins by default
|
||
Ghost.prototype.initPlugins = function (pluginsToLoad) {
|
||
pluginsToLoad = pluginsToLoad || config.activePlugins;
|
||
|
||
var self = this;
|
||
|
||
return plugins.init(this, pluginsToLoad).then(function (loadedPlugins) {
|
||
// Extend the loadedPlugins onto the available plugins
|
||
_.extend(self.availablePlugins, loadedPlugins);
|
||
}, errors.logAndThrowError);
|
||
};
|
||
|
||
// Initialise Theme or admin
|
||
Ghost.prototype.initTheme = function (app) {
|
||
var self = this;
|
||
return function initTheme(req, res, next) {
|
||
app.set('view engine', 'hbs');
|
||
// return the correct mime type for woff files
|
||
express['static'].mime.define({'application/font-woff': ['woff']});
|
||
|
||
if (!res.isAdmin) {
|
||
app.engine('hbs', hbs.express3(
|
||
{partialsDir: path.join(self.paths().activeTheme, 'partials')}
|
||
));
|
||
app.set('views', self.paths().activeTheme);
|
||
} else {
|
||
app.engine('hbs', hbs.express3({partialsDir: self.paths().adminViews + 'partials'}));
|
||
app.set('views', self.paths().adminViews);
|
||
app.use('/public', express['static'](path.join(__dirname, '/client/assets')));
|
||
app.use('/public', express['static'](path.join(__dirname, '/client')));
|
||
app.use('/shared', express['static'](path.join(__dirname, '/shared/')));
|
||
|
||
}
|
||
app.use(express['static'](self.paths().activeTheme));
|
||
app.use('/content/images', express['static'](path.join(__dirname, '/../content/images')));
|
||
next();
|
||
};
|
||
};
|
||
|
||
// TODO: Expose the defaults for other people to see/manipulate as a static value?
|
||
// Ghost.defaults = defaults;
|
||
|
||
module.exports = Ghost;
|