Issues (208)

server/plugin.js (14 issues)

1
var fs      = require('fs-extra');
2
var request = require('request');
3
var express = require('express');
4
var extend  = require('extend');
5
6
// ------------------------------------------
7
//  CONSTRUCTOR
8
// ------------------------------------------
9
10
var init = function(){
11
  info('Starting PluginManager ...');
12
  
13
  // Refresh local plugins
14
  refresh();
15
  
16
  return PluginManager;
17
}
18
19
// ------------------------------------------
20
//  HELPER: REQUIRE 
21
// ------------------------------------------
22
23
/**
24
 * Removes a module from the cache
25
 */
26
require.uncache = function (moduleName) {
27
  // Run over the cache looking for the files
28
  // loaded by the specified module name
29
  require.searchCache(moduleName, function (mod) {
30
    delete require.cache[mod.id];
31
  });
32
};
33
34
/**
35
 * Runs over the cache to search for all the cached files
36
 */
37
require.searchCache = function (moduleName, callback) {
38
  // Resolve the module identified by the specified name
39
  var mod = require.resolve(moduleName);
40
41
  // Check if the module has been resolved and found within
42
  // the cache
43
  if (mod && ((mod = require.cache[mod]) !== undefined)) {
44
    // Recursively go over the results
45
    (function run(mod) {
46
      // Go over each of the module's children and
47
      // run over it
48
      mod.children.forEach(function (child) {
49
          run(child);
50
      });
51
52
      // Call the specified callback providing the
53
      // found module
54
      callback(mod);
55
    })(mod);
56
  }
57
};
58
59
// ------------------------------------------
60
//  CLASS: PLUGIN
61
// ------------------------------------------
62
63
var TYPE_MODULES  = 'modules';
64
var TYPE_PHANTOMS = 'phantoms';
65
var TYPE_CRON     = 'cron';
66
67
function Plugin(options) {
68
  extend(false, this, options);
69
  
70
  // Link configuration
71
  this.config   = Config[TYPE_MODULES][this.name];
72
  this.phantoms = Config[TYPE_PHANTOMS][this.name];
73
  this.cron     = Config[TYPE_CRON][this.name];
74
  
75
  // Check has {plugin}.js
76
  var script = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/'+this.name+'.js';
77
  if (fs.existsSync(script)){ 
78
    this.script = script;
79
  }
80
  
81
  // Check has custom portlet.ejs
82
  var template = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/portlet.ejs';
83
  if (fs.existsSync(template)){
84
    this.template = template;
85
  } else {
86
    this.template = 'portlet.html';
87
  }
88
  
89
  // Check has index.ejs
90
  var index = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/index.ejs';
91
  if (fs.existsSync(index)){
92
    this.index = index;
93
  }
94
}
95
96
Plugin.prototype.isDisabled = function(){
97
  if (!this.script) return true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
98
  return this.config.disabled;
99
}
100
101
102
Plugin.prototype.getLocale = function(locale){
103
  var path = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/locales/'+locale+'.js';
104
  if (!fs.existsSync(path)){ info('No locals',path); return; }
105
  try { 
106
    var json = fs.readFileSync(path,'utf-8');
107
    info('Loading locales %s', path); 
108
    if (json) return JSON.parse(json); 
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
109
  } 
110
  catch(ex){ warn("Can't parse %s locales in %s", this.name, locale); }
111
  
112
  return false;
113
}
114
115
Plugin.prototype.getInstance = function(uncache){
116
  try {
117
    // Dispose
118
    if (Config.debug || uncache){ 
119
      if (this._script && this._script.dispose){ this._script.dispose(SARAH); }
120
      require.uncache(this.script); 
121
    }
122
    
123
    // Require
124
    this._script = require(this.script);
125
    
126
    // Initialise
127
    if (!this._script.initialized){
128
      this._script.initialized = true;
129
      if (this._script.init){ this._script.init(SARAH); }
130
    }
131
    
132
    // Last Modified
133
    var modified = fs.statSync(this.script).mtime.getTime();
134
    if (!this._script.lastModified){
135
      this._script.lastModified = modified;
136
    }
137
    
138
    // Reload if new version
139
    if (this._script.lastModified < modified){
140
      info('Reloading: ', this.name);
141
      return this.getInstance(true);
142
    }
143
    
144
    return this._script;
145
  } 
146
  catch (ex) { 
147
    warn('Error while loading plugin: %s', this.name, ex.message, ex.stack);
148
  }
149
}
150
151
// ------------------------------------------
152
//  CACHE PLUGINS
153
// ------------------------------------------
154
155
var cache    = {};
156
var getCache = function(){ return cache;  }
157
158
var refresh = function(){
159
  
160
  cache = {};
161
  
162
  // Find config
163
  var keys = Object.keys(Config[TYPE_MODULES]);
164
  
165
  // Build a list of plugins
166
  for(var i = 0 ; i < keys.length ; i++){
167
    var key = keys[i];
168
    cache[key] = new Plugin ({'name' : key });
169
    cache[key].getInstance();
170
  }
171
  
172
  keys = Object.keys(Config[TYPE_PHANTOMS]);
173
  for(var i = 0 ; i < keys.length ; i++){
174
    var key = keys[i];
175
    if (cache[key]) continue;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
176
    cache[key] = new Plugin ({'name' : key });
177
    cache[key].getInstance();
178
  }
179
  
180
  keys = Object.keys(Config[TYPE_CRON]);
181
  for(var i = 0 ; i < keys.length ; i++){
182
    var key = keys[i];
183
    if (cache[key]) continue;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
184
    cache[key] = new Plugin ({'name' : key });
185
    cache[key].getInstance();
186
  }
187
}
188
189
// ------------------------------------------
190
//  PLUGIN LOCALES
191
// ------------------------------------------
192
193
var getLocales = function(locale){ 
194
  var prop   = {}
195
  var keys   = Object.keys(cache);
196
  for(var i  = 0 ; i < keys.length ; i++){
197
    var key  = keys[i];
198
    var json = cache[key].getLocale(locale);
199
    if (json){ extend(true, prop, json); }
200
  }
201
  return prop;  
202
}
203
204
// ------------------------------------------
205
//  PLUGIN LIST
206
// ------------------------------------------
207
208
var sort = function(ids, xPos, yPos){
209
  for(var i = 0 ; i < ids.length ; i++){
210
    var tmp = cache[ids[i]];
211
    if (tmp){
212
      var cfg = tmp.config;
213
      cfg.x = parseInt(xPos[i])+1;
214
      cfg.y = parseInt(yPos[i])+1;
215
    }
216
  }
217
  getList(true);
218
}
219
220
var getList = function(clean){ 
221
  
222
  if (clean){ refresh(); }
223
  
224
  var keys = Object.keys(cache);
225
  keys = keys.sort(function(k1, k2){
226
    var conf1 = cache[k1].config;
227
    var conf2 = cache[k2].config;
228
    
229
    if (!conf1.y) return  1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
230
    if (!conf2.y) return -1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
231
    
232
    if (conf1.y < conf2.y) return  -1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
233
    if (conf1.y > conf2.y) return   1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
234
    return conf1.x < conf2.x ? -1 : 1;
235
  });
236
  
237
  var list = [];
238
  for(var i = 0 ; i < keys.length ; i++){
239
    var key = keys[i];
240
    var plugin = cache[key];
241
    
242
    // Skip disabled plugin
243
    if (plugin.isDisabled()){ continue; }
244
    
245
    list.push(plugin);
246
  }
247
  return list;
248
}
249
250
251
// ------------------------------------------
252
//  FIND / SEEK
253
// ------------------------------------------
254
255
var find = function(name){
256
  return cache[name];
257
}
258
259
var exists = function(name){
260
  var plugin = find(name);
261
  return plugin ? true : false;
262
}
263
264
var remove = function(name, callback){
265
  var plugin = find(name);
266
  if (!plugin){ return callback(); }
267
  
268
  // Remove from filesystem
269
  var path = SARAH.ConfigManager.PLUGIN+'/'+name;
270
  info('Removing %s plugin...', path);
271
  if (fs.existsSync(path)){ fs.removeSync(path); }
272
  
273
  // Remove in memory
274
  refresh();
275
  
276
  callback();
277
}
278
279
// ------------------------------------------
280
//  EVENT
281
// ------------------------------------------
282
283
var events = require('events');
284
var ee = new events.EventEmitter();
285
286
var listen = function(event, callback){
287
  ee.on(event, callback);
288
}
289
290
var trigger = function(event, data){
291
  ee.emit(event, data);
292
}
293
294
var socket = function(io){
295
  io.on('connection', function(socket){
296
    socket.on('disconnect', function(){ });
297
    
298
    for(var name in cache){
299
      plugin = cache[name].getInstance();
300
      if (plugin.socket) plugin.socket(io, socket);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
301
    }
302
  });
303
}
304
305
// ------------------------------------------
306
//  ROUTER
307
// ------------------------------------------
308
309
var Router = express.Router();
310
311
Router.get('/plugin/help/:name', function(req, res, next) { 
312
  var name   = req.params.name; 
313
  var plugin = find(name);
314
  
315
  if (plugin && plugin.index) {
316
    return res.render(plugin.index, {'title' : i18n('modal.plugin.help', name)});
317
  }
318
  next();
319
});
320
321
Router.get('/plugin/github/:name', function(req, res, next) { 
322
  var name   = req.params.name; 
323
  SARAH.Marketplace.getCommits(name, function(commits){
324
    res.render('plugin/github.ejs', {
325
      'title'   : i18n('modal.plugin.github', name),
326
      'commits' : commits
327
    });
328
  });
329
});
330
331
Router.get('/plugin/config/:name', function(req, res, next) { 
332
  var name   = req.params.name; 
333
  return res.render('plugin/config.ejs', {'title' : i18n('modal.plugin.config', name) });
334
});
335
336
Router.post('/plugin/config/:name', function(req, res, next) { 
337
  var name    = req.params.name; 
338
  var plugin  = find(name);
339
  var keys    = Object.keys(req.body);
340
  for(var i   = 0 ; i < keys.length ; i++){
341
    var key   = keys[i];
342
    var value = Helper.parse(req.body[key]);
343
    var pfx   = key.substring(0, key.indexOf('.'));  
344
    var prop  = key.substring(key.indexOf('.')+1);
345
    
346
    // skip internal parameter like ajax 
347
    if (!pfx) continue;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
348
    
349
    info('[%s] %s.%s.%s = %s',key, pfx, name, prop, value);
350
    Config[pfx][name][prop] = value;
351
  }
352
  SARAH.ConfigManager.save();
353
354
  var referer = req.headers.referer;
355
  if (referer && referer.indexOf('/portal') < 0){
356
    return res.redirect(referer);
357
  } else {
358
    return res.render('plugin/config.ejs', {'title' : i18n('modal.plugin.config', name), 'message' : true });
359
  }
360
});
361
362
Router.get('/plugin/edit/:name', function(req, res, next) { 
363
  var name   = req.params.name; 
364
  var plugin = find(name);
365
  return res.render('plugin/edit.ejs', {'title' : i18n('modal.plugin.edit', name)});
366
});
367
368
Router.all('/plugin/sort', function(req, res, next) { 
369
  sort(req.query.ids, req.query.xPos, req.query.yPos);
370
  SARAH.ConfigManager.save();
371
  res.end();
372
});
373
374
Router.all('/plugin/:name/*', function(req, res, next) {
375
  var name   = req.params.name;
376
  var plugin = find(name);
377
  if (!plugin) return res.end();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
378
  
379
  var path = req.params[0];
380
  if (!path) return res.end();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
381
  
382
  try {
383
    var fullpath = SARAH.ConfigManager.PLUGIN+'/'+name+'/'+path;
384
    if (fs.existsSync(fullpath)){ 
385
      res.render(fullpath, { "plugin" : plugin, "title" : (req.query.title || req.body.title || "") });
386
    } else {
387
      warn('path not found:', fullpath);
388
    }
389
  } 
390
  catch (ex){ 
391
    warn('Error' , fullpath, ex.stack); 
392
  }
393
});
394
395
Router.all('/plugin/:name*', function(req, res, next) { 
396
  var plugin = find(req.params.name);
397
  if (!plugin) return res.end();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
398
  
399
  var render = function(){
400
    res.render('portal/portlet.ejs', { "plugin" : plugin});
401
  }
402
  
403
  var instance = plugin.getInstance();
404
  if (instance.ajax){ instance.ajax(req, res, render); } else { render(); }
405
});
406
407
Router.all('/static/:name/*', function(req, res, next) { 
408
  var plugin = find(req.params.name);
409
  if (!plugin) return res.end();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
410
  
411
  var instance = plugin.getInstance();
412
  if (instance.route) { 
413
    instance.route(req, res, next); 
414
  } else { next(); }
415
});
416
417
418
// ------------------------------------------
419
//  PUBLIC
420
// ------------------------------------------
421
422
var PluginManager = {
423
  'init' : init,
424
  'getCache'      : getCache,
425
  'getList'       : getList,
426
  'getLocales'    : getLocales,
427
  
428
  'find'          : find,
429
  'exists'        : exists,
430
  'remove'        : remove,
431
  
432
  'socket'        : socket,            
433
  'trigger'       : trigger,
434
  'listen'        : listen,
435
  
436
  'Router'        : Router
437
}
438
439
// Exports Manager
440
exports.init = PluginManager.init;