Passed
Push — MINOR_OPTIMIZATIONS_241106 ( 63568c )
by Rafael
57:48
created

DolibarrModules   F

Complexity

Total Complexity 456

Size/Duplication

Total Lines 2733
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1180
dl 0
loc 2733
rs 0.88
c 0
b 0
f 0
wmc 456

55 Methods

Rating   Name   Duplication   Size   Complexity  
B setPerms() 0 35 7
A getModules() 0 6 2
B _getModules() 0 32 8
A _getModuleName() 0 14 6
A __construct() 0 3 1
D insert_module_parts() 0 94 25
B insert_tabs() 0 50 10
B _active() 0 29 7
A getImportDatasetLabel() 0 12 2
A remove() 0 3 1
B getChangeLog() 0 38 6
A forceDeactivate() 0 11 1
A _unactive() 0 15 3
A getObj() 0 9 2
A getLastActivationDate() 0 18 3
B getDescLongReadmeFound() 0 27 7
A getModulePosition() 0 10 3
A isExperimental() 0 3 1
A getExportDatasetLabel() 0 11 2
A isDeprecated() 0 3 1
D insert_permissions() 0 147 21
A getLangFilesArray() 0 3 1
A getPublisher() 0 3 1
A delete_cronjobs() 0 23 4
B delete_module_parts() 0 29 9
A delete_tabs() 0 14 2
A getLastActivationInfo() 0 25 6
A isActivated() 0 4 1
A isDevelopment() 0 3 1
B delete_boxes() 0 66 11
A delete_permissions() 0 24 4
F _remove() 0 86 17
A delete_const() 0 25 6
A delete_menus() 0 23 3
A init() 0 3 1
A getNameOf() 0 16 3
A getModule() 0 20 5
B getDescLong() 0 47 7
A getPublisherUrl() 0 3 1
A delete_dirs() 0 14 2
A insert_dirs() 0 28 3
A checkForUpdate() 0 20 5
A declareNewDictionary() 0 10 5
B insert_const() 0 48 11
F getKanbanView() 0 82 19
D insert_cronjobs() 0 124 41
A getName() 0 25 6
B isCoreOrExternalModule() 0 15 8
F insert_menus() 0 90 23
F _init() 0 100 20
D create_dirs() 0 57 18
D insert_boxes() 0 88 23
F _load_tables() 0 174 54
A getDesc() 0 25 6
B getVersion() 0 24 10

How to fix   Complexity   

Complex Class

Complex classes like DolibarrModules 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 DolibarrModules, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* Copyright (C) 2003-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004		Sebastien Di Cintio		    <[email protected]>
5
 * Copyright (C) 2004		Benoit Mortier			    <[email protected]>
6
 * Copyright (C) 2004		Eric Seigne				    <[email protected]>
7
 * Copyright (C) 2005-2013	Laurent Destailleur		    <[email protected]>
8
 * Copyright (C) 2005-2024	Regis Houssin			    <[email protected]>
9
 * Copyright (C) 2014		Raphaël Doursenaud		    <[email protected]>
10
 * Copyright (C) 2018		Josep Lluís Amador		    <[email protected]>
11
 * Copyright (C) 2019-2024	Frédéric France			    <[email protected]>
12
 * Copyright (C) 2024		MDW						    <[email protected]>
13
 * Copyright (C) 2024       Rafael San José             <[email protected]>
14
 *
15
 * This program is free software; you can redistribute it and/or modify
16
 * it under the terms of the GNU General Public License as published by
17
 * the Free Software Foundation; either version 3 of the License, or
