2014-07-04 14:43:03 -04:00
'use strict' ;
2016-03-09 14:53:32 +02:00
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 hotswap = require ( './hotswap' ) ;
var app ;
var middleware ;
2013-12-02 16:19:30 -05:00
2017-05-27 01:44:26 -04:00
var Plugins = module . exports ;
require ( './plugins/install' ) ( Plugins ) ;
require ( './plugins/load' ) ( Plugins ) ;
require ( './plugins/hooks' ) ( Plugins ) ;
Plugins . data = require ( './plugins/data' ) ;
Plugins . getPluginPaths = Plugins . data . getPluginPaths ;
Plugins . loadPluginInfo = Plugins . data . loadPluginInfo ;
Plugins . pluginsData = { } ;
Plugins . libraries = { } ;
Plugins . loadedHooks = { } ;
Plugins . staticDirs = { } ;
Plugins . cssFiles = [ ] ;
Plugins . lessFiles = [ ] ;
2017-08-21 17:48:58 -04:00
Plugins . acpLessFiles = [ ] ;
2017-05-27 01:44:26 -04:00
Plugins . clientScripts = [ ] ;
Plugins . acpScripts = [ ] ;
Plugins . libraryPaths = [ ] ;
Plugins . versionWarning = [ ] ;
Plugins . soundpacks = [ ] ;
Plugins . languageData = { } ;
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 ( ) ;
}
if ( nbbApp ) {
app = nbbApp ;
middleware = nbbMiddleware ;
hotswap . prepare ( nbbApp ) ;
}
if ( global . env === 'development' ) {
winston . verbose ( '[plugins] Initializing plugins system' ) ;
}
Plugins . reload ( function ( err ) {
if ( err ) {
2017-11-01 18:58:44 -06:00
winston . error ( '[plugins] NodeBB encountered a problem while loading plugins' , err ) ;
2017-05-27 01:44:26 -04:00
return callback ( err ) ;
2016-11-17 11:51:38 -05:00
}
2014-09-21 14:29:27 -04:00
2013-12-03 13:36:44 -05:00
if ( global . env === 'development' ) {
2017-05-27 01:44:26 -04:00
winston . info ( '[plugins] Plugins OK' ) ;
2013-12-03 13:36:44 -05:00
}
2017-05-27 01:44:26 -04:00
Plugins . initialized = true ;
callback ( ) ;
} ) ;
} ;
2013-12-03 13:36:44 -05:00
2017-05-27 01:44:26 -04:00
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 ;
2017-08-21 17:48:58 -04:00
Plugins . acpLessFiles . length = 0 ;
2017-05-27 01:44:26 -04:00
Plugins . clientScripts . length = 0 ;
Plugins . acpScripts . length = 0 ;
Plugins . libraryPaths . length = 0 ;
async . waterfall ( [
Plugins . getPluginPaths ,
function ( paths , next ) {
async . eachSeries ( paths , Plugins . loadPlugin , next ) ;
} ,
function ( next ) {
// If some plugins are incompatible, throw the warning here
if ( Plugins . versionWarning . length && nconf . get ( 'isPrimary' ) === 'true' ) {
2017-11-27 13:44:30 -07:00
console . log ( '' ) ;
2017-05-27 01:44:26 -04:00
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 += 1 ) {
2017-11-27 13:44:30 -07:00
console . log ( ' * ' . yellow + Plugins . versionWarning [ x ] ) ;
2015-08-20 11:01:59 -04:00
}
2017-11-27 13:44:30 -07:00
console . log ( '' ) ;
2017-05-27 01:44:26 -04:00
}
2015-08-20 11:01:59 -04:00
2017-05-27 01:44:26 -04:00
Object . keys ( Plugins . loadedHooks ) . forEach ( function ( hook ) {
var hooks = Plugins . loadedHooks [ hook ] ;
hooks . sort ( function ( a , b ) {
return a . priority - b . priority ;
2013-12-03 13:36:44 -05:00
} ) ;
2017-05-27 01:44:26 -04:00
} ) ;
2013-11-03 17:15:18 -05:00
2017-05-27 01:44:26 -04:00
next ( ) ;
} ,
] , callback ) ;
} ;
2013-12-03 13:36:44 -05:00
2017-05-27 01:44:26 -04:00
Plugins . reloadRoutes = function ( callback ) {
var router = express . Router ( ) ;
2016-08-26 18:50:37 +03:00
2017-05-27 01:44:26 -04:00
router . hotswapId = 'plugins' ;
router . render = function ( ) {
app . render . apply ( app , arguments ) ;
} ;
2014-09-21 14:29:27 -04:00
2017-05-27 01:44:26 -04:00
var controllers = require ( './controllers' ) ;
Plugins . fireHook ( 'static:app.load' , { app : app , router : router , middleware : middleware , controllers : controllers } , function ( err ) {
if ( err ) {
2017-11-01 18:58:44 -06:00
winston . error ( '[plugins] Encountered error while executing post-router plugins hooks' , err ) ;
2017-05-27 01:44:26 -04:00
return callback ( err ) ;
}
2015-04-16 14:26:58 -04:00
2017-05-27 01:44:26 -04:00
hotswap . replace ( 'plugins' , router ) ;
winston . verbose ( '[plugins] All plugins reloaded and rerouted' ) ;
callback ( ) ;
} ) ;
} ;
2014-08-24 12:30:49 -04:00
2017-05-27 01:44:26 -04:00
Plugins . get = function ( id , callback ) {
var url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins/' + id ;
2015-07-08 17:04:21 -04:00
2017-05-27 01:44:26 -04:00
require ( 'request' ) ( url , {
json : true ,
} , function ( err , res , body ) {
if ( res . statusCode === 404 || ! body . payload ) {
return callback ( err , { } ) ;
}
2015-08-06 12:41:16 -04:00
2017-05-27 01:44:26 -04:00
Plugins . normalise ( [ body . payload ] , function ( err , normalised ) {
normalised = normalised . filter ( function ( plugin ) {
return plugin . id === id ;
2015-07-08 17:04:21 -04:00
} ) ;
2017-05-27 01:44:26 -04:00
return callback ( err , ! err ? normalised [ 0 ] : undefined ) ;
2015-07-08 17:04:21 -04:00
} ) ;
2017-05-27 01:44:26 -04:00
} ) ;
} ;
Plugins . list = function ( matching , callback ) {
if ( arguments . length === 1 && typeof matching === 'function' ) {
callback = matching ;
matching = true ;
}
var version = require ( path . join ( nconf . get ( 'base_dir' ) , 'package.json' ) ) . version ;
var url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins' + ( matching !== false ? '?version=' + version : '' ) ;
require ( 'request' ) ( url , {
json : true ,
} , function ( err , res , body ) {
2018-01-04 10:48:08 -05:00
if ( err || ( res && res . statusCode !== 200 ) ) {
winston . error ( 'Error loading ' + url , err || body ) ;
2017-11-28 12:42:26 -05:00
return Plugins . normalise ( [ ] , callback ) ;
2015-08-24 13:41:25 -04:00
}
2015-03-11 18:04:27 -04:00
2017-05-27 01:44:26 -04:00
Plugins . normalise ( body , callback ) ;
} ) ;
} ;
Plugins . normalise = function ( apiReturn , callback ) {
2018-06-27 15:12:02 -04:00
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/ ;
2017-05-27 01:44:26 -04:00
var pluginMap = { } ;
var dependencies = require ( path . join ( nconf . get ( 'base_dir' ) , 'package.json' ) ) . dependencies ;
2018-01-04 10:48:08 -05:00
apiReturn = Array . isArray ( apiReturn ) ? apiReturn : [ ] ;
2017-05-27 01:44:26 -04:00
for ( var i = 0 ; i < apiReturn . length ; i += 1 ) {
apiReturn [ i ] . id = apiReturn [ i ] . name ;
apiReturn [ i ] . installed = false ;
apiReturn [ i ] . active = false ;
apiReturn [ i ] . url = apiReturn [ i ] . url || ( apiReturn [ i ] . repository ? apiReturn [ i ] . repository . url : '' ) ;
pluginMap [ apiReturn [ i ] . name ] = apiReturn [ i ] ;
}
Plugins . showInstalled ( function ( err , installedPlugins ) {
if ( err ) {
return callback ( err ) ;
}
2014-04-22 21:02:58 -04:00
2017-05-27 01:44:26 -04:00
installedPlugins = installedPlugins . filter ( function ( plugin ) {
return plugin && ! plugin . system ;
2015-07-08 17:04:21 -04:00
} ) ;
2017-05-27 01:44:26 -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 ( ) ;
}
2015-07-08 17:04:21 -04:00
2017-05-27 01:44:26 -04:00
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 ;
2018-06-09 16:26:28 -06:00
pluginMap [ plugin . id ] . isTheme = themeNamePattern . test ( plugin . id ) ;
2017-05-27 01:44:26 -04:00
pluginMap [ plugin . id ] . error = plugin . error || false ;
pluginMap [ plugin . id ] . active = plugin . active ;
pluginMap [ plugin . id ] . version = plugin . version ;
pluginMap [ plugin . id ] . settingsRoute = plugin . settingsRoute ;
2017-08-03 10:48:31 -04:00
pluginMap [ plugin . id ] . license = plugin . license ;
2017-05-27 01:44:26 -04:00
// If package.json defines a version to use, stick to that
if ( dependencies . hasOwnProperty ( plugin . id ) && semver . valid ( dependencies [ plugin . id ] ) ) {
pluginMap [ plugin . id ] . latest = dependencies [ plugin . id ] ;
} else {
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 ) {
2015-07-08 17:04:21 -04:00
if ( err ) {
return callback ( err ) ;
2014-04-23 21:23:03 -04:00
}
2017-05-27 01:44:26 -04:00
var pluginArray = [ ] ;
2015-08-13 12:33:24 -04:00
2017-05-27 01:44:26 -04:00
for ( var key in pluginMap ) {
if ( pluginMap . hasOwnProperty ( key ) ) {
pluginArray . push ( pluginMap [ key ] ) ;
2014-04-23 21:23:03 -04:00
}
2017-05-27 01:44:26 -04:00
}
2014-04-22 21:02:58 -04:00
2017-05-27 01:44:26 -04:00
pluginArray . sort ( function ( a , b ) {
if ( a . name > b . name ) {
return 1 ;
} else if ( a . name < b . name ) {
return - 1 ;
2015-07-08 17:04:21 -04:00
}
2017-05-27 01:44:26 -04:00
return 0 ;
2014-04-23 21:23:03 -04:00
} ) ;
2013-12-03 13:36:44 -05:00
2017-05-27 01:44:26 -04:00
callback ( null , pluginArray ) ;
} ) ;
} ) ;
} ;
2017-07-19 11:38:51 -06:00
Plugins . nodeModulesPath = path . join ( _ _dirname , '../node_modules' ) ;
2017-05-27 01:44:26 -04:00
Plugins . showInstalled = function ( callback ) {
2017-07-12 23:31:29 -06:00
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/ ;
2017-05-27 01:44:26 -04:00
async . waterfall ( [
2017-07-12 23:31:29 -06:00
function ( next ) {
2017-07-19 11:38:51 -06:00
fs . readdir ( Plugins . nodeModulesPath , next ) ;
2017-07-12 23:31:29 -06:00
} ,
2017-05-27 01:44:26 -04:00
function ( dirs , next ) {
2017-07-12 23:31:29 -06:00
var pluginPaths = [ ] ;
2014-04-08 21:36:18 +01:00
2017-07-12 23:31:29 -06:00
async . each ( dirs , function ( dirname , next ) {
2017-07-19 11:38:51 -06:00
var dirPath = path . join ( Plugins . nodeModulesPath , dirname ) ;
2017-07-12 23:31:29 -06:00
async . waterfall ( [
function ( cb ) {
fs . stat ( dirPath , function ( err , stats ) {
if ( err && err . code !== 'ENOENT' ) {
2017-07-19 11:38:51 -06:00
return cb ( err ) ;
2017-07-12 23:31:29 -06:00
}
if ( err || ! stats . isDirectory ( ) ) {
return next ( ) ;
}
if ( pluginNamePattern . test ( dirname ) ) {
pluginPaths . push ( dirname ) ;
return next ( ) ;
}
if ( dirname [ 0 ] !== '@' ) {
return next ( ) ;
}
fs . readdir ( dirPath , cb ) ;
} ) ;
} ,
function ( subdirs , cb ) {
async . each ( subdirs , function ( subdir , next ) {
if ( ! pluginNamePattern . test ( subdir ) ) {
return next ( ) ;
}
var subdirPath = path . join ( dirPath , subdir ) ;
fs . stat ( subdirPath , function ( err , stats ) {
if ( err && err . code !== 'ENOENT' ) {
return next ( err ) ;
}
if ( err || ! stats . isDirectory ( ) ) {
return next ( ) ;
}
pluginPaths . push ( dirname + '/' + subdir ) ;
next ( ) ;
} ) ;
} , cb ) ;
} ,
] , next ) ;
} , function ( err ) {
next ( err , pluginPaths ) ;
} ) ;
2017-05-27 01:44:26 -04:00
} ,
2017-07-12 23:31:29 -06:00
function ( dirs , next ) {
dirs = dirs . map ( function ( dir ) {
2017-07-19 11:38:51 -06:00
return path . join ( Plugins . nodeModulesPath , dir ) ;
2017-07-12 23:31:29 -06:00
} ) ;
2017-05-27 01:44:26 -04:00
var plugins = [ ] ;
2017-07-12 23:31:29 -06:00
async . each ( dirs , function ( file , next ) {
2017-05-27 01:44:26 -04:00
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' ) ) ;
2017-01-02 22:23:17 -07:00
}
2013-07-28 01:16:21 -04:00
2017-05-27 01:44:26 -04:00
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
}
2013-07-28 01:16:21 -04:00
2017-05-27 01:44:26 -04:00
plugins . push ( pluginData ) ;
next ( ) ;
2013-12-03 13:36:44 -05:00
} ) ;
2017-05-27 01:44:26 -04:00
} , function ( err ) {
next ( err , plugins ) ;
} ) ;
} ,
] , callback ) ;
} ;