'use strict' ;
var fs = require ( 'fs' ) ,
path = require ( 'path' ) ,
semver = require ( 'semver' ) ,
async = require ( 'async' ) ,
winston = require ( 'winston' ) ,
pkg = require ( '../../package.json' ) ,
utils = require ( '../../public/src/utils' ) ;
module . exports = function ( Plugins ) {
Plugins . loadPlugin = function ( pluginPath , callback ) {
fs . readFile ( path . join ( pluginPath , 'plugin.json' ) , function ( err , data ) {
if ( err ) {
return callback ( pluginPath . match ( 'nodebb-theme' ) ? null : err ) ;
}
var pluginData , staticDir ;
try {
pluginData = JSON . parse ( data ) ;
} catch ( err ) {
var pluginDir = pluginPath . split ( path . sep ) ;
pluginDir = pluginDir [ pluginDir . length - 1 ] ;
winston . error ( '[plugins/' + pluginDir + '] Plugin not loaded - please check its plugin.json for errors' ) ;
return callback ( ) ;
}
if ( pluginData . compatibility && semver . validRange ( pluginData . compatibility ) ) {
if ( ! semver . gtr ( pkg . version , pluginData . compatibility ) ) {
// NodeBB may not be new enough to run this plugin
process . stdout . write ( '\n' ) ;
winston . warn ( '[plugins/' + pluginData . id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.' ) ;
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' ) ;
}
}
async . parallel ( [
function ( next ) {
registerHooks ( pluginData , pluginPath , next ) ;
} ,
function ( next ) {
mapStaticDirectories ( pluginData , pluginPath , next ) ;
} ,
function ( next ) {
mapFiles ( pluginData , 'css' , 'cssFiles' , next ) ;
} ,
function ( next ) {
mapFiles ( pluginData , 'less' , 'lessFiles' , next ) ;
} ,
function ( next ) {
mapClientSideScripts ( pluginData , next ) ;
} ,
function ( next ) {
loadLanguages ( pluginData , next ) ;
}
] , function ( err ) {
if ( err ) {
winston . verbose ( '[plugins] Could not load plugin : ' + pluginData . id ) ;
return callback ( err ) ;
}
winston . verbose ( '[plugins] Loaded plugin: ' + pluginData . id ) ;
callback ( ) ;
} ) ;
} ) ;
} ;
function registerHooks ( pluginData , pluginPath , callback ) {
function libraryNotFound ( ) {
winston . warn ( '[plugins.reload] Library not found for plugin: ' + pluginData . id ) ;
callback ( ) ;
}
if ( ! pluginData . library ) {
return libraryNotFound ( ) ;
}
var libraryPath = path . join ( pluginPath , pluginData . library ) ;
fs . exists ( libraryPath , function ( exists ) {
if ( ! exists ) {
return libraryNotFound ( ) ;
}
if ( ! Plugins . libraries [ pluginData . id ] ) {
Plugins . requireLibrary ( pluginData , libraryPath ) ;
}
if ( Array . isArray ( pluginData . hooks ) && pluginData . hooks . length > 0 ) {
async . each ( pluginData . hooks , function ( hook , next ) {
Plugins . registerHook ( pluginData . id , hook , next ) ;
} , callback ) ;
} else {
callback ( ) ;
}
} ) ;
}
function mapStaticDirectories ( pluginData , pluginPath , callback ) {
function mapStaticDirs ( mappedPath , callback ) {
if ( Plugins . staticDirs [ mappedPath ] ) {
winston . warn ( '[plugins/' + pluginData . id + '] Mapped path (' + mappedPath + ') already specified!' ) ;
callback ( ) ;
} else if ( ! validMappedPath . test ( mappedPath ) ) {
winston . warn ( '[plugins/' + pluginData . id + '] Invalid mapped path specified: ' + mappedPath + '. Path must adhere to: ' + validMappedPath . toString ( ) ) ;
callback ( ) ;
} else {
var realPath = pluginData . staticDirs [ mappedPath ] ;
var staticDir = path . join ( pluginPath , realPath ) ;
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.' ) ;
}
callback ( ) ;
} ) ;
}
}
var validMappedPath = /^[\w\-_]+$/ ;
pluginData . staticDirs = pluginData . staticDirs || { } ;
var dirs = Object . keys ( pluginData . staticDirs ) ;
async . each ( dirs , mapStaticDirs , callback ) ;
}
function mapFiles ( pluginData , type , globalArray , callback ) {
if ( Array . isArray ( pluginData [ type ] ) ) {
if ( global . env === 'development' ) {
winston . verbose ( '[plugins] Found ' + pluginData [ type ] . length + ' ' + type + ' file(s) for plugin ' + pluginData . id ) ;
}
Plugins [ globalArray ] = Plugins [ globalArray ] . concat ( pluginData [ type ] . map ( function ( file ) {
return path . join ( pluginData . id , file ) ;
} ) ) ;
}
callback ( ) ;
}
function mapClientSideScripts ( pluginData , callback ) {
if ( Array . isArray ( pluginData . scripts ) ) {
if ( global . env === 'development' ) {
winston . verbose ( '[plugins] Found ' + pluginData . scripts . length + ' js file(s) for plugin ' + pluginData . id ) ;
}
Plugins . clientScripts = Plugins . clientScripts . concat ( pluginData . scripts . map ( function ( file ) {
return path . join ( _ _dirname , '../../node_modules/' , pluginData . id , file ) ;
} ) ) ;
}
callback ( ) ;
}
function loadLanguages ( pluginData , callback ) {
if ( typeof pluginData . languages !== 'string' ) {
return callback ( ) ;
}
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 ) {
if ( err ) {
return next ( err ) ;
}
var json ;
try {
json = JSON . parse ( file . toString ( ) ) ;
} catch ( err ) {
winston . error ( '[plugins] Unable to parse custom language file: ' + pathToLang + '\r\n' + err . stack ) ;
return next ( err ) ;
}
arr . push ( {
file : json ,
route : pathToLang . replace ( pathToFolder , '' )
} ) ;
next ( ) ;
} ) ;
} , function ( err ) {
if ( err ) {
return callback ( err ) ;
}
Plugins . customLanguages = Plugins . customLanguages . concat ( arr ) ;
callback ( ) ;
} ) ;
} ) ;
}
} ;