18
 * (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
27
 */
28
29
namespace Dolibarr\Core\Base;
30
31
use Dolibarr\Code\Core\Classes\InfoBox;
32
use Dolibarr\Code\Core\Classes\Menubase;
33
use Dolibarr\Code\User\Classes\User;
34
use Dolibarr\Core\Model\Constant;
35
use DoliDB;
36
37
/**
38
 * \file           htdocs/core/modules/DolibarrModules.class.php
39
 * \brief          File of parent class of module descriptor class files
40
 */
41
42
/**
43
 * Class DolibarrModules
44
 *
45
 * Parent class for module descriptor class files
46
 */
47
abstract class DolibarrModules
48
{
49
    const KEY_ID = 0;
50
    const KEY_LABEL = 1;
51
    const KEY_TYPE = 2;
52
    const KEY_DEFAULT = 3;
53
    const KEY_FIRST_LEVEL = 4;
54
    const KEY_SECOND_LEVEL = 5;
55
    const KEY_MODULE = 6;
56
    const KEY_ENABLED = 7;
57
    /**
58
     * @var DoliDB  Database handler
59
     */
60
    public $db;
61
    /**
62
     * @var int     Module unique ID
63
     * @see https://wiki.dolibarr.org/index.php/List_of_modules_id
64
     */
65
    public $numero;
66
    /**
67
     * @var string  Publisher name
68
     */
69
    public $editor_name;
70
    /**
71
     * @var string  URL of module at publisher site
72
     */
73
    public $editor_url;
74
    /**
75
     * @var string  URL of logo of the publisher. Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@mymodule'.
76
     */
77
    public $editor_squarred_logo;
78
    /**
79
     * @var string  Family
80
     * @see $familyinfo
81
     *
82
     * Native values: 'crm', 'financial', 'hr', 'projects', 'products', 'ecm', 'technic', 'other'.
83
     * Use familyinfo to declare a custom value.
84
     */
85
    public $family;
86
    /**
87
     * @var array<string,array{position:string,label:string}> Custom family information
88
     * @see $family
89
     *
90
     * e.g.:
91
     * array(
92
     *     'myownfamily' => array(
93
     *         'position' => '001',
94
     *         'label' => $langs->trans("MyOwnFamily")
95
     *     )
96
     * );
97
     */
98
    public $familyinfo;
99
    /**
100
     * @var string  Module position on 2 digits
101
     */
102
    public $module_position = '50';
103
    /**
104
     * @var string  Module name
105
     *
106
     * Only used if Module[ID]Name translation string is not found.
107
     *
108
     * You can use the following code to automatically derive it from your module's class name:
109
     * preg_replace('/^mod/i', '', get_only_class($this))
110
     */
111
    public $name;
112
    /**
113
     * @var string[] Paths to create when module is activated
114
     *
115
     * e.g.: array('/mymodule/temp')
116
     */
117
    public $dirs = array();
118
    /**
119
     * @var array Module boxes
120
     */
121
    public $boxes = array(); // deprecated
122
    /**
123
     * @var array Module constants
124
     */
125
    public $const = array();
126
    /**
127
     * @var array Module cron jobs entries
128
     */
129
    public $cronjobs = array();
130
    /**
131
     * @var array   Module access rights
132
     */
133
    public $rights;
134
    /**
135
     * @var int     1=Admin is always granted of permission of modules (even when module is disabled)
136
     */
137
    public $rights_admin_allowed;
138
    /**
139
     * @var string  Module access rights family
140
     */
141
    public $rights_class;
142
    /**
143
     * @var array|int   Module menu entries (1 means the menu entries are not declared into module descriptor but are hardcoded into menu manager)
144
     */
145
    public $menu = array();
146
147
    /**
148
     * @var array{triggers?:int<0,1>,login?:int<0,1>,substitutions?:int<0,1>,menus?:int<0,1>,theme?:int<0,1>,tpl?:int<0,1>,barcode?:int<0,1>,models?:int<0,1>,printing?:int<0,1>,css?:string[],js?:string[],hooks?:array{data?:string[],entity?:string},moduleforexternal?:int<0,1>,websitetemplates?:int<0,1>,contactelement?:int<0,1>} Module parts
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{triggers?:int<0,1>...ntactelement?:int<0,1>} at position 4 could not be parsed: Expected '}' at position 4, but found 'int'.
Loading history...
149
     *  array(
150
     *      // Set this to 1 if module has its own trigger directory (/mymodule/core/triggers)
151
     *      'triggers' => 0,
152
     *      // Set this to 1 if module has its own login method directory (/mymodule/core/login)
153
     *      'login' => 0,
154
     *      // Set this to 1 if module has its own substitution function file (/mymodule/core/substitutions)
155
     *      'substitutions' => 0,
156
     *      // Set this to 1 if module has its own menus handler directory (/mymodule/core/menus)
157
     *      'menus' => 0,
158
     *      // Set this to 1 if module has its own theme directory (/mymodule/theme)
159
     *      'theme' => 0,
160
     *      // Set this to 1 if module overwrite template dir (/mymodule/core/tpl)
161
     *      'tpl' => 0,
162
     *      // Set this to 1 if module has its own barcode directory (/mymodule/core/modules/barcode)
163
     *      'barcode' => 0,
164
     *      // Set this to 1 if module has its own models directory (/mymodule/core/modules/xxx)
165
     *      'models' => 0,
166
     *      // Set this to relative path of css file if module has its own css file
167
     *      'css' => '/mymodule/css/mymodule.css.php',
168
     *      // Set this to relative path of js file if module must load a js on all pages
169
     *      'js' => '/mymodule/js/mymodule.js',
170
     *      // Set here all hooks context managed by module
171
     *      'hooks' => array('hookcontext1','hookcontext2')
172
     *  )
173
     */
174
    public $module_parts = array();
175
176
    /**
177
     * @var string Error message
178
     */
179
    public $error;
180
181
    /**
182
     * @var string[] Array of Errors messages
183
     */
184
    public $errors;
185
186
    /**
187
     * @var string Module version
188
     * @see http://semver.org
189
     *
190
     * The following keywords can also be used:
191
     * 'development'
192
     * 'experimental'
193
     * 'dolibarr': only for core modules that share its version
194
     * 'dolibarr_deprecated': only for deprecated core modules
195
     */
196
    public $version;
197
198
    /**
199
     * Module last version
200
     * @var string $lastVersion
201
     */
202
    public $lastVersion = '';
203
204
    /**
205
     * true indicate this module need update
206
     * @var bool $needUpdate
207
     */
208
    public $needUpdate = false;
209
210
    /**
211
     * @var string Module description (short text)
212
     *
213
     * Only used if Module[ID]Desc translation string is not found.
214
     */
215
    public $description;
216
217
    /**
218
     * @var   string Module description (long text)
219
     * @since 4.0.0
220
     *
221
     * HTML content supported.
222
     */
223
    public $descriptionlong;
224
225
    /**
226
     * @var array dictionaries description
227
     */
228
    public $dictionaries = array();
229
230
    /**
231
     * @var array tabs description
232
     */
233
    public $tabs;
234
235
    // For exports
236
237
    /**
238
     * @var string Module export code
239
     */
240
    public $export_code;
241
242
    /**
243
     * @var string[] Module export label
244
     */
245
    public $export_label;
246
247
    public $export_icon;
248
249
    /**
250
     * @var array export enabled
251
     */
252
    public $export_enabled;
253
    public $export_permission;
254
    public $export_fields_array;
255
    public $export_TypeFields_array; // Array of key=>type where type can be 'Numeric', 'Date', 'Text', 'Boolean', 'Status', 'List:xxx:fieldlabel:rowid'
256
    public $export_entities_array;
257
    public $export_aggregate_array;
258
    public $export_examplevalues_array;
259
    public $export_help_array;
260
    public $export_special_array; // special or computed field
261
    public $export_dependencies_array;
262
    public $export_sql_start;
263
    public $export_sql_end;
264
    public $export_sql_order;
265
266
267
    // For import
268
269
    /**
270
     * @var string Module import code
271
     */
272
    public $import_code;
273
274
    /**
275
     * @var string[] Module import label
276
     */
277
    public $import_label;
278
279
    public $import_icon;
280
    public $import_entities_array;
281
    public $import_tables_array;
282
    public $import_tables_creator_array;
283
    public $import_fields_array;
284
    public $import_fieldshidden_array;
285
    public $import_convertvalue_array;
286
    public $import_regex_array;
287
    public $import_examplevalues_array;
288
    public $import_updatekeys_array;
289
    public $import_run_sql_after_array;
290
    public $import_TypeFields_array;
291
    public $import_help_array;
292
293
    /**
294
     * @var string Module constant name
295
     */
296
    public $const_name;
297
298
    /**
299
     * @var bool Module can't be disabled
300
     */
301
    public $always_enabled;
302
303
    /**
304
     * @var bool Module is disabled
305
     */
306
    public $disabled;
307
308
    /**
309
     * @var int Module is enabled globally (Multicompany support)
310
     */
311
    public $core_enabled;
312
313
    /**
314
     * @var string Name of image file used for this module
315
     *
316
     * If file is in theme/yourtheme/img directory under name object_pictoname.png use 'pictoname'
317
     * If file is in module/img directory under name object_pictoname.png use 'pictoname@module'
318
     */
319
    public $picto;
320
321
    /**
322
     * @var string[]|string     List of config pages (Old modules uses a string. New one must use an array)
323
     *
324
     * Name of php pages stored into module/admin directory, used to setup module.
325
     * e.g.: array("setup.php@mymodule")
326
     */
327
    public $config_page_url;
328
329
330
    /**
331
     * @var array   List of module class names that must be enabled if this module is enabled. e.g.: array('modAnotherModule', 'FR'=>'modYetAnotherModule')
332
     *              Another example : array('always'=>array("modBanque", "modFacture", "modProduct", "modCategorie"), 'FR'=>array('modBlockedLog'));
333
     * @see $requiredby
334
     */
335
    public $depends;
336
337
    /**
338
     * @var string[] List of module class names to disable if the module is disabled.
339
     * @see $depends
340
     */
341
    public $requiredby;
342
343
    /**
344
     * @var string[] List of module class names as string this module is in conflict with.
345
     * @see $depends
346
     */
347
    public $conflictwith;
348
349
    /**
350
     * @var string[] Module language files
351
     */
352
    public $langfiles;
353
354
    /**
355
     * @var array<string,string> Array of warnings to show when we activate the module
356
     *
357
     * array('always'='text') or array('FR'='text')
358
     */
359
    public $warnings_activation;
360
361
    /**
362
     * @var array<string,string> Array of warnings to show when we activate an external module
363
     *
364
     * array('always'='text') or array('FR'='text')
365
     */
366
    public $warnings_activation_ext;
367
368
    /**
369
     * @var array<string,string> Array of warnings to show when we disable the module
370
     *
371
     * array('always'='text') or array('FR'='text')
372
     */
373
    public $warnings_unactivation;
374
375
    /**
376
     * @var array Minimum version of PHP required by module.
377
     * e.g.: PHP ≥ 7.0 = array(7, 0)
378
     */
379
    public $phpmin;
380
381
    public $phpmax;
382
383
    /**
384
     * @var array Minimum version of Dolibarr required by module.
385
     * e.g.: Dolibarr ≥ 3.6 = array(3, 6)
386
     */
387
    public $need_dolibarr_version;
388
389
    public $need_javascript_ajax;
390
391
    public $enabled_bydefault;
392
393
    /**
394
     * @var bool|int Whether to hide the module.
395
     */
396
    public $hidden = false;
397
398
    /**
399
     * @var string url to check for module update
400
     */
401
    public $url_last_version;
402
403
404
    /**
405
     * Constructor. Define names, constants, directories, boxes, permissions
406
     *
407
     * @param DoliDB $db Database handler
408
     */
409
    public function __construct($db)
410
    {
411
        $this->db = $db;
412
    }
413
414
    public static function setPerms($db, $langs, $entity)
415
    {
416
        $db->begin();
417
418
// Search all modules with permission and reload permissions def.
419
        $modules = array();
420
        $modulesdir = dolGetModulesDirs();
421
422
        $allModules = static::getModules($modulesdir);
423
        foreach ($allModules as $modName => $filename) {
424
            if (str_starts_with($modName, 'mod')) {
425
                include_once $filename;
426
                $objMod = new $modName($db);
427
            } else {
428
                $className = "\\Dolibarr\\Modules\\" . $modName;
429
                $objMod = new $className($db);
430
            }
431
432
            // Load all lang files of module
433
            if (isset($objMod->langfiles) && is_array($objMod->langfiles)) {
434
                foreach ($objMod->langfiles as $domain) {
435
                    $langs->load($domain);
436
                }
437
            }
438
            // Load all permissions
439
            if ($objMod->rights_class) {
440
                $ret = $objMod->insert_permissions(0, $entity);
441
                $modules[$objMod->rights_class] = $objMod;
442
                //print "modules[".$objMod->rights_class."]=$objMod;";
443
            }
444
        }
445
446
        $db->commit();
447
448
        return $modules;
449
    }
450
451
    public static function getModules($modulesdir = null)
452
    {
453
        if (!isset($modulesdir)) {
454
            $modulesdir = dolGetModulesDirs();
455
        }
456
        return static::_getModules($modulesdir);
457
    }
458
    // We should but can't set this as abstract because this will make dolibarr hang
459
    // after migration due to old module not implementing. We must wait PHP is able to make
460
    // a try catch on Fatal error to manage this correctly.
461
    // We need constructor into function unActivateModule into admin.lib.php
462
463
    /**
464
     * Obtiene un array con los módulos instalados.
465
     * TODO: Sustituir donde se use:
466
     * if (is_readable($dir . $file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
467
     *
468
     * @param $modulesdir
469
     * @return array
470
     * @throws \Exception
471
     */
472
    private static function _getModules($modulesdir): array
473
    {
474
        $result = [];
475
        foreach ($modulesdir as $dir) {
476
            dol_syslog("Scan directory " . $dir . " for module descriptor files (XXX.php OR modXXX.class.php)");
477
            $handle = @opendir($dir);
478
            if (!is_resource($handle)) {
479
                dol_syslog("htdocs/admin/modules.php: Failed to open directory " . $dir . ". See permission and open_basedir option.", LOG_WARNING);
480
                continue;
481
            }
482
483
            while (($file = readdir($handle)) !== false) {
484
                $filename = $dir . $file;
485
                if (is_dir($filename) || !is_readable($filename)) {
486
                    continue;
487
                }
488
489
                $className = static::_getModuleName($file);
490
491
                if (empty($className)) {
492
                    continue;
493
                }
494
495
                if (isset($result[$className])) {
496
                    continue;
497
                }
498
499
                $result[$className] = $filename;
500
            }
501
            closedir($handle);
502
        }
503
        return $result;
504
    }
505
506
    public static function _getModuleName($file)
507
    {
508
        $new_type = !str_starts_with($file, 'mod') && str_ends_with($file, '.php');
509
        $old_type = str_starts_with($file, 'mod') && str_ends_with($file, '.class.php');
510
511
        if (!($new_type || $old_type)) {
512
            return null;
513
        }
514
515
        if ($new_type) {
516
            return substr($file, 0, strrpos($file, '.'));
517
        }
518
519
        return substr($file, 0, dol_strlen($file) - 10);
520
    }
521
522
    /**
523
     * Adds access rights
524
     *
525
     * @param int $reinitadminperms If 1, we also grant them to all admin users
526
     * @param int $force_entity Force current entity
527
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
528
     * @return int                      Error count (0 if OK)
529
     */
530
    public function insert_permissions($reinitadminperms = 0, $force_entity = null, $notrigger = 0)
531
    {
532
        // phpcs:enable
533
        global $conf, $user;
534
535
        $err = 0;
536
        $entity = (!empty($force_entity) ? $force_entity : $conf->entity);
537
538
        dol_syslog(get_only_class($this) . "::insert_permissions", LOG_DEBUG);
539
540
        // Test if module is activated
541
        $result = Constant::isActivated($this->const_name, $entity);
542
        if ($result !== null) {
543
            if ($result && !empty($this->rights)) {
544
                // TODO rights parameters with integer indexes are deprecated
545
                // $this->rights[$key][0] = $this->rights[$key][self::KEY_ID]
546
                // $this->rights[$key][1] = $this->rights[$key][self::KEY_LABEL]
547
                // $this->rights[$key][3] = $this->rights[$key][self::KEY_DEFAULT]
548
                // $this->rights[$key][4] = $this->rights[$key][self::KEY_FIRST_LEVEL]
549
                // $this->rights[$key][5] = $this->rights[$key][self::KEY_SECOND_LEVEL]
550
551
                // new parameters
552
                // $this->rights[$key][self::KEY_MODULE]    // possibility to define user right for an another module (default: current module name)
553
                // $this->rights[$key][self::KEY_ENABLED]   // condition to show or hide a user right (default: 1) (eg isModEnabled('anothermodule'))
554
555
                // If the module is active
556
                foreach ($this->rights as $key => $value) {
557
                    $r_id = $this->rights[$key][self::KEY_ID];  // permission id in llx_rights_def (not unique because primary key is couple id-entity)
558
                    $r_label = $this->rights[$key][self::KEY_LABEL];
559
                    $r_type = $this->rights[$key][self::KEY_TYPE] ?? 'w';   // TODO deprecated
560
                    $r_default = $this->rights[$key][self::KEY_DEFAULT] ?? 0;
561
                    $r_perms = $this->rights[$key][self::KEY_FIRST_LEVEL] ?? '';
562
                    $r_subperms = $this->rights[$key][self::KEY_SECOND_LEVEL] ?? '';
563
564
                    // KEY_FIRST_LEVEL (perms) must not be empty
565
                    if (empty($r_perms)) {
566
                        continue;
567
                    }
568
569
                    // name of module (default: current module name)
570
                    $r_module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
571
572
                    // name of the module from which the right comes (default: empty means same module the permission is for)
573
                    $r_module_origin = '';
574
575
                    if (isset($this->rights[$key][self::KEY_MODULE])) {
576
                        // name of the module to which the right must be applied
577
                        $r_module = $this->rights[$key][self::KEY_MODULE];
578
                        // name of the module from which the right comes
579
                        $r_module_origin = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
580
                    }
581
582
                    // condition to show or hide a user right (default: 1) (eg isModEnabled('anothermodule') or ($conf->global->MAIN_FEATURES_LEVEL > 0) or etc..)
583
                    $r_enabled = $this->rights[$key][self::KEY_ENABLED] ?? '1';
584
585
                    // Search if perm already present
586
                    $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "rights_def";
587
                    $sql .= " WHERE entity = " . ((int)$entity);
588
                    $sql .= " AND id = " . ((int)$r_id);
589
590
                    $resqlselect = $this->db->query($sql);
591
                    if ($resqlselect) {
592
                        $objcount = $this->db->fetch_object($resqlselect);
593
                        if ($objcount && $objcount->nb == 0) {
594
                            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "rights_def (";
595
                            $sql .= "id";
596
                            $sql .= ", entity";
597
                            $sql .= ", libelle";
598
                            $sql .= ", module";
599
                            $sql .= ", module_origin";
600
                            $sql .= ", type";   // TODO deprecated
601
                            $sql .= ", bydefault";
602
                            $sql .= ", perms";
603
                            $sql .= ", subperms";
604
                            $sql .= ", enabled";
605
                            $sql .= ") VALUES (";
606
                            $sql .= ((int)$r_id);
607
                            $sql .= ", " . ((int)$entity);
608
                            $sql .= ", '" . $this->db->escape($r_label) . "'";
609
                            $sql .= ", '" . $this->db->escape($r_module) . "'";
610
                            $sql .= ", '" . $this->db->escape($r_module_origin) . "'";
611
                            $sql .= ", '" . $this->db->escape($r_type) . "'";   // TODO deprecated
612
                            $sql .= ", " . ((int)$r_default);
613
                            $sql .= ", '" . $this->db->escape($r_perms) . "'";
614
                            $sql .= ", '" . $this->db->escape($r_subperms) . "'";
615
                            $sql .= ", '" . $this->db->escape($r_enabled) . "'";
616
                            $sql .= ")";
617
618
                            $resqlinsert = $this->db->query($sql, 1);
619
620
                            if (!$resqlinsert) {
621
                                if ($this->db->errno() != "DB_ERROR_RECORD_ALREADY_EXISTS") {
622
                                    $this->error = $this->db->lasterror();
623
                                    $err++;
624
                                    break;
625
                                } else {
626
                                    dol_syslog(get_only_class($this) . "::insert_permissions record already exists", LOG_INFO);
627
                                }
628
                            }
629
630
                            $this->db->free($resqlinsert);
631
                        }
632
633
                        $this->db->free($resqlselect);
634
                    }
635
636
                    // If we want to init permissions on admin users
637
                    if (!empty($reinitadminperms)) {
638
                        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "user WHERE admin = 1";
639
                        dol_syslog(get_only_class($this) . "::insert_permissions Search all admin users", LOG_DEBUG);
640
641
                        $resqlseladmin = $this->db->query($sql, 1);
642
643
                        if ($resqlseladmin) {
644
                            $num = $this->db->num_rows($resqlseladmin);
645
                            $i = 0;
646
                            while ($i < $num) {
647
                                $obj2 = $this->db->fetch_object($resqlseladmin);
648
                                dol_syslog(get_only_class($this) . "::insert_permissions Add permission id " . $r_id . " to user id=" . $obj2->rowid);
649
650
                                $tmpuser = new User($this->db);
651
                                $result = $tmpuser->fetch($obj2->rowid);
652
                                if ($result > 0) {
653
                                    $tmpuser->addrights($r_id, '', '', 0, 1);
654
                                } else {
655
                                    dol_syslog(get_only_class($this) . "::insert_permissions Failed to add the permission to user because fetch return an error", LOG_ERR);
656
                                }
657
                                $i++;
658
                            }
659
                        } else {
660
                            dol_print_error($this->db);
661
                        }
662
                    }
663
                }
664
665
                if (!empty($reinitadminperms) && !empty($user->admin)) {  // Reload permission for current user if defined
666
                    // We reload permissions
667
                    $user->clearrights();
668
                    $user->getrights();
669
                }
670
            }
671
        } else {
672
            $this->error = 'isActivated query fails';
673
            $err++;
674
        }
675
676
        return $err;
677
    }
678
679
    public static function isActivated($modName)
680
    {
681
        $name = 'MAIN_MODULE_' . static::getNameOf($modName);
682
        return !empty(getDolGlobalString($name));
683
    }
684
685
    public static function getNameOf($modName)
686
    {
687
        /**
688
         * Find the last occurrence of a double backslash, to remove the namespace (if it exists)
689
         */
690
        $name = strrchr($modName, '\\');
691
        $name = $name ? ltrim($name, '\\') : $modName;
692
693
        /**
694
         * If the name is preceded by 'mod', 'mod' is removed.
695
         */
696
        if (str_starts_with($modName, 'mod')) {
697
            $name = substr($name, 3);
698
        }
699
700
        return strtoupper($name);
701
    }
702
703
    public static function forceDeactivate($modName)
704
    {
705
        // phpcs:enable
706
        global $conf, $db;
707
708
        $name = static::getNameOf($modName);
709
        $entity = $conf->entity;
710
711
        Constant::deleteByName($name, $entity);
712
713
        dol_syslog($name . "::_unactive", LOG_DEBUG);
714
    }
715
716
    /**
717
     * Obtains an instance of $modName, or null.
718
     *
719
     * @param $modName
720
     * @return DolibarrModules
721
     */
722
    public static function getModule($modName): ?DolibarrModules
723
    {
724
        global $db;
725
726
        $modules = static::getModules();
727
        if (str_starts_with($modName, 'mod')) {
728
            if (isset($modules[$modName]) && file_exists($modules[$modName])) {
729
                dump('FileExists ' . $modules[$modName] . ' for ' . $modName);
730
                include_once $modules[$modName];
731
                return new $modName($db);
732
            }
733
            $modName = str_replace('mod', '', $modName);
734
        }
735
736
        if (!isset($modules[$modName])) {
737
            return null;
738
        }
739
740
        $className = "\\Dolibarr\\Modules\\" . $modName;
741
        return new $className($db);
742
    }
743
744
    public static function getObj($db, $modName, $filename)
745
    {
746
        if (str_starts_with($modName, 'mod')) {
747
            include_once $filename;
748
            return new $modName($db);
749
        }
750
751
        $className = "Dolibarr\\Modules\\" . $modName;
752
        return $objMod = new $className($db);
753
    }
754
755
    public function isDevelopment()
756
    {
757
        return $this->version === 'development';
758
    }
759
760
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
761
762
    public function isExperimental()
763
    {
764
        return $this->version === 'development';
765
    }
766
767
    public function isDeprecated()
768
    {
769
        return str_contains($this->version, 'deprecated');
770
    }
771
772
    /**
773
     * Function called when module is disabled.
774
     * The remove function removes tabs, constants, boxes, permissions and menus from Dolibarr database.
775
     * Data directories are not deleted
776
     *
777
     * @param string $options Options when enabling module ('', 'noboxes')
778
     * @return int                     1 if OK, 0 if KO
779
     */
780
    public function remove($options = '')
781
    {
782
        return $this->_remove(array(), $options);
783
    }
784
785
    /**
786
     * Disable function. Deletes the module constants and boxes from the database.
787
     *
788
     * @param string[] $array_sql SQL requests to be executed when module is disabled
789
     * @param string $options Options when disabling module:
790
     *
791
     * @return int                     1 if OK, 0 if KO
792
     */
793
    protected function _remove($array_sql, $options = '')
794
    {
795
        global $conf;
796
797
        // phpcs:enable
798
        $err = 0;
799
800
        $this->db->begin();
801
802
        // Remove activation module line (constant MAIN_MODULE_MYMODULE in llx_const)
803
        if (!$err) {
804
            $err += $this->_unactive();
805
        }
806
807
        // Remove activation of module's new tabs (MAIN_MODULE_MYMODULE_TABS_XXX in llx_const)
808
        if (!$err) {
809
            $err += $this->delete_tabs();
810
        }
811
812
        // Remove activation of module's parts (MAIN_MODULE_MYMODULE_XXX in llx_const)
813
        if (!$err) {
814
            $err += $this->delete_module_parts();
815
        }
816
817
        // Remove constants defined by modules
818
        if (!$err) {
819
            $err += $this->delete_const();
820
        }
821
822
        // Remove list of module's available boxes (entry in llx_boxes)
823
        if (!$err && !preg_match('/(newboxdefonly|noboxes)/', $options)) {
824
            $err += $this->delete_boxes(); // We don't have to delete if option ask to keep boxes safe or ask to add new box def only
825
        }
826
827
        // Remove list of module's cron job entries (entry in llx_cronjobs)
828
        if (!$err) {
829
            $err += $this->delete_cronjobs();
830
        }
831
832
        // Remove module's permissions from list of available permissions (entries in llx_rights_def)
833
        if (!$err) {
834
            $err += $this->delete_permissions();
835
        }
836
837
        // Remove module's menus (entries in llx_menu)
838
        if (!$err) {
839
            $err += $this->delete_menus();
840
        }
841
842
        // Remove module's directories
843
        if (!$err) {
844
            $err += $this->delete_dirs();
845
        }
846
847
        // Run complementary sql requests
848
        $num = count((array)$array_sql);
849
        for ($i = 0; $i < $num; $i++) {
850
            if (!$err) {
851
                dol_syslog(get_only_class($this) . "::_remove", LOG_DEBUG);
852
                $result = $this->db->query($array_sql[$i]);
853
                if (!$result) {
854
                    $this->error = $this->db->error();
855
                    $err++;
856
                }
857
            }
858
        }
859
860
        // Return code
861
        if (!$err) {
862
            $this->db->commit();
863
864
            // Disable modules
865
            $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
866
            // two exceptions to handle
867
            if ($moduleNameInConf === 'propale') {
868
                $moduleNameInConf = 'propal';
869
            } elseif ($moduleNameInConf === 'supplierproposal') {
870
                $moduleNameInConf = 'supplier_proposal';
871
            }
872
873
            unset($conf->modules[$moduleNameInConf]);   // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
874
875
            return 1;
876
        } else {
877
            $this->db->rollback();
878
            return 0;
879
        }
880
    }
881
882
    /**
883
     * Module deactivation
884
     *
885
     * @return int Error count (0 if OK)
886
     */
887
    protected function _unactive()
888
    {
889
        // phpcs:enable
890
        global $conf;
891
892
        $err = 0;
893
894
        // Common module
895
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
896
897
        dol_syslog(get_only_class($this) . "::_unactive", LOG_DEBUG);
898
899
        Constant::deleteByName($this->const_name, $entity);
900
901
        return $err;
902
    }
903
904
    /**
905
     * Removes tabs
906
     *
907
     * @return int Error count (0 if OK)
908
     */
909
    public function delete_tabs()
910
    {
911
        // phpcs:enable
912
        global $conf;
913
914
        $err = 0;
915
916
        dol_syslog(get_only_class($this) . "::delete_tabs", LOG_DEBUG);
917
        if (!Constant::deleteByNameLike($this->db->escape($this->const_name) . '_TABS_', $conf->entity)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Dolibarr\Core\Model\Cons..._TABS_', $conf->entity) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
918
            $this->error = $this->db->lasterror();
919
            $err++;
920
        }
921
922
        return $err;
923
    }
924
925
    /**
926
     * Removes generic parts
927
     *
928
     * @return int Error count (0 if OK)
929
     */
930
    public function delete_module_parts()
931
    {
932
        // phpcs:enable
933
        global $conf;
934
935
        $err = 0;
936
937
        if (is_array($this->module_parts)) {
938
            dol_syslog(get_only_class($this) . "::delete_module_parts", LOG_DEBUG);
939
940
            if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
941
                $this->module_parts['icon'] = $this->picto;
942
            }
943
944
            foreach ($this->module_parts as $key => $value) {
945
                // If entity is defined
946
                if (is_array($value) && isset($value['entity'])) {
947
                    $entity = $value['entity'];
948
                } else {
949
                    $entity = $conf->entity;
950
                }
951
952
                if (!Constant::deleteByNameLike($this->db->escape($this->const_name) . '_' . strtoupper($key), $entity)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Dolibarr\Core\Model\Cons...toupper($key), $entity) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
953
                    $this->error = $this->db->lasterror();
954
                    $err++;
955
                }
956
            }
957
        }
958
        return $err;
959
    }
