Passed
Push — main ( f1540e...02d90d )
by Rafael
45:15
created

Interfaces   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 381
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 207
dl 0
loc 381
rs 3.52
c 1
b 0
f 0
wmc 61

3 Methods

Rating   Name   Duplication   Size   Complexity  
F run_triggers() 0 169 31
A __construct() 0 3 1
F getTriggersList() 0 157 29

How to fix   Complexity   

Complex Class

Complex classes like Interfaces often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Interfaces, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* Copyright (C) 2024      Rafael San José      <[email protected]>
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
 */
18
19
namespace DoliCore\Model;
20
21
use DoliDB;
22
23
/**
24
 *   \file          htdocs/core/class/interfaces.class.php
25
 *   \ingroup       core
26
 *   \brief         Fichier de la class de gestion des triggers
27
 */
28
29
/**
30
 *   Class to manage triggers
31
 */
32
class Interfaces
33
{
34
    /**
35
     * @var DoliDB Database handler.
36
     */
37
    public $db;
38
39
    public $dir; // Directory with all core and external triggers files
40
41
    /**
42
     * @var string      Last module name in error
43
     */
44
    public $lastmoduleerror;
45
46
    /**
47
     * @var string[] Error codes (or messages)
48
     */
49
    public $errors = [];
50
51
    /**
52
     *  Constructor
53
     *
54
     * @param DoliDB $db Database handler
55
     */
56
    public function __construct($db)
57
    {
58
        $this->db = $db;
59
    }
60
61
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
62
63
    /**
64
     *   Function called when a Dolibarr business event occurs
65
     *   This function call all qualified triggers.
66
     *
67
     * @param string    $action Trigger event code
68
     * @param object    $object Object concerned. Some context information may also be provided into array property
69
     *                          object->context.
70
     * @param User      $user   Object user
71
     * @param Translate $langs  Object lang
0 ignored issues
show
Bug introduced by
The type DoliCore\Model\Translate was not found. Did you mean Translate? If so, make sure to prefix the type with \.
Loading history...
72
     * @param Conf      $conf   Object conf
0 ignored issues
show
Bug introduced by
The type DoliCore\Model\Conf was not found. Did you mean Conf? If so, make sure to prefix the type with \.
Loading history...
73
     *
74
     * @return    int                     Nb of triggers ran if no error, -Nb of triggers with errors otherwise.
75
     */
76
    public function run_triggers($action, $object, $user, $langs, $conf)
77
    {
78
        // phpcs:enable
79
80
        if (getDolGlobalInt('MAIN_TRIGGER_DEBUG')) {
81
            // This his too much verbose, enabled if const enabled only
82
            dol_syslog(get_class($this) . "::run_triggers action=" . $action . " Launch run_triggers", LOG_DEBUG);
83
        }
84
85
        // Check parameters
86
        if (!is_object($object) || !is_object($conf)) { // Error
87
            $error = 'function run_triggers called with wrong parameters action=' . $action . ' object=' . is_object($object) . ' user=' . is_object($user) . ' langs=' . is_object($langs) . ' conf=' . is_object($conf);
88
            dol_syslog(get_class($this) . '::run_triggers ' . $error, LOG_ERR);
89
            $this->errors[] = $error;
90
            return -1;
91
        }
92
        if (!is_object($langs)) {   // Warning
93
            dol_syslog(get_class($this) . '::run_triggers was called with wrong parameters action=' . $action . ' object=' . is_object($object) . ' user=' . is_object($user) . ' langs=' . is_object($langs) . ' conf=' . is_object($conf), LOG_WARNING);
94
        }
95
        if (!is_object($user)) {        // Warning
96
            dol_syslog(get_class($this) . '::run_triggers was called with wrong parameters action=' . $action . ' object=' . is_object($object) . ' user=' . is_object($user) . ' langs=' . is_object($langs) . ' conf=' . is_object($conf), LOG_WARNING);
97
            $user = new User($this->db);
98
        }
99
100
        $nbfile = $nbtotal = $nbok = $nbko = 0;
101
        $this->lastmoduleerror = '';
102
103
        $files = [];
104
        $modules = [];
105
        $orders = [];
106
        $i = 0;
107
108
        $dirtriggers = array_merge(['/core/triggers'], $conf->modules_parts['triggers']);
109
        foreach ($dirtriggers as $reldir) {
110
            $dir = dol_buildpath($reldir, 0);
111
            $newdir = dol_osencode($dir);
112
            //print "xx".$dir;exit;
113
114
            // Check if directory exists (we do not use dol_is_dir to avoir loading files.lib.php at each call)
115
            if (!is_dir($newdir)) {
116
                continue;
117
            }
118
119
            $handle = opendir($newdir);
120
            if (is_resource($handle)) {
121
                $fullpathfiles = [];
122
                while (($file = readdir($handle)) !== false) {
123
                    $reg = [];
124
                    if (is_readable($newdir . "/" . $file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php$/i', $file, $reg)) {
125
                        $part1 = $reg[1];
126
                        $part2 = $reg[2];
127
                        $part3 = $reg[3];
128
129
                        $nbfile++;
130
131
                        // Check if trigger file is disabled by name
132
                        if (preg_match('/NORUN$/i', $file)) {
133
                            continue;
134
                        }
135
                        // Check if trigger file is for a particular module
136
                        $qualified = true;
137
                        if (strtolower($reg[2]) != 'all') {
138
                            $module = preg_replace('/^mod/i', '', $reg[2]);
139
                            if (!isModEnabled(strtolower($module))) {
140
                                $qualified = false;
141
                            }
142
                        }
143
144
                        if (!$qualified) {
145
                            //dol_syslog(get_class($this)."::run_triggers action=".$action." Triggers for file '".$file."' need module to be enabled", LOG_DEBUG);
146
                            continue;
147
                        }
148
149
                        $modName = "Interface" . ucfirst($reg[3]);
150
                        //print "file=$file - modName=$modName\n";
151
                        if (in_array($modName, $modules)) {    // $modules = list of modName already loaded
152
                            $langs->load("errors");
153
                            dol_syslog(get_class($this) . "::run_triggers action=" . $action . " " . $langs->trans("ErrorDuplicateTrigger", $newdir . "/" . $file, $fullpathfiles[$modName]), LOG_WARNING);
154
                            continue;
155
                        }
156
157
                        try {
158
                            //print 'Todo for '.$modName." : ".$newdir.'/'.$file."\n";
159
                            include_once $newdir . '/' . $file;
160
                            //print 'Done for '.$modName."\n";
161
                        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type DoliCore\Model\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
162
                            dol_syslog('ko for ' . $modName . " " . $e->getMessage() . "\n", LOG_ERR);
163
                        }
164
165
                        $modules[$i] = $modName;
166
                        $files[$i] = $file;
167
                        $fullpathfiles[$modName] = $newdir . '/' . $file;
168
                        $orders[$i] = $part1 . '_' . $part2 . '_' . $part3; // Set sort criteria value
169
170
                        $i++;
171
                    }
172
                }
173
174
                closedir($handle);
175
            }
176
        }
177
178
        asort($orders, SORT_NATURAL);
179
180
        // Loop on each trigger
181
        foreach ($orders as $key => $value) {
182
            $modName = $modules[$key];
183
            if (empty($modName)) {
184
                continue;
185
            }
186
187
            $objMod = new $modName($this->db);
188
            if ($objMod) {
189
                $dblevelbefore = $this->db->transaction_opened;
190
191
                $result = 0;
192
193
                if (method_exists($objMod, 'runTrigger')) { // New method to implement
194
                    //dol_syslog(get_class($this)."::run_triggers action=".$action." Launch runTrigger for file '".$files[$key]."'", LOG_DEBUG);
195
                    $result = $objMod->runTrigger($action, $object, $user, $langs, $conf);
196
                } elseif (method_exists($objMod, 'run_trigger')) {  // Deprecated method
197
                    dol_syslog(get_class($this) . "::run_triggers action=" . $action . " Launch old method run_trigger (rename your trigger into runTrigger) for file '" . $files[$key] . "'", LOG_WARNING);
198
                    $result = $objMod->run_trigger($action, $object, $user, $langs, $conf);
199
                } else {
200
                    dol_syslog(get_class($this) . "::run_triggers action=" . $action . " A trigger was declared for class " . get_class($objMod) . " but method runTrigger was not found", LOG_ERR);
201
                }
202
203
                $dblevelafter = $this->db->transaction_opened;
204
205
                if ($dblevelbefore != $dblevelafter) {
206
                    $errormessage = "Error, the balance begin/close of db transactions has been broken into trigger " . $modName . " with action=" . $action . " before=" . $dblevelbefore . " after=" . $dblevelafter;
207
                    $this->errors[] = $errormessage;
208
                    dol_syslog($errormessage, LOG_ERR);
209
                    $result = -1;
210
                }
211
212
                if ($result > 0) {
213
                    // Action OK
214
                    $nbtotal++;
215
                    $nbok++;
216
                }
217
                if ($result == 0) {
218
                    // Aucune action faite
219
                    $nbtotal++;
220
                }
221
                if ($result < 0) {
222
                    // Action KO
223
                    //dol_syslog("Error in trigger ".$action." - result = ".$result." - Nb of error string returned = ".count($objMod->errors), LOG_ERR);
224
                    $nbtotal++;
225
                    $nbko++;
226
                    $this->lastmoduleerror = $modName;
227
                    if (!empty($objMod->errors)) {
228
                        $this->errors = array_merge($this->errors, $objMod->errors);
229
                    } elseif (!empty($objMod->error)) {
230
                        $this->errors[] = $objMod->error;
231
                    }
232
                    //dol_syslog("Error in trigger ".$action." - Nb of error string returned = ".count($this->errors), LOG_ERR);
233
                }
234
            } else {
235
                dol_syslog(get_class($this) . "::run_triggers action=" . $action . " Failed to instantiate trigger for file '" . $files[$key] . "'", LOG_ERR);
236
            }
237
        }
238
239
        if ($nbko) {
240
            dol_syslog(get_class($this) . "::run_triggers action=" . $action . " Files found: " . $nbfile . ", Files launched: " . $nbtotal . ", Done: " . $nbok . ", Failed: " . $nbko . ($this->lastmoduleerror ? " - Last module in error: " . $this->lastmoduleerror : "") . " - Nb of error string returned in this->errors = " . count($this->errors), LOG_ERR);
241
            return -$nbko;
242
        } else {
243
            //dol_syslog(get_class($this)."::run_triggers Files found: ".$nbfile.", Files launched: ".$nbtotal.", Done: ".$nbok.", Failed: ".$nbko, LOG_DEBUG);
244
            return $nbok;
245
        }
246
    }
247
248
    /**
249
     *  Return list of triggers. Function used by admin page htdoc/admin/triggers.
250
     *  List is sorted by trigger filename so by priority to run.
251
     *
252
     * @param array $forcedirtriggers null=All default directories. This parameter is used by modulebuilder module only.
253
     *
254
     * @return array                               Array list of triggers
255
     */
256
    public function getTriggersList($forcedirtriggers = null)
257
    {
258
        global $conf, $langs, $db;
259
260
        $files = [];
261
        $fullpath = [];
262
        $relpath = [];
263
        $iscoreorexternal = [];
264
        $modules = [];
265
        $orders = [];
266
        $i = 0;
267
268
        /**
269
         * TODO: Add new system modules outside of htdocs
270
         */
271
        $dirtriggers = array_merge(['/../Dolibarr/Core/Triggers/'], $conf->modules_parts['triggers']);
272
        if (is_array($forcedirtriggers)) {
273
            $dirtriggers = $forcedirtriggers;
274
        }
275
276
        foreach ($dirtriggers as $reldir) {
277
            $dir = dol_buildpath($reldir, 0);
278
            $newdir = dol_osencode($dir);
279
280
            // Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php at each call)
281
            if (!is_dir($newdir)) {
282
                continue;
283
            }
284
285
            $handle = opendir($newdir);
286
            if (is_resource($handle)) {
287
                while (($file = readdir($handle)) !== false) {
288
                    $reg = [];
289
                    if (is_readable($newdir . '/' . $file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/', $file, $reg)) {
290
                        if (preg_match('/\.back$/', $file)) {
291
                            continue;
292
                        }
293
294
                        $part1 = $reg[1];
295
                        $part2 = $reg[2];
296
                        $part3 = $reg[3];
297
298
                        $modName = 'Interface' . ucfirst($reg[3]);
299
                        //print "file=$file"; print "modName=$modName"; exit;
300
                        if (in_array($modName, $modules)) {
301
                            $langs->load("errors");
302
                            print '<div class="error">' . $langs->trans("Error") . ' : ' . $langs->trans("ErrorDuplicateTrigger", $modName, "/htdocs/core/triggers/") . '</div>';
303
                        } else {
304
                            include_once $newdir . '/' . $file;
305
                        }
306
307
                        $files[$i] = $file;
308
                        $fullpath[$i] = $dir . '/' . $file;
309
                        $relpath[$i] = preg_replace('/^\//', '', $reldir) . '/' . $file;
310
                        $iscoreorexternal[$i] = ($reldir == '/core/triggers/' ? 'internal' : 'external');
311
                        $modules[$i] = $modName;
312
                        $orders[$i] = $part1 . '_' . $part2 . '_' . $part3; // Set sort criteria value
313
314
                        $i++;
315
                    }
316
                }
317
                closedir($handle);
318
            }
319
        }
320
321
        asort($orders, SORT_NATURAL);
322
323
        $triggers = [];
324
        $j = 0;
325
326
        // Loop on each trigger
327
        foreach ($orders as $key => $value) {
328
            $modName = $modules[$key];
329
            if (empty($modName)) {
330
                continue;
331
            }
332
333
            if (!class_exists($modName)) {
334
                print 'Error: A trigger file was found but its class "' . $modName . '" was not found.' . "<br>\n";
335
                continue;
336
            }
337
338
            $text = '';
339
340
            try {
341
                $objMod = new $modName($db);
342
343
                if (is_subclass_of($objMod, 'DoliCore\Base\DolibarrTriggers')) {
344
                    // Define disabledbyname and disabledbymodule
345
                    $disabledbyname = 0;
346
                    $disabledbymodule = 1;
347
                    $module = '';
348
349
                    // Check if trigger file is disabled by name
350
                    if (preg_match('/NORUN$/i', $files[$key])) {
351
                        $disabledbyname = 1;
352
                    }
353
                    // Check if trigger file is for a particular module
354
                    if (preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/i', $files[$key], $reg)) {
355
                        $module = preg_replace('/^mod/i', '', $reg[2]);
356
                        if (strtolower($module) == 'all') {
357
                            $disabledbymodule = 0;
358
                        } elseif (!isModEnabled(strtolower($module))) {
359
                            $disabledbymodule = 2;
360
                        }
361
                        $triggers[$j]['module'] = strtolower($module);
362
                    }
363
364
                    // We set info of modules
365
                    $triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule "') : img_object('', 'generic', 'class="valignmiddle pictomodule "');
366
                    $triggers[$j]['file'] = $files[$key];
367
                    $triggers[$j]['fullpath'] = $fullpath[$key];
368
                    $triggers[$j]['relpath'] = $relpath[$key];
369
                    $triggers[$j]['iscoreorexternal'] = $iscoreorexternal[$key];
370
                    $triggers[$j]['version'] = $objMod->getVersion();
371
                    $triggers[$j]['status'] = img_picto($langs->trans("Active"), 'tick');
372
                    if ($disabledbyname > 0 || $disabledbymodule > 1) {
373
                        $triggers[$j]['status'] = '';
374
                    }
375
376
                    $text = '<b>' . $langs->trans("Description") . ':</b><br>';
377
                    $text .= $objMod->getDesc() . '<br>';
378
                    $text .= '<br><b>' . $langs->trans("Status") . ':</b><br>';
379
                    if ($disabledbyname == 1) {
380
                        $text .= $langs->trans("TriggerDisabledByName") . '<br>';
381
                        if ($disabledbymodule == 2) {
382
                            $text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module) . '<br>';
383
                        }
384
                    } else {
385
                        if ($disabledbymodule == 0) {
386
                            $text .= $langs->trans("TriggerAlwaysActive") . '<br>';
387
                        }
388
                        if ($disabledbymodule == 1) {
389
                            $text .= $langs->trans("TriggerActiveAsModuleActive", $module) . '<br>';
390
                        }
391
                        if ($disabledbymodule == 2) {
392
                            $text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module) . '<br>';
393
                        }
394
                    }
395
                } else {
396
                    $triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule "') : img_object('', 'generic', 'class="valignmiddle pictomodule "');
397
                    $triggers[$j]['file'] = $files[$key];
398
                    $triggers[$j]['fullpath'] = $fullpath[$key];
399
                    $triggers[$j]['relpath'] = $relpath[$key];
400
                    $triggers[$j]['status'] = img_picto('Error: Trigger ' . $modName . ' does not extends DolibarrTriggers', 'warning');
401
402
                    //print 'Error: Trigger '.$modName.' does not extends DolibarrTriggers<br>';
403
                    $text = 'Error: Trigger ' . $modName . ' does not extends DolibarrTriggers';
404
                }
405
            } catch (Exception $e) {
406
                print $e->getMessage();
407
            }
408
409
            $triggers[$j]['info'] = $text;
410
            $j++;
411
        }
412
        return $triggers;
413
    }
414
}
415