2014-07-04 14:43:03 -04:00
'use strict' ;
2013-09-17 13:09:37 -04:00
var fs = require ( 'fs' ) ,
2013-07-28 01:16:21 -04:00
path = require ( 'path' ) ,
2013-07-28 02:24:41 -04:00
async = require ( 'async' ) ,
2013-08-13 14:45:28 -04:00
winston = require ( 'winston' ) ,
2014-02-13 22:38:24 -05:00
semver = require ( 'semver' ) ,
2014-08-24 12:30:49 -04:00
express = require ( 'express' ) ,
2015-02-12 16:15:28 -05:00
nconf = require ( 'nconf' ) ,
2014-02-13 22:38:24 -05:00
2014-01-30 11:57:50 -05:00
db = require ( './database' ) ,
2014-07-21 13:14:12 -04:00
emitter = require ( './emitter' ) ,
2014-02-13 22:38:24 -05:00
meta = require ( './meta' ) ,
2015-03-31 15:11:38 -04:00
translator = require ( '../public/src/modules/translator' ) ,
2014-07-04 14:43:03 -04:00
utils = require ( '../public/src/utils' ) ,
2014-08-24 12:30:49 -04:00
hotswap = require ( './hotswap' ) ,
controllers = require ( './controllers' ) ,
app , middleware ;
2013-12-02 16:19:30 -05:00
2013-12-03 13:36:44 -05:00
( function ( Plugins ) {
2014-12-26 18:54:20 -05:00
require ( './plugins/install' ) ( Plugins ) ;
require ( './plugins/load' ) ( Plugins ) ;
require ( './plugins/hooks' ) ( Plugins ) ;
2013-12-03 13:36:44 -05:00
Plugins . libraries = { } ;
Plugins . loadedHooks = { } ;
Plugins . staticDirs = { } ;
Plugins . cssFiles = [ ] ;
2014-02-25 14:13:09 -05:00
Plugins . lessFiles = [ ] ;
2014-03-02 13:28:09 -05:00
Plugins . clientScripts = [ ] ;
2014-07-21 14:37:40 -04:00
Plugins . customLanguages = [ ] ;
2014-10-03 17:29:42 -04:00
Plugins . libraryPaths = [ ] ;
2015-08-20 11:01:59 -04:00
Plugins . versionWarning = [ ] ;
2013-09-13 11:10:17 -04:00
2013-12-03 13:36:44 -05:00
Plugins . initialized = false ;
2013-07-28 01:16:21 -04:00
2015-01-06 23:29:48 -05:00
Plugins . requireLibrary = function ( pluginID , libraryPath ) {
Plugins . libraries [ pluginID ] = require ( libraryPath ) ;
2014-12-26 18:54:20 -05:00
Plugins . libraryPaths . push ( libraryPath ) ;
} ;
2015-04-27 20:26:02 -04:00
Plugins . init = function ( nbbApp , nbbMiddleware , callback ) {
callback = callback || function ( ) { } ;
2013-12-03 13:36:44 -05:00
if ( Plugins . initialized ) {
2015-04-27 20:26:02 -04:00
return callback ( ) ;
2013-12-03 13:36:44 -05:00
}
2014-09-21 14:29:27 -04:00
app = nbbApp ;
middleware = nbbMiddleware ;
hotswap . prepare ( nbbApp ) ;
2013-12-03 13:36:44 -05:00
if ( global . env === 'development' ) {
2014-11-26 13:28:10 -05:00
winston . verbose ( '[plugins] Initializing plugins system' ) ;
2013-12-03 13:36:44 -05:00
}
Plugins . reload ( function ( err ) {
if ( err ) {
2014-11-26 13:28:10 -05:00
winston . error ( '[plugins] NodeBB encountered a problem while loading plugins' , err . message ) ;
2015-04-27 20:26:02 -04:00
return callback ( err ) ;
2013-12-03 13:36:44 -05:00
}
2013-11-03 17:15:18 -05:00
2013-12-03 13:36:44 -05:00
if ( global . env === 'development' ) {
winston . info ( '[plugins] Plugins OK' ) ;
}
2014-07-21 13:25:25 -04:00
2013-12-03 13:36:44 -05:00
Plugins . initialized = true ;
2014-07-21 13:14:12 -04:00
emitter . emit ( 'plugins:loaded' ) ;
2015-04-27 20:26:02 -04:00
callback ( ) ;
2013-12-03 13:36:44 -05:00
} ) ;
} ;
Plugins . reload = function ( callback ) {
// Resetting all local plugin data
2014-10-03 17:29:42 -04:00
Plugins . libraries = { } ;
2013-12-03 13:36:44 -05:00
Plugins . loadedHooks = { } ;
Plugins . staticDirs = { } ;
2015-08-20 11:01:59 -04:00
Plugins . versionWarning = [ ] ;
2013-12-03 13:36:44 -05:00
Plugins . cssFiles . length = 0 ;
2014-03-06 17:50:15 -05:00
Plugins . lessFiles . length = 0 ;
Plugins . clientScripts . length = 0 ;
2014-10-03 17:29:42 -04:00
Plugins . libraryPaths . length = 0 ;
2013-12-03 13:36:44 -05:00
2015-05-21 14:37:23 -04:00
Plugins . registerHook ( 'core' , {
hook : 'static:app.load' ,
method : addLanguages
} ) ;
2013-12-03 13:36:44 -05:00
async . waterfall ( [
function ( next ) {
2015-02-23 14:57:22 -05:00
db . getSortedSetRange ( 'plugins:active' , 0 , - 1 , next ) ;
2013-12-03 13:36:44 -05:00
} ,
function ( plugins , next ) {
2015-02-23 14:57:22 -05:00
if ( ! Array . isArray ( plugins ) ) {
2014-07-02 16:19:55 -04:00
return next ( ) ;
2014-04-08 21:36:18 +01:00
}
2014-01-30 11:57:50 -05:00
2014-04-08 21:36:18 +01:00
plugins . push ( meta . config [ 'theme:id' ] ) ;
2014-02-03 20:41:42 -05:00
2014-04-08 21:36:18 +01:00
plugins = plugins . filter ( function ( plugin ) {
return plugin && typeof plugin === 'string' ;
} ) . map ( function ( plugin ) {
return path . join ( _ _dirname , '../node_modules/' , plugin ) ;
} ) ;
async . filter ( plugins , fs . exists , function ( plugins ) {
2015-02-23 15:55:35 -05:00
async . eachSeries ( plugins , Plugins . loadPlugin , next ) ;
2014-04-08 21:36:18 +01:00
} ) ;
2013-12-03 13:36:44 -05:00
} ,
function ( next ) {
2015-08-20 11:01:59 -04:00
// If some plugins are incompatible, throw the warning here
if ( Plugins . versionWarning . length ) {
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 < numPlugins ; x ++ ) {
process . stdout . write ( ' * ' . yellow + Plugins . versionWarning [ x ] . reset + '\n' ) ;
}
process . stdout . write ( '\n' ) ;
}
2013-12-03 13:36:44 -05:00
Object . keys ( Plugins . loadedHooks ) . forEach ( function ( hook ) {
var hooks = Plugins . loadedHooks [ hook ] ;
hooks = hooks . sort ( function ( a , b ) {
return a . priority - b . priority ;
} ) ;
} ) ;
2013-11-03 17:15:18 -05:00
2013-12-03 13:36:44 -05:00
next ( ) ;
2015-04-16 14:26:58 -04:00
}
2013-12-03 13:36:44 -05:00
] , callback ) ;
} ;
2014-08-24 12:30:49 -04:00
Plugins . reloadRoutes = function ( callback ) {
2015-04-16 14:26:58 -04:00
callback = callback || function ( ) { } ;
2014-09-21 14:29:27 -04:00
var router = express . Router ( ) ;
router . hotswapId = 'plugins' ;
router . render = function ( ) {
app . render . apply ( app , arguments ) ;
} ;
2015-04-16 14:26:58 -04:00
Plugins . fireHook ( 'static:app.load' , { app : app , router : router , middleware : middleware , controllers : controllers } , function ( err ) {
if ( err ) {
return winston . error ( '[plugins] Encountered error while executing post-router plugins hooks: ' + err . message ) ;
}
2014-09-21 14:29:27 -04:00
hotswap . replace ( 'plugins' , router ) ;
2014-11-26 13:28:10 -05:00
winston . verbose ( '[plugins] All plugins reloaded and rerouted' ) ;
2014-09-21 14:29:27 -04:00
callback ( ) ;
} ) ;
2014-08-24 12:30:49 -04:00
} ;
2014-03-06 12:06:19 -05:00
Plugins . getTemplates = function ( callback ) {
var templates = { } ;
Plugins . showInstalled ( function ( err , plugins ) {
async . each ( plugins , function ( plugin , next ) {
2014-03-09 20:45:37 -04:00
if ( plugin . templates && plugin . id && plugin . active ) {
2014-03-06 12:06:19 -05:00
var templatesPath = path . join ( _ _dirname , '../node_modules' , plugin . id , plugin . templates ) ;
utils . walk ( templatesPath , function ( err , pluginTemplates ) {
2014-09-26 13:19:23 -04:00
if ( pluginTemplates ) {
pluginTemplates . forEach ( function ( pluginTemplate ) {
templates [ "/" + pluginTemplate . replace ( templatesPath , '' ) . substring ( 1 ) ] = pluginTemplate ;
} ) ;
} else {
winston . warn ( '[plugins/' + plugin . id + '] A templates directory was defined for this plugin, but was not found.' ) ;
}
2014-03-06 12:06:19 -05:00
2015-01-07 17:23:39 -05:00
next ( false ) ;
2014-03-06 12:06:19 -05:00
} ) ;
} else {
next ( false ) ;
}
} , function ( err ) {
callback ( err , templates ) ;
} ) ;
} ) ;
} ;
2013-08-22 10:47:24 -04:00
2015-07-08 17:04:21 -04:00
Plugins . get = function ( id , callback ) {
var url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins/' + id ;
require ( 'request' ) ( url , {
json : true
} , function ( err , res , body ) {
2015-08-06 12:41:16 -04:00
if ( res . statusCode === 404 || ! body . payload ) {
return callback ( err , { } ) ;
}
2015-07-08 17:04:21 -04:00
Plugins . normalise ( [ body . payload ] , function ( err , normalised ) {
2015-07-08 17:27:45 -04:00
normalised = normalised . filter ( function ( plugin ) {
2015-08-06 12:41:16 -04:00
return plugin . id === id ;
2015-07-08 17:27:45 -04:00
} ) ;
2015-07-08 17:04:21 -04:00
return callback ( err , ! err ? normalised [ 0 ] : undefined ) ;
} ) ;
} ) ;
} ;
2014-04-22 21:02:58 -04:00
Plugins . getAll = function ( callback ) {
2015-02-23 17:25:36 -05:00
var url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins?version=' + require ( '../package.json' ) . version ;
2015-03-11 18:04:27 -04:00
2015-07-08 17:04:21 -04:00
require ( 'request' ) ( url , {
json : true
} , function ( err , res , body ) {
2014-04-22 21:02:58 -04:00
var plugins = [ ] ;
2014-09-24 18:43:02 -04:00
2015-07-08 17:04:21 -04:00
if ( err ) {
2014-04-22 21:02:58 -04:00
winston . error ( 'Error parsing plugins : ' + err . message ) ;
2014-09-24 18:43:02 -04:00
plugins = [ ] ;
2014-04-22 21:02:58 -04:00
}
2015-07-08 17:04:21 -04:00
Plugins . normalise ( body , callback ) ;
} ) ;
} ;
Plugins . normalise = function ( apiReturn , callback ) {
var pluginMap = { } ;
for ( var i = 0 ; i < apiReturn . length ; ++ i ) {
apiReturn [ i ] . id = apiReturn [ i ] . name ;
apiReturn [ i ] . installed = false ;
apiReturn [ i ] . active = false ;
apiReturn [ i ] . url = apiReturn [ i ] . url ? apiReturn [ i ] . url : apiReturn [ i ] . repository ? apiReturn [ i ] . repository . url : '' ;
apiReturn [ i ] . latest = apiReturn [ i ] . latest ;
pluginMap [ apiReturn [ i ] . name ] = apiReturn [ i ] ;
}
Plugins . showInstalled ( function ( err , installedPlugins ) {
if ( err ) {
return callback ( err ) ;
2014-04-23 21:23:03 -04:00
}
2015-08-13 12:33:24 -04:00
installedPlugins = installedPlugins . filter ( function ( plugin ) {
return plugin && ! plugin . system ;
} ) ;
2015-07-08 17:04:21 -04:00
async . each ( installedPlugins , function ( plugin , next ) {
// If it errored out because a package.json or plugin.json couldn't be read, no need to do this stuff
if ( plugin . error ) {
pluginMap [ plugin . id ] = pluginMap [ plugin . id ] || { } ;
pluginMap [ plugin . id ] . installed = true ;
pluginMap [ plugin . id ] . error = true ;
return next ( ) ;
}
pluginMap [ plugin . id ] = pluginMap [ plugin . id ] || { } ;
pluginMap [ plugin . id ] . id = pluginMap [ plugin . id ] . id || plugin . id ;
pluginMap [ plugin . id ] . name = plugin . name || pluginMap [ plugin . id ] . name ;
pluginMap [ plugin . id ] . description = plugin . description ;
pluginMap [ plugin . id ] . url = pluginMap [ plugin . id ] . url || plugin . url ;
pluginMap [ plugin . id ] . installed = true ;
pluginMap [ plugin . id ] . isTheme = ! ! plugin . id . match ( 'nodebb-theme-' ) ;
pluginMap [ plugin . id ] . error = plugin . error || false ;
pluginMap [ plugin . id ] . active = plugin . active ;
pluginMap [ plugin . id ] . version = plugin . version ;
pluginMap [ plugin . id ] . latest = pluginMap [ plugin . id ] . latest || plugin . version ;
pluginMap [ plugin . id ] . outdated = semver . gt ( pluginMap [ plugin . id ] . latest , pluginMap [ plugin . id ] . version ) ;
next ( ) ;
} , function ( err ) {
2014-04-23 21:23:03 -04:00
if ( err ) {
return callback ( err ) ;
}
2014-04-22 21:02:58 -04:00
2015-07-08 17:04:21 -04:00
var pluginArray = [ ] ;
2014-12-03 16:17:12 -05:00
2015-07-08 17:04:21 -04:00
for ( var key in pluginMap ) {
if ( pluginMap . hasOwnProperty ( key ) ) {
pluginArray . push ( pluginMap [ key ] ) ;
2014-04-22 21:02:58 -04:00
}
2015-07-08 17:04:21 -04:00
}
2014-04-22 21:02:58 -04:00
2015-07-08 17:04:21 -04:00
pluginArray . sort ( function ( a , b ) {
if ( a . name > b . name ) {
return 1 ;
} else if ( a . name < b . name ) {
return - 1 ;
} else {
return 0 ;
2014-04-23 21:23:03 -04:00
}
} ) ;
2015-07-08 17:04:21 -04:00
callback ( null , pluginArray ) ;
2014-04-23 21:23:03 -04:00
} ) ;
2014-04-22 21:02:58 -04:00
} ) ;
} ;
2013-12-03 13:36:44 -05:00
Plugins . showInstalled = function ( callback ) {
2014-04-08 21:36:18 +01:00
var npmPluginPath = path . join ( _ _dirname , '../node_modules' ) ;
2013-12-03 13:36:44 -05:00
async . waterfall ( [
2014-04-08 21:36:18 +01:00
async . apply ( fs . readdir , npmPluginPath ) ,
function ( dirs , next ) {
dirs = dirs . filter ( function ( dir ) {
2015-03-11 18:04:27 -04:00
return dir . startsWith ( 'nodebb-plugin-' ) ||
dir . startsWith ( 'nodebb-widget-' ) ||
dir . startsWith ( 'nodebb-rewards-' ) ||
2015-02-23 15:55:35 -05:00
dir . startsWith ( 'nodebb-theme-' ) ;
2014-04-08 21:36:18 +01:00
} ) . map ( function ( dir ) {
return path . join ( npmPluginPath , dir ) ;
} ) ;
async . filter ( dirs , function ( dir , callback ) {
fs . stat ( dir , function ( err , stats ) {
2015-03-11 18:04:27 -04:00
callback ( ! err && stats . isDirectory ( ) ) ;
2014-04-23 21:23:03 -04:00
} ) ;
2014-04-08 21:36:18 +01:00
} , function ( plugins ) {
next ( null , plugins ) ;
2013-12-03 13:36:44 -05:00
} ) ;
} ,
2014-04-08 21:36:18 +01:00
2013-12-03 13:36:44 -05:00
function ( files , next ) {
var plugins = [ ] ;
async . each ( files , function ( file , next ) {
async . waterfall ( [
function ( next ) {
2015-01-06 23:29:48 -05:00
Plugins . loadPluginInfo ( file , next ) ;
2013-12-03 13:36:44 -05:00
} ,
2015-01-06 23:29:48 -05:00
function ( pluginData , next ) {
Plugins . isActive ( pluginData . name , function ( err , active ) {
2013-12-04 17:57:51 -05:00
if ( err ) {
2014-12-08 17:50:45 -05:00
return next ( new Error ( 'no-active-state' ) ) ;
2013-12-04 17:57:51 -05:00
}
2013-07-28 01:16:21 -04:00
2015-01-06 23:29:48 -05:00
delete pluginData . hooks ;
delete pluginData . library ;
pluginData . active = active ;
pluginData . installed = true ;
pluginData . error = false ;
next ( null , pluginData ) ;
2013-12-03 13:36:44 -05:00
} ) ;
}
2015-03-11 18:04:27 -04:00
] , function ( err , pluginData ) {
2014-07-04 14:43:03 -04:00
if ( err ) {
return next ( ) ; // Silently fail
}
2013-07-28 01:16:21 -04:00
2015-03-11 18:04:27 -04:00
plugins . push ( pluginData ) ;
2013-12-03 13:36:44 -05:00
next ( ) ;
} ) ;
} , function ( err ) {
next ( null , plugins ) ;
} ) ;
}
2014-12-26 18:54:20 -05:00
] , callback ) ;
2014-03-06 12:06:19 -05:00
} ;
2014-07-21 13:42:33 -04:00
2014-10-03 17:29:42 -04:00
Plugins . clearRequireCache = function ( next ) {
2014-11-29 20:50:14 -05:00
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 ) ;
} ) ;
2014-07-21 14:37:40 -04:00
2015-02-10 23:34:06 -05:00
Plugins . fireHook ( 'filter:plugins.clearRequireCache' , { paths : paths } , next ) ;
} ,
function ( data , next ) {
for ( var x = 0 , numPaths = data . paths . length ; x < numPaths ; x ++ ) {
delete require . cache [ data . paths [ x ] ] ;
}
winston . verbose ( '[plugins] Plugin libraries removed from Node.js cache' ) ;
next ( ) ;
2015-04-02 03:16:16 +08:00
}
2015-02-10 23:34:06 -05:00
] , next ) ;
2014-10-03 17:29:42 -04:00
} ;
2014-07-21 14:37:40 -04:00
2014-11-08 15:20:56 -05:00
function addLanguages ( params , callback ) {
2014-07-21 14:37:40 -04:00
Plugins . customLanguages . forEach ( function ( lang ) {
2014-11-08 15:20:56 -05:00
params . router . get ( '/language' + lang . route , function ( req , res , next ) {
2014-07-21 14:37:40 -04:00
res . json ( lang . file ) ;
} ) ;
var components = lang . route . split ( '/' ) ,
language = components [ 1 ] ,
filename = components [ 2 ] . replace ( '.json' , '' ) ;
2014-07-25 19:15:31 -04:00
2014-07-21 14:37:40 -04:00
translator . addTranslation ( language , filename , lang . file ) ;
} ) ;
2015-07-15 12:08:43 -04:00
var fallbackPath ;
for ( var resource in Plugins . customLanguageFallbacks ) {
fallbackPath = Plugins . customLanguageFallbacks [ resource ] ;
params . router . get ( '/language/:lang/' + resource + '.json' , function ( req , res , next ) {
winston . verbose ( '[translator] No resource file found for ' + req . params . lang + '/' + resource + ', using provided fallback language file' ) ;
res . sendFile ( fallbackPath ) ;
} ) ;
}
2014-07-21 14:37:40 -04:00
callback ( null ) ;
}
2013-12-03 13:36:44 -05:00
} ( exports ) ) ;