960
961
    /**
962
     * Removes constants tagged 'deleteonunactive'
963
     *
964
     * @return int Return integer <0 if KO, 0 if OK
965
     */
966
    public function delete_const()
967
    {
968
        // phpcs:enable
969
        global $conf;
970
971
        $err = 0;
972
973
        if (empty($this->const)) {
974
            return 0;
975
        }
976
977
        foreach ($this->const as $key => $value) {
978
            $name = $this->const[$key][0];
979
            $deleteonunactive = (!empty($this->const[$key][6])) ? 1 : 0;
980
981
            if ($deleteonunactive) {
982
                dol_syslog(get_only_class($this) . "::delete_const", LOG_DEBUG);
983
                if (!Constant::deleteByName($name, $conf->entity)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Dolibarr\Core\Model\Cons...e($name, $conf->entity) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
984
                    $this->error = $this->db->lasterror();
985
                    $err++;
986
                }
987
            }
988
        }
989
990
        return $err;
991
    }
992
993
    /**
994
     * Removes boxes
995
     *
996
     * @return int Error count (0 if OK)
997
     */
998
    public function delete_boxes()
999
    {
1000
        // phpcs:enable
1001
        global $conf;
1002
1003
        $err = 0;
1004
1005
        if (is_array($this->boxes)) {
1006
            foreach ($this->boxes as $key => $value) {
1007
                //$titre = $this->boxes[$key][0];
1008
                if (empty($this->boxes[$key]['file'])) {
1009
                    $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1010
                } else {
1011
                    $file = $this->boxes[$key]['file'];
1012
                }
1013
1014
                //$note  = $this->boxes[$key][2];
1015
1016
                // TODO If the box is also included by another module and the other module is still on, we should not remove it.
1017
                // For the moment, we manage this with hard coded exception
1018
                //print "Remove box ".$file.'<br>';
1019
                if ($file == 'box_graph_product_distribution.php') {
1020
                    if (isModEnabled("product") || isModEnabled("service")) {
1021
                        dol_syslog("We discard deleting module " . $file . " because another module still active requires it.");
1022
                        continue;
1023
                    }
1024
                }
1025
1026
                if ($this->db->type == 'sqlite3') {
1027
                    // sqlite doesn't support "USING" syntax.
1028
                    // TODO: remove this dependency.
1029
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes ";
1030
                    $sql .= "WHERE " . MAIN_DB_PREFIX . "boxes.box_id IN (";
1031
                    $sql .= "SELECT " . MAIN_DB_PREFIX . "boxes_def.rowid ";
1032
                    $sql .= "FROM " . MAIN_DB_PREFIX . "boxes_def ";
1033
                    $sql .= "WHERE " . MAIN_DB_PREFIX . "boxes_def.file = '" . $this->db->escape($file) . "') ";
1034
                    $sql .= "AND " . MAIN_DB_PREFIX . "boxes.entity = " . $conf->entity;
1035
                } else {
1036
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes";
1037
                    $sql .= " USING " . MAIN_DB_PREFIX . "boxes, " . MAIN_DB_PREFIX . "boxes_def";
1038
                    $sql .= " WHERE " . MAIN_DB_PREFIX . "boxes.box_id = " . MAIN_DB_PREFIX . "boxes_def.rowid";
1039
                    $sql .= " AND " . MAIN_DB_PREFIX . "boxes_def.file = '" . $this->db->escape($file) . "'";
1040
                    $sql .= " AND " . MAIN_DB_PREFIX . "boxes.entity = " . $conf->entity;
1041
                }
1042
1043
                dol_syslog(get_only_class($this) . "::delete_boxes", LOG_DEBUG);
1044
                $resql = $this->db->query($sql);
1045
                if (!$resql) {
1046
                    $this->error = $this->db->lasterror();
1047
                    $err++;
1048
                }
1049
1050
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes_def";
1051
                $sql .= " WHERE file = '" . $this->db->escape($file) . "'";
1052
                $sql .= " AND entity = " . $conf->entity;     // Do not use getEntity here, we want to delete only in current company
1053
1054
                dol_syslog(get_only_class($this) . "::delete_boxes", LOG_DEBUG);
1055
                $resql = $this->db->query($sql);
1056
                if (!$resql) {
1057
                    $this->error = $this->db->lasterror();
1058
                    $err++;
1059
                }
1060
            }
1061
        }
1062
1063
        return $err;
1064
    }
1065
1066
    /**
1067
     * Removes boxes
1068
     *
1069
     * @return int Error count (0 if OK)
1070
     */
