diff --git a/.gitignore b/.gitignore index 1e10161d21..e42137c3dd 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ projectFilesBackup /_site /content/tmp/* /content/data/* -/content/plugins/**/* +/content/apps/**/* /content/themes/**/* /content/images/**/* !/content/themes/casper/** diff --git a/Gruntfile.js b/Gruntfile.js index 9c4aa152a9..811c16cc89 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -23,8 +23,8 @@ var path = require('path'), 'content/images/README.md', '!content/themes/**', 'content/themes/casper/**', - '!content/plugins/**', - 'content/plugins/README.md', + '!content/apps/**', + 'content/apps/README.md', '!node_modules/**', '!core/test/**', '!core/client/assets/sass/**', diff --git a/content/apps/README.md b/content/apps/README.md new file mode 100644 index 0000000000..3ed1b899d0 --- /dev/null +++ b/content/apps/README.md @@ -0,0 +1,3 @@ +# Content / Apps + +Coming soon, Ghost apps will appear here. \ No newline at end of file diff --git a/content/plugins/README.md b/content/plugins/README.md deleted file mode 100644 index 923603c5b2..0000000000 --- a/content/plugins/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Content / Plugins - -Coming soon, Ghost plugins will appear here. \ No newline at end of file diff --git a/core/server/apps/index.js b/core/server/apps/index.js new file mode 100644 index 0000000000..911384d704 --- /dev/null +++ b/core/server/apps/index.js @@ -0,0 +1,92 @@ + +var _ = require('underscore'), + when = require('when'), + errors = require('../errorHandling'), + api = require('../api'), + loader = require('./loader'), + // Holds the available apps + availableApps = {}; + + +function getInstalledApps() { + return api.settings.read('installedApps').then(function (installed) { + installed.value = installed.value || '[]'; + + try { + installed = JSON.parse(installed.value); + } catch (e) { + return when.reject(e); + } + + return installed; + }); +} + +function saveInstalledApps(installedApps) { + return getInstalledApps().then(function (currentInstalledApps) { + var updatedAppsInstalled = _.uniq(installedApps.concat(currentInstalledApps)); + + return api.settings.edit('installedApps', updatedAppsInstalled); + }); +} + +module.exports = { + init: function () { + var appsToLoad; + + try { + // We have to parse the value because it's a string + api.settings.read('activeApps').then(function (aApps) { + appsToLoad = JSON.parse(aApps.value) || []; + }); + } catch (e) { + errors.logError( + 'Failed to parse activeApps setting value: ' + e.message, + 'Your apps will not be loaded.', + 'Check your settings table for typos in the activeApps value. It should look like: ["app-1", "app2"] (double quotes required).' + ); + return when.resolve(); + } + + // Grab all installed apps, install any not already installed that are in appsToLoad. + return getInstalledApps().then(function (installedApps) { + var loadedApps = {}, + recordLoadedApp = function (name, loadedApp) { + // After loading the app, add it to our hash of loaded apps + loadedApps[name] = loadedApp; + + return when.resolve(loadedApp); + }, + loadPromises = _.map(appsToLoad, function (app) { + // If already installed, just activate the app + if (_.contains(installedApps, app)) { + return loader.activateAppByName(app).then(function (loadedApp) { + return recordLoadedApp(app, loadedApp); + }); + } + + // Install, then activate the app + return loader.installAppByName(app).then(function () { + return loader.activateAppByName(app); + }).then(function (loadedApp) { + return recordLoadedApp(app, loadedApp); + }); + }); + + return when.all(loadPromises).then(function () { + // Save our installed apps to settings + return saveInstalledApps(_.keys(loadedApps)); + }).then(function () { + // Extend the loadedApps onto the available apps + _.extend(availableApps, loadedApps); + }).otherwise(function (err) { + errors.logError( + err.message || err, + 'The app will not be loaded', + 'Check with the app creator, or read the app documentation for more details on app requirements' + ); + }); + }); + }, + availableApps: availableApps +}; \ No newline at end of file diff --git a/core/server/apps/loader.js b/core/server/apps/loader.js new file mode 100644 index 0000000000..81d34881f4 --- /dev/null +++ b/core/server/apps/loader.js @@ -0,0 +1,70 @@ + +var path = require('path'), + _ = require('underscore'), + when = require('when'), + appProxy = require('./proxy'), + config = require('../config'), + loader; + + + +// Get a relative path to the given apps root, defaults +// to be relative to __dirname +function getAppRelativePath(name, relativeTo) { + relativeTo = relativeTo || __dirname; + + return path.relative(relativeTo, path.join(config.paths().appPath, name)); +} + + +function getAppByName(name) { + // Grab the app class to instantiate + var AppClass = require(getAppRelativePath(name)), + app; + + // Check for an actual class, otherwise just use whatever was returned + if (_.isFunction(AppClass)) { + app = new AppClass(appProxy); + } else { + app = AppClass; + } + + return app; +} + +// The loader is responsible for loading apps +loader = { + // Load a app and return the instantiated app + installAppByName: function (name) { + var app = getAppByName(name); + + // Check for an install() method on the app. + if (!_.isFunction(app.install)) { + return when.reject(new Error("Error loading app named " + name + "; no install() method defined.")); + } + + // Wrapping the install() with a when because it's possible + // to not return a promise from it. + return when(app.install(appProxy)).then(function () { + return when.resolve(app); + }); + }, + + // Activate a app and return it + activateAppByName: function (name) { + var app = getAppByName(name); + + // Check for an activate() method on the app. + if (!_.isFunction(app.activate)) { + return when.reject(new Error("Error loading app named " + name + "; no activate() method defined.")); + } + + // Wrapping the activate() with a when because it's possible + // to not return a promise from it. + return when(app.activate(appProxy)).then(function () { + return when.resolve(app); + }); + } +}; + +module.exports = loader; \ No newline at end of file diff --git a/core/server/plugins/proxy.js b/core/server/apps/proxy.js similarity index 100% rename from core/server/plugins/proxy.js rename to core/server/apps/proxy.js diff --git a/core/server/config/paths.js b/core/server/config/paths.js index 84139b4c98..bc50c5c571 100644 --- a/core/server/config/paths.js +++ b/core/server/config/paths.js @@ -11,14 +11,14 @@ var moment = require('moment'), corePath = path.resolve(appRoot, 'core/'), contentPath = path.resolve(appRoot, 'content/'), themePath = path.resolve(contentPath + '/themes'), - pluginPath = path.resolve(contentPath + '/plugins'), + appPath = path.resolve(contentPath + '/apps'), themeDirectories = requireTree(themePath), - pluginDirectories = requireTree(pluginPath), + appDirectories = requireTree(appPath), localPath = '', configUrl = '', availableThemes, - availablePlugins; + availableApps; function paths() { @@ -32,7 +32,7 @@ function paths() { 'contentPath': contentPath, 'corePath': corePath, 'themePath': themePath, - 'pluginPath': pluginPath, + 'appPath': appPath, 'imagesPath': path.resolve(contentPath, 'images/'), 'imagesRelPath': 'content/images', 'adminViews': path.join(corePath, '/server/views/'), @@ -41,7 +41,7 @@ function paths() { 'lang': path.join(corePath, '/shared/lang/'), 'debugPath': subdir + '/ghost/debug/', 'availableThemes': availableThemes, - 'availablePlugins': availablePlugins + 'availableApps': availableApps }; } @@ -56,9 +56,9 @@ function update(configURL) { localPath = localPath.replace(/\/$/, ''); } - return when.all([themeDirectories, pluginDirectories]).then(function (paths) { + return when.all([themeDirectories, appDirectories]).then(function (paths) { availableThemes = paths[0]; - availablePlugins = paths[1]; + availableApps = paths[1]; return; }); } diff --git a/core/server/data/default-settings.json b/core/server/data/default-settings.json index 2365f796da..8d9b757df5 100644 --- a/core/server/data/default-settings.json +++ b/core/server/data/default-settings.json @@ -68,11 +68,11 @@ "defaultValue": "casper" } }, - "plugin": { - "activePlugins": { + "app": { + "activeApps": { "defaultValue": "[]" }, - "installedPlugins": { + "installedApps": { "defaultValue": "[]" } } diff --git a/core/server/index.js b/core/server/index.js index 0fad4702fa..72876b9aa6 100644 --- a/core/server/index.js +++ b/core/server/index.js @@ -18,7 +18,7 @@ var crypto = require('crypto'), middleware = require('./middleware'), models = require('./models'), permissions = require('./permissions'), - plugins = require('./plugins'), + apps = require('./apps'), routes = require('./routes'), packageInfo = require('../../package.json'), @@ -71,7 +71,7 @@ function initDbHashAndFirstRun() { } // Sets up the express server instance. -// Instantiates the ghost singleton, helpers, routes, middleware, and plugins. +// Instantiates the ghost singleton, helpers, routes, middleware, and apps. // Finally it starts the http server. function setup(server) { @@ -200,8 +200,8 @@ function setup(server) { } - // Initialize plugins then start the server - plugins.init().then(function () { + // Initialize apps then start the server + apps.init().then(function () { // ## Start Ghost App if (getSocket()) { diff --git a/core/server/plugins/index.js b/core/server/plugins/index.js deleted file mode 100644 index 52ce57c1c3..0000000000 --- a/core/server/plugins/index.js +++ /dev/null @@ -1,92 +0,0 @@ - -var _ = require('underscore'), - when = require('when'), - errors = require('../errorHandling'), - api = require('../api'), - loader = require('./loader'), - // Holds the available plugins - availablePlugins = {}; - - -function getInstalledPlugins() { - return api.settings.read('installedPlugins').then(function (installed) { - installed.value = installed.value || '[]'; - - try { - installed = JSON.parse(installed.value); - } catch (e) { - return when.reject(e); - } - - return installed; - }); -} - -function saveInstalledPlugins(installedPlugins) { - return getInstalledPlugins().then(function (currentInstalledPlugins) { - var updatedPluginsInstalled = _.uniq(installedPlugins.concat(currentInstalledPlugins)); - - return api.settings.edit('installedPlugins', updatedPluginsInstalled); - }); -} - -module.exports = { - init: function () { - var pluginsToLoad; - - try { - // We have to parse the value because it's a string - api.settings.read('activePlugins').then(function (aPlugins) { - pluginsToLoad = JSON.parse(aPlugins.value) || []; - }); - } catch (e) { - errors.logError( - 'Failed to parse activePlugins setting value: ' + e.message, - 'Your plugins will not be loaded.', - 'Check your settings table for typos in the activePlugins value. It should look like: ["plugin-1", "plugin2"] (double quotes required).' - ); - return when.resolve(); - } - - // Grab all installed plugins, install any not already installed that are in pluginsToLoad. - return getInstalledPlugins().then(function (installedPlugins) { - var loadedPlugins = {}, - recordLoadedPlugin = function (name, loadedPlugin) { - // After loading the plugin, add it to our hash of loaded plugins - loadedPlugins[name] = loadedPlugin; - - return when.resolve(loadedPlugin); - }, - loadPromises = _.map(pluginsToLoad, function (plugin) { - // If already installed, just activate the plugin - if (_.contains(installedPlugins, plugin)) { - return loader.activatePluginByName(plugin).then(function (loadedPlugin) { - return recordLoadedPlugin(plugin, loadedPlugin); - }); - } - - // Install, then activate the plugin - return loader.installPluginByName(plugin).then(function () { - return loader.activatePluginByName(plugin); - }).then(function (loadedPlugin) { - return recordLoadedPlugin(plugin, loadedPlugin); - }); - }); - - return when.all(loadPromises).then(function () { - // Save our installed plugins to settings - return saveInstalledPlugins(_.keys(loadedPlugins)); - }).then(function () { - // Extend the loadedPlugins onto the available plugins - _.extend(availablePlugins, loadedPlugins); - }).otherwise(function (err) { - errors.logError( - err.message || err, - 'The plugin will not be loaded', - 'Check with the plugin creator, or read the plugin documentation for more details on plugin requirements' - ); - }); - }); - }, - availablePlugins: availablePlugins -}; \ No newline at end of file diff --git a/core/server/plugins/loader.js b/core/server/plugins/loader.js deleted file mode 100644 index 0238a66c0b..0000000000 --- a/core/server/plugins/loader.js +++ /dev/null @@ -1,70 +0,0 @@ - -var path = require('path'), - _ = require('underscore'), - when = require('when'), - appProxy = require('./proxy'), - config = require('../config'), - loader; - - - -// Get a relative path to the given plugins root, defaults -// to be relative to __dirname -function getPluginRelativePath(name, relativeTo) { - relativeTo = relativeTo || __dirname; - - return path.relative(relativeTo, path.join(config.paths().pluginPath, name)); -} - - -function getPluginByName(name) { - // Grab the plugin class to instantiate - var PluginClass = require(getPluginRelativePath(name)), - plugin; - - // Check for an actual class, otherwise just use whatever was returned - if (_.isFunction(PluginClass)) { - plugin = new PluginClass(appProxy); - } else { - plugin = PluginClass; - } - - return plugin; -} - -// The loader is responsible for loading plugins -loader = { - // Load a plugin and return the instantiated plugin - installPluginByName: function (name) { - var plugin = getPluginByName(name); - - // Check for an install() method on the plugin. - if (!_.isFunction(plugin.install)) { - return when.reject(new Error("Error loading plugin named " + name + "; no install() method defined.")); - } - - // Wrapping the install() with a when because it's possible - // to not return a promise from it. - return when(plugin.install(appProxy)).then(function () { - return when.resolve(plugin); - }); - }, - - // Activate a plugin and return it - activatePluginByName: function (name) { - var plugin = getPluginByName(name); - - // Check for an activate() method on the plugin. - if (!_.isFunction(plugin.activate)) { - return when.reject(new Error("Error loading plugin named " + name + "; no activate() method defined.")); - } - - // Wrapping the activate() with a when because it's possible - // to not return a promise from it. - return when(plugin.activate(appProxy)).then(function () { - return when.resolve(plugin); - }); - } -}; - -module.exports = loader; \ No newline at end of file diff --git a/core/server/storage/index.js b/core/server/storage/index.js index f976548222..26500240c3 100644 --- a/core/server/storage/index.js +++ b/core/server/storage/index.js @@ -2,7 +2,7 @@ var errors = require('../errorHandling'), storage; function get_storage() { - // TODO: this is where the check for storage plugins should go + // TODO: this is where the check for storage apps should go // Local file system is the default var storageChoice = 'localfilesystem'; diff --git a/core/server/update-check.js b/core/server/update-check.js index 3c8099f91c..26948b6032 100644 --- a/core/server/update-check.js +++ b/core/server/update-check.js @@ -16,7 +16,7 @@ // - post count - total number of posts // - user count - total number of users // - theme - name of the currently active theme -// - apps - names of any active plugins +// - apps - names of any active apps var crypto = require('crypto'), exec = require('child_process').exec, @@ -51,7 +51,7 @@ function updateCheckData() { ops.push(api.settings.read('dbHash').otherwise(errors.rejectError)); ops.push(api.settings.read('activeTheme').otherwise(errors.rejectError)); - ops.push(api.settings.read('activePlugins') + ops.push(api.settings.read('activeApps') .then(function (apps) { try { apps = JSON.parse(apps.value); diff --git a/core/test/unit/plugin_proxy_spec.js b/core/test/unit/app_proxy_spec.js similarity index 97% rename from core/test/unit/plugin_proxy_spec.js rename to core/test/unit/app_proxy_spec.js index c5357e2fd3..e389c40e42 100644 --- a/core/test/unit/plugin_proxy_spec.js +++ b/core/test/unit/app_proxy_spec.js @@ -6,7 +6,7 @@ var should = require('should'), filters = require('../../server/filters'), // Stuff we are testing - appProxy = require('../../server/plugins/proxy'); + appProxy = require('../../server/apps/proxy'); describe('App Proxy', function () { diff --git a/core/test/unit/config_spec.js b/core/test/unit/config_spec.js index 228e106e21..b3255a5872 100644 --- a/core/test/unit/config_spec.js +++ b/core/test/unit/config_spec.js @@ -294,7 +294,7 @@ describe('Config', function () { 'contentPath', 'corePath', 'themePath', - 'pluginPath', + 'appPath', 'imagesPath', 'imagesRelPath', 'adminViews', @@ -303,7 +303,7 @@ describe('Config', function () { 'lang', 'debugPath', 'availableThemes', - 'availablePlugins' + 'availableApps' ); }); diff --git a/core/test/utils/api.js b/core/test/utils/api.js index 384bd7ee2b..73d9f0581b 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -11,7 +11,7 @@ var _ = require('underscore'), 'updated_by', 'published_at', 'published_by', 'page', 'author', 'user', 'tags'], // TODO: remove databaseVersion, dbHash settings: ['databaseVersion', 'dbHash', 'title', 'description', 'email', 'logo', 'cover', 'defaultLang', - "permalinks", 'postsPerPage', 'forceI18n', 'activeTheme', 'activePlugins', 'installedPlugins', + "permalinks", 'postsPerPage', 'forceI18n', 'activeTheme', 'activeApps', 'installedApps', 'availableThemes', 'nextUpdateCheck', 'displayUpdateNotification'], tag: ['id', 'uuid', 'name', 'slug', 'description', 'parent_id', 'meta_title', 'meta_description', 'created_at', 'created_by', 'updated_at', 'updated_by'], diff --git a/core/test/utils/fixtures/export-001.json b/core/test/utils/fixtures/export-001.json index 82732f0021..9ad624a0df 100644 --- a/core/test/utils/fixtures/export-001.json +++ b/core/test/utils/fixtures/export-001.json @@ -284,9 +284,9 @@ { "id": 13, "uuid": "f3afce35-5166-453e-86c3-50dfff74dca7", - "key": "activePlugins", + "key": "activeApps", "value": "[]", - "type": "plugin", + "type": "app", "created_at": 1388318310831, "created_by": 1, "updated_at": 1388318310831, @@ -295,9 +295,9 @@ { "id": 14, "uuid": "2ea560a3-2304-449d-a62b-f7b622987510", - "key": "installedPlugins", + "key": "installedApps", "value": "[]", - "type": "plugin", + "type": "app", "created_at": 1388318310831, "created_by": 1, "updated_at": 1388318310831,