Plugins::saveEnabledPlugins()   B
last analyzed

Complexity

Conditions 9
Paths 34

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 16
nc 34
nop 0
dl 0
loc 27
rs 8.0555
c 1
b 0
f 0
1
<?php
2
3
namespace Trapdirector;
4
5
use Exception as Exception;
6
use Throwable;
7
use stdClass;
8
9
/**
10
 * Plugins management class
11
 * Used to load and execute plugins 
12
 * Default directory for plugins is : ../Plugins/
13
 * 
14
 * @license GPL
15
 * @author Patrick Proy
16
 *
17
 */
18
class Plugins
19
{
20
    /** Array of plugin objects. Keys ar plugin name
21
     * @var PluginTemplate[] $pluginsList Plugins array with name as index
22
     * $pluginsList[plugin name]['object']  : plugin object (NULL of not loaded)
23
     * $pluginsList[plugin name]['allOID']  : bool true if plugin catches all oid
24
     * $pluginsList[plugin name]['target']  : bool true if plugin can be trap processing target
25
     * $pluginsList[plugin name]['enabled'] : bool true if plugin is in enabled list 
26
     **/
27
    protected $pluginsList = array();
28
29
    /** Array of functions names
30
     * @var array $functionList 
31
     * $functionList[name]['plugin'] : Plugin name
32
     * $functionList[name]['function'] : Plugin function to call (null if plugin not loaded)
33
    */
34
    protected $functionList=array();
35
    
36
    /** @var string[] $enabledPlugins list of enabled plugins */
37
    //public $enabledPlugins = array();
38
39
    
40
    /** @var Logging $logClass */
41
    protected $logClass;
42
43
    /** @var Trap $trapClass */
44
    protected $trapClass;
45
    
46
    /** @var string $pluginDir */
47
    protected $pluginDir;
48
    
49
    /** Setup class
50
     * @param Trap $logClass  the top trap class
51
     * @param string $plugin_dir optional plugin directory
52
     * @throws \Exception
53
     */
54
    function __construct(Trap $trapClass,string $pluginDir='')
55
    {
56
        if ($pluginDir == '')
57
        {
58
            $this->pluginDir=dirname(__DIR__).'/Plugins';
59
        }
60
        else 
61
        {
62
            $this->pluginDir=$pluginDir;
63
        }
64
        // Set and check Logging class
65
        $this->trapClass=$trapClass;
66
        if ($this->trapClass === null)
67
        {
68
            throw new Exception('Log class not loaded into trap class');
69
        }
70
        $this->logClass=$trapClass->logging;
71
        if ($this->logClass === null)
72
        {
73
            throw new Exception('Log class not loaded into trap class');
74
        }
75
        // check DB class and get plugins list.
76
        if ($this->trapClass->trapsDB === null)
77
        {
78
            throw new Exception('Database class not loaded into trap class');
79
        }
80
        $this->loadEnabledPlugins();
81
    }
82
    
83
    
84
    /**
85
     * Load enabled plugins from database config table.
86
     * Fills enabledPlugins and functionList properties
87
     * @throws \Exception
88
     */
89
    private function loadEnabledPlugins()
90
    {
91
        $PluginList = $this->trapClass->trapsDB->getDBConfig('enabled_plugins');
92
               
93
        if ($PluginList === null || $PluginList == '')
94
        {
95
            $this->logClass->log('No enabled plugins',DEBUG);
96
            return;
97
        }
98
        else
99
        {   // Saved config : <plugin name>;<Catch all OID ? 1|0>;<Trap target ? 1|0>;<func 1 name>|<func 2 name>... ,<plugin2 name>....
100
            $this->logClass->log('Enabled plugins = '.$PluginList,DEBUG);
101
            
102
            $pluginArray = explode(',', $PluginList);
103
            foreach ($pluginArray as $pluginElmt)
104
            {
105
                $pluginElmt = explode(';',$pluginElmt);
106
                if ($pluginElmt === false || count($pluginElmt) != 4)
107
                {
108
                    throw new \Exception('Invalid plugin configuration : '. $PluginList );
109
                }
110
                $pluginName=$pluginElmt[0];
111
                
112
                $pluginListElmt = array();
113
                $pluginListElmt['object'] = null; // class not loaded
114
                $pluginListElmt['allOID'] = ($pluginElmt[1]=='1') ? true : false;
115
                $pluginListElmt['target'] = ($pluginElmt[2]=='1') ? true : false;
116
                $pluginListElmt['enabled'] = true;
117
                
118
                $this->pluginsList[$pluginName] = $pluginListElmt;
119
                
120
                // deal with plugin functions
121
                $pluginFunctions = explode('|',$pluginElmt[3]);
122
                if ($pluginFunctions !== false)
123
                {
124
                    foreach ($pluginFunctions as $function)
125
                    {
126
                        $this->functionList[$function] = array(
127
                            'plugin'    =>   $pluginName,
128
                            'function'  =>  null
129
                        );
130
                    }
131
                }
132
            }
133
134
        }
135
        
136
    }
137
138
    /**
139
     * Save enabled plugin array in DB config
140
     * @return bool true if OK, or false (error logged by DB Class)
141
     */
142
    private function saveEnabledPlugins()
143
    {
144
        $saveString='';
145
        foreach ($this->pluginsList as $name => $value)
146
        {
147
            if ($value['enabled'] == false)
148
            {
149
                continue;
150
            }
151
            $functionString='';
152
            foreach ($this->functionList as $fName => $fvalue)
153
            {
154
                if ($fvalue['plugin'] != $name)
155
                {
156
                    continue;
157
                }
158
                $functionString .= ($functionString == '') ? '' : '|'; // add separator if not empty
159
                $functionString .= $fName;
160
            }
161
            $saveString .= ($saveString == '')?'':',' ;
162
            
163
            $allOID = ($value['allOID'] === true) ? 1 : 0;
164
            $target = ($value['target'] === true) ? 1 : 0;
165
            $saveString .= $name . ';' . $allOID . ';' . $target . ';' . $functionString ;
166
        }
167
        $this->logClass->log('Saving : ' . $saveString,DEBUG);
168
        return $this->trapClass->trapsDB->setDBConfig('enabled_plugins', $saveString);
169
    }
170
    
171
    /** Get enabled plugin list by name
172
     * @return array
173
     */
174
    public function getEnabledPlugins() : array
175
    {
176
        $retArray=array();
177
        foreach ($this->pluginsList as $name => $value)
178
        {
179
            if ($value['enabled'] == true)
180
            {
181
                array_push($retArray,$name);
182
            }
183
        }
184
        return $retArray;
185
    }
186
187
    /** Enable plugin (enabling an enabled plugin is OK, same for disabled).
188
     *  and save in DB config
189
     * @param string $pluginName
190
     * @param bool $enabled true to enable, false to disable
191
     * @return bool true if OK, or false (error logged)
192
     */
193
    public function enablePlugin(string $pluginName,bool $enabled)
194
    {
195
        if ($enabled === false)
196
        {
197
            // If plugin is defined set to disable
198
            if ( isset($this->pluginsList[$pluginName]))
199
            {
200
                $this->pluginsList[$pluginName]['enabled'] = false;
201
            }            
202
            return $this->saveEnabledPlugins();
203
        }
204
        // Check if plugin is loaded / exists
205
        if ( ! isset($this->pluginsList[$pluginName]) || 
206
                $this->pluginsList[$pluginName]['object'] === null)
207
        {
208
            try {
209
                $this->registerPlugin($pluginName);
210
            } catch (Exception $e) {
211
                $this->logClass->log('Cannot enable plugin : ' . $e->getMessage(),WARN);
212
                return false;
213
            }
214
        }
215
        $this->pluginsList[$pluginName]['enabled'] = true;
216
        // save in DB and return 
217
        return $this->saveEnabledPlugins();
218
    }
219
   
220
    /**
221
     * Destroy plugin objects and reload them with new enabled list.
222
     * TODO : Code this function (ref DAEMON_MODE)
223
     */
224
    public function reloadAllPlugins()
225
    {
226
        return;
227
    }
228
 
229
    /** Load plugin by name. Create entry if not in $pluginsList
230
     * @param string $pluginName Plugin name to load
231
     * @return bool true if created, false if already loaded
232
     * @throws Exception on error loading plugin
233
     */
234
    public function registerPlugin(string $pluginName)
235
    {
236
        if ( ! isset($this->pluginsList[$pluginName]) ) // Plugin isn't enable, create entry
237
        {
238
            $pluginListElmt = array();
239
            $pluginListElmt['object'] = null; // class not loaded
240
            $pluginListElmt['enabled'] = false;
241
            $this->pluginsList[$pluginName] = $pluginListElmt;
242
        }
243
        
244
        if ($this->pluginsList[$pluginName]['object'] !== null)
245
        {
246
            return false;
247
        }
248
        try {
249
            // Include plugin file
250
            include_once($this->pluginDir.'/' . $pluginName . '.php');
251
            
252
            // Create full class name with namespace
253
            $pluginClassName = __NAMESPACE__ . '\\Plugins\\' . $pluginName;
254
            
255
            // Create class
256
            $newClass = new $pluginClassName();
257
            
258
            // Set logging
259
            $newClass->setLoggingClass($this->logClass);
260
            
261
            // Add in plugin array
262
            $this->pluginsList[$pluginName]['object']=$newClass;
263
            $this->pluginsList[$pluginName]['allOID']=$newClass->catchAllTraps;
264
            $this->pluginsList[$pluginName]['target']=$newClass->processTraps;
265
            
266
            // Delete old functions
267
            foreach ($this->functionList as $fname => $fvalue)
268
            {
269
                if ($fvalue['plugin'] == $pluginName)
270
                {
271
                    unset($this->functionList[$fname]);
272
                }
273
            }
274
            // Add functions
275
            foreach ($newClass->functions as $fname => $function)
276
            {
277
                if (isset($this->functionList[$fname]))
278
                {
279
                    if ($this->functionList[$fname]['plugin'] != $pluginName )
280
                    {
281
                        throw new Exception('Duplicate function name '.$fname . ' in ' 
282
                            . $pluginName . ' and ' . $this->functionList[$fname]['plugin']);
283
                    }
284
                    
285
                }
286
                else
287
                {
288
                    $this->functionList[$fname]=array();
289
                    $this->functionList[$fname]['plugin'] = $pluginName;
290
                }
291
                $this->functionList[$fname]['function']=$function['function'];
292
            }
293
            $this->logClass->log('Registered plugin '.$pluginName,DEBUG);
294
            
295
        } catch (Exception $e) {
296
            unset($this->pluginsList[$pluginName]);
297
            $errorMessage = "Error registering plugin $pluginName : ".$e->getMessage();
298
            $this->logClass->log($errorMessage,WARN);
299
            // Disable the plugin
300
            $this->enablePlugin($pluginName, false);
301
            throw new \Exception($errorMessage);
302
        } catch (Throwable $t) {
303
            unset($this->pluginsList[$pluginName]);
304
            $errorMessage = $t->getMessage() . ' in file ' . $t->getFile() . ' line ' . $t->getLine();
305
            $this->logClass->log($errorMessage,WARN);
306
            // Disable the plugin
307
            $this->enablePlugin($pluginName, false);
308
            throw new \Exception($errorMessage);
309
        }
310
        return true;
311
    }
312
    
313
    /** Registers all plugins (check=false) or only those with name present in array (check=true)
314
     * @param bool $checkEnabled Check if plugin is enabled before loading it
315
     * @return string Errors encountered while registering plugins
316
     */
317
    public function registerAllPlugins(bool $checkEnabled=true)
318
    {
319
        $retDisplay='';
320
        // First load enabled plugins
321
        foreach (array_keys($this->pluginsList) as $pluginName)
322
        {
323
            try {
324
                $this->registerPlugin($pluginName);
325
            } catch (Exception $e) {
326
                $retDisplay .= $e->getMessage() . ' / ';
327
            }
328
        }
329
        if ($checkEnabled === false) // Load all php files in plugin dir
330
        {
331
            foreach (glob($this->pluginDir."/*.php") as $filename)
332
            {             
333
                $pluginName=basename($filename,'.php');
334
                if (!preg_match('/^[a-zA-Z0-9]+$/',$pluginName))
335
                {
336
                    $this->logClass->log("Invalid plugin name : ".$pluginName, WARN);
337
                    $retDisplay .= "Invalid plugin name : ".$pluginName . " / ";
338
                    break;
339
                }
340
                try { // Already registerd plugin will simply return false
341
                    $this->registerPlugin($pluginName);               
342
                } catch (Exception $e) {
343
                    $retDisplay .= $e->getMessage() . ' / ';
344
                }
345
            }
346
        }
347
        
348
        if ($retDisplay == '')
349
        {
350
            return 'All plugins loaded OK';
351
        }
352
        else
353
        {
354
            return $retDisplay;
355
        }
356
    }
357
    
358
    /**
359
     * Returns array of name of loaded plugins
360
     * @return array
361
     */
362
    public function pluginList() : array
363
    {
364
        return array_keys($this->pluginsList);    
365
    }
366
367
    /**
368
     * Get plugin details
369
     * @param string $name name of plugins
370
     * @return boolean|stdClass result as stdClass or false if plugin not found.
371
     * @throws \Exception if registering is not possible
372
     */
373
    public function pluginDetails(string $name)
374
    {
375
        if (!array_key_exists($name, $this->pluginsList))
376
        {
377
            return false;
378
        }
379
        if ($this->pluginsList[$name]['object'] === null)
380
        {
381
            $this->registerPlugin($name); // can throw exception handled by caller
382
        }
383
        $retObj = new stdClass();
384
        $retObj->name           = $name;
385
        $retObj->catchAllTraps  = $this->pluginsList[$name]['allOID'];
386
        $retObj->processTraps   = $this->pluginsList[$name]['target'];
387
        $retObj->description    = $this->pluginsList[$name]['object']->description;
388
        $functions=array();
389
        foreach ($this->functionList as $fName => $func)
390
        {
391
            if ($func['plugin'] == $name)
392
            {
393
                array_push($functions,$fName);
394
            }
395
        }
396
        $retObj->funcArray=$functions;
397
        return $retObj;
398
    }
399
       
400
    /**
401
     * Get plugin name from function name
402
     * @param string $funcName
403
     * @param string $pluginName
404
     * @return boolean returns plugin object of false;
405
     */
406
    public function getFunction($funcName,&$pluginName)
407
    {
408
        if (! isset($this->functionList[$funcName]) )
409
        {
410
            return false;
411
        }
412
        $pluginName = $this->functionList[$funcName]['plugin'];
413
        return true;
414
    }
415
    
416
    /**
417
     * Get functions params and description
418
     * @param string $funcName
419
     * @return boolean|stdClass false if not found or object (name,params,description)
420
     * @throws \Exception if registering is not possible
421
     */
422
    public function getFunctionDetails($funcName)
423
    {
424
        if (! isset($this->functionList[$funcName]) )
425
        {
426
            return false;
427
        }
428
        $pluginName = $this->functionList[$funcName]['plugin']; // plugin name
429
        $plugin = $this->pluginsList[$pluginName]['object']; // plugin object
430
        if ($plugin === null)
431
        {
432
            $this->registerPlugin($pluginName); // can throw exception handled by caller
433
        }
434
        $retObj = new stdClass();
435
        $retObj->name           = $funcName;
436
        $retObj->plugin         = $pluginName;
437
        $retObj->params         = $plugin->functions[$funcName]['params'];
438
        $retObj->description    = $plugin->functions[$funcName]['description'];
439
        return $retObj;
440
    }
441
    
442
    /**
443
     * Evaluate function with parameters
444
     * @param string $funcName
445
     * @param mixed $params
446
     * @throws Exception
447
     * @return bool
448
     */
449
    public function getFunctionEval(string $funcName,$params) : bool
450
    {
451
        if (! isset($this->functionList[$funcName]) )
452
        {
453
            throw new Exception($funcName . ' not found.');
454
        }
455
        $pluginName = $this->functionList[$funcName]['plugin']; // plugin name
456
        $plugin = $this->pluginsList[$pluginName]['object']; // plugin object
457
458
        if ($plugin === null)
459
        {
460
            $this->registerPlugin($pluginName); // can throw exception handled by caller
461
            $plugin = $this->pluginsList[$pluginName]['object'];
462
        }
463
        
464
        $propertyName = $this->functionList[$funcName]['function'];
465
        $this->logClass->log('Using property '. $propertyName . ' of class : '.$pluginName,DEBUG);
466
        
467
        return $plugin->{$propertyName}($params);        
468
    }
469
    
470
    public function evaluateFunctionString(string $functionString) : bool
471
    {
472
        $matches=array();
473
        // Cleanup spaces
474
        //$functionString = $this->trapClass->ruleClass->eval_cleanup($functionString);
475
        //$this->logClass->log('eval cleanup : '.$functionString,DEBUG);
476
        
477
        // Match function call
478
        $num=preg_match('/^__([a-zA-Z0-9]+)\((.+)\)$/', $functionString , $matches);
479
        if ($num !=1)
480
        {
481
            throw new \ErrorException('Function syntax error : ' . $functionString );
482
        }
483
        $this->logClass->log('Got function : '. $matches[1] . ', params : '.$matches[2],DEBUG);
484
        $funcName=$matches[1];
485
        
486
        // Get parameters comma separated
487
        $funcParams=str_getcsv($matches[2],',','"',"\\");
488
        $this->logClass->log('Function params : ' . print_r($funcParams,true),DEBUG);
489
        
490
        // return evaluation
491
        return $this->getFunctionEval($funcName, $funcParams);        
492
        
493
    }
494
    
495
}
496
497
abstract class PluginTemplate
498
{
499
    
500
    /** @var Logging $loggingClass */
501
    private $loggingClass;
502
    
503
    /** @var string $name Name of plugin */
504
    public $name;
505
    
506
    /** @var string $description Description of plugin */
507
    public $description='Default plugin description';
508
    
509
    /** @var array $functions Functions of this plugin for rule eval*/
510
    public $functions=array();
511
    
512
    /** @var boolean $catchAllTraps Set to true if all traps will be sent to the plugin */
513
    public $catchAllTraps=false;
514
    
515
    /** @var boolean $processTraps Set to true if plugins can handle traps */
516
    public $processTraps=false;
517
    
518
    /**
519
     * @param \Trapdirector\Logging $loggingClass
520
     */
521
    public function setLoggingClass($loggingClass)
522
    {
523
        $this->loggingClass = $loggingClass;
524
    }
525
    
526
    /**
527
     * 
528
     * @param string $message
529
     * @param int $level DEBUG/INFO/WARN/CRIT
530
     */
531
    public function log($message,$level)
532
    {
533
        $this->loggingClass->log('[ '.get_class($this).'] '. $message, $level);
534
    }
535
}