1071
    public function delete_cronjobs()
1072
    {
1073
        // phpcs:enable
1074
        global $conf;
1075
1076
        $err = 0;
1077
1078
        if (is_array($this->cronjobs)) {
1079
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "cronjob";
1080
            $sql .= " WHERE module_name = '" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "'";
1081
            $sql .= " AND entity = " . $conf->entity;
1082
            $sql .= " AND test = '1'"; // We delete on lines that are not set with a complete test that is '$conf->module->enabled' so when module is disabled, the cron is also removed.
1083
            // For crons declared with a '$conf->module->enabled', there is no need to delete the line, so we don't loose setup if we reenable module.
1084
1085
            dol_syslog(get_only_class($this) . "::delete_cronjobs", LOG_DEBUG);
1086
            $resql = $this->db->query($sql);
1087
            if (!$resql) {
1088
                $this->error = $this->db->lasterror();
1089
                $err++;
1090
            }
1091
        }
1092
1093
        return $err;
1094
    }
1095
1096
    /**
1097
     * Removes access rights
1098
     *
1099
     * @return int                     Error count (0 if OK)
1100
     */
1101
    public function delete_permissions()
1102
    {
1103
        // phpcs:enable
1104
        global $conf;
1105
1106
        $err = 0;
1107
1108
        $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1109
1110
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "rights_def";
1111
        $sql .= " WHERE (module = '" . $this->db->escape($module) . "' OR module_origin = '" . $this->db->escape($module) . "')";
1112
1113
        // Delete all entities if core module
1114
        if (empty($this->core_enabled)) {
1115
            $sql .= " AND entity = " . ((int)$conf->entity);
1116
        }
1117
1118
        dol_syslog(get_only_class($this) . "::delete_permissions", LOG_DEBUG);
1119
        if (!$this->db->query($sql)) {
1120
            $this->error = $this->db->lasterror();
1121
            $err++;
1122
        }
1123
1124
        return $err;
1125
    }
1126
1127
    /**
1128
     * Removes menu entries
1129
     *
1130
     * @return int Error count (0 if OK)
1131
     */
1132
    public function delete_menus()
1133
    {
1134
        // phpcs:enable
1135
        global $conf;
1136
1137
        $err = 0;
1138
1139
        //$module=strtolower($this->name);        TODO When right_class will be same than module name
1140
        $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1141
1142
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "menu";
1143
        $sql .= " WHERE module = '" . $this->db->escape($module) . "'";
1144
        $sql .= " AND menu_handler = 'all'";    // We delete only lines that were added manually or by the module activation. We keep entry added by menuhandler like 'auguria'
1145
        $sql .= " AND entity IN (0, " . $conf->entity . ")";
1146
1147
        dol_syslog(get_only_class($this) . "::delete_menus", LOG_DEBUG);
1148
        $resql = $this->db->query($sql);
1149
        if (!$resql) {
1150
            $this->error = $this->db->lasterror();
1151
            $err++;
1152
        }
1153
1154
        return $err;
1155
    }
1156
1157
    /**
1158
     * Removes directories
1159
     *
1160
     * @return int Error count (0 if OK)
1161
     */
1162
    public function delete_dirs()
1163
    {
1164
        // phpcs:enable
1165
        global $conf;
1166
1167
        $err = 0;
1168
1169
        dol_syslog(get_only_class($this) . "::delete_dirs", LOG_DEBUG);
1170
        if (!Constant::deleteByNameLike($this->db->escape($this->const_name) . '_DIR_', $conf->entity)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Dolibarr\Core\Model\Cons...'_DIR_', $conf->entity) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1171
            $this->error = $this->db->lasterror();
1172
            $err++;
1173
        }
1174
1175
        return $err;
1176
    }
1177
1178
    /**
1179
     * Gives the long description of a module. First check README-la_LA.md then README.md
1180
     * If no markdown files found, it returns translated value of the key ->descriptionlong.
1181
     *
1182
     * @return string     Long description of a module from README.md of from property.
1183
     */
1184
    public function getDescLong()
1185
    {
1186
        global $langs;
1187
        $langs->load("admin");
1188
1189
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1190
        include_once DOL_DOCUMENT_ROOT . '/core/lib/geturl.lib.php';
1191
1192
        $content = '';
1193
        $pathoffile = $this->getDescLongReadmeFound();
1194
1195
        if ($pathoffile) {     // Mostly for external modules
1196
            $content = file_get_contents($pathoffile, false, null, 0, 1024 * 1024); // Max size loaded 1Mb
1197
1198
            if ((float)DOL_VERSION >= 6.0) {
1199
                @include_once DOL_DOCUMENT_ROOT . '/core/lib/parsemd.lib.php';
1200
1201
                $content = dolMd2Html(
1202
                    $content,
1203
                    'parsedown',
1204
                    array(
1205
                        'doc/' => dol_buildpath(strtolower($this->name) . '/doc/', 1),
1206
                        'img/' => dol_buildpath(strtolower($this->name) . '/img/', 1),
1207
                        'images/' => dol_buildpath(strtolower($this->name) . '/images/', 1),
1208
                    )
1209
                );
1210
1211
                $content = preg_replace('/<a href="/', '<a target="_blank" rel="noopener noreferrer" href="', $content);
1212
            } else {
1213
                $content = nl2br($content);
1214
            }
1215
        } else {
1216
            // Mostly for internal modules
1217
            if (!empty($this->descriptionlong)) {
1218
                if (is_array($this->langfiles)) {
1219
                    foreach ($this->langfiles as $val) {
1220
                        if ($val) {
1221
                            $langs->load($val);
1222
                        }
1223
                    }
1224
                }
1225
1226
                $content = $langs->transnoentitiesnoconv($this->descriptionlong);
1227
            }
1228
        }
1229
1230
        return $content;
1231
    }
1232
1233
    /**
1234
     * Return path of file if a README file was found.
1235
     *
1236
     * @return string      Path of file if a README file was found.
1237
     */
1238
    public function getDescLongReadmeFound()
1239
    {
1240
        global $langs;
1241
1242
        $filefound = false;
1243
1244
        // Define path to file README.md.
1245
        // First check README-la_LA.md then README-la.md then README.md
1246
        $pathoffile = dol_buildpath(strtolower($this->name) . '/README-' . $langs->defaultlang . '.md', 0);
1247
        if (dol_is_file($pathoffile)) {
1248
            $filefound = true;
1249
        }
1250
        if (!$filefound) {
1251
            $tmp = explode('_', $langs->defaultlang);
1252
            $pathoffile = dol_buildpath(strtolower($this->name) . '/README-' . $tmp[0] . '.md', 0);
1253
            if (dol_is_file($pathoffile)) {
1254
                $filefound = true;
1255
            }
1256
        }
1257
        if (!$filefound) {
1258
            $pathoffile = dol_buildpath(strtolower($this->name) . '/README.md', 0);
1259
            if (dol_is_file($pathoffile)) {
1260
                $filefound = true;
1261
            }
1262
        }
1263
1264
        return ($filefound ? $pathoffile : '');
1265
    }
1266
1267
    /**
1268
     * Gives the changelog. First check ChangeLog-la_LA.md then ChangeLog.md
1269
     *
1270
     * @return string  Content of ChangeLog
1271
     */
1272
    public function getChangeLog()
1273
    {
1274
        global $langs;
1275
        $langs->load("admin");
1276
1277
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1278
        include_once DOL_DOCUMENT_ROOT . '/core/lib/geturl.lib.php';
1279
1280
        $filefound = false;
1281
1282
        // Define path to file README.md.
1283
        // First check ChangeLog-la_LA.md then ChangeLog.md
1284
        $pathoffile = dol_buildpath(strtolower($this->name) . '/ChangeLog-' . $langs->defaultlang . '.md', 0);
1285
        if (dol_is_file($pathoffile)) {
1286
            $filefound = true;
1287
        }
1288
        if (!$filefound) {
1289
            $pathoffile = dol_buildpath(strtolower($this->name) . '/ChangeLog.md', 0);
1290
            if (dol_is_file($pathoffile)) {
1291
                $filefound = true;
1292
            }
1293
        }
1294
1295
        if ($filefound) {     // Mostly for external modules
1296
            $content = file_get_contents($pathoffile);
1297
1298
            if ((float)DOL_VERSION >= 6.0) {
1299
                @include_once DOL_DOCUMENT_ROOT . '/core/lib/parsemd.lib.php';
1300
1301
                $content = dolMd2Html($content, 'parsedown', array('doc/' => dol_buildpath(strtolower($this->name) . '/doc/', 1)));
1302
            } else {
1303
                $content = nl2br($content);
1304
            }
1305
        } else {
1306
            $content = '';
1307
        }
1308
1309
        return $content;
1310
    }
1311
1312
1313
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1314
1315
    /**
1316
     * Gives the publisher name
1317
     *
1318
     * @return string  Publisher name
1319
     */
1320
    public function getPublisher()
1321
    {
1322
        return $this->editor_name;
1323
    }
1324
1325
1326
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1327
1328
    /**
1329
     * Gives the publisher url
1330
     *
1331
     * @return string  Publisher url
1332
     */
1333
    public function getPublisherUrl()
1334
    {
1335
        return $this->editor_url;
1336
    }
1337
1338
1339
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps,PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1340
1341
    /**
1342
     * Gives the module position
1343
     *
1344
     * @return string   Module position (an external module should never return a value lower than 100000. 1-100000 are reserved for core)
1345
     */
1346
    public function getModulePosition()
1347
    {
1348
        if (in_array($this->version, array('dolibarr', 'experimental', 'development'))) {   // core module
1349
            return $this->module_position;
1350
        } else {                                                                            // external module
1351
            if ($this->module_position >= 100000) {
1352
                return $this->module_position;
1353
            } else {
1354
                $position = intval($this->module_position) + 100000;
1355
                return strval($position);
1356
            }
1357
        }
1358
    }
1359
1360
1361
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1362
1363
    /**
1364
     * Gives module related language files list
1365
     *
1366
     * @return string[]    Language files list
1367
     */
1368
    public function getLangFilesArray()
1369
    {
1370
        return $this->langfiles;
1371
    }
1372
1373
1374
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1375
1376
    /**
1377
     * Gives translated label of an export dataset
1378
     *
1379
     * @param int $r Dataset index
1380
     *
1381
     * @return string       Translated databaset label
1382
     */
1383
    public function getExportDatasetLabel($r)
1384
    {
1385
        global $langs;
1386
1387
        $langstring = "ExportDataset_" . $this->export_code[$r];
1388
        if ($langs->trans($langstring) == $langstring) {
1389
            // Translation not found
1390
            return $langs->trans($this->export_label[$r]);
1391
        } else {
1392
            // Translation found
1393
            return $langs->trans($langstring);
1394
        }
1395
    }
1396
1397
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1398
1399
    /**
1400
     * Gives translated label of an import dataset
1401
     *
1402
     * @param int $r Dataset index
1403
     *
1404
     * @return string      Translated dataset label
1405
     */
1406
    public function getImportDatasetLabel($r)
1407
    {
1408
        global $langs;
1409
1410
        $langstring = "ImportDataset_" . $this->import_code[$r];
1411
        //print "x".$langstring;
1412
        if ($langs->trans($langstring) == $langstring) {
1413
            // Translation not found
1414
            return $langs->transnoentitiesnoconv($this->import_label[$r]);
1415
        } else {
1416
            // Translation found
1417
            return $langs->transnoentitiesnoconv($langstring);
1418
        }
1419
    }
1420
1421
1422
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1423
1424
    /**
1425
     * Gives the last date of activation
1426
     *
1427
     * @return  int|string          Date of last activation or '' if module was never activated
1428
     */
1429
    public function getLastActivationDate()
1430
    {
1431
        global $conf;
1432
1433
        $err = 0;
1434
1435
        $result = Constant::selectByName($this->db->escape($this->const_name), $conf->entity);
1436
        dol_syslog(get_only_class($this) . "::getLastActiveDate", LOG_DEBUG);
1437
1438
        if (!$result === null) {
1439
            $err++;
1440
        } else {
1441
            if ($result) {
1442
                return $this->db->jdate($result->tms);
1443
            }
1444
        }
1445
1446
        return '';
1447
    }
1448
1449
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1450
1451
    /**
1452
     * Gives the last author of activation
1453
     *
1454
     * @return array       Array array('authorid'=>Id of last activation user, 'lastactivationdate'=>Date of last activation)
1455
     */
1456
    public function getLastActivationInfo()
