'use strict'; var fs = require('fs'); var path = require('path'); var async = require('async'); var winston = require('winston'); var semver = require('semver'); var express = require('express'); var nconf = require('nconf'); var db = require('./database'); var emitter = require('./emitter'); var translator = require('../public/src/modules/translator'); var utils = require('../public/src/utils'); var hotswap = require('./hotswap'); var file = require('./file'); var controllers = require('./controllers'); var app; var middleware; (function(Plugins) { require('./plugins/install')(Plugins); require('./plugins/load')(Plugins); require('./plugins/hooks')(Plugins); Plugins.libraries = {}; Plugins.loadedHooks = {}; Plugins.staticDirs = {}; Plugins.cssFiles = []; Plugins.lessFiles = []; Plugins.clientScripts = []; Plugins.acpScripts = []; Plugins.customLanguages = {}; Plugins.customLanguageFallbacks = {}; Plugins.libraryPaths = []; Plugins.versionWarning = []; Plugins.initialized = false; Plugins.requireLibrary = function(pluginID, libraryPath) { Plugins.libraries[pluginID] = require(libraryPath); Plugins.libraryPaths.push(libraryPath); }; Plugins.init = function(nbbApp, nbbMiddleware, callback) { callback = callback || function() {}; if (Plugins.initialized) { return callback(); } app = nbbApp; middleware = nbbMiddleware; hotswap.prepare(nbbApp); if (global.env === 'development') { winston.verbose('[plugins] Initializing plugins system'); } Plugins.reload(function(err) { if (err) { winston.error('[plugins] NodeBB encountered a problem while loading plugins', err.message); return callback(err); } if (global.env === 'development') { winston.info('[plugins] Plugins OK'); } Plugins.initialized = true; emitter.emit('plugins:loaded'); callback(); }); }; Plugins.reload = function(callback) { // Resetting all local plugin data Plugins.libraries = {}; Plugins.loadedHooks = {}; Plugins.staticDirs = {}; Plugins.versionWarning = []; Plugins.cssFiles.length = 0; Plugins.lessFiles.length = 0; Plugins.clientScripts.length = 0; Plugins.acpScripts.length = 0; Plugins.libraryPaths.length = 0; // Plugins.registerHook('core', { // hook: 'static:app.load', // method: addLanguages // }); async.waterfall([ function(next) { // Build language code list fs.readdir(path.join(__dirname, '../public/language'), function(err, directories) { if (err) { return next(err); } Plugins.languageCodes = directories.filter(function(code) { return code !== 'TODO'; }); next(); }); }, function(next) { db.getSortedSetRange('plugins:active', 0, -1, next); }, function(plugins, next) { if (!Array.isArray(plugins)) { return next(); } plugins = plugins.filter(function(plugin){ return plugin && typeof plugin === 'string'; }).map(function(plugin){ return path.join(__dirname, '../node_modules/', plugin); }); async.filter(plugins, file.exists, function(plugins) { async.eachSeries(plugins, Plugins.loadPlugin, next); }); }, function(next) { // If some plugins are incompatible, throw the warning here if (Plugins.versionWarning.length && nconf.get('isPrimary') === 'true') { process.stdout.write('\n'); winston.warn('[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.'); for(var x=0,numPlugins=Plugins.versionWarning.length;x b.name ) { return 1; } else if (a.name < b.name ){ return -1; } else { return 0; } }); callback(null, pluginArray); }); }); }; Plugins.showInstalled = function(callback) { var npmPluginPath = path.join(__dirname, '../node_modules'); async.waterfall([ async.apply(fs.readdir, npmPluginPath), function(dirs, next) { dirs = dirs.filter(function(dir){ return dir.startsWith('nodebb-plugin-') || dir.startsWith('nodebb-widget-') || dir.startsWith('nodebb-rewards-') || dir.startsWith('nodebb-theme-'); }).map(function(dir){ return path.join(npmPluginPath, dir); }); async.filter(dirs, function(dir, callback){ fs.stat(dir, function(err, stats){ callback(!err && stats.isDirectory()); }); }, function(plugins){ next(null, plugins); }); }, function(files, next) { var plugins = []; async.each(files, function(file, next) { async.waterfall([ function(next) { Plugins.loadPluginInfo(file, next); }, function(pluginData, next) { Plugins.isActive(pluginData.name, function(err, active) { if (err) { return next(new Error('no-active-state')); } delete pluginData.hooks; delete pluginData.library; pluginData.active = active; pluginData.installed = true; pluginData.error = false; next(null, pluginData); }); } ], function(err, pluginData) { if (err) { return next(); // Silently fail } plugins.push(pluginData); next(); }); }, function(err) { next(err, plugins); }); } ], callback); }; Plugins.clearRequireCache = function(next) { var cached = Object.keys(require.cache); async.waterfall([ async.apply(async.map, Plugins.libraryPaths, fs.realpath), function(paths, next) { paths = paths.map(function(pluginLib) { var parent = path.dirname(pluginLib); return cached.filter(function(libPath) { return libPath.indexOf(parent) !== -1; }); }).reduce(function(prev, cur) { return prev.concat(cur); }); Plugins.fireHook('filter:plugins.clearRequireCache', {paths: paths}, next); }, function(data, next) { for (var x=0,numPaths=data.paths.length;x