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-01-06 23:44:32 -05:00
nconf = require ( 'nconf' ) ,
2014-02-13 22:38:24 -05:00
semver = require ( 'semver' ) ,
2014-08-24 12:30:49 -04:00
express = require ( 'express' ) ,
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' ) ,
2014-07-21 14:37:40 -04:00
translator = require ( '../public/src/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' ) ,
pkg = require ( '../package.json' ) ,
controllers = require ( './controllers' ) ,
app , middleware ;
2013-12-02 16:19:30 -05:00
2013-12-03 13:36:44 -05:00
( function ( Plugins ) {
2013-09-13 11:10:17 -04:00
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 = [ ] ;
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
2014-09-21 14:29:27 -04:00
Plugins . init = function ( nbbApp , nbbMiddleware ) {
2013-12-03 13:36:44 -05:00
if ( Plugins . initialized ) {
return ;
}
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' ) {
winston . info ( '[plugins] Initializing plugins system' ) ;
}
Plugins . reload ( function ( err ) {
if ( err ) {
if ( global . env === 'development' ) {
winston . info ( '[plugins] NodeBB encountered a problem while loading plugins' , err . message ) ;
2013-11-03 17:15:18 -05:00
}
2013-12-03 13:36:44 -05:00
return ;
}
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' ) ;
2013-12-03 13:36:44 -05:00
} ) ;
2014-07-21 14:37:40 -04:00
Plugins . registerHook ( 'core' , {
2014-07-25 19:28:27 -04:00
hook : 'static:app.load' ,
2014-07-21 14:37:40 -04:00
method : addLanguages
} ) ;
2013-12-03 13:36:44 -05:00
} ;
Plugins . ready = function ( callback ) {
if ( ! Plugins . initialized ) {
2014-07-21 13:14:12 -04:00
emitter . once ( 'plugins:loaded' , callback ) ;
2013-12-03 13:36:44 -05:00
} else {
callback ( ) ;
}
} ;
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 = { } ;
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
// Read the list of activated plugins and require their libraries
async . waterfall ( [
function ( next ) {
db . getSetMembers ( 'plugins:active' , next ) ;
} ,
function ( plugins , next ) {
2014-04-08 21:36:18 +01:00
if ( ! plugins || ! 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 ) {
async . each ( plugins , Plugins . loadPlugin , next ) ;
} ) ;
2013-12-03 13:36:44 -05:00
} ,
function ( next ) {
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 ( ) ;
2014-08-24 12:30:49 -04:00
} ,
async . apply ( Plugins . reloadRoutes )
2013-12-03 13:36:44 -05:00
] , callback ) ;
} ;
2014-08-24 12:30:49 -04:00
Plugins . reloadRoutes = function ( callback ) {
2014-09-21 14:29:27 -04:00
var router = express . Router ( ) ;
router . hotswapId = 'plugins' ;
router . render = function ( ) {
app . render . apply ( app , arguments ) ;
} ;
// Deprecated as of v0.5.0, remove this hook call for NodeBB v0.6.0-1
Plugins . fireHook ( 'action:app.load' , router , middleware , controllers ) ;
Plugins . fireHook ( 'static:app.load' , router , middleware , controllers , function ( ) {
hotswap . replace ( 'plugins' , router ) ;
winston . info ( '[plugins] All plugins reloaded and rerouted' ) ;
callback ( ) ;
} ) ;
2014-08-24 12:30:49 -04:00
} ;
2013-12-03 13:36:44 -05:00
Plugins . loadPlugin = function ( pluginPath , callback ) {
fs . readFile ( path . join ( pluginPath , 'plugin.json' ) , function ( err , data ) {
if ( err ) {
2014-03-24 15:35:58 -04:00
return callback ( pluginPath . match ( 'nodebb-theme' ) ? null : err ) ;
2013-12-03 13:36:44 -05:00
}
2014-10-04 18:54:23 -04:00
var pluginData , libraryPath , staticDir ;
2014-09-23 13:57:59 -04:00
try {
2014-10-04 18:54:23 -04:00
pluginData = JSON . parse ( data ) ;
2014-09-23 13:57:59 -04:00
} catch ( err ) {
var pluginDir = pluginPath . split ( path . sep ) ;
pluginDir = pluginDir [ pluginDir . length - 1 ] ;
2014-09-29 18:09:42 -04:00
2014-09-23 13:57:59 -04:00
winston . error ( '[plugins/' + pluginDir + '] Plugin not loaded - please check its plugin.json for errors' ) ;
2014-09-23 15:03:13 -04:00
return callback ( ) ;
2014-09-23 13:57:59 -04:00
}
2013-12-03 13:36:44 -05:00
2014-05-27 09:52:27 -04:00
/ *
Starting v0 . 5.0 , ` minver ` is deprecated in favour of ` compatibility ` .
` minver ` will be transparently parsed to ` compatibility ` until v0 . 6.0 ,
at which point ` minver ` will not be parsed altogether .
Please see NodeBB / NodeBB # 1437 for more details
* /
if ( pluginData . minver && ! pluginData . compatibility ) {
pluginData . compatibility = '~' + pluginData . minver ;
}
// End backwards compatibility block (#1437)
if ( pluginData . compatibility && semver . validRange ( pluginData . compatibility ) ) {
2014-10-07 02:12:22 -04:00
if ( ! semver . gtr ( pkg . version , pluginData . compatibility ) ) {
2014-05-27 09:52:27 -04:00
// NodeBB may not be new enough to run this plugin
process . stdout . write ( '\n' ) ;
2014-02-27 10:28:49 -05:00
winston . warn ( '[plugins/' + pluginData . id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.' ) ;
2014-05-27 09:52:27 -04:00
winston . warn ( '[plugins/' + pluginData . id + '] In the event of an unresponsive NodeBB caused by this plugin, run ./nodebb reset plugin="' + pluginData . id + '".' ) ;
process . stdout . write ( '\n' ) ;
2014-02-13 22:38:24 -05:00
}
}
2013-12-03 13:36:44 -05:00
async . parallel ( [
2013-07-28 01:16:21 -04:00
function ( next ) {
2013-12-03 13:36:44 -05:00
if ( pluginData . library ) {
libraryPath = path . join ( pluginPath , pluginData . library ) ;
fs . exists ( libraryPath , function ( exists ) {
if ( exists ) {
if ( ! Plugins . libraries [ pluginData . id ] ) {
Plugins . libraries [ pluginData . id ] = require ( libraryPath ) ;
2014-10-03 17:29:42 -04:00
Plugins . libraryPaths . push ( libraryPath ) ;
2013-12-03 13:36:44 -05:00
}
// Register hooks for this plugin
if ( pluginData . hooks && Array . isArray ( pluginData . hooks ) && pluginData . hooks . length > 0 ) {
async . each ( pluginData . hooks , function ( hook , next ) {
Plugins . registerHook ( pluginData . id , hook , next ) ;
} , next ) ;
} else {
next ( null ) ;
}
} else {
winston . warn ( '[plugins.reload] Library not found for plugin: ' + pluginData . id ) ;
next ( ) ;
2013-09-01 15:27:45 -04:00
}
2013-12-03 13:36:44 -05:00
} ) ;
} else {
winston . warn ( '[plugins.reload] Library not found for plugin: ' + pluginData . id ) ;
next ( ) ;
}
2013-09-02 21:53:15 -04:00
} ,
function ( next ) {
2013-12-03 13:36:44 -05:00
// Static Directories for Plugins
2014-02-02 13:02:52 -05:00
var realPath ,
validMappedPath = /^[\w\-_]+$/ ;
pluginData . staticDirs = pluginData . staticDirs || { } ;
// Deprecated, to be removed v0.5
2013-12-03 13:36:44 -05:00
if ( pluginData . staticDir ) {
2014-02-02 13:02:52 -05:00
winston . warn ( '[plugins/' + pluginData . id + '] staticDir is deprecated, use staticDirs instead' ) ;
Plugins . staticDirs [ pluginData . id ] = path . join ( pluginPath , pluginData . staticDir ) ;
}
2013-12-03 13:36:44 -05:00
2014-08-01 16:55:29 -04:00
function mapStaticDirs ( mappedPath ) {
2014-08-01 16:56:35 -04:00
if ( Plugins . staticDirs [ mappedPath ] ) {
winston . warn ( '[plugins/' + pluginData . id + '] Mapped path (' + mappedPath + ') already specified!' ) ;
} else if ( ! validMappedPath . test ( mappedPath ) ) {
winston . warn ( '[plugins/' + pluginData . id + '] Invalid mapped path specified: ' + mappedPath + '. Path must adhere to: ' + validMappedPath . toString ( ) ) ;
} else {
realPath = pluginData . staticDirs [ mappedPath ] ;
staticDir = path . join ( pluginPath , realPath ) ;
( function ( staticDir ) {
fs . exists ( staticDir , function ( exists ) {
if ( exists ) {
Plugins . staticDirs [ pluginData . id + '/' + mappedPath ] = staticDir ;
} else {
winston . warn ( '[plugins/' + pluginData . id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.' ) ;
}
} ) ;
} ( staticDir ) ) ;
2014-08-01 16:55:29 -04:00
}
}
for ( var key in pluginData . staticDirs ) {
if ( pluginData . staticDirs . hasOwnProperty ( key ) ) {
mapStaticDirs ( key ) ;
}
2014-02-02 13:02:52 -05:00
}
next ( ) ;
2013-12-03 13:36:44 -05:00
} ,
function ( next ) {
// CSS Files for plugins
if ( pluginData . css && pluginData . css instanceof Array ) {
if ( global . env === 'development' ) {
winston . info ( '[plugins] Found ' + pluginData . css . length + ' CSS file(s) for plugin ' + pluginData . id ) ;
2013-11-03 17:15:18 -05:00
}
2013-11-28 17:37:17 -05:00
2014-03-06 17:50:15 -05:00
Plugins . cssFiles = Plugins . cssFiles . concat ( pluginData . css . map ( function ( file ) {
2014-05-23 19:12:54 -04:00
return path . join ( pluginData . id , file ) ;
} ) ) ;
2013-12-03 17:43:12 -05:00
}
2014-03-06 17:50:15 -05:00
next ( ) ;
2014-02-25 14:13:09 -05:00
} ,
function ( next ) {
// LESS files for plugins
if ( pluginData . less && pluginData . less instanceof Array ) {
if ( global . env === 'development' ) {
winston . info ( '[plugins] Found ' + pluginData . less . length + ' LESS file(s) for plugin ' + pluginData . id ) ;
}
Plugins . lessFiles = Plugins . lessFiles . concat ( pluginData . less . map ( function ( file ) {
return path . join ( pluginData . id , file ) ;
} ) ) ;
}
2014-03-02 13:31:13 -05:00
next ( ) ;
} ,
function ( next ) {
// Client-side scripts
if ( pluginData . scripts && pluginData . scripts instanceof Array ) {
if ( global . env === 'development' ) {
winston . info ( '[plugins] Found ' + pluginData . scripts . length + ' js file(s) for plugin ' + pluginData . id ) ;
}
Plugins . clientScripts = Plugins . clientScripts . concat ( pluginData . scripts . map ( function ( file ) {
2014-03-02 16:44:41 -05:00
return path . join ( _ _dirname , '../node_modules/' , pluginData . id , file ) ;
2014-03-02 13:31:13 -05:00
} ) ) ;
}
2014-02-25 14:13:09 -05:00
next ( ) ;
2014-07-21 14:37:40 -04:00
} ,
function ( next ) {
if ( pluginData . languages && typeof pluginData . languages === 'string' ) {
var pathToFolder = path . join ( _ _dirname , '../node_modules/' , pluginData . id , pluginData . languages ) ;
utils . walk ( pathToFolder , function ( err , languages ) {
var arr = [ ] ;
async . each ( languages , function ( pathToLang , next ) {
fs . readFile ( pathToLang , function ( err , file ) {
2014-08-01 16:55:29 -04:00
var json ;
2014-07-27 23:21:34 -04:00
try {
2014-08-01 16:55:29 -04:00
json = JSON . parse ( file . toString ( ) ) ;
2014-07-27 23:21:34 -04:00
} catch ( err ) {
winston . error ( '[plugins] Unable to parse custom language file: ' + pathToLang + '\r\n' + err . stack ) ;
return next ( err ) ;
}
2014-07-21 14:37:40 -04:00
arr . push ( {
2014-07-27 23:21:34 -04:00
file : json ,
2014-07-21 14:37:40 -04:00
route : pathToLang . replace ( pathToFolder , '' )
} ) ;
next ( err ) ;
} ) ;
} , function ( err ) {
Plugins . customLanguages = Plugins . customLanguages . concat ( arr ) ;
next ( err ) ;
} ) ;
} ) ;
} else {
next ( ) ;
}
2013-12-03 13:36:44 -05:00
}
] , function ( err ) {
if ( ! err ) {
2013-12-03 17:43:12 -05:00
if ( global . env === 'development' ) {
winston . info ( '[plugins] Loaded plugin: ' + pluginData . id ) ;
}
2013-12-03 13:36:44 -05:00
callback ( ) ;
2013-12-03 17:43:12 -05:00
} else {
callback ( new Error ( 'Could not load plugin system' ) ) ;
}
2013-08-22 09:50:29 -04:00
} ) ;
2013-12-03 13:36:44 -05:00
} ) ;
} ;
Plugins . registerHook = function ( id , data , callback ) {
/ *
` data ` is an object consisting of ( * is required ) :
` data.hook ` * , the name of the NodeBB hook
` data.method ` * , the method called in that plugin
` data.priority ` , the relative priority of the method when it is eventually called ( default : 10 )
* /
2014-02-28 20:39:27 -05:00
var method ;
2014-07-21 13:42:33 -04:00
if ( data . hook && data . method ) {
2013-12-03 13:36:44 -05:00
data . id = id ;
2014-07-04 14:43:03 -04:00
if ( ! data . priority ) {
data . priority = 10 ;
}
2013-12-03 13:36:44 -05:00
2014-07-21 13:42:33 -04:00
if ( typeof data . method === 'string' && data . method . length > 0 ) {
method = data . method . split ( '.' ) . reduce ( function ( memo , prop ) {
if ( memo !== null && memo [ prop ] ) {
return memo [ prop ] ;
} else {
// Couldn't find method by path, aborting
return null ;
}
} , Plugins . libraries [ data . id ] ) ;
// Write the actual method reference to the hookObj
data . method = method ;
register ( ) ;
} else if ( typeof data . method === 'function' ) {
register ( ) ;
} else {
2014-02-28 20:39:27 -05:00
winston . warn ( '[plugins/' + id + '] Hook method mismatch: ' + data . hook + ' => ' + data . method ) ;
}
2014-07-21 13:42:33 -04:00
}
2014-02-28 20:39:27 -05:00
2014-07-21 13:42:33 -04:00
function register ( ) {
2013-12-03 13:36:44 -05:00
Plugins . loadedHooks [ data . hook ] = Plugins . loadedHooks [ data . hook ] || [ ] ;
Plugins . loadedHooks [ data . hook ] . push ( data ) ;
2014-07-21 13:25:25 -04:00
if ( typeof callback === 'function' ) {
callback ( ) ;
}
2014-07-04 14:43:03 -04:00
}
2013-12-03 13:36:44 -05:00
} ;
2014-01-26 13:21:23 -05:00
Plugins . hasListeners = function ( hook ) {
2014-05-06 18:42:38 -04:00
return ! ! ( Plugins . loadedHooks [ hook ] && Plugins . loadedHooks [ hook ] . length > 0 ) ;
2014-01-26 13:10:39 -05:00
} ;
2014-03-06 14:07:56 -05:00
Plugins . fireHook = function ( hook ) {
2014-05-15 03:07:38 -04:00
var callback = typeof arguments [ arguments . length - 1 ] === 'function' ? arguments [ arguments . length - 1 ] : null ,
2014-03-06 14:07:56 -05:00
args = arguments . length ? Array . prototype . slice . call ( arguments , 1 ) : [ ] ;
if ( callback ) {
args . pop ( ) ;
}
2014-03-10 21:54:30 -04:00
2014-05-15 03:07:38 -04:00
var hookList = Plugins . loadedHooks [ hook ] ;
2013-12-03 13:36:44 -05:00
2014-07-26 14:55:17 -04:00
if ( Array . isArray ( hookList ) ) {
2014-03-10 21:54:30 -04:00
// if (global.env === 'development') winston.info('[plugins] Firing hook: \'' + hook + '\'');
2013-12-03 13:36:44 -05:00
var hookType = hook . split ( ':' ) [ 0 ] ;
switch ( hookType ) {
case 'filter' :
async . reduce ( hookList , args , function ( value , hookObj , next ) {
if ( hookObj . method ) {
2014-03-10 16:58:16 -04:00
if ( ! hookObj . hasOwnProperty ( 'callbacked' ) || hookObj . callbacked === true ) {
2014-07-21 10:35:57 -04:00
// omg, after 6 months I finally realised what this does...
// It adds the callback to the arguments passed-in, since the callback
// is defined in *this* file (the async cb), and not the hooks themselves.
2014-08-01 16:55:29 -04:00
value = hookObj . method . apply ( Plugins , value . concat ( function ( ) {
2014-03-06 14:07:56 -05:00
next ( arguments [ 0 ] , Array . prototype . slice . call ( arguments , 1 ) ) ;
} ) ) ;
2014-03-10 16:58:16 -04:00
2014-07-16 21:09:25 -04:00
/ *
Backwards compatibility block for v0 . 5.0
Remove this once NodeBB enters v0 . 5.0 - 1
* /
2014-03-10 17:32:50 -04:00
if ( value !== undefined && value !== callback ) {
2014-03-10 16:58:16 -04:00
winston . warn ( '[plugins/' + hookObj . id + '] "callbacked" deprecated as of 0.4x. Use asynchronous method instead for hook: ' + hook ) ;
next ( null , [ value ] ) ;
}
2014-03-06 14:07:56 -05:00
} else {
2014-03-10 16:58:16 -04:00
winston . warn ( '[plugins/' + hookObj . id + '] "callbacked" deprecated as of 0.4x. Use asynchronous method instead for hook: ' + hook ) ;
2014-03-06 14:07:56 -05:00
value = hookObj . method . apply ( Plugins , value ) ;
2014-03-06 14:23:48 -05:00
next ( null , [ value ] ) ;
2013-07-28 01:16:21 -04:00
}
2014-07-16 21:19:13 -04:00
/* End backwards compatibility block */
2013-12-03 13:36:44 -05:00
} else {
2013-12-03 17:43:12 -05:00
if ( global . env === 'development' ) {
2013-12-31 17:19:37 -05:00
winston . info ( '[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj . id + '\' not found, skipping.' ) ;
2013-12-03 17:43:12 -05:00
}
2014-03-06 14:23:48 -05:00
next ( null , [ value ] ) ;
2013-12-03 13:36:44 -05:00
}
2014-03-06 14:07:56 -05:00
} , function ( err , values ) {
2013-12-03 13:36:44 -05:00
if ( err ) {
if ( global . env === 'development' ) {
2014-05-15 03:08:24 -04:00
winston . info ( '[plugins] Problem executing hook: ' + hook + ' err: ' + JSON . stringify ( err ) ) ;
2013-07-28 01:16:21 -04:00
}
2013-12-03 13:36:44 -05:00
}
2013-07-28 01:16:21 -04:00
2014-07-21 10:35:57 -04:00
if ( callback ) {
callback . apply ( Plugins , [ err ] . concat ( values ) ) ;
}
2013-12-03 13:36:44 -05:00
} ) ;
break ;
case 'action' :
2014-07-16 21:09:25 -04:00
var deprecationWarn = [ ] ;
async . each ( hookList , function ( hookObj , next ) {
/ *
Backwards compatibility block for v0 . 5.0
2014-07-25 19:15:31 -04:00
Remove this once NodeBB enters v0 . 6.0 - 1
2014-07-16 21:09:25 -04:00
* /
if ( hook === 'action:app.load' ) {
deprecationWarn . push ( hookObj . id ) ;
}
/* End backwards compatibility block */
2013-12-03 13:36:44 -05:00
if ( hookObj . method ) {
2014-03-06 14:07:56 -05:00
hookObj . method . apply ( Plugins , args ) ;
2013-12-03 13:36:44 -05:00
} else {
if ( global . env === 'development' ) {
winston . info ( '[plugins] Expected method \'' + hookObj . method + '\' in plugin \'' + hookObj . id + '\' not found, skipping.' ) ;
2013-07-28 14:24:34 -04:00
}
2013-12-03 13:36:44 -05:00
}
2014-07-16 21:09:25 -04:00
next ( ) ;
} , function ( ) {
2014-07-16 21:19:13 -04:00
/ *
Backwards compatibility block for v0 . 5.0
2014-07-25 19:15:31 -04:00
Remove this once NodeBB enters v0 . 6.0 - 1
2014-07-16 21:19:13 -04:00
* /
2014-07-16 21:09:25 -04:00
if ( deprecationWarn . length ) {
2014-07-25 19:15:31 -04:00
winston . warn ( '[plugins] The `action:app.load` hook is deprecated in favour of `static:app.load`, please notify the developers of the following plugins:' ) ;
2014-07-16 21:09:25 -04:00
for ( var x = 0 , numDeprec = deprecationWarn . length ; x < numDeprec ; x ++ ) {
process . stdout . write ( ' * ' + deprecationWarn [ x ] + '\n' ) ;
}
}
2014-07-16 21:19:13 -04:00
/* End backwards compatibility block */
2013-12-03 13:36:44 -05:00
} ) ;
break ;
2014-07-25 19:15:31 -04:00
case 'static' :
async . each ( hookList , function ( hookObj , next ) {
if ( hookObj . method ) {
hookObj . method . apply ( Plugins , args . concat ( next ) ) ;
}
} , function ( err ) {
callback ( err ) ;
} ) ;
break ;
2013-12-03 13:36:44 -05:00
default :
// Do nothing...
break ;
}
} else {
// Otherwise, this hook contains no methods
if ( callback ) {
2014-03-06 14:07:56 -05:00
callback . apply ( this , [ null ] . concat ( args ) ) ;
2013-07-28 01:16:21 -04:00
}
2014-03-06 14:07:56 -05:00
return args [ 0 ] ;
2013-12-03 13:36:44 -05:00
}
} ;
Plugins . isActive = function ( id , callback ) {
db . isSetMember ( 'plugins:active' , id , callback ) ;
} ;
Plugins . toggleActive = function ( id , callback ) {
Plugins . isActive ( id , function ( err , active ) {
if ( err ) {
2014-04-22 21:02:58 -04:00
if ( global . env === 'development' ) {
winston . info ( '[plugins] Could not toggle active state on plugin \'' + id + '\'' ) ;
}
return callback ( err ) ;
2013-12-03 13:36:44 -05:00
}
db [ ( active ? 'setRemove' : 'setAdd' ) ] ( 'plugins:active' , id , function ( err , success ) {
2013-07-29 15:15:49 -04:00
if ( err ) {
2014-04-22 21:02:58 -04:00
if ( global . env === 'development' ) {
winston . info ( '[plugins] Could not toggle active state on plugin \'' + id + '\'' ) ;
}
return callback ( err ) ;
2013-07-29 15:15:49 -04:00
}
2014-04-16 15:11:00 -04:00
meta . restartRequired = true ;
2013-12-18 15:32:38 -05:00
2014-04-22 21:02:58 -04:00
if ( active ) {
2013-12-18 15:32:38 -05:00
Plugins . fireHook ( 'action:plugin.deactivate' , id ) ;
}
2013-12-03 13:36:44 -05:00
// Reload meta data
2013-12-04 17:57:51 -05:00
Plugins . reload ( function ( ) {
2013-12-18 15:32:38 -05:00
if ( ! active ) {
Plugins . fireHook ( 'action:plugin.activate' , id ) ;
}
2013-07-29 15:15:49 -04:00
2014-04-22 21:02:58 -04:00
if ( typeof callback === 'function' ) {
callback ( null , {
2013-12-03 13:36:44 -05:00
id : id ,
active : ! active
} ) ;
}
2013-07-29 15:15:49 -04:00
} ) ;
} ) ;
2013-12-03 13:36:44 -05:00
} ) ;
2014-03-06 12:06:19 -05:00
} ;
2014-04-22 21:02:58 -04:00
Plugins . toggleInstall = function ( id , callback ) {
Plugins . isInstalled ( id , function ( err , installed ) {
if ( err ) {
return callback ( err ) ;
}
var npm = require ( 'npm' ) ;
async . waterfall ( [
function ( next ) {
Plugins . isActive ( id , next ) ;
} ,
function ( active , next ) {
if ( active ) {
Plugins . toggleActive ( id , function ( err , status ) {
next ( err ) ;
} ) ;
return ;
}
next ( ) ;
} ,
function ( next ) {
npm . load ( { } , next ) ;
} ,
function ( res , next ) {
npm . commands [ installed ? 'uninstall' : 'install' ] ( installed ? id : [ id ] , next ) ;
}
] , function ( err ) {
callback ( err , {
id : id ,
installed : ! installed
} ) ;
} ) ;
} ) ;
} ;
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
next ( err ) ;
} ) ;
} else {
next ( false ) ;
}
} , function ( err ) {
callback ( err , templates ) ;
} ) ;
} ) ;
} ;
2013-08-22 10:47:24 -04:00
2014-04-22 21:02:58 -04:00
Plugins . getAll = function ( callback ) {
var request = require ( 'request' ) ;
request ( 'http://npm.aws.af.cm/api/v1/plugins' , function ( err , res , body ) {
var plugins = [ ] ;
2014-09-24 18:43:02 -04:00
2014-04-22 21:02:58 -04:00
try {
plugins = JSON . parse ( body ) ;
} catch ( err ) {
winston . error ( 'Error parsing plugins : ' + err . message ) ;
2014-09-24 18:43:02 -04:00
plugins = [ ] ;
2014-04-22 21:02:58 -04:00
}
2014-04-23 21:23:03 -04:00
var pluginMap = { } ;
for ( var i = 0 ; i < plugins . length ; ++ i ) {
plugins [ i ] . id = plugins [ i ] . name ;
plugins [ i ] . installed = false ;
plugins [ i ] . active = false ;
2014-08-01 15:41:50 -04:00
plugins [ i ] . url = plugins [ i ] . repository ? plugins [ i ] . repository . url : '' ;
2014-04-23 21:23:03 -04:00
pluginMap [ plugins [ i ] . name ] = plugins [ i ] ;
}
Plugins . showInstalled ( function ( err , installedPlugins ) {
if ( err ) {
return callback ( err ) ;
}
2014-04-22 21:02:58 -04:00
2014-04-23 21:23:03 -04:00
async . each ( installedPlugins , function ( plugin , next ) {
2014-04-22 21:02:58 -04:00
2014-10-09 13:50:25 -04:00
pluginMap [ plugin . name ] = pluginMap [ plugin . name ] || { } ;
pluginMap [ plugin . name ] . id = pluginMap [ plugin . name ] . id || plugin . name ;
pluginMap [ plugin . name ] . name = pluginMap [ plugin . name ] . name || plugin . name ;
pluginMap [ plugin . name ] . description = plugin . description ;
pluginMap [ plugin . name ] . url = pluginMap [ plugin . name ] . url || plugin . url ;
pluginMap [ plugin . name ] . installed = true ;
pluginMap [ plugin . name ] . active = plugin . active ;
pluginMap [ plugin . name ] . version = plugin . version ;
2014-10-09 17:31:47 -04:00
getVersion ( plugin . id , function ( err , version ) {
2014-10-09 13:50:25 -04:00
pluginMap [ plugin . name ] . latest = version ;
pluginMap [ plugin . name ] . outdated = version !== pluginMap [ plugin . name ] . version ;
2014-04-23 21:23:03 -04:00
next ( ) ;
} ) ;
} , function ( err ) {
2014-04-22 21:02:58 -04:00
if ( err ) {
2014-04-23 21:23:03 -04:00
return callback ( err ) ;
2014-04-22 21:02:58 -04:00
}
2014-04-23 21:23:03 -04:00
var pluginArray = [ ] ;
for ( var key in pluginMap ) {
2014-04-23 23:18:10 -04:00
if ( pluginMap . hasOwnProperty ( key ) ) {
pluginArray . push ( pluginMap [ key ] ) ;
}
2014-04-23 21:23:03 -04:00
}
pluginArray . sort ( function ( a , b ) {
if ( a . installed && ! b . installed ) {
return - 1 ;
} else if ( ! a . installed && b . installed ) {
return 1 ;
}
return 0 ;
} ) ;
callback ( null , pluginArray ) ;
} ) ;
} ) ;
2014-04-22 21:02:58 -04:00
} ) ;
} ;
2014-10-09 13:50:25 -04:00
function getVersion ( name , callback ) {
var npm = require ( 'npm' ) ;
npm . load ( { } , function ( ) {
npm . commands . show ( [ name , 'version' ] , true , function ( err , version ) {
if ( err || ! version ) {
return callback ( null , 'no version' ) ;
}
var obj = Object . keys ( version ) ;
callback ( null , Array . isArray ( obj ) && obj . length ? obj [ 0 ] : 'no version' ) ;
} ) ;
} ) ;
}
2014-04-22 21:02:58 -04:00
Plugins . isInstalled = function ( id , callback ) {
var pluginDir = path . join ( _ _dirname , '../node_modules' , id ) ;
fs . stat ( pluginDir , function ( err , stats ) {
callback ( null , err ? false : stats . isDirectory ( ) ) ;
} ) ;
} ;
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 ) {
return dir . substr ( 0 , 14 ) === 'nodebb-plugin-' || dir . substr ( 0 , 14 ) === 'nodebb-widget-' ;
} ) . map ( function ( dir ) {
return path . join ( npmPluginPath , dir ) ;
} ) ;
async . filter ( dirs , function ( dir , callback ) {
fs . stat ( dir , function ( err , stats ) {
if ( err ) {
return callback ( false ) ;
2014-02-23 22:44:49 -05:00
}
2013-07-28 12:52:58 -04:00
2014-04-08 21:36:18 +01:00
callback ( 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 ) {
var configPath ;
async . waterfall ( [
function ( next ) {
2014-10-09 17:31:47 -04:00
async . parallel ( {
packageJSON : function ( next ) {
fs . readFile ( path . join ( file , 'package.json' ) , next ) ;
} ,
pluginJSON : function ( next ) {
fs . readFile ( path . join ( file , 'plugin.json' ) , next ) ;
}
} , next ) ;
2013-12-03 13:36:44 -05:00
} ,
2014-10-09 17:31:47 -04:00
function ( results , next ) {
var packageInfo , pluginInfo ;
2014-08-01 16:55:29 -04:00
2013-12-03 13:36:44 -05:00
try {
2014-10-09 17:31:47 -04:00
packageInfo = JSON . parse ( results . packageJSON ) ;
pluginInfo = JSON . parse ( results . pluginJSON ) ;
2013-12-03 13:36:44 -05:00
} catch ( err ) {
2014-04-23 21:23:03 -04:00
winston . warn ( "Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors." ) ;
2013-12-03 13:36:44 -05:00
return next ( err , null ) ;
2013-07-29 15:15:49 -04:00
}
2014-10-09 17:31:47 -04:00
Plugins . isActive ( packageInfo . name , function ( err , active ) {
2013-12-04 17:57:51 -05:00
if ( err ) {
next ( new Error ( 'no-active-state' ) ) ;
}
2013-07-28 01:16:21 -04:00
2014-10-09 17:31:47 -04:00
delete pluginInfo . hooks ;
delete pluginInfo . library ;
pluginInfo . active = active ;
pluginInfo . installed = true ;
pluginInfo . version = packageInfo . version ;
2014-10-09 13:50:25 -04:00
2014-10-09 17:31:47 -04:00
next ( null , pluginInfo ) ;
2013-12-03 13:36:44 -05:00
} ) ;
}
] , function ( err , config ) {
2014-07-04 14:43:03 -04:00
if ( err ) {
return next ( ) ; // Silently fail
}
2013-07-28 01:16:21 -04:00
2013-12-03 13:36:44 -05:00
plugins . push ( config ) ;
next ( ) ;
} ) ;
} , function ( err ) {
next ( null , plugins ) ;
} ) ;
}
] , function ( err , plugins ) {
callback ( err , plugins ) ;
} ) ;
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 ) {
async . map ( Plugins . libraryPaths , fs . realpath , function ( err , paths ) {
for ( var x = 0 , numPaths = paths . length ; x < numPaths ; x ++ ) {
delete require . cache [ paths [ x ] ] ;
}
winston . info ( '[plugins] Plugin libraries removed from Node.js cache' ) ;
2014-07-21 14:37:40 -04:00
2014-10-03 17:29:42 -04:00
next ( ) ;
} ) ;
} ;
2014-07-21 14:37:40 -04:00
function addLanguages ( router , middleware , controllers , callback ) {
Plugins . customLanguages . forEach ( function ( lang ) {
router . get ( '/language' + lang . route , function ( req , res , next ) {
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 ) ;
} ) ;
callback ( null ) ;
}
2013-12-03 13:36:44 -05:00
} ( exports ) ) ;