1457
    {
1458
        global $conf;
1459
1460
        $err = 0;
1461
1462
        $result = Constant::selectByName($this->const_name, $conf->entity);
1463
1464
        dol_syslog(get_only_class($this) . "::getLastActiveDate", LOG_DEBUG);
1465
        if (!$result === null) {
1466
            $err++;
1467
        } else {
1468
            $tmp = array();
1469
            if ($result->note) {
1470
                $tmp = json_decode($result->note, true);
1471
            }
1472
            return array(
1473
                'authorid' => empty($tmp['authorid']) ? '' : $tmp['authorid'],
1474
                'ip' => empty($tmp['ip']) ? '' : $tmp['ip'],
1475
                'lastactivationdate' => $this->db->jdate($result->tms),
1476
                'lastactivationversion' => (!empty($tmp['lastactivationversion']) ? $tmp['lastactivationversion'] : 'unknown'),
1477
            );
1478
        }
1479
1480
        return array();
1481
    }
1482
1483
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1484
1485
    /**
1486
     * Function called when module is enabled.
1487
     * The init function adds tabs, constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
1488
     * It also creates data directories
1489
     *
1490
     * @param string $options Options when enabling module ('', 'newboxdefonly', 'noboxes', 'menuonly')
1491
     *                         'noboxes' = Do not insert boxes 'newboxdefonly' = For boxes, insert def of boxes only and not boxes activation
1492
     * @return int                1 if OK, 0 if KO
1493
     */
1494
    public function init($options = '')
1495
    {
1496
        return $this->_init(array(), $options);
1497
    }
1498
1499
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1500
1501
    /**
1502
     * Enables a module.
1503
     * Inserts all information into database.
1504
     *
1505
     * @param array $array_sql SQL requests to be executed when enabling module
1506
     * @param string $options String with options when disabling module:
1507
     *                              - 'noboxes' = Do all actions but do not insert boxes
1508
     *                              - 'newboxdefonly' = Do all actions but for boxes, insert def of boxes only and not boxes activation
1509
     * @return int                  1 if OK, 0 if KO
1510
     */
1511
    protected function _init($array_sql, $options = '')
1512
    {
1513
        // phpcs:enable
1514
        global $conf;
1515
1516
        $err = 0;
1517
1518
        $this->db->begin();
1519
1520
        // Insert activation module constant
1521
        if (!$err) {
1522
            $err += $this->_active();
1523
        }
1524
1525
        // Insert new pages for tabs (into llx_const)
1526
        if (!$err) {
1527
            $err += $this->insert_tabs();
1528
        }
1529
1530
        // Insert activation of module's parts. Copy website templates into doctemplates.
1531
        if (!$err) {
1532
            $err += $this->insert_module_parts();
1533
        }
1534
1535
        // Insert constant defined by modules (into llx_const)
1536
        if (!$err && !preg_match('/newboxdefonly/', $options)) {
1537
            $err += $this->insert_const(); // Test on newboxdefonly to avoid to erase value during upgrade
1538
        }
1539
1540
        // Insert boxes def (into llx_boxes_def) and boxes setup (into llx_boxes)
1541
        if (!$err && !preg_match('/noboxes/', $options)) {
1542
            $err += $this->insert_boxes($options);
1543
        }
1544
1545
        // Insert cron job entries (entry in llx_cronjobs)
1546
        if (!$err) {
1547
            $err += $this->insert_cronjobs();
1548
        }
1549
1550
        // Insert permission definitions of module into llx_rights_def. If user is admin, grant this permission to user.
1551
        if (!$err) {
1552
            $err += $this->insert_permissions(1, null, 1);
1553
        }
1554
1555
        // Insert specific menus entries into database
1556
        if (!$err) {
1557
            $err += $this->insert_menus();
1558
        }
1559
1560
        // Create module's directories
1561
        if (!$err) {
1562
            $err += $this->create_dirs();
1563
        }
1564
1565
        // Execute addons requests
1566
        $num = count($array_sql);
1567
        for ($i = 0; $i < $num; $i++) {
1568
            if (!$err) {
1569
                $val = $array_sql[$i];
1570
                $sql = $val;
1571
                $ignoreerror = 0;
1572
                if (is_array($val)) {
1573
                    $sql = $val['sql'];
1574
                    $ignoreerror = $val['ignoreerror'];
1575
                }
1576
                // Add current entity id
1577
                $ssql = $sql;
1578
                $sql = str_replace('__ENTITY__', (string)$conf->entity, $sql);
1579
1580
                dol_syslog(get_only_class($this) . "::_init ignoreerror=" . $ignoreerror, LOG_DEBUG);
1581
                $result = $this->db->query($sql, $ignoreerror);
1582
                if (!$result) {
1583
                    if (!$ignoreerror) {
1584
                        $this->error = $this->db->lasterror();
1585
                        $err++;
1586
                    } else {
1587
                        dol_syslog(get_only_class($this) . "::_init Warning " . $this->db->lasterror(), LOG_WARNING);
1588
                    }
1589
                }
1590
            }
1591
        }
1592
1593
        // Return code
1594
        if (!$err) {
1595
            $this->db->commit();
1596
1597
            $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
1598
            // two exceptions to handle
1599
            if ($moduleNameInConf === 'propale') {
1600
                $moduleNameInConf = 'propal';
1601
            } elseif ($moduleNameInConf === 'supplierproposal') {
1602
                $moduleNameInConf = 'supplier_proposal';
1603
            }
1604
1605
            $conf->modules[$moduleNameInConf] = $moduleNameInConf; // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
1606
1607
            return 1;
1608
        } else {
1609
            $this->db->rollback();
1610
            return 0;
1611
        }
1612
    }
1613
1614
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1615
1616
    /**
1617
     * Insert constants for module activation
1618
     *
1619
     * @return int Error count (0 if OK)
1620
     */
1621
    protected function _active()
1622
    {
1623
        // phpcs:enable
1624
        global $conf, $user;
1625
1626
        $err = 0;
1627
1628
        // Common module
1629
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1630
1631
        dol_syslog(get_only_class($this) . "::_active delete activation constant", LOG_DEBUG);
1632
        if (!Constant::deleteByName($this->db->escape($this->const_name), $entity)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Dolibarr\Core\Model\Cons...->const_name), $entity) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1633
//            $err++;
1634
        }
1635
1636
        $note = json_encode(
1637
            array(
1638
                'authorid' => (is_object($user) ? $user->id : 0),
1639
                'ip' => (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']),
1640
                'lastactivationversion' => $this->version,
1641
            )
1642
        );
1643
1644
        dol_syslog(get_only_class($this) . "::_active insert activation constant", LOG_DEBUG);
1645
        if (!Constant::insert($this->const_name, 1, null, $note, 0, $entity)) {
1646
            $err++;
1647
        }
1648
1649
        return $err;
1650
    }
1651
1652
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1653
1654
    /**
1655
     * Adds tabs
1656
     *
1657
     * @return int  Error count (0 if ok)
1658
     */
1659
    public function insert_tabs()
1660
    {
1661
        // phpcs:enable
1662
        global $conf;
1663
1664
        $err = 0;
1665
1666
        if (!empty($this->tabs)) {
1667
            dol_syslog(get_only_class($this) . "::insert_tabs", LOG_DEBUG);
1668
1669
            $i = 0;
1670
            foreach ($this->tabs as $key => $value) {
1671
                if (is_array($value) && count($value) == 0) {
1672
                    continue; // Discard empty arrays
1673
                }
1674
1675
                $entity = $conf->entity;
1676
                $newvalue = $value;
1677
1678
                if (is_array($value)) {
1679
                    $newvalue = $value['data'];
1680
                    if (isset($value['entity'])) {
1681
                        $entity = $value['entity'];
1682
                    }
1683
                }
1684
1685
                if ($newvalue) {
1686
                    $res = Constant::insert(
1687
                        $this->const_name . "_TABS_" . $i,
1688
                        $newvalue,
1689
                        'chaine',
1690
                        '',
1691
                        0,
1692
                        $entity,
1693
                    );
1694
1695
                    if (!$res) {
1696
                        dol_syslog($this->db->lasterror(), LOG_ERR);
1697
                        if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1698
                            $this->error = $this->db->lasterror();
1699
                            $this->errors[] = $this->db->lasterror();
1700
                            $err++;
1701
                            break;
1702
                        }
1703
                    }
1704
                }
1705
                $i++;
1706
            }
1707
        }
1708
        return $err;
1709
    }
1710
1711
1712
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1713
1714
    /**
1715
     * Save configuration for generic features.
1716
     * This also generate website templates if the module provide some.
1717
     *
1718
     * @return int Error count (0 if OK)
1719
     */
1720
    public function insert_module_parts()
1721
    {
1722
        // phpcs:enable
1723
        global $conf, $langs;
1724
1725
        $error = 0;
1726
1727
        if (is_array($this->module_parts)) {
1728
            if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
1729
                $this->module_parts['icon'] = $this->picto;
1730
            }
1731
1732
            foreach ($this->module_parts as $key => $value) {
1733
                if (is_array($value) && count($value) == 0) {
1734
                    continue; // Discard empty arrays
1735
                }
1736
1737
                // If module brings website templates, we must generate the zip like we do whenenabling the website module
1738
                if ($key == 'websitetemplates' && $value == 1) {
1739
                    $srcroot = dol_buildpath('/' . strtolower($this->name) . '/doctemplates/websites');
1740
1741
                    // Copy templates in dir format (recommended) into zip file
1742
                    $docs = dol_dir_list($srcroot, 'directories', 0, 'website_.*$');
1743
                    foreach ($docs as $cursorfile) {
1744
                        $src = $srcroot . '/' . $cursorfile['name'];
1745
                        $dest = DOL_DATA_ROOT . '/doctemplates/websites/' . $cursorfile['name'];
1746
1747
                        dol_delete_file($dest . '.zip');
1748
1749
                        // Compress it
1750
                        global $errormsg;
1751
                        $errormsg = '';
1752
                        $result = dol_compress_dir($src, $dest . '.zip', 'zip');
1753
                        if ($result < 0) {
1754
                            $error++;
1755
                            $this->error = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
1756
                            $this->errors[] = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
1757
                        }
1758
                    }
1759
1760
                    // Copy also the preview website_xxx.jpg file
1761
                    $docs = dol_dir_list($srcroot, 'files', 0, 'website_.*\.jpg$');
1762
                    foreach ($docs as $cursorfile) {
1763
                        $src = $srcroot . '/' . $cursorfile['name'];
1764
                        $dest = DOL_DATA_ROOT . '/doctemplates/websites/' . $cursorfile['name'];
1765
1766
                        dol_copy($src, $dest);
1767
                    }
1768
                }
1769
1770
                $entity = $conf->entity; // Reset the current entity
1771
                $newvalue = $value;
1772
1773
                // Serialize array parameters
1774
                if (is_array($value)) {
1775
                    // Can defined other parameters
1776
                    // Example when $key='hooks', then $value is an array('data'=>array('hookcontext1','hookcontext2'), 'entity'=>X)
1777
                    if (isset($value['data']) && is_array($value['data'])) {
1778
                        $newvalue = json_encode($value['data']);
1779
                        if (isset($value['entity'])) {
1780
                            $entity = $value['entity'];
1781
                        }
1782
                    } elseif (isset($value['data']) && !is_array($value['data'])) {
1783
                        $newvalue = $value['data'];
1784
                        if (isset($value['entity'])) {
1785
                            $entity = $value['entity'];
1786
                        }
1787
                    } else { // when hook is declared with syntax 'hook'=>array('hookcontext1','hookcontext2',...)
1788
                        $newvalue = json_encode($value);
1789
                    }
1790
                }
1791
1792
                if (!empty($newvalue)) {
1793
                    $res = Constant::insert(
1794
                        $this->const_name . "_" . strtoupper($key),
1795
                        $newvalue,
1796
                        'chaine',
1797
                        '',
1798
                        0,
1799
                        $entity
1800
                    );
1801
                    dol_syslog(get_only_class($this) . "::insert_module_parts for key=" . $this->const_name . "_" . strtoupper($key), LOG_DEBUG);
1802
                    if (!$res) {
1803
                        if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1804
                            $error++;
1805
                            $this->error = $this->db->lasterror();
1806
                        } else {
1807
                            dol_syslog(get_only_class($this) . "::insert_module_parts for " . $this->const_name . "_" . strtoupper($key) . " Record already exists.", LOG_WARNING);
1808
                        }
1809
                    }
1810
                }
1811
            }
1812
        }
1813
        return $error;
1814
    }
1815
1816
1817
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1818
1819
    /**
1820
     * Adds constants
1821
     *
1822
     * @return int Error count (0 if OK)
1823
     */
1824
    public function insert_const()
1825
    {
1826
        // phpcs:enable
1827
        global $conf;
1828
1829
        $err = 0;
1830
1831
        if (empty($this->const)) {
1832
            return 0;
1833
        }
1834
1835
        dol_syslog(__METHOD__, LOG_DEBUG);
1836
1837
        foreach ($this->const as $key => $value) {
1838
            $name = $this->const[$key][0];
1839
            $type = $this->const[$key][1];
1840
            $val = $this->const[$key][2];
1841
            $note = isset($this->const[$key][3]) ? $this->const[$key][3] : '';
1842
            $visible = isset($this->const[$key][4]) ? $this->const[$key][4] : 0;
1843
            $entity = (!empty($this->const[$key][5]) && $this->const[$key][5] != 'current') ? 0 : $conf->entity;
1844
1845
            // Clean
1846
            if (empty($visible)) {
1847
                $visible = '0';
1848
            }
1849
            if (empty($val) && $val != '0') {
1850
                $val = '';
1851
            }
1852
1853
            $res = Constant::updateOrCreate([
1854
                'name' => $name,
1855
                'value' => $val ?? '',
1856
                'type' => $type,
1857
                'note' => $note ?? '',
1858
                'visible' => $visible,
1859
                'entity' => $entity,
1860
            ]);
1861
1862
            if ($res === null) {
1863
                $this->error = $this->db->lasterror();
1864
                $err++;
1865
                return $err;
1866
            }
1867
1868
            dol_syslog(__METHOD__ . " constant '" . $name . "' already exists", LOG_DEBUG);
1869
1870
        }
1871
        return $err;
1872
    }
1873
1874
1875
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1876
1877
    /**
1878
     * Adds boxes
1879
     *
1880
     * @param string $option Options when disabling module ('newboxdefonly'=insert only boxes definition)
1881
     *
1882
     * @return int             Error count (0 if OK)
1883
     */
1884
    public function insert_boxes($option = '')
1885
    {
1886
        // phpcs:enable
1887
1888
        global $conf;
1889
1890
        $err = 0;
1891
1892
        if (is_array($this->boxes)) {
1893
            dol_syslog(get_only_class($this) . "::insert_boxes", LOG_DEBUG);
1894
1895
            $pos_name = InfoBox::getListOfPagesForBoxes();
1896
1897
            foreach ($this->boxes as $key => $value) {
1898
                $file = isset($this->boxes[$key]['file']) ? $this->boxes[$key]['file'] : '';
1899
                $note = isset($this->boxes[$key]['note']) ? $this->boxes[$key]['note'] : '';
1900
                $enabledbydefaulton = isset($this->boxes[$key]['enabledbydefaulton']) ? $this->boxes[$key]['enabledbydefaulton'] : 'Home';
1901
1902
                if (empty($file)) {
1903
                    $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1904
                }
1905
                if (empty($note)) {
1906
                    $note = isset($this->boxes[$key][2]) ? $this->boxes[$key][2] : ''; // For backward compatibility
1907
                }
1908
1909
                // Search if boxes def already present
1910
                $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "boxes_def";
1911
                $sql .= " WHERE file = '" . $this->db->escape($file) . "'";
1912
                $sql .= " AND entity = " . $conf->entity;
1913
                if ($note) {
1914
                    $sql .= " AND note ='" . $this->db->escape($note) . "'";
1915
                }
1916
1917
                $result = $this->db->query($sql);
1918
                if ($result) {
1919
                    $obj = $this->db->fetch_object($result);
1920
                    if ($obj->nb == 0) {
1921
                        $this->db->begin();
1922
1923
                        if (!$err) {
1924
                            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "boxes_def (file, entity, note)";
1925
                            $sql .= " VALUES ('" . $this->db->escape($file) . "', ";
1926
                            $sql .= $conf->entity . ", ";
1927
                            $sql .= $note ? "'" . $this->db->escape($note) . "'" : "null";
1928
                            $sql .= ")";
1929
1930
                            dol_syslog(get_only_class($this) . "::insert_boxes", LOG_DEBUG);
1931
                            $resql = $this->db->query($sql);
1932
                            if (!$resql) {
1933
                                $err++;
1934
                            }
1935
                        }
1936
                        if (!$err && !preg_match('/newboxdefonly/', $option)) {
1937
                            $lastid = $this->db->last_insert_id(MAIN_DB_PREFIX . "boxes_def", "rowid");
1938
1939
                            foreach ($pos_name as $key2 => $val2) {
1940
                                //print 'key2='.$key2.'-val2='.$val2."<br>\n";
1941
                                if ($enabledbydefaulton && $val2 != $enabledbydefaulton) {
1942
                                    continue; // Not enabled by default onto this page.
1943
                                }
1944
1945
                                $sql = "INSERT INTO " . MAIN_DB_PREFIX . "boxes (box_id, position, box_order, fk_user, entity)";
1946
                                $sql .= " VALUES (" . ((int)$lastid) . ", " . ((int)$key2) . ", '0', 0, " . ((int)$conf->entity) . ")";
1947
1948
                                dol_syslog(get_only_class($this) . "::insert_boxes onto page " . $key2 . "=" . $val2, LOG_DEBUG);
1949
                                $resql = $this->db->query($sql);
1950
                                if (!$resql) {
1951
                                    $err++;
1952
                                }
1953
                            }
1954
                        }
1955
1956
                        if (!$err) {
1957
                            $this->db->commit();
1958
                        } else {
1959
                            $this->error = $this->db->lasterror();
1960
                            $this->db->rollback();
1961
                        }
1962
                    }
1963
                    // else box already registered into database
1964
                } else {
1965
                    $this->error = $this->db->lasterror();
1966
                    $err++;
1967
                }
1968
            }
1969
        }
1970
1971
        return $err;
1972
    }
1973
1974
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1975
1976
    /**
1977
     * Adds cronjobs
1978
     *
1979
     * @return int             Error count (0 if OK)
1980
     */
1981
    public function insert_cronjobs()
1982
    {
1983
        // phpcs:enable
1984
1985
        global $conf;
1986
1987
        $err = 0;
1988
1989
        if (is_array($this->cronjobs)) {
1990
            dol_syslog(get_only_class($this) . "::insert_cronjobs", LOG_DEBUG);
1991
1992
            foreach ($this->cronjobs as $key => $value) {
1993
                $entity = isset($this->cronjobs[$key]['entity']) ? $this->cronjobs[$key]['entity'] : $conf->entity;
1994
                $label = isset($this->cronjobs[$key]['label']) ? $this->cronjobs[$key]['label'] : '';
1995
                $jobtype = isset($this->cronjobs[$key]['jobtype']) ? $this->cronjobs[$key]['jobtype'] : '';
1996
                $class = isset($this->cronjobs[$key]['class']) ? $this->cronjobs[$key]['class'] : '';
1997
                $objectname = isset($this->cronjobs[$key]['objectname']) ? $this->cronjobs[$key]['objectname'] : '';
1998
                $method = isset($this->cronjobs[$key]['method']) ? $this->cronjobs[$key]['method'] : '';
1999
                $command = isset($this->cronjobs[$key]['command']) ? $this->cronjobs[$key]['command'] : '';
2000
                $parameters = isset($this->cronjobs[$key]['parameters']) ? $this->cronjobs[$key]['parameters'] : '';
2001
                $comment = isset($this->cronjobs[$key]['comment']) ? $this->cronjobs[$key]['comment'] : '';
2002
                $frequency = isset($this->cronjobs[$key]['frequency']) ? $this->cronjobs[$key]['frequency'] : '';
2003
                $unitfrequency = isset($this->cronjobs[$key]['unitfrequency']) ? $this->cronjobs[$key]['unitfrequency'] : '';
2004
                $priority = isset($this->cronjobs[$key]['priority']) ? $this->cronjobs[$key]['priority'] : '';
2005
                $datestart = isset($this->cronjobs[$key]['datestart']) ? $this->cronjobs[$key]['datestart'] : '';
2006
                $dateend = isset($this->cronjobs[$key]['dateend']) ? $this->cronjobs[$key]['dateend'] : '';
2007
                $status = isset($this->cronjobs[$key]['status']) ? $this->cronjobs[$key]['status'] : '';
2008
                $test = isset($this->cronjobs[$key]['test']) ? $this->cronjobs[$key]['test'] : ''; // Line must be enabled or not (so visible or not)
2009
2010
                // Search if cron entry already present
2011
                $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "cronjob";
2012
                //$sql .= " WHERE module_name = '".$this->db->escape(empty($this->rights_class) ?strtolower($this->name) : $this->rights_class)."'";
2013
                $sql .= " WHERE label = '" . $this->db->escape($label) . "'";
2014
                /*if ($class) {
2015
                    $sql .= " AND classesname = '".$this->db->escape($class)."'";
2016
                }
2017
                if ($objectname) {
2018
                    $sql .= " AND objectname = '".$this->db->escape($objectname)."'";
2019
                }
2020
                if ($method) {
2021
                    $sql .= " AND methodename = '".$this->db->escape($method)."'";
2022
                }
2023
                if ($command) {
2024
                    $sql .= " AND command = '".$this->db->escape($command)."'";
2025
                }
2026
                if ($parameters) {
2027
                    $sql .= " AND params = '".$this->db->escape($parameters)."'";
2028
                }*/
2029
                $sql .= " AND entity = " . ((int)$entity); // Must be exact entity
2030
2031
                $now = dol_now();
2032
2033
                $result = $this->db->query($sql);
2034
                if ($result) {
2035
                    $obj = $this->db->fetch_object($result);
2036
                    if ($obj->nb == 0) {
2037
                        $this->db->begin();
2038
2039
                        if (!$err) {
2040
                            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "cronjob (module_name, datec, datestart, dateend, label, jobtype, classesname, objectname, methodename, command, params, note,";
2041
                            if (is_int($frequency)) {
2042
                                $sql .= ' frequency,';
2043
                            }
2044
                            if (is_int($unitfrequency)) {
2045
                                $sql .= ' unitfrequency,';
2046
                            }
2047
                            if (is_int($priority)) {
2048
                                $sql .= ' priority,';
2049
                            }
2050
                            if (is_int($status)) {
2051
                                $sql .= ' status,';
2052
                            }
2053
                            $sql .= " entity, test)";
2054
                            $sql .= " VALUES (";
2055
                            $sql .= "'" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "', ";
2056
                            $sql .= "'" . $this->db->idate($now) . "', ";
2057
                            $sql .= ($datestart ? "'" . $this->db->idate($datestart) . "'" : "'" . $this->db->idate($now) . "'") . ", ";
2058
                            $sql .= ($dateend ? "'" . $this->db->idate($dateend) . "'" : "NULL") . ", ";
2059
                            $sql .= "'" . $this->db->escape($label) . "', ";
2060
                            $sql .= "'" . $this->db->escape($jobtype) . "', ";
2061
                            $sql .= ($class ? "'" . $this->db->escape($class) . "'" : "null") . ",";
2062
                            $sql .= ($objectname ? "'" . $this->db->escape($objectname) . "'" : "null") . ",";
2063
                            $sql .= ($method ? "'" . $this->db->escape($method) . "'" : "null") . ",";
2064
                            $sql .= ($command ? "'" . $this->db->escape($command) . "'" : "null") . ",";
2065
                            $sql .= ($parameters ? "'" . $this->db->escape($parameters) . "'" : "null") . ",";
2066
                            $sql .= ($comment ? "'" . $this->db->escape($comment) . "'" : "null") . ",";
2067
                            if (is_int($frequency)) {
2068
                                $sql .= "'" . $this->db->escape($frequency) . "', ";
2069
                            }
2070
                            if (is_int($unitfrequency)) {
2071
                                $sql .= "'" . $this->db->escape($unitfrequency) . "', ";
2072
                            }
2073
                            if (is_int($priority)) {
2074
                                $sql .= "'" . $this->db->escape($priority) . "', ";
2075
                            }
2076
                            if (is_int($status)) {
2077
                                $sql .= ((int)$status) . ", ";
2078
                            }
2079
                            $sql .= $entity . ",";
2080
                            $sql .= "'" . $this->db->escape($test) . "'";
2081
                            $sql .= ")";
2082
2083
                            $resql = $this->db->query($sql);
2084
                            if (!$resql) {
2085
                                $err++;
2086
                            }
2087
                        }
2088
2089
                        if (!$err) {
2090
                            $this->db->commit();
2091
                        } else {
2092
                            $this->error = $this->db->lasterror();
2093
                            $this->db->rollback();
2094
                        }
2095
                    }
2096
                    // else box already registered into database
2097
                } else {
2098
                    $this->error = $this->db->lasterror();
2099
                    $err++;
2100
                }
2101
            }
2102
        }
2103
2104
        return $err;
2105
    }
2106
2107
2108
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2109
2110
    /**
2111
     * Adds menu entries
2112
     *
2113
     * @return int     Error count (0 if OK)
2114
     */
2115
    public function insert_menus()
2116
    {
2117
        // phpcs:enable
2118
        global $conf, $user;
2119
2120
        if (!is_array($this->menu) || empty($this->menu)) {
2121
            return 0;
2122
        }
2123
2124
        dol_syslog(get_only_class($this) . "::insert_menus", LOG_DEBUG);
2125
2126
        $err = 0;
2127
2128
        // Common module
2129
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
2130
2131
        $this->db->begin();
2132
2133
        foreach ($this->menu as $key => $value) {
2134
            $menu = new Menubase($this->db);
2135
            $menu->menu_handler = 'all';
2136
2137
            //$menu->module=strtolower($this->name);    TODO When right_class will be same than module name
2138
            $menu->module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2139
2140
            if (!$this->menu[$key]['fk_menu']) {
2141
                $menu->fk_menu = 0;
2142
            } else {
2143
                $foundparent = 0;
2144
                $fk_parent = $this->menu[$key]['fk_menu'];
2145
                $reg = array();
2146
                if (preg_match('/^r=/', $fk_parent)) {    // old deprecated method
2147
                    $fk_parent = str_replace('r=', '', $fk_parent);
2148
                    if (isset($this->menu[$fk_parent]['rowid'])) {
2149
                        $menu->fk_menu = $this->menu[$fk_parent]['rowid'];
2150
                        $foundparent = 1;
2151
                    }
2152
                } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+),fk_leftmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2153
                    $menu->fk_menu = -1;
2154
                    $menu->fk_mainmenu = $reg[1];
2155
                    $menu->fk_leftmenu = $reg[2];
2156
                    $foundparent = 1;
2157
                } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2158
                    $menu->fk_menu = -1;
2159
                    $menu->fk_mainmenu = $reg[1];
2160
                    $menu->fk_leftmenu = '';
2161
                    $foundparent = 1;
2162
                }
2163
                if (!$foundparent) {
2164
                    $this->error = "ErrorBadDefinitionOfMenuArrayInModuleDescriptor";
2165
                    dol_syslog(get_only_class($this) . "::insert_menus " . $this->error . " " . $this->menu[$key]['fk_menu'], LOG_ERR);
2166
                    $err++;
2167
                }
2168
            }
2169
            $menu->type = $this->menu[$key]['type'];
2170
            $menu->mainmenu = isset($this->menu[$key]['mainmenu']) ? $this->menu[$key]['mainmenu'] : (isset($menu->fk_mainmenu) ? $menu->fk_mainmenu : '');
2171
            $menu->leftmenu = isset($this->menu[$key]['leftmenu']) ? $this->menu[$key]['leftmenu'] : '';
2172
            $menu->title = $this->menu[$key]['titre'];
2173
            $menu->prefix = isset($this->menu[$key]['prefix']) ? $this->menu[$key]['prefix'] : '';
2174
            $menu->url = $this->menu[$key]['url'];
2175
            $menu->langs = isset($this->menu[$key]['langs']) ? $this->menu[$key]['langs'] : '';
2176
            $menu->position = $this->menu[$key]['position'];
2177
            $menu->perms = $this->menu[$key]['perms'];
2178
            $menu->target = isset($this->menu[$key]['target']) ? $this->menu[$key]['target'] : '';
2179
            $menu->user = $this->menu[$key]['user'];
2180
            $menu->enabled = isset($this->menu[$key]['enabled']) ? $this->menu[$key]['enabled'] : 0;
2181
            $menu->position = $this->menu[$key]['position'];
2182
            $menu->entity = $entity;
2183
2184
            if (!$err) {
2185
                $result = $menu->create($user); // Save menu entry into table llx_menu
2186
                if ($result > 0) {
2187
                    $this->menu[$key]['rowid'] = $result;
2188
                } else {
2189
                    $this->error = $menu->error;
2190
                    dol_syslog(get_only_class($this) . '::insert_menus result=' . $result . " " . $this->error, LOG_ERR);
2191
                    $err++;
2192
                    break;
2193
                }
2194
            }
2195
        }
2196
2197
        if (!$err) {
2198
            $this->db->commit();
2199
        } else {
2200
            dol_syslog(get_only_class($this) . "::insert_menus " . $this->error, LOG_ERR);
2201
            $this->db->rollback();
2202
        }
2203
2204
        return $err;
2205
    }
2206
2207
2208
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2209
2210
    /**
2211
     * Creates directories
2212
     *
2213
     * @return int Error count (0 if OK)
2214
     */
2215
    public function create_dirs()
2216
    {
2217
        // phpcs:enable
2218
        global $langs, $conf;
2219
2220
        $err = 0;
2221
        $name = '';
2222
2223
        if (isset($this->dirs) && is_array($this->dirs)) {
2224
            foreach ($this->dirs as $key => $value) {
2225
                $addtodatabase = 0;
2226
2227
                if (!is_array($value)) {
2228
                    $dir = $value; // Default simple mode
2229
                } else {
2230
                    $constname = $this->const_name . "_DIR_";
2231
                    $dir = $this->dirs[$key][1];
2232
                    $addtodatabase = empty($this->dirs[$key][2]) ? '' : $this->dirs[$key][2]; // Create constante in llx_const
2233
                    $subname = empty($this->dirs[$key][3]) ? '' : strtoupper($this->dirs[$key][3]); // Add submodule name (ex: $conf->module->submodule->dir_output)
2234
                    $forcename = empty($this->dirs[$key][4]) ? '' : strtoupper($this->dirs[$key][4]); // Change the module name if different
2235
2236
                    if (!empty($forcename)) {
2237
                        $constname = 'MAIN_MODULE_' . $forcename . "_DIR_";
2238
                    }
2239
                    if (!empty($subname)) {
2240
                        $constname = $constname . $subname . "_";
2241
                    }
2242
2243
                    $name = $constname . strtoupper($this->dirs[$key][0]);
2244
                }
2245
2246
                // Define directory full path ($dir must start with "/")
2247
                if (!getDolGlobalString('MAIN_MODULE_MULTICOMPANY') || $conf->entity == 1) {
2248
                    $fulldir = DOL_DATA_ROOT . $dir;
2249
                } else {
2250
                    $fulldir = DOL_DATA_ROOT . "/" . $conf->entity . $dir;
2251
                }
2252
                // Create dir if it does not exists
2253
                if (!empty($fulldir) && !file_exists($fulldir)) {
2254
                    if (dol_mkdir($fulldir, DOL_DATA_ROOT) < 0) {
2255
                        $this->error = $langs->trans("ErrorCanNotCreateDir", $fulldir);
2256
                        dol_syslog(get_only_class($this) . "::_init " . $this->error, LOG_ERR);
2257
                        $err++;
2258
                    }
2259
                }
2260
2261
                // Define the constant in database if requested (not the default mode)
2262
                if (!empty($addtodatabase) && !empty($name)) {
2263
                    $result = $this->insert_dirs($name, $dir);
2264
                    if ($result) {
2265
                        $err++;
2266
                    }
2267
                }
2268
            }
2269
        }
2270
2271
        return $err;
2272
    }
2273
2274
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2275
2276
    /**
2277
     * Adds directories definitions
2278
     *
2279
     * @param string $name Name
2280
     * @param string $dir Directory
2281
     *
2282
     * @return int             Error count (0 if OK)
2283
     */
2284
    public function insert_dirs($name, $dir)
2285
    {
2286
        // phpcs:enable
2287
        global $conf;
2288
2289
        $err = 0;
2290
2291
        $res = Constant::getByName($name, $conf->entity);
2292
        if ($res === null) {
2293
            $this->error = $this->db->lasterror();
2294
            $err++;
2295
            return $err;
2296
        }
2297
2298
        dol_syslog(get_only_class($this) . "::insert_dirs", LOG_DEBUG);
2299
        if (!$res) {
2300
            dol_syslog(get_only_class($this) . "::insert_dirs", LOG_DEBUG);
2301
            Constant::insert(
2302
                $name,
2303
                $dir,
2304
                'chaine',
2305
                $this->db->escape("Directory for module " . $this->name),
2306
                0,
2307
                $conf->entity,
2308
            );
2309
        }
2310
2311
        return $err;
2312
    }
2313
2314
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2315
2316
    /**
2317
     * Return Kanban view of a module
2318
     *
2319
     * @param string $codeenabledisable HTML code for button to enable/disable module
2320
     * @param string $codetoconfig HTML code to go to config page
2321
     * @return  string                          HTML code of Kanban view
2322
     */
2323
    public function getKanbanView($codeenabledisable = '', $codetoconfig = '')
2324
    {
2325
        global $langs;
2326
2327
        // Define imginfo
2328
        $imginfo = "info";
2329
        if ($this->isCoreOrExternalModule() == 'external') {
2330
            $imginfo = "info_black";
2331
        }
2332
2333
        $const_name = 'MAIN_MODULE_' . strtoupper(preg_replace('/^mod/i', '', get_only_class($this)));
2334
2335
        $version = $this->getVersion(0);
2336
        $versiontrans = '';
2337
        if (preg_match('/development/i', $version)) {
2338
            $versiontrans .= 'warning';
2339
        }
2340
        if (preg_match('/experimental/i', $version)) {
2341
            $versiontrans .= 'warning';
2342
        }
2343
        if (preg_match('/deprecated/i', $version)) {
2344
            $versiontrans .= 'warning';
2345
        }
2346
2347
        $return = '
2348
    	<div class="box-flex-item info-box-module'
2349
            . (getDolGlobalString($const_name) ? '' : ' --disabled')
2350
            . ($this->isCoreOrExternalModule() == 'external' ? ' --external' : '')
2351
            . ($this->needUpdate ? ' --need-update' : '')
2352
            . '">
2353
	    <div class="info-box info-box-sm info-box-module">
2354
	    <div class="info-box-icon' . (!getDolGlobalString($const_name) ? '' : ' info-box-icon-module-enabled' . ($versiontrans ? ' info-box-icon-module-warning' : '')) . '">';
2355
2356
        $alttext = '';
2357
        //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
2358
        //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
2359
        if (!empty($this->picto)) {
2360
            if (preg_match('/^\//i', $this->picto)) {
2361
                $return .= img_picto($alttext, $this->picto, 'class="inline-block valignmiddle"', 1);
2362
            } else {
2363
                $return .= img_object($alttext, $this->picto, 'class="inline-block valignmiddle"');
2364
            }
2365
        } else {
2366
            $return .= img_object($alttext, 'generic', 'class="inline-block valignmiddle"');
2367
        }
2368
2369
        if ($this->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
2370
            $versionTitle = $langs->trans("Version") . ' ' . $this->getVersion(1);
2371
            if ($this->needUpdate) {
2372
                $versionTitle .= '<br>' . $langs->trans('ModuleUpdateAvailable') . ' : ' . $this->lastVersion;
2373
            }
2374
2375
            $return .= '<span class="info-box-icon-version' . ($versiontrans ? ' ' . $versiontrans : '') . ' classfortooltip" title="' . dol_escape_js($versionTitle) . '" >';
2376
            $return .= $this->getVersion(1);
2377
            $return .= '</span>';
2378
        }
2379
2380
        $return .= '</div>
2381
	    <div class="info-box-content info-box-text-module' . (!getDolGlobalString($const_name) ? '' : ' info-box-module-enabled' . ($versiontrans ? ' info-box-content-warning' : '')) . '">
2382
	    <span class="info-box-title">' . $this->getName() . '</span>
2383
	    <span class="info-box-desc twolinesmax opacitymedium" title="' . dol_escape_htmltag($this->getDesc()) . '">' . nl2br($this->getDesc()) . '</span>';
2384
2385
        $return .= '<div class="valignmiddle inline-block info-box-more">';
2386
        //if ($versiontrans) print img_warning($langs->trans("Version").' '.$this->getVersion(1)).' ';
2387
        $return .= '<a class="valignmiddle inline-block" href="javascript:document_preview(\'' . constant('BASE_URL') . '/admin/modulehelp.php?id=' . ((int)$this->numero) . '\',\'text/html\',\'' . dol_escape_js($langs->trans("Module")) . '\')">' . img_picto(($this->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule") . ' - ' : '') . $langs->trans("ClickToShowDescription"), $imginfo) . '</a>';
2388
        $return .= '</div><br>';
2389
2390
        $return .= '<div class="valignmiddle inline-block info-box-actions">';
2391
        $return .= '<div class="valignmiddle inline-block info-box-setup">';
2392
        $return .= $codetoconfig;
2393
        $return .= '</div>';
2394
        $return .= '<div class="valignmiddle inline-block marginleftonly marginrightonly">';
2395
        $return .= $codeenabledisable;
2396
        $return .= '</div>';
2397
        $return .= '</div>';
2398
2399
        $return .= '
2400
	    </div><!-- /.info-box-content -->
2401
	    </div><!-- /.info-box -->
2402
	    </div>';
2403
2404
        return $return;
2405
    }
2406
2407
    /**
2408
     * Tells if module is core or external.
2409
     * 'dolibarr' and 'dolibarr_deprecated' is core
2410
     * 'experimental' and 'development' is core
2411
     *
2412
     * @return string  'core', 'external' or 'unknown'
2413
     */
2414
    public function isCoreOrExternalModule()
2415
    {
2416
        if ($this->version == 'dolibarr' || $this->version == 'dolibarr_deprecated') {
2417
            return 'core';
2418
        }
2419
        if (!empty($this->version) && !in_array($this->version, array('experimental', 'development'))) {
2420
            return 'external';
2421
        }
2422
        if (!empty($this->editor_name) || !empty($this->editor_url)) {
2423
            return 'external';
2424
        }
2425
        if ($this->numero >= 100000) {
2426
            return 'external';
2427
        }
2428
        return 'unknown';
2429
    }
2430
2431
    /**
2432
     * Gives module version (translated if param $translated is on)
2433
     * For 'experimental' modules, gives 'experimental' translation
2434
     * For 'dolibarr' modules, gives Dolibarr version
2435
     *
2436
     * @param int $translated 1=Special version keys are translated, 0=Special version keys are not translated
2437
     * @return string                       Module version
2438
     */
2439
    public function getVersion($translated = 1)
2440
    {
2441
        global $langs;
2442
        $langs->load("admin");
2443
2444
        $ret = '';
2445
2446
        $newversion = preg_replace('/_deprecated/', '', $this->version);
2447
        if ($newversion == 'experimental') {
2448
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionExperimental") : $newversion);
2449
        } elseif ($newversion == 'development') {
2450
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionDevelopment") : $newversion);
2451
        } elseif ($newversion == 'dolibarr') {
2452
            $ret = DOL_VERSION;
2453
        } elseif ($newversion) {
2454
            $ret = $newversion;
2455
        } else {
2456
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionUnknown") : 'unknown');
2457
        }
2458
2459
        if (preg_match('/_deprecated/', $this->version)) {
2460
            $ret .= ($translated ? ' (' . $langs->transnoentitiesnoconv("Deprecated") . ')' : $this->version);
2461
        }
2462
        return $ret;
2463
    }
2464
2465
    /**
2466
     * Gives the translated module name if translation exists in admin.lang or into language files of module.
2467
     * Otherwise return the module key name.
2468
     *
2469
     * @return string  Translated module name
2470
     */
2471
    public function getName()
2472
    {
2473
        global $langs;
2474
        $langs->load("admin");
2475
2476
        if ($langs->transnoentitiesnoconv("Module" . $this->numero . "Name") != "Module" . $this->numero . "Name") {
2477
            // If module name translation exists
2478
            return $langs->transnoentitiesnoconv("Module" . $this->numero . "Name");
2479
        } else {
2480
            // If module name translation using it's unique id does not exist, we try to use its name to find translation
2481
            if (is_array($this->langfiles)) {
2482
                foreach ($this->langfiles as $val) {
2483
                    if ($val) {
2484
                        $langs->load($val);
2485
                    }
2486
                }
2487
            }
2488
2489
            if ($langs->trans("Module" . $this->name . "Name") != "Module" . $this->name . "Name") {
2490
                // If module name translation exists
2491
                return $langs->transnoentitiesnoconv("Module" . $this->name . "Name");
2492
            }
2493
2494
            // Last chance with simple label
2495
            return $langs->transnoentitiesnoconv($this->name);
2496
        }
2497
    }
2498
2499
    /**
2500
     * Gives the translated module description if translation exists in admin.lang or the default module description
2501
     *
2502
     * @return string  Translated module description
2503
     */
2504
    public function getDesc()
2505
    {
2506
        global $langs;
2507
        $langs->load("admin");
2508
2509
        if ($langs->transnoentitiesnoconv("Module" . $this->numero . "Desc") != "Module" . $this->numero . "Desc") {
2510
            // If module description translation exists
2511
            return $langs->transnoentitiesnoconv("Module" . $this->numero . "Desc");
2512
        } else {
2513
            // If module description translation does not exist using its unique id, we can use its name to find translation
2514
            if (is_array($this->langfiles)) {
2515
                foreach ($this->langfiles as $val) {
2516
                    if ($val) {
2517
                        $langs->load($val);
2518
                    }
2519
                }
2520
            }
2521
2522
            if ($langs->transnoentitiesnoconv("Module" . $this->name . "Desc") != "Module" . $this->name . "Desc") {
2523
                // If module name translation exists
2524
                return $langs->trans("Module" . $this->name . "Desc");
2525
            }
2526
2527
            // Last chance with simple label
2528
            return $langs->trans($this->description);
2529
        }
2530
    }
2531
2532
    /**
2533
     * Check for module update
2534
     * TODO : store results for $this->url_last_version and $this->needUpdate
2535
     * Add a cron task to monitor for updates
2536
     *
2537
     * @return int Return integer <0 if Error, 0 == no update needed,  >0 if need update
2538
     */
2539
    public function checkForUpdate()
2540
    {
2541
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/geturl.lib.php';
2542
        if (!empty($this->url_last_version)) {
2543
            $lastVersion = getURLContent($this->url_last_version, 'GET', '', 1, array(), array('http', 'https'), 0);    // Accept http or https links on external remote server only
2544
            if (isset($lastVersion['content']) && strlen($lastVersion['content']) < 30) {
2545
                // Security warning :  be careful with remote data content, the module editor could be hacked (or evil) so limit to a-z A-Z 0-9 _ . -
2546
                $this->lastVersion = preg_replace("/[^a-zA-Z0-9_\.\-]+/", "", $lastVersion['content']);
2547
                if (version_compare($this->lastVersion, $this->version) > 0) {
2548
                    $this->needUpdate = true;
2549
                    return 1;
2550
                } else {
2551
                    $this->needUpdate = false;
2552
                    return 0;
2553
                }
2554
            } else {
2555
                return -1;
2556
            }
2557
        }
2558
        return 0;
2559
    }
2560
2561
    /**
2562
     * Create tables and keys required by module:
2563
     * - Files table.sql or table-module.sql with create table instructions
2564
     * - Then table.key.sql or table-module.key.sql with create keys instructions
2565
     * - Then data_xxx.sql (usually provided by external modules only)
2566
     * - Then update_xxx.sql (usually provided by external modules only)
2567
     * Files must be stored in subdirectory 'tables' or 'data' into directory $reldir (Example: '/install/mysql/' or '/module/sql/')
2568
     * This function may also be called by :
2569
     * - _load_tables('/install/mysql/', 'modulename') into the this->init() of core module descriptors.
2570
     * - _load_tables('/mymodule/sql/') into the this->init() of external module descriptors.
2571
     *
2572
     * @param string $reldir Relative directory where to scan files. Example: '/install/mysql/' or '/module/sql/'
2573
     * @param string $onlywithsuffix Only with the defined suffix
2574
     * @return  int<0,1>                        Return integer <=0 if KO, >0 if OK
2575
     */
2576
    protected function _load_tables($reldir, $onlywithsuffix = '')
2577
    {
2578
        // phpcs:enable
2579
        global $conf;
2580
2581
        $error = 0;
2582
        $dirfound = 0;
2583
        $ok = 1;
2584
2585
        if (empty($reldir)) {
2586
            return 1;
2587
        }
2588
2589
        include_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
2590
2591
        foreach ($conf->file->dol_document_root as $dirroot) {
2592
            if ($ok == 1) {
2593
                $dirsql = $dirroot . $reldir;
2594
                $ok = 0;
2595
2596
                // We will loop on xxx/, xxx/tables/, xxx/data/
2597
                $listofsubdir = array('', 'tables/', 'data/');
2598
                if ($this->db->type == 'pgsql') {
2599
                    $listofsubdir[] = '../pgsql/functions/';
2600
                }
2601
2602
                foreach ($listofsubdir as $subdir) {
2603
                    $dir = $dirsql . $subdir;
2604
2605
                    if (!is_dir($dir)) {
2606
                        continue;
2607
                    }
2608
                    $handle = opendir($dir); // Dir may not exists
2609
2610
                    if (is_resource($handle)) {
2611
                        $dirfound++;
2612
2613
                        // Run llx_mytable.sql files, then llx_mytable_*.sql
2614
                        $files = array();
2615
                        while (($file = readdir($handle)) !== false) {
2616
                            $files[] = $file;
2617
                        }
2618
                        sort($files);
2619
                        foreach ($files as $file) {
2620
                            if ($onlywithsuffix) {
2621
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2622
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2623
                                    continue;
2624
                                } else {
2625
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2626
                                }
2627
                            }
2628
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
2629
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2630
                                if ($result <= 0) {
2631
                                    $error++;
2632
                                }
2633
                            }
2634
                        }
2635
2636
                        rewinddir($handle);
2637
2638
                        // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) then then llx_mytable_*.key.sql
2639
                        $files = array();
2640
                        while (($file = readdir($handle)) !== false) {
2641
                            $files[] = $file;
2642
                        }
2643
                        sort($files);
2644
                        foreach ($files as $file) {
2645
                            if ($onlywithsuffix) {
2646
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2647
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2648
                                    continue;
2649
                                } else {
2650
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2651
                                }
2652
                            }
2653
                            if (preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
2654
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2655
                                if ($result <= 0) {
2656
                                    $error++;
2657
                                }
2658
                            }
2659
                        }
2660
2661
                        rewinddir($handle);
2662
2663
                        // Run functions-xxx.sql files (Must be done after llx_mytable.key.sql)
2664
                        $files = array();
2665
                        while (($file = readdir($handle)) !== false) {
2666
                            $files[] = $file;
2667
                        }
2668
                        sort($files);
2669
                        foreach ($files as $file) {
2670
                            if ($onlywithsuffix) {
2671
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2672
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2673
                                    continue;
2674
                                } else {
2675
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2676
                                }
2677
                            }
2678
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 9) == 'functions') {
2679
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2680
                                if ($result <= 0) {
2681
                                    $error++;
2682
                                }
2683
                            }
2684
                        }
2685
2686
                        rewinddir($handle);
2687
2688
                        // Run data_xxx.sql files (Must be done after llx_mytable.key.sql)
2689
                        $files = array();
2690
                        while (($file = readdir($handle)) !== false) {
2691
                            $files[] = $file;
2692
                        }
2693
                        sort($files);
2694
                        foreach ($files as $file) {
2695
                            if ($onlywithsuffix) {
2696
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2697
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2698
                                    continue;
2699
                                } else {
2700
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2701
                                }
2702
                            }
2703
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'data') {
2704
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2705
                                if ($result <= 0) {
2706
                                    $error++;
2707
                                }
2708
                            }
2709
                        }
2710
2711
                        rewinddir($handle);
2712
2713
                        // Run update_xxx.sql files
2714
                        $files = array();
2715
                        while (($file = readdir($handle)) !== false) {
2716
                            $files[] = $file;
2717
                        }
2718
                        sort($files);
2719
                        foreach ($files as $file) {
2720
                            if ($onlywithsuffix) {
2721
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2722
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2723
                                    continue;
2724
                                } else {
2725
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2726
                                }
2727
                            }
2728
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 6) == 'update') {
2729
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2730
                                if ($result <= 0) {
2731
                                    $error++;
2732
                                }
2733
                            }
2734
                        }
2735
2736
                        closedir($handle);
2737
                    }
2738
                }
2739
2740
                if ($error == 0) {
2741
                    $ok = 1;
2742
                }
2743
            }
2744
        }
2745
2746
        if (!$dirfound) {
2747
            dol_syslog("A module wants to load sql files from " . $reldir . " but this directory was not found.", LOG_WARNING);
2748
        }
2749
        return $ok;
2750
    }
2751
2752
    /**
2753
     * Helper method to declare dictionaries one at a time (rather than declaring dictionaries property by property).
2754
     *
2755
     * @param array $dictionaryArray Array describing one dictionary. Keys are:
2756
     *                               'name',        table name (without prefix)
2757
     *                               'lib',         dictionary label
2758
     *                               'sql',         query for select
2759
     *                               'sqlsort',     sort order
2760
     *                               'field',       comma-separated list of fields to select
2761
     *                               'fieldvalue',  list of columns used for editing existing rows
2762
     *                               'fieldinsert', list of columns used for inserting new rows
2763
     *                               'rowid',       name of the technical ID (primary key) column, usually 'rowid'
2764
     *                               'cond',        condition for the dictionary to be shown / active
2765
     *                               'help',        optional array of translation keys by column for tooltips
2766
     *                               'fieldcheck'   (appears to be unused)
2767
     * @param string $langs Optional translation file to include (appears to be unused)
2768
     * @return void
2769
     */
2770
    protected function declareNewDictionary($dictionaryArray, $langs = '')
2771
    {
2772
        $fields = array('name', 'lib', 'sql', 'sqlsort', 'field', 'fieldvalue', 'fieldinsert', 'rowid', 'cond', 'help', 'fieldcheck');
2773
2774
        foreach ($fields as $field) {
2775
            if (!empty($dictionaryArray[$field])) {
2776
                $this->dictionaries['tab' . $field][] = $dictionaryArray[$field];
2777
            }
2778
        }
2779
        if ($langs && !in_array($langs, $this->dictionaries[$langs])) $this->dictionaries['langs'][] = $langs;
2780
    }
2781
}
2782