Passed
Push — main ( 4b1ce6...7d2b49 )
by Rafael
80:06
created

DolibarrModules   F

Complexity

Total Complexity 460

Size/Duplication

Total Lines 2817
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1261
dl 0
loc 2817
rs 0.8
c 0
b 0
f 0
wmc 460

54 Methods

Rating   Name   Duplication   Size   Complexity  
A getImportDatasetLabel() 0 12 2
A insert_dirs() 0 30 3
A remove() 0 3 1
B setPerms() 0 35 7
B getChangeLog() 0 38 6
A forceDeactivate() 0 16 1
A _unactive() 0 18 3
D insert_module_parts() 0 105 25
A getObj() 0 9 2
A getLastActivationDate() 0 22 3
A checkForUpdate() 0 20 5
A declareNewDictionary() 0 10 5
F insert_const() 0 61 15
B getDescLongReadmeFound() 0 27 7
F getKanbanView() 0 82 19
A getModulePosition() 0 10 3
D insert_cronjobs() 0 125 41
A isExperimental() 0 3 1
A getModules() 0 6 2
A getName() 0 25 6
B isCoreOrExternalModule() 0 15 8
A getExportDatasetLabel() 0 11 2
A isDeprecated() 0 3 1
D insert_permissions() 0 158 22
F insert_menus() 0 92 23
B _active() 0 41 7
A getLangFilesArray() 0 3 1
F _init() 0 98 20
D create_dirs() 0 57 18
A getPublisher() 0 3 1
A delete_cronjobs() 0 23 4
B delete_module_parts() 0 33 9
A delete_tabs() 0 18 2
B insert_tabs() 0 59 10
C _getModules() 0 39 12
B getLastActivationInfo() 0 31 7
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 28 6
A delete_menus() 0 23 3
A init() 0 3 1
D insert_boxes() 0 89 23
A getNameOf() 0 16 3
A getModule() 0 20 5
A __construct() 0 3 1
B getDescLong() 0 47 7
A getPublisherUrl() 0 3 1
A delete_dirs() 0 18 2
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\Lib\Modules;
32
use InfoBox;
33
use Menubase;
34
use User;
35
36
/**
37
 * \file           htdocs/core/modules/DolibarrModules.class.php
38
 * \brief          File of parent class of module descriptor class files
39
 */
40
41
/**
42
 * Class DolibarrModules
43
 *
44
 * Parent class for module descriptor class files
45
 */
46
abstract class DolibarrModules
47
{
48
    const KEY_ID = 0;
49
    const KEY_LABEL = 1;
50
    const KEY_TYPE = 2;
51
    const KEY_DEFAULT = 3;
52
    const KEY_FIRST_LEVEL = 4;
53
    const KEY_SECOND_LEVEL = 5;
54
    const KEY_MODULE = 6;
55
    const KEY_ENABLED = 7;
56
    /**
57
     * @var DoliDB  Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Core\Base\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
58
     */
59
    public $db;
60
    /**
61
     * @var int     Module unique ID
62
     * @see https://wiki.dolibarr.org/index.php/List_of_modules_id
63
     */
64
    public $numero;
65
    /**
66
     * @var string  Publisher name
67
     */
68
    public $editor_name;
69
    /**
70
     * @var string  URL of module at publisher site
71
     */
72
    public $editor_url;
73
    /**
74
     * @var string  URL of logo of the publisher. Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@mymodule'.
75
     */
76
    public $editor_squarred_logo;
77
    /**
78
     * @var string  Family
79
     * @see $familyinfo
80
     *
81
     * Native values: 'crm', 'financial', 'hr', 'projects', 'products', 'ecm', 'technic', 'other'.
82
     * Use familyinfo to declare a custom value.
83
     */
84
    public $family;
85
    /**
86
     * @var array<string,array{position:string,label:string}> Custom family information
87
     * @see $family
88
     *
89
     * e.g.:
90
     * array(
91
     *     'myownfamily' => array(
92
     *         'position' => '001',
93
     *         'label' => $langs->trans("MyOwnFamily")
94
     *     )
95
     * );
96
     */
97
    public $familyinfo;
98
    /**
99
     * @var string  Module position on 2 digits
100
     */
101
    public $module_position = '50';
102
    /**
103
     * @var string  Module name
104
     *
105
     * Only used if Module[ID]Name translation string is not found.
106
     *
107
     * You can use the following code to automatically derive it from your module's class name:
108
     * preg_replace('/^mod/i', '', get_class($this))
109
     */
110
    public $name;
111
    /**
112
     * @var string[] Paths to create when module is activated
113
     *
114
     * e.g.: array('/mymodule/temp')
115
     */
116
    public $dirs = array();
117
    /**
118
     * @var array Module boxes
119
     */
120
    public $boxes = array(); // deprecated
121
    /**
122
     * @var array Module constants
123
     */
124
    public $const = array();
125
    /**
126
     * @var array Module cron jobs entries
127
     */
128
    public $cronjobs = array();
129
    /**
130
     * @var array   Module access rights
131
     */
132
    public $rights;
133
    /**
134
     * @var int     1=Admin is always granted of permission of modules (even when module is disabled)
135
     */
136
    public $rights_admin_allowed;
137
    /**
138
     * @var string  Module access rights family
139
     */
140
    public $rights_class;
141
    /**
142
     * @var array|int   Module menu entries (1 means the menu entries are not declared into module descriptor but are hardcoded into menu manager)
143
     */
144
    public $menu = array();
145
146
    /**
147
     * @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...
148
     *  array(
149
     *      // Set this to 1 if module has its own trigger directory (/mymodule/core/triggers)
150
     *      'triggers' => 0,
151
     *      // Set this to 1 if module has its own login method directory (/mymodule/core/login)
152
     *      'login' => 0,
153
     *      // Set this to 1 if module has its own substitution function file (/mymodule/core/substitutions)
154
     *      'substitutions' => 0,
155
     *      // Set this to 1 if module has its own menus handler directory (/mymodule/core/menus)
156
     *      'menus' => 0,
157
     *      // Set this to 1 if module has its own theme directory (/mymodule/theme)
158
     *      'theme' => 0,
159
     *      // Set this to 1 if module overwrite template dir (/mymodule/core/tpl)
160
     *      'tpl' => 0,
161
     *      // Set this to 1 if module has its own barcode directory (/mymodule/core/modules/barcode)
162
     *      'barcode' => 0,
163
     *      // Set this to 1 if module has its own models directory (/mymodule/core/modules/xxx)
164
     *      'models' => 0,
165
     *      // Set this to relative path of css file if module has its own css file
166
     *      'css' => '/mymodule/css/mymodule.css.php',
167
     *      // Set this to relative path of js file if module must load a js on all pages
168
     *      'js' => '/mymodule/js/mymodule.js',
169
     *      // Set here all hooks context managed by module
170
     *      'hooks' => array('hookcontext1','hookcontext2')
171
     *  )
172
     */
173
    public $module_parts = array();
174
175
    /**
176
     * @var string Error message
177
     */
178
    public $error;
179
180
    /**
181
     * @var string[] Array of Errors messages
182
     */
183
    public $errors;
184
185
    /**
186
     * @var string Module version
187
     * @see http://semver.org
188
     *
189
     * The following keywords can also be used:
190
     * 'development'
191
     * 'experimental'
192
     * 'dolibarr': only for core modules that share its version
193
     * 'dolibarr_deprecated': only for deprecated core modules
194
     */
195
    public $version;
196
197
    /**
198
     * Module last version
199
     * @var string $lastVersion
200
     */
201
    public $lastVersion = '';
202
203
    /**
204
     * true indicate this module need update
205
     * @var bool $needUpdate
206
     */
207
    public $needUpdate = false;
208
209
    /**
210
     * @var string Module description (short text)
211
     *
212
     * Only used if Module[ID]Desc translation string is not found.
213
     */
214
    public $description;
215
216
    /**
217
     * @var   string Module description (long text)
218
     * @since 4.0.0
219
     *
220
     * HTML content supported.
221
     */
222
    public $descriptionlong;
223
224
    /**
225
     * @var array dictionaries description
226
     */
227
    public $dictionaries = array();
228
229
    /**
230
     * @var array tabs description
231
     */
232
    public $tabs;
233
234
    // For exports
235
236
    /**
237
     * @var string Module export code
238
     */
239
    public $export_code;
240
241
    /**
242
     * @var string[] Module export label
243
     */
244
    public $export_label;
245
246
    public $export_icon;
247
248
    /**
249
     * @var array export enabled
250
     */
251
    public $export_enabled;
252
    public $export_permission;
253
    public $export_fields_array;
254
    public $export_TypeFields_array; // Array of key=>type where type can be 'Numeric', 'Date', 'Text', 'Boolean', 'Status', 'List:xxx:fieldlabel:rowid'
255
    public $export_entities_array;
256
    public $export_aggregate_array;
257
    public $export_examplevalues_array;
258
    public $export_help_array;
259
    public $export_special_array; // special or computed field
260
    public $export_dependencies_array;
261
    public $export_sql_start;
262
    public $export_sql_end;
263
    public $export_sql_order;
264
265
266
    // For import
267
268
    /**
269
     * @var string Module import code
270
     */
271
    public $import_code;
272
273
    /**
274
     * @var string[] Module import label
275
     */
276
    public $import_label;
277
278
    public $import_icon;
279
    public $import_entities_array;
280
    public $import_tables_array;
281
    public $import_tables_creator_array;
282
    public $import_fields_array;
283
    public $import_fieldshidden_array;
284
    public $import_convertvalue_array;
285
    public $import_regex_array;
286
    public $import_examplevalues_array;
287
    public $import_updatekeys_array;
288
    public $import_run_sql_after_array;
289
    public $import_TypeFields_array;
290
    public $import_help_array;
291
292
    /**
293
     * @var string Module constant name
294
     */
295
    public $const_name;
296
297
    /**
298
     * @var bool Module can't be disabled
299
     */
300
    public $always_enabled;
301
302
    /**
303
     * @var bool Module is disabled
304
     */
305
    public $disabled;
306
307
    /**
308
     * @var int Module is enabled globally (Multicompany support)
309
     */
310
    public $core_enabled;
311
312
    /**
313
     * @var string Name of image file used for this module
314
     *
315
     * If file is in theme/yourtheme/img directory under name object_pictoname.png use 'pictoname'
316
     * If file is in module/img directory under name object_pictoname.png use 'pictoname@module'
317
     */
318
    public $picto;
319
320
    /**
321
     * @var string[]|string     List of config pages (Old modules uses a string. New one must use an array)
322
     *
323
     * Name of php pages stored into module/admin directory, used to setup module.
324
     * e.g.: array("setup.php@mymodule")
325
     */
326
    public $config_page_url;
327
328
329
    /**
330
     * @var array   List of module class names that must be enabled if this module is enabled. e.g.: array('modAnotherModule', 'FR'=>'modYetAnotherModule')
331
     *              Another example : array('always'=>array("modBanque", "modFacture", "modProduct", "modCategorie"), 'FR'=>array('modBlockedLog'));
332
     * @see $requiredby
333
     */
334
    public $depends;
335
336
    /**
337
     * @var string[] List of module class names to disable if the module is disabled.
338
     * @see $depends
339
     */
340
    public $requiredby;
341
342
    /**
343
     * @var string[] List of module class names as string this module is in conflict with.
344
     * @see $depends
345
     */
346
    public $conflictwith;
347
348
    /**
349
     * @var string[] Module language files
350
     */
351
    public $langfiles;
352
353
    /**
354
     * @var array<string,string> Array of warnings to show when we activate the module
355
     *
356
     * array('always'='text') or array('FR'='text')
357
     */
358
    public $warnings_activation;
359
360
    /**
361
     * @var array<string,string> Array of warnings to show when we activate an external module
362
     *
363
     * array('always'='text') or array('FR'='text')
364
     */
365
    public $warnings_activation_ext;
366
367
    /**
368
     * @var array<string,string> Array of warnings to show when we disable the module
369
     *
370
     * array('always'='text') or array('FR'='text')
371
     */
372
    public $warnings_unactivation;
373
374
    /**
375
     * @var array Minimum version of PHP required by module.
376
     * e.g.: PHP ≥ 7.0 = array(7, 0)
377
     */
378
    public $phpmin;
379
380
    public $phpmax;
381
382
    /**
383
     * @var array Minimum version of Dolibarr required by module.
384
     * e.g.: Dolibarr ≥ 3.6 = array(3, 6)
385
     */
386
    public $need_dolibarr_version;
387
388
    public $need_javascript_ajax;
389
390
    public $enabled_bydefault;
391
392
    /**
393
     * @var bool|int Whether to hide the module.
394
     */
395
    public $hidden = false;
396
397
    /**
398
     * @var string url to check for module update
399
     */
400
    public $url_last_version;
401
402
403
    /**
404
     * Constructor. Define names, constants, directories, boxes, permissions
405
     *
406
     * @param DoliDB $db Database handler
407
     */
408
    public function __construct($db)
409
    {
410
        $this->db = $db;
411
    }
412
413
    public static function setPerms($db, $langs, $entity)
414
    {
415
        $db->begin();
416
417
// Search all modules with permission and reload permissions def.
418
        $modules = array();
419
        $modulesdir = dolGetModulesDirs();
420
421
        $allModules = static::getModules($modulesdir);
422
        foreach ($allModules as $modName => $filename) {
423
            if (str_starts_with($modName, 'mod')) {
424
                include_once $filename;
425
                $objMod = new $modName($db);
426
            } else {
427
                $className = "\\Dolibarr\\Modules\\" . $modName;
428
                $objMod = new $className($db);
429
            }
430
431
            // Load all lang files of module
432
            if (isset($objMod->langfiles) && is_array($objMod->langfiles)) {
433
                foreach ($objMod->langfiles as $domain) {
434
                    $langs->load($domain);
435
                }
436
            }
437
            // Load all permissions
438
            if ($objMod->rights_class) {
439
                $ret = $objMod->insert_permissions(0, $entity);
440
                $modules[$objMod->rights_class] = $objMod;
441
                //print "modules[".$objMod->rights_class."]=$objMod;";
442
            }
443
        }
444
445
        $db->commit();
446
447
        return $modules;
448
    }
449
450
    public static function getModules($modulesdir = null)
451
    {
452
        if (!isset($modulesdir)) {
453
            $modulesdir = dolGetModulesDirs();
454
        }
455
        return static::_getModules($modulesdir);
456
    }
457
    // We should but can't set this as abstract because this will make dolibarr hang
458
    // after migration due to old module not implementing. We must wait PHP is able to make
459
    // a try catch on Fatal error to manage this correctly.
460
    // We need constructor into function unActivateModule into admin.lib.php
461
462
    /**
463
     * Obtiene un array con los módulos instalados.
464
     * TODO: Sustituir donde se use:
465
     * if (is_readable($dir . $file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
466
     *
467
     * @param $modulesdir
468
     * @return array
469
     * @throws \Exception
470
     */
471
    private static function _getModules($modulesdir): array
472
    {
473
        $result = [];
474
        foreach ($modulesdir as $dir) {
475
            dol_syslog("Scan directory " . $dir . " for module descriptor files (XXX.php OR modXXX.class.php)");
476
            $handle = @opendir($dir);
477
            if (!is_resource($handle)) {
478
                dol_syslog("htdocs/admin/modules.php: Failed to open directory " . $dir . ". See permission and open_basedir option.", LOG_WARNING);
479
                continue;
480
            }
481
482
            while (($file = readdir($handle)) !== false) {
483
                $filename = $dir . $file;
484
                if (is_dir($filename) || !is_readable($filename)) {
485
                    continue;
486
                }
487
488
                $new_type = !str_starts_with($file, 'mod') && str_ends_with($file, '.php');
489
                $old_type = str_starts_with($file, 'mod') && str_ends_with($file, '.class.php');
490
491
                if (!($new_type || $old_type)) {
492
                    continue;
493
                }
494
495
                if ($new_type) {
496
                    $className = substr($file, 0, strrpos($file, '.'));
497
                } else {
498
                    $className = substr($file, 0, dol_strlen($file) - 10);
499
                }
500
501
                if (isset($result[$className])) {
502
                    continue;
503
                }
504
505
                $result[$className] = $filename;
506
            }
507
            closedir($handle);
508
        }
509
        return $result;
510
    }
511
512
    /**
513
     * Adds access rights
514
     *
515
     * @param int $reinitadminperms If 1, we also grant them to all admin users
516
     * @param int $force_entity Force current entity
517
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
518
     * @return int                      Error count (0 if OK)
519
     */
520
    public function insert_permissions($reinitadminperms = 0, $force_entity = null, $notrigger = 0)
521
    {
522
        // phpcs:enable
523
        global $conf, $user;
524
525
        $err = 0;
526
        $entity = (!empty($force_entity) ? $force_entity : $conf->entity);
527
528
        dol_syslog(get_class($this) . "::insert_permissions", LOG_DEBUG);
529
530
        // Test if module is activated
531
        $sql_del = "SELECT " . $this->db->decrypt('value') . " as value";
532
        $sql_del .= " FROM " . MAIN_DB_PREFIX . "const";
533
        $sql_del .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($this->const_name) . "'";
534
        $sql_del .= " AND entity IN (0," . ((int)$entity) . ")";
535
536
        $resql = $this->db->query($sql_del);
537
538
        if ($resql) {
539
            $obj = $this->db->fetch_object($resql);
540
541
            if ($obj !== null && !empty($obj->value) && !empty($this->rights)) {
542
                include_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php';
543
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_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_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_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_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
            $this->db->free($resql);
672
        } else {
673
            $this->error = $this->db->lasterror();
674
            $err++;
675
        }
676
677
        return $err;
678
    }
679
680
    public static function forceDeactivate($modName)
681
    {
682
        // phpcs:enable
683
        global $conf, $db;
684
685
        $name = static::getNameOf($modName);
686
        $entity = $conf->entity;
687
688
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
689
        $sql .= " WHERE " . $db->decrypt('name') . " = '" . $name . "'";
690
        $sql .= " AND entity IN (0, " . $entity . ")";
691
692
        dump(['sql in DolibarrModules::forceDeactivate' => $sql]);
693
694
        dol_syslog($name . "::_unactive", LOG_DEBUG);
695
        $db->query($sql);
696
    }
697
698
    public static function getNameOf($modName)
699
    {
700
        /**
701
         * Find the last occurrence of a double backslash, to remove the namespace (if it exists)
702
         */
703
        $name = strrchr($modName, '\\');
704
        $name = $name ? ltrim($name, '\\') : $modName;
705
706
        /**
707
         * If the name is preceded by 'mod', 'mod' is removed.
708
         */
709
        if (str_starts_with($modName, 'mod')) {
710
            $name = substr($name, 3);
711
        }
712
713
        return strtoupper($name);
714
    }
715
716
    public static function isActivated($modName)
717
    {
718
        $name = 'MAIN_MODULE_' . static::getNameOf($modName);
719
        return !empty(getDolGlobalString($name));
720
    }
721
722
    /**
723
     * Obtains an instance of $modName, or null.
724
     *
725
     * @param $modName
726
     * @return DolibarrModules
727
     */
728
    public static function getModule($modName): ?DolibarrModules
729
    {
730
        global $db;
731
732
        $modules = static::getModules();
733
        if (str_starts_with($modName, 'mod')) {
734
            if (isset($modules[$modName]) && file_exists($modules[$modName])) {
735
                dump('FileExists ' . $modules[$modName] . ' for ' . $modName);
736
                include_once $modules[$modName];
737
                return new $modName($db);
738
            }
739
            $modName = str_replace('mod', '', $modName);
740
        }
741
742
        if (!isset($modules[$modName])) {
743
            return null;
744
        }
745
746
        $className = "\\Dolibarr\\Modules\\" . $modName;
747
        return new $className($db);
748
    }
749
750
    public static function getObj($db, $modName, $filename)
751
    {
752
        if (str_starts_with($modName, 'mod')) {
753
            include_once $filename;
754
            return new $modName($db);
755
        }
756
757
        $className = "Dolibarr\\Modules\\" . $modName;
758
        return $objMod = new $className($db);
759
    }
760
761
    public function isDevelopment()
762
    {
763
        return $this->version === 'development';
764
    }
765
766
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
767
768
    public function isExperimental()
769
    {
770
        return $this->version === 'development';
771
    }
772
773
    public function isDeprecated()
774
    {
775
        return str_contains($this->version, 'deprecated');
776
    }
777
778
    /**
779
     * Function called when module is disabled.
780
     * The remove function removes tabs, constants, boxes, permissions and menus from Dolibarr database.
781
     * Data directories are not deleted
782
     *
783
     * @param string $options Options when enabling module ('', 'noboxes')
784
     * @return int                     1 if OK, 0 if KO
785
     */
786
    public function remove($options = '')
787
    {
788
        return $this->_remove(array(), $options);
789
    }
790
791
    /**
792
     * Disable function. Deletes the module constants and boxes from the database.
793
     *
794
     * @param string[] $array_sql SQL requests to be executed when module is disabled
795
     * @param string $options Options when disabling module:
796
     *
797
     * @return int                     1 if OK, 0 if KO
798
     */
799
    protected function _remove($array_sql, $options = '')
800
    {
801
        global $conf;
802
803
        // phpcs:enable
804
        $err = 0;
805
806
        $this->db->begin();
807
808
        // Remove activation module line (constant MAIN_MODULE_MYMODULE in llx_const)
809
        if (!$err) {
810
            $err += $this->_unactive();
811
        }
812
813
        // Remove activation of module's new tabs (MAIN_MODULE_MYMODULE_TABS_XXX in llx_const)
814
        if (!$err) {
815
            $err += $this->delete_tabs();
816
        }
817
818
        // Remove activation of module's parts (MAIN_MODULE_MYMODULE_XXX in llx_const)
819
        if (!$err) {
820
            $err += $this->delete_module_parts();
821
        }
822
823
        // Remove constants defined by modules
824
        if (!$err) {
825
            $err += $this->delete_const();
826
        }
827
828
        // Remove list of module's available boxes (entry in llx_boxes)
829
        if (!$err && !preg_match('/(newboxdefonly|noboxes)/', $options)) {
830
            $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
831
        }
832
833
        // Remove list of module's cron job entries (entry in llx_cronjobs)
834
        if (!$err) {
835
            $err += $this->delete_cronjobs();
836
        }
837
838
        // Remove module's permissions from list of available permissions (entries in llx_rights_def)
839
        if (!$err) {
840
            $err += $this->delete_permissions();
841
        }
842
843
        // Remove module's menus (entries in llx_menu)
844
        if (!$err) {
845
            $err += $this->delete_menus();
846
        }
847
848
        // Remove module's directories
849
        if (!$err) {
850
            $err += $this->delete_dirs();
851
        }
852
853
        // Run complementary sql requests
854
        $num = count((array)$array_sql);
855
        for ($i = 0; $i < $num; $i++) {
856
            if (!$err) {
857
                dol_syslog(get_class($this) . "::_remove", LOG_DEBUG);
858
                $result = $this->db->query($array_sql[$i]);
859
                if (!$result) {
860
                    $this->error = $this->db->error();
861
                    $err++;
862
                }
863
            }
864
        }
865
866
        // Return code
867
        if (!$err) {
868
            $this->db->commit();
869
870
            // Disable modules
871
            $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
872
            // two exceptions to handle
873
            if ($moduleNameInConf === 'propale') {
874
                $moduleNameInConf = 'propal';
875
            } elseif ($moduleNameInConf === 'supplierproposal') {
876
                $moduleNameInConf = 'supplier_proposal';
877
            }
878
879
            unset($conf->modules[$moduleNameInConf]);   // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
880
881
            return 1;
882
        } else {
883
            $this->db->rollback();
884
            return 0;
885
        }
886
    }
887
888
    /**
889
     * Module deactivation
890
     *
891
     * @return int Error count (0 if OK)
892
     */
893
    protected function _unactive()
894
    {
895
        // phpcs:enable
896
        global $conf;
897
898
        $err = 0;
899
900
        // Common module
901
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
902
903
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
904
        $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($this->const_name) . "'";
905
        $sql .= " AND entity IN (0, " . $entity . ")";
906
907
        dol_syslog(get_class($this) . "::_unactive", LOG_DEBUG);
908
        $this->db->query($sql);
909
910
        return $err;
911
    }
912
913
    /**
914
     * Removes tabs
915
     *
916
     * @return int Error count (0 if OK)
917
     */
918
    public function delete_tabs()
919
    {
920
        // phpcs:enable
921
        global $conf;
922
923
        $err = 0;
924
925
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
926
        $sql .= " WHERE " . $this->db->decrypt('name') . " like '" . $this->db->escape($this->const_name) . "_TABS_%'";
927
        $sql .= " AND entity = " . $conf->entity;
928
929
        dol_syslog(get_class($this) . "::delete_tabs", LOG_DEBUG);
930
        if (!$this->db->query($sql)) {
931
            $this->error = $this->db->lasterror();
932
            $err++;
933
        }
934
935
        return $err;
936
    }
937
938
    /**
939
     * Removes generic parts
940
     *
941
     * @return int Error count (0 if OK)
942
     */
943
    public function delete_module_parts()
944
    {
945
        // phpcs:enable
946
        global $conf;
947
948
        $err = 0;
949
950
        if (is_array($this->module_parts)) {
951
            dol_syslog(get_class($this) . "::delete_module_parts", LOG_DEBUG);
952
953
            if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
954
                $this->module_parts['icon'] = $this->picto;
955
            }
956
957
            foreach ($this->module_parts as $key => $value) {
958
                // If entity is defined
959
                if (is_array($value) && isset($value['entity'])) {
960
                    $entity = $value['entity'];
961
                } else {
962
                    $entity = $conf->entity;
963
                }
964
965
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
966
                $sql .= " WHERE " . $this->db->decrypt('name') . " LIKE '" . $this->db->escape($this->const_name) . "_" . strtoupper($key) . "'";
967
                $sql .= " AND entity = " . ((int)$entity);
968
969
                if (!$this->db->query($sql)) {
970
                    $this->error = $this->db->lasterror();
971
                    $err++;
972
                }
973
            }
974
        }
975
        return $err;
976
    }
977
978
    /**
979
     * Removes constants tagged 'deleteonunactive'
980
     *
981
     * @return int Return integer <0 if KO, 0 if OK
982
     */
983
    public function delete_const()
984
    {
985
        // phpcs:enable
986
        global $conf;
987
988
        $err = 0;
989
990
        if (empty($this->const)) {
991
            return 0;
992
        }
993
994
        foreach ($this->const as $key => $value) {
995
            $name = $this->const[$key][0];
996
            $deleteonunactive = (!empty($this->const[$key][6])) ? 1 : 0;
997
998
            if ($deleteonunactive) {
999
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
1000
                $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($name) . "'";
1001
                $sql .= " AND entity in (0, " . $conf->entity . ")";
1002
                dol_syslog(get_class($this) . "::delete_const", LOG_DEBUG);
1003
                if (!$this->db->query($sql)) {
1004
                    $this->error = $this->db->lasterror();
1005
                    $err++;
1006
                }
1007
            }
1008
        }
1009
1010
        return $err;
1011
    }
1012
1013
    /**
1014
     * Removes boxes
1015
     *
1016
     * @return int Error count (0 if OK)
1017
     */
1018
    public function delete_boxes()
1019
    {
1020
        // phpcs:enable
1021
        global $conf;
1022
1023
        $err = 0;
1024
1025
        if (is_array($this->boxes)) {
1026
            foreach ($this->boxes as $key => $value) {
1027
                //$titre = $this->boxes[$key][0];
1028
                if (empty($this->boxes[$key]['file'])) {
1029
                    $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1030
                } else {
1031
                    $file = $this->boxes[$key]['file'];
1032
                }
1033
1034
                //$note  = $this->boxes[$key][2];
1035
1036
                // TODO If the box is also included by another module and the other module is still on, we should not remove it.
1037
                // For the moment, we manage this with hard coded exception
1038
                //print "Remove box ".$file.'<br>';
1039
                if ($file == 'box_graph_product_distribution.php') {
1040
                    if (isModEnabled("product") || isModEnabled("service")) {
1041
                        dol_syslog("We discard deleting module " . $file . " because another module still active requires it.");
1042
                        continue;
1043
                    }
1044
                }
1045
1046
                if ($this->db->type == 'sqlite3') {
1047
                    // sqlite doesn't support "USING" syntax.
1048
                    // TODO: remove this dependency.
1049
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes ";
1050
                    $sql .= "WHERE " . MAIN_DB_PREFIX . "boxes.box_id IN (";
1051
                    $sql .= "SELECT " . MAIN_DB_PREFIX . "boxes_def.rowid ";
1052
                    $sql .= "FROM " . MAIN_DB_PREFIX . "boxes_def ";
1053
                    $sql .= "WHERE " . MAIN_DB_PREFIX . "boxes_def.file = '" . $this->db->escape($file) . "') ";
1054
                    $sql .= "AND " . MAIN_DB_PREFIX . "boxes.entity = " . $conf->entity;
1055
                } else {
1056
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes";
1057
                    $sql .= " USING " . MAIN_DB_PREFIX . "boxes, " . MAIN_DB_PREFIX . "boxes_def";
1058
                    $sql .= " WHERE " . MAIN_DB_PREFIX . "boxes.box_id = " . MAIN_DB_PREFIX . "boxes_def.rowid";
1059
                    $sql .= " AND " . MAIN_DB_PREFIX . "boxes_def.file = '" . $this->db->escape($file) . "'";
1060
                    $sql .= " AND " . MAIN_DB_PREFIX . "boxes.entity = " . $conf->entity;
1061
                }
1062
1063
                dol_syslog(get_class($this) . "::delete_boxes", LOG_DEBUG);
1064
                $resql = $this->db->query($sql);
1065
                if (!$resql) {
1066
                    $this->error = $this->db->lasterror();
1067
                    $err++;
1068
                }
1069
1070
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "boxes_def";
1071
                $sql .= " WHERE file = '" . $this->db->escape($file) . "'";
1072
                $sql .= " AND entity = " . $conf->entity;     // Do not use getEntity here, we want to delete only in current company
1073
1074
                dol_syslog(get_class($this) . "::delete_boxes", LOG_DEBUG);
1075
                $resql = $this->db->query($sql);
1076
                if (!$resql) {
1077
                    $this->error = $this->db->lasterror();
1078
                    $err++;
1079
                }
1080
            }
1081
        }
1082
1083
        return $err;
1084
    }
1085
1086
    /**
1087
     * Removes boxes
1088
     *
1089
     * @return int Error count (0 if OK)
1090
     */
1091
    public function delete_cronjobs()
1092
    {
1093
        // phpcs:enable
1094
        global $conf;
1095
1096
        $err = 0;
1097
1098
        if (is_array($this->cronjobs)) {
1099
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "cronjob";
1100
            $sql .= " WHERE module_name = '" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "'";
1101
            $sql .= " AND entity = " . $conf->entity;
1102
            $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.
1103
            // 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.
1104
1105
            dol_syslog(get_class($this) . "::delete_cronjobs", LOG_DEBUG);
1106
            $resql = $this->db->query($sql);
1107
            if (!$resql) {
1108
                $this->error = $this->db->lasterror();
1109
                $err++;
1110
            }
1111
        }
1112
1113
        return $err;
1114
    }
1115
1116
    /**
1117
     * Removes access rights
1118
     *
1119
     * @return int                     Error count (0 if OK)
1120
     */
1121
    public function delete_permissions()
1122
    {
1123
        // phpcs:enable
1124
        global $conf;
1125
1126
        $err = 0;
1127
1128
        $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1129
1130
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "rights_def";
1131
        $sql .= " WHERE (module = '" . $this->db->escape($module) . "' OR module_origin = '" . $this->db->escape($module) . "')";
1132
1133
        // Delete all entities if core module
1134
        if (empty($this->core_enabled)) {
1135
            $sql .= " AND entity = " . ((int)$conf->entity);
1136
        }
1137
1138
        dol_syslog(get_class($this) . "::delete_permissions", LOG_DEBUG);
1139
        if (!$this->db->query($sql)) {
1140
            $this->error = $this->db->lasterror();
1141
            $err++;
1142
        }
1143
1144
        return $err;
1145
    }
1146
1147
    /**
1148
     * Removes menu entries
1149
     *
1150
     * @return int Error count (0 if OK)
1151
     */
1152
    public function delete_menus()
1153
    {
1154
        // phpcs:enable
1155
        global $conf;
1156
1157
        $err = 0;
1158
1159
        //$module=strtolower($this->name);        TODO When right_class will be same than module name
1160
        $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1161
1162
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "menu";
1163
        $sql .= " WHERE module = '" . $this->db->escape($module) . "'";
1164
        $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'
1165
        $sql .= " AND entity IN (0, " . $conf->entity . ")";
1166
1167
        dol_syslog(get_class($this) . "::delete_menus", LOG_DEBUG);
1168
        $resql = $this->db->query($sql);
1169
        if (!$resql) {
1170
            $this->error = $this->db->lasterror();
1171
            $err++;
1172
        }
1173
1174
        return $err;
1175
    }
1176
1177
    /**
1178
     * Removes directories
1179
     *
1180
     * @return int Error count (0 if OK)
1181
     */
1182
    public function delete_dirs()
1183
    {
1184
        // phpcs:enable
1185
        global $conf;
1186
1187
        $err = 0;
1188
1189
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
1190
        $sql .= " WHERE " . $this->db->decrypt('name') . " LIKE '" . $this->db->escape($this->const_name) . "_DIR_%'";
1191
        $sql .= " AND entity = " . $conf->entity;
1192
1193
        dol_syslog(get_class($this) . "::delete_dirs", LOG_DEBUG);
1194
        if (!$this->db->query($sql)) {
1195
            $this->error = $this->db->lasterror();
1196
            $err++;
1197
        }
1198
1199
        return $err;
1200
    }
1201
1202
    /**
1203
     * Gives the long description of a module. First check README-la_LA.md then README.md
1204
     * If no markdown files found, it returns translated value of the key ->descriptionlong.
1205
     *
1206
     * @return string     Long description of a module from README.md of from property.
1207
     */
1208
    public function getDescLong()
1209
    {
1210
        global $langs;
1211
        $langs->load("admin");
1212
1213
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1214
        include_once DOL_DOCUMENT_ROOT . '/core/lib/geturl.lib.php';
1215
1216
        $content = '';
1217
        $pathoffile = $this->getDescLongReadmeFound();
1218
1219
        if ($pathoffile) {     // Mostly for external modules
1220
            $content = file_get_contents($pathoffile, false, null, 0, 1024 * 1024); // Max size loaded 1Mb
1221
1222
            if ((float)DOL_VERSION >= 6.0) {
1223
                @include_once DOL_DOCUMENT_ROOT . '/core/lib/parsemd.lib.php';
1224
1225
                $content = dolMd2Html(
1226
                    $content,
1227
                    'parsedown',
1228
                    array(
1229
                        'doc/' => dol_buildpath(strtolower($this->name) . '/doc/', 1),
1230
                        'img/' => dol_buildpath(strtolower($this->name) . '/img/', 1),
1231
                        'images/' => dol_buildpath(strtolower($this->name) . '/images/', 1),
1232
                    )
1233
                );
1234
1235
                $content = preg_replace('/<a href="/', '<a target="_blank" rel="noopener noreferrer" href="', $content);
1236
            } else {
1237
                $content = nl2br($content);
1238
            }
1239
        } else {
1240
            // Mostly for internal modules
1241
            if (!empty($this->descriptionlong)) {
1242
                if (is_array($this->langfiles)) {
1243
                    foreach ($this->langfiles as $val) {
1244
                        if ($val) {
1245
                            $langs->load($val);
1246
                        }
1247
                    }
1248
                }
1249
1250
                $content = $langs->transnoentitiesnoconv($this->descriptionlong);
1251
            }
1252
        }
1253
1254
        return $content;
1255
    }
1256
1257
    /**
1258
     * Return path of file if a README file was found.
1259
     *
1260
     * @return string      Path of file if a README file was found.
1261
     */
1262
    public function getDescLongReadmeFound()
1263
    {
1264
        global $langs;
1265
1266
        $filefound = false;
1267
1268
        // Define path to file README.md.
1269
        // First check README-la_LA.md then README-la.md then README.md
1270
        $pathoffile = dol_buildpath(strtolower($this->name) . '/README-' . $langs->defaultlang . '.md', 0);
1271
        if (dol_is_file($pathoffile)) {
1272
            $filefound = true;
1273
        }
1274
        if (!$filefound) {
1275
            $tmp = explode('_', $langs->defaultlang);
1276
            $pathoffile = dol_buildpath(strtolower($this->name) . '/README-' . $tmp[0] . '.md', 0);
1277
            if (dol_is_file($pathoffile)) {
1278
                $filefound = true;
1279
            }
1280
        }
1281
        if (!$filefound) {
1282
            $pathoffile = dol_buildpath(strtolower($this->name) . '/README.md', 0);
1283
            if (dol_is_file($pathoffile)) {
1284
                $filefound = true;
1285
            }
1286
        }
1287
1288
        return ($filefound ? $pathoffile : '');
1289
    }
1290
1291
    /**
1292
     * Gives the changelog. First check ChangeLog-la_LA.md then ChangeLog.md
1293
     *
1294
     * @return string  Content of ChangeLog
1295
     */
1296
    public function getChangeLog()
1297
    {
1298
        global $langs;
1299
        $langs->load("admin");
1300
1301
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1302
        include_once DOL_DOCUMENT_ROOT . '/core/lib/geturl.lib.php';
1303
1304
        $filefound = false;
1305
1306
        // Define path to file README.md.
1307
        // First check ChangeLog-la_LA.md then ChangeLog.md
1308
        $pathoffile = dol_buildpath(strtolower($this->name) . '/ChangeLog-' . $langs->defaultlang . '.md', 0);
1309
        if (dol_is_file($pathoffile)) {
1310
            $filefound = true;
1311
        }
1312
        if (!$filefound) {
1313
            $pathoffile = dol_buildpath(strtolower($this->name) . '/ChangeLog.md', 0);
1314
            if (dol_is_file($pathoffile)) {
1315
                $filefound = true;
1316
            }
1317
        }
1318
1319
        if ($filefound) {     // Mostly for external modules
1320
            $content = file_get_contents($pathoffile);
1321
1322
            if ((float)DOL_VERSION >= 6.0) {
1323
                @include_once DOL_DOCUMENT_ROOT . '/core/lib/parsemd.lib.php';
1324
1325
                $content = dolMd2Html($content, 'parsedown', array('doc/' => dol_buildpath(strtolower($this->name) . '/doc/', 1)));
1326
            } else {
1327
                $content = nl2br($content);
1328
            }
1329
        } else {
1330
            $content = '';
1331
        }
1332
1333
        return $content;
1334
    }
1335
1336
1337
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1338
1339
    /**
1340
     * Gives the publisher name
1341
     *
1342
     * @return string  Publisher name
1343
     */
1344
    public function getPublisher()
1345
    {
1346
        return $this->editor_name;
1347
    }
1348
1349
1350
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1351
1352
    /**
1353
     * Gives the publisher url
1354
     *
1355
     * @return string  Publisher url
1356
     */
1357
    public function getPublisherUrl()
1358
    {
1359
        return $this->editor_url;
1360
    }
1361
1362
1363
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps,PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1364
1365
    /**
1366
     * Gives the module position
1367
     *
1368
     * @return string   Module position (an external module should never return a value lower than 100000. 1-100000 are reserved for core)
1369
     */
1370
    public function getModulePosition()
1371
    {
1372
        if (in_array($this->version, array('dolibarr', 'experimental', 'development'))) {   // core module
1373
            return $this->module_position;
1374
        } else {                                                                            // external module
1375
            if ($this->module_position >= 100000) {
1376
                return $this->module_position;
1377
            } else {
1378
                $position = intval($this->module_position) + 100000;
1379
                return strval($position);
1380
            }
1381
        }
1382
    }
1383
1384
1385
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1386
1387
    /**
1388
     * Gives module related language files list
1389
     *
1390
     * @return string[]    Language files list
1391
     */
1392
    public function getLangFilesArray()
1393
    {
1394
        return $this->langfiles;
1395
    }
1396
1397
1398
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1399
1400
    /**
1401
     * Gives translated label of an export dataset
1402
     *
1403
     * @param int $r Dataset index
1404
     *
1405
     * @return string       Translated databaset label
1406
     */
1407
    public function getExportDatasetLabel($r)
1408
    {
1409
        global $langs;
1410
1411
        $langstring = "ExportDataset_" . $this->export_code[$r];
1412
        if ($langs->trans($langstring) == $langstring) {
1413
            // Translation not found
1414
            return $langs->trans($this->export_label[$r]);
1415
        } else {
1416
            // Translation found
1417
            return $langs->trans($langstring);
1418
        }
1419
    }
1420
1421
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1422
1423
    /**
1424
     * Gives translated label of an import dataset
1425
     *
1426
     * @param int $r Dataset index
1427
     *
1428
     * @return string      Translated dataset label
1429
     */
1430
    public function getImportDatasetLabel($r)
1431
    {
1432
        global $langs;
1433
1434
        $langstring = "ImportDataset_" . $this->import_code[$r];
1435
        //print "x".$langstring;
1436
        if ($langs->trans($langstring) == $langstring) {
1437
            // Translation not found
1438
            return $langs->transnoentitiesnoconv($this->import_label[$r]);
1439
        } else {
1440
            // Translation found
1441
            return $langs->transnoentitiesnoconv($langstring);
1442
        }
1443
    }
1444
1445
1446
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1447
1448
    /**
1449
     * Gives the last date of activation
1450
     *
1451
     * @return  int|string          Date of last activation or '' if module was never activated
1452
     */
1453
    public function getLastActivationDate()
1454
    {
1455
        global $conf;
1456
1457
        $err = 0;
1458
1459
        $sql = "SELECT tms FROM " . MAIN_DB_PREFIX . "const";
1460
        $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($this->const_name) . "'";
1461
        $sql .= " AND entity IN (0, " . ((int)$conf->entity) . ")";
1462
1463
        dol_syslog(get_class($this) . "::getLastActiveDate", LOG_DEBUG);
1464
        $resql = $this->db->query($sql);
1465
        if (!$resql) {
1466
            $err++;
1467
        } else {
1468
            $obj = $this->db->fetch_object($resql);
1469
            if ($obj) {
1470
                return $this->db->jdate($obj->tms);
1471
            }
1472
        }
1473
1474
        return '';
1475
    }
1476
1477
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1478
1479
    /**
1480
     * Gives the last author of activation
1481
     *
1482
     * @return array       Array array('authorid'=>Id of last activation user, 'lastactivationdate'=>Date of last activation)
1483
     */
1484
    public function getLastActivationInfo()
1485
    {
1486
        global $conf;
1487
1488
        $err = 0;
1489
1490
        $sql = "SELECT tms, note FROM " . MAIN_DB_PREFIX . "const";
1491
        $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($this->const_name) . "'";
1492
        $sql .= " AND entity IN (0, " . $conf->entity . ")";
1493
1494
        dol_syslog(get_class($this) . "::getLastActiveDate", LOG_DEBUG);
1495
        $resql = $this->db->query($sql);
1496
        if (!$resql) {
1497
            $err++;
1498
        } else {
1499
            $obj = $this->db->fetch_object($resql);
1500
            if ($obj) {
1501
                $tmp = array();
1502
                if ($obj->note) {
1503
                    $tmp = json_decode($obj->note, true);
1504
                }
1505
                return array(
1506
                    'authorid' => empty($tmp['authorid']) ? '' : $tmp['authorid'],
1507
                    'ip' => empty($tmp['ip']) ? '' : $tmp['ip'],
1508
                    'lastactivationdate' => $this->db->jdate($obj->tms),
1509
                    'lastactivationversion' => (!empty($tmp['lastactivationversion']) ? $tmp['lastactivationversion'] : 'unknown'),
1510
                );
1511
            }
1512
        }
1513
1514
        return array();
1515
    }
1516
1517
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1518
1519
    /**
1520
     * Function called when module is enabled.
1521
     * The init function adds tabs, constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
1522
     * It also creates data directories
1523
     *
1524
     * @param string $options Options when enabling module ('', 'newboxdefonly', 'noboxes', 'menuonly')
1525
     *                         'noboxes' = Do not insert boxes 'newboxdefonly' = For boxes, insert def of boxes only and not boxes activation
1526
     * @return int                1 if OK, 0 if KO
1527
     */
1528
    public function init($options = '')
1529
    {
1530
        return $this->_init(array(), $options);
1531
    }
1532
1533
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1534
1535
    /**
1536
     * Enables a module.
1537
     * Inserts all information into database.
1538
     *
1539
     * @param array $array_sql SQL requests to be executed when enabling module
1540
     * @param string $options String with options when disabling module:
1541
     *                              - 'noboxes' = Do all actions but do not insert boxes
1542
     *                              - 'newboxdefonly' = Do all actions but for boxes, insert def of boxes only and not boxes activation
1543
     * @return int                  1 if OK, 0 if KO
1544
     */
1545
    protected function _init($array_sql, $options = '')
1546
    {
1547
        // phpcs:enable
1548
        global $conf;
1549
        $err = 0;
1550
1551
        $this->db->begin();
1552
1553
        // Insert activation module constant
1554
        if (!$err) {
1555
            $err += $this->_active();
1556
        }
1557
1558
        // Insert new pages for tabs (into llx_const)
1559
        if (!$err) {
1560
            $err += $this->insert_tabs();
1561
        }
1562
1563
        // Insert activation of module's parts. Copy website templates into doctemplates.
1564
        if (!$err) {
1565
            $err += $this->insert_module_parts();
1566
        }
1567
1568
        // Insert constant defined by modules (into llx_const)
1569
        if (!$err && !preg_match('/newboxdefonly/', $options)) {
1570
            $err += $this->insert_const(); // Test on newboxdefonly to avoid to erase value during upgrade
1571
        }
1572
1573
        // Insert boxes def (into llx_boxes_def) and boxes setup (into llx_boxes)
1574
        if (!$err && !preg_match('/noboxes/', $options)) {
1575
            $err += $this->insert_boxes($options);
1576
        }
1577
1578
        // Insert cron job entries (entry in llx_cronjobs)
1579
        if (!$err) {
1580
            $err += $this->insert_cronjobs();
1581
        }
1582
1583
        // Insert permission definitions of module into llx_rights_def. If user is admin, grant this permission to user.
1584
        if (!$err) {
1585
            $err += $this->insert_permissions(1, null, 1);
1586
        }
1587
1588
        // Insert specific menus entries into database
1589
        if (!$err) {
1590
            $err += $this->insert_menus();
1591
        }
1592
1593
        // Create module's directories
1594
        if (!$err) {
1595
            $err += $this->create_dirs();
1596
        }
1597
1598
        // Execute addons requests
1599
        $num = count($array_sql);
1600
        for ($i = 0; $i < $num; $i++) {
1601
            if (!$err) {
1602
                $val = $array_sql[$i];
1603
                $sql = $val;
1604
                $ignoreerror = 0;
1605
                if (is_array($val)) {
1606
                    $sql = $val['sql'];
1607
                    $ignoreerror = $val['ignoreerror'];
1608
                }
1609
                // Add current entity id
1610
                $sql = str_replace('__ENTITY__', (string)$conf->entity, $sql);
1611
1612
                dol_syslog(get_class($this) . "::_init ignoreerror=" . $ignoreerror, LOG_DEBUG);
1613
                $result = $this->db->query($sql, $ignoreerror);
1614
                if (!$result) {
1615
                    if (!$ignoreerror) {
1616
                        $this->error = $this->db->lasterror();
1617
                        $err++;
1618
                    } else {
1619
                        dol_syslog(get_class($this) . "::_init Warning " . $this->db->lasterror(), LOG_WARNING);
1620
                    }
1621
                }
1622
            }
1623
        }
1624
1625
        // Return code
1626
        if (!$err) {
1627
            $this->db->commit();
1628
1629
            $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
1630
            // two exceptions to handle
1631
            if ($moduleNameInConf === 'propale') {
1632
                $moduleNameInConf = 'propal';
1633
            } elseif ($moduleNameInConf === 'supplierproposal') {
1634
                $moduleNameInConf = 'supplier_proposal';
1635
            }
1636
1637
            $conf->modules[$moduleNameInConf] = $moduleNameInConf; // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
1638
1639
            return 1;
1640
        } else {
1641
            $this->db->rollback();
1642
            return 0;
1643
        }
1644
    }
1645
1646
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1647
1648
    /**
1649
     * Insert constants for module activation
1650
     *
1651
     * @return int Error count (0 if OK)
1652
     */
1653
    protected function _active()
1654
    {
1655
        // phpcs:enable
1656
        global $conf, $user;
1657
1658
        $err = 0;
1659
1660
        // Common module
1661
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1662
1663
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "const";
1664
        $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($this->const_name) . "'";
1665
        $sql .= " AND entity IN (0, " . $entity . ")";
1666
1667
        dol_syslog(get_class($this) . "::_active delete activation constant", LOG_DEBUG);
1668
        $resql = $this->db->query($sql);
1669
        if (!$resql) {
1670
            $err++;
1671
        }
1672
1673
        $note = json_encode(
1674
            array(
1675
                'authorid' => (is_object($user) ? $user->id : 0),
1676
                'ip' => (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']),
1677
                'lastactivationversion' => $this->version,
1678
            )
1679
        );
1680
1681
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "const (name, value, visible, entity, note) VALUES";
1682
        $sql .= " (" . $this->db->encrypt($this->const_name);
1683
        $sql .= ", " . $this->db->encrypt('1');
1684
        $sql .= ", 0, " . ((int)$entity);
1685
        $sql .= ", '" . $this->db->escape($note) . "')";
1686
1687
        dol_syslog(get_class($this) . "::_active insert activation constant", LOG_DEBUG);
1688
        $resql = $this->db->query($sql);
1689
        if (!$resql) {
1690
            $err++;
1691
        }
1692
1693
        return $err;
1694
    }
1695
1696
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1697
1698
    /**
1699
     * Adds tabs
1700
     *
1701
     * @return int  Error count (0 if ok)
1702
     */
1703
    public function insert_tabs()
1704
    {
1705
        // phpcs:enable
1706
        global $conf;
1707
1708
        $err = 0;
1709
1710
        if (!empty($this->tabs)) {
1711
            dol_syslog(get_class($this) . "::insert_tabs", LOG_DEBUG);
1712
1713
            $i = 0;
1714
            foreach ($this->tabs as $key => $value) {
1715
                if (is_array($value) && count($value) == 0) {
1716
                    continue; // Discard empty arrays
1717
                }
1718
1719
                $entity = $conf->entity;
1720
                $newvalue = $value;
1721
1722
                if (is_array($value)) {
1723
                    $newvalue = $value['data'];
1724
                    if (isset($value['entity'])) {
1725
                        $entity = $value['entity'];
1726
                    }
1727
                }
1728
1729
                if ($newvalue) {
1730
                    $sql = "INSERT INTO " . MAIN_DB_PREFIX . "const (";
1731
                    $sql .= "name";
1732
                    $sql .= ", type";
1733
                    $sql .= ", value";
1734
                    $sql .= ", note";
1735
                    $sql .= ", visible";
1736
                    $sql .= ", entity";
1737
                    $sql .= ")";
1738
                    $sql .= " VALUES (";
1739
                    $sql .= $this->db->encrypt($this->const_name . "_TABS_" . $i);
1740
                    $sql .= ", 'chaine'";
1741
                    $sql .= ", " . $this->db->encrypt($newvalue);
1742
                    $sql .= ", null";
1743
                    $sql .= ", '0'";
1744
                    $sql .= ", " . ((int)$entity);
1745
                    $sql .= ")";
1746
1747
                    $resql = $this->db->query($sql);
1748
                    if (!$resql) {
1749
                        dol_syslog($this->db->lasterror(), LOG_ERR);
1750
                        if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1751
                            $this->error = $this->db->lasterror();
1752
                            $this->errors[] = $this->db->lasterror();
1753
                            $err++;
1754
                            break;
1755
                        }
1756
                    }
1757
                }
1758
                $i++;
1759
            }
1760
        }
1761
        return $err;
1762
    }
1763
1764
1765
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1766
1767
    /**
1768
     * Save configuration for generic features.
1769
     * This also generate website templates if the module provide some.
1770
     *
1771
     * @return int Error count (0 if OK)
1772
     */
1773
    public function insert_module_parts()
1774
    {
1775
        // phpcs:enable
1776
        global $conf, $langs;
1777
1778
        $error = 0;
1779
1780
        if (is_array($this->module_parts)) {
1781
            if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
1782
                $this->module_parts['icon'] = $this->picto;
1783
            }
1784
1785
            foreach ($this->module_parts as $key => $value) {
1786
                if (is_array($value) && count($value) == 0) {
1787
                    continue; // Discard empty arrays
1788
                }
1789
1790
                // If module brings website templates, we must generate the zip like we do whenenabling the website module
1791
                if ($key == 'websitetemplates' && $value == 1) {
1792
                    $srcroot = dol_buildpath('/' . strtolower($this->name) . '/doctemplates/websites');
1793
1794
                    // Copy templates in dir format (recommended) into zip file
1795
                    $docs = dol_dir_list($srcroot, 'directories', 0, 'website_.*$');
1796
                    foreach ($docs as $cursorfile) {
1797
                        $src = $srcroot . '/' . $cursorfile['name'];
1798
                        $dest = DOL_DATA_ROOT . '/doctemplates/websites/' . $cursorfile['name'];
1799
1800
                        dol_delete_file($dest . '.zip');
1801
1802
                        // Compress it
1803
                        global $errormsg;
1804
                        $errormsg = '';
1805
                        $result = dol_compress_dir($src, $dest . '.zip', 'zip');
1806
                        if ($result < 0) {
1807
                            $error++;
1808
                            $this->error = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
1809
                            $this->errors[] = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
1810
                        }
1811
                    }
1812
1813
                    // Copy also the preview website_xxx.jpg file
1814
                    $docs = dol_dir_list($srcroot, 'files', 0, 'website_.*\.jpg$');
1815
                    foreach ($docs as $cursorfile) {
1816
                        $src = $srcroot . '/' . $cursorfile['name'];
1817
                        $dest = DOL_DATA_ROOT . '/doctemplates/websites/' . $cursorfile['name'];
1818
1819
                        dol_copy($src, $dest);
1820
                    }
1821
                }
1822
1823
                $entity = $conf->entity; // Reset the current entity
1824
                $newvalue = $value;
1825
1826
                // Serialize array parameters
1827
                if (is_array($value)) {
1828
                    // Can defined other parameters
1829
                    // Example when $key='hooks', then $value is an array('data'=>array('hookcontext1','hookcontext2'), 'entity'=>X)
1830
                    if (isset($value['data']) && is_array($value['data'])) {
1831
                        $newvalue = json_encode($value['data']);
1832
                        if (isset($value['entity'])) {
1833
                            $entity = $value['entity'];
1834
                        }
1835
                    } elseif (isset($value['data']) && !is_array($value['data'])) {
1836
                        $newvalue = $value['data'];
1837
                        if (isset($value['entity'])) {
1838
                            $entity = $value['entity'];
1839
                        }
1840
                    } else { // when hook is declared with syntax 'hook'=>array('hookcontext1','hookcontext2',...)
1841
                        $newvalue = json_encode($value);
1842
                    }
1843
                }
1844
1845
                if (!empty($newvalue)) {
1846
                    $sql = "INSERT INTO " . MAIN_DB_PREFIX . "const (";
1847
                    $sql .= "name";
1848
                    $sql .= ", type";
1849
                    $sql .= ", value";
1850
                    $sql .= ", note";
1851
                    $sql .= ", visible";
1852
                    $sql .= ", entity";
1853
                    $sql .= ")";
1854
                    $sql .= " VALUES (";
1855
                    $sql .= " " . $this->db->encrypt($this->const_name . "_" . strtoupper($key), 1);
1856
                    $sql .= ", 'chaine'";
1857
                    $sql .= ", " . $this->db->encrypt($newvalue, 1);
1858
                    $sql .= ", null";
1859
                    $sql .= ", '0'";
1860
                    $sql .= ", " . ((int)$entity);
1861
                    $sql .= ")";
1862
1863
                    dol_syslog(get_class($this) . "::insert_module_parts for key=" . $this->const_name . "_" . strtoupper($key), LOG_DEBUG);
1864
1865
                    $resql = $this->db->query($sql, 1);
1866
                    if (!$resql) {
1867
                        if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1868
                            $error++;
1869
                            $this->error = $this->db->lasterror();
1870
                        } else {
1871
                            dol_syslog(get_class($this) . "::insert_module_parts for " . $this->const_name . "_" . strtoupper($key) . " Record already exists.", LOG_WARNING);
1872
                        }
1873
                    }
1874
                }
1875
            }
1876
        }
1877
        return $error;
1878
    }
1879
1880
1881
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1882
1883
    /**
1884
     * Adds constants
1885
     *
1886
     * @return int Error count (0 if OK)
1887
     */
1888
    public function insert_const()
1889
    {
1890
        // phpcs:enable
1891
        global $conf;
1892
1893
        $err = 0;
1894
1895
        if (empty($this->const)) {
1896
            return 0;
1897
        }
1898
1899
        dol_syslog(__METHOD__, LOG_DEBUG);
1900
1901
        foreach ($this->const as $key => $value) {
1902
            $name = $this->const[$key][0];
1903
            $type = $this->const[$key][1];
1904
            $val = $this->const[$key][2];
1905
            $note = isset($this->const[$key][3]) ? $this->const[$key][3] : '';
1906
            $visible = isset($this->const[$key][4]) ? $this->const[$key][4] : 0;
1907
            $entity = (!empty($this->const[$key][5]) && $this->const[$key][5] != 'current') ? 0 : $conf->entity;
1908
1909
            // Clean
1910
            if (empty($visible)) {
1911
                $visible = '0';
1912
            }
1913
            if (empty($val) && $val != '0') {
1914
                $val = '';
1915
            }
1916
1917
            $sql = "SELECT count(*) as nb";
1918
            $sql .= " FROM " . MAIN_DB_PREFIX . "const";
1919
            $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($name) . "'";
1920
            $sql .= " AND entity = " . ((int)$entity);
1921
1922
            $result = $this->db->query($sql);
1923
            if ($result) {
1924
                $row = $this->db->fetch_row($result);
1925
1926
                if ($row[0] == 0) {   // If not found
1927
                    $sql = "INSERT INTO " . MAIN_DB_PREFIX . "const (name,type,value,note,visible,entity)";
1928
                    $sql .= " VALUES (";
1929
                    $sql .= $this->db->encrypt($name);
1930
                    $sql .= ",'" . $this->db->escape($type) . "'";
1931
                    $sql .= "," . (($val != '') ? $this->db->encrypt($val) : "''");
1932
                    $sql .= "," . ($note ? "'" . $this->db->escape($note) . "'" : "null");
1933
                    $sql .= ",'" . $this->db->escape($visible) . "'";
1934
                    $sql .= "," . $entity;
1935
                    $sql .= ")";
1936
1937
                    if (!$this->db->query($sql)) {
1938
                        $err++;
1939
                    }
1940
                } else {
1941
                    dol_syslog(__METHOD__ . " constant '" . $name . "' already exists", LOG_DEBUG);
1942
                }
1943
            } else {
1944
                $err++;
1945
            }
1946
        }
1947
1948
        return $err;
1949
    }
1950
1951
1952
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1953
1954
    /**
1955
     * Adds boxes
1956
     *
1957
     * @param string $option Options when disabling module ('newboxdefonly'=insert only boxes definition)
1958
     *
1959
     * @return int             Error count (0 if OK)
1960
     */
1961
    public function insert_boxes($option = '')
1962
    {
1963
        // phpcs:enable
1964
        include_once DOL_DOCUMENT_ROOT . '/core/class/infobox.class.php';
1965
1966
        global $conf;
1967
1968
        $err = 0;
1969
1970
        if (is_array($this->boxes)) {
1971
            dol_syslog(get_class($this) . "::insert_boxes", LOG_DEBUG);
1972
1973
            $pos_name = InfoBox::getListOfPagesForBoxes();
1974
1975
            foreach ($this->boxes as $key => $value) {
1976
                $file = isset($this->boxes[$key]['file']) ? $this->boxes[$key]['file'] : '';
1977
                $note = isset($this->boxes[$key]['note']) ? $this->boxes[$key]['note'] : '';
1978
                $enabledbydefaulton = isset($this->boxes[$key]['enabledbydefaulton']) ? $this->boxes[$key]['enabledbydefaulton'] : 'Home';
1979
1980
                if (empty($file)) {
1981
                    $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1982
                }
1983
                if (empty($note)) {
1984
                    $note = isset($this->boxes[$key][2]) ? $this->boxes[$key][2] : ''; // For backward compatibility
1985
                }
1986
1987
                // Search if boxes def already present
1988
                $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "boxes_def";
1989
                $sql .= " WHERE file = '" . $this->db->escape($file) . "'";
1990
                $sql .= " AND entity = " . $conf->entity;
1991
                if ($note) {
1992
                    $sql .= " AND note ='" . $this->db->escape($note) . "'";
1993
                }
1994
1995
                $result = $this->db->query($sql);
1996
                if ($result) {
1997
                    $obj = $this->db->fetch_object($result);
1998
                    if ($obj->nb == 0) {
1999
                        $this->db->begin();
2000
2001
                        if (!$err) {
2002
                            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "boxes_def (file, entity, note)";
2003
                            $sql .= " VALUES ('" . $this->db->escape($file) . "', ";
2004
                            $sql .= $conf->entity . ", ";
2005
                            $sql .= $note ? "'" . $this->db->escape($note) . "'" : "null";
2006
                            $sql .= ")";
2007
2008
                            dol_syslog(get_class($this) . "::insert_boxes", LOG_DEBUG);
2009
                            $resql = $this->db->query($sql);
2010
                            if (!$resql) {
2011
                                $err++;
2012
                            }
2013
                        }
2014
                        if (!$err && !preg_match('/newboxdefonly/', $option)) {
2015
                            $lastid = $this->db->last_insert_id(MAIN_DB_PREFIX . "boxes_def", "rowid");
2016
2017
                            foreach ($pos_name as $key2 => $val2) {
2018
                                //print 'key2='.$key2.'-val2='.$val2."<br>\n";
2019
                                if ($enabledbydefaulton && $val2 != $enabledbydefaulton) {
2020
                                    continue; // Not enabled by default onto this page.
2021
                                }
2022
2023
                                $sql = "INSERT INTO " . MAIN_DB_PREFIX . "boxes (box_id, position, box_order, fk_user, entity)";
2024
                                $sql .= " VALUES (" . ((int)$lastid) . ", " . ((int)$key2) . ", '0', 0, " . ((int)$conf->entity) . ")";
2025
2026
                                dol_syslog(get_class($this) . "::insert_boxes onto page " . $key2 . "=" . $val2, LOG_DEBUG);
2027
                                $resql = $this->db->query($sql);
2028
                                if (!$resql) {
2029
                                    $err++;
2030
                                }
2031
                            }
2032
                        }
2033
2034
                        if (!$err) {
2035
                            $this->db->commit();
2036
                        } else {
2037
                            $this->error = $this->db->lasterror();
2038
                            $this->db->rollback();
2039
                        }
2040
                    }
2041
                    // else box already registered into database
2042
                } else {
2043
                    $this->error = $this->db->lasterror();
2044
                    $err++;
2045
                }
2046
            }
2047
        }
2048
2049
        return $err;
2050
    }
2051
2052
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2053
2054
    /**
2055
     * Adds cronjobs
2056
     *
2057
     * @return int             Error count (0 if OK)
2058
     */
2059
    public function insert_cronjobs()
2060
    {
2061
        // phpcs:enable
2062
        include_once DOL_DOCUMENT_ROOT . '/core/class/infobox.class.php';
2063
2064
        global $conf;
2065
2066
        $err = 0;
2067
2068
        if (is_array($this->cronjobs)) {
2069
            dol_syslog(get_class($this) . "::insert_cronjobs", LOG_DEBUG);
2070
2071
            foreach ($this->cronjobs as $key => $value) {
2072
                $entity = isset($this->cronjobs[$key]['entity']) ? $this->cronjobs[$key]['entity'] : $conf->entity;
2073
                $label = isset($this->cronjobs[$key]['label']) ? $this->cronjobs[$key]['label'] : '';
2074
                $jobtype = isset($this->cronjobs[$key]['jobtype']) ? $this->cronjobs[$key]['jobtype'] : '';
2075
                $class = isset($this->cronjobs[$key]['class']) ? $this->cronjobs[$key]['class'] : '';
2076
                $objectname = isset($this->cronjobs[$key]['objectname']) ? $this->cronjobs[$key]['objectname'] : '';
2077
                $method = isset($this->cronjobs[$key]['method']) ? $this->cronjobs[$key]['method'] : '';
2078
                $command = isset($this->cronjobs[$key]['command']) ? $this->cronjobs[$key]['command'] : '';
2079
                $parameters = isset($this->cronjobs[$key]['parameters']) ? $this->cronjobs[$key]['parameters'] : '';
2080
                $comment = isset($this->cronjobs[$key]['comment']) ? $this->cronjobs[$key]['comment'] : '';
2081
                $frequency = isset($this->cronjobs[$key]['frequency']) ? $this->cronjobs[$key]['frequency'] : '';
2082
                $unitfrequency = isset($this->cronjobs[$key]['unitfrequency']) ? $this->cronjobs[$key]['unitfrequency'] : '';
2083
                $priority = isset($this->cronjobs[$key]['priority']) ? $this->cronjobs[$key]['priority'] : '';
2084
                $datestart = isset($this->cronjobs[$key]['datestart']) ? $this->cronjobs[$key]['datestart'] : '';
2085
                $dateend = isset($this->cronjobs[$key]['dateend']) ? $this->cronjobs[$key]['dateend'] : '';
2086
                $status = isset($this->cronjobs[$key]['status']) ? $this->cronjobs[$key]['status'] : '';
2087
                $test = isset($this->cronjobs[$key]['test']) ? $this->cronjobs[$key]['test'] : ''; // Line must be enabled or not (so visible or not)
2088
2089
                // Search if cron entry already present
2090
                $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "cronjob";
2091
                //$sql .= " WHERE module_name = '".$this->db->escape(empty($this->rights_class) ?strtolower($this->name) : $this->rights_class)."'";
2092
                $sql .= " WHERE label = '" . $this->db->escape($label) . "'";
2093
                /*if ($class) {
2094
                    $sql .= " AND classesname = '".$this->db->escape($class)."'";
2095
                }
2096
                if ($objectname) {
2097
                    $sql .= " AND objectname = '".$this->db->escape($objectname)."'";
2098
                }
2099
                if ($method) {
2100
                    $sql .= " AND methodename = '".$this->db->escape($method)."'";
2101
                }
2102
                if ($command) {
2103
                    $sql .= " AND command = '".$this->db->escape($command)."'";
2104
                }
2105
                if ($parameters) {
2106
                    $sql .= " AND params = '".$this->db->escape($parameters)."'";
2107
                }*/
2108
                $sql .= " AND entity = " . ((int)$entity); // Must be exact entity
2109
2110
                $now = dol_now();
2111
2112
                $result = $this->db->query($sql);
2113
                if ($result) {
2114
                    $obj = $this->db->fetch_object($result);
2115
                    if ($obj->nb == 0) {
2116
                        $this->db->begin();
2117
2118
                        if (!$err) {
2119
                            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "cronjob (module_name, datec, datestart, dateend, label, jobtype, classesname, objectname, methodename, command, params, note,";
2120
                            if (is_int($frequency)) {
2121
                                $sql .= ' frequency,';
2122
                            }
2123
                            if (is_int($unitfrequency)) {
2124
                                $sql .= ' unitfrequency,';
2125
                            }
2126
                            if (is_int($priority)) {
2127
                                $sql .= ' priority,';
2128
                            }
2129
                            if (is_int($status)) {
2130
                                $sql .= ' status,';
2131
                            }
2132
                            $sql .= " entity, test)";
2133
                            $sql .= " VALUES (";
2134
                            $sql .= "'" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "', ";
2135
                            $sql .= "'" . $this->db->idate($now) . "', ";
2136
                            $sql .= ($datestart ? "'" . $this->db->idate($datestart) . "'" : "'" . $this->db->idate($now) . "'") . ", ";
2137
                            $sql .= ($dateend ? "'" . $this->db->idate($dateend) . "'" : "NULL") . ", ";
2138
                            $sql .= "'" . $this->db->escape($label) . "', ";
2139
                            $sql .= "'" . $this->db->escape($jobtype) . "', ";
2140
                            $sql .= ($class ? "'" . $this->db->escape($class) . "'" : "null") . ",";
2141
                            $sql .= ($objectname ? "'" . $this->db->escape($objectname) . "'" : "null") . ",";
2142
                            $sql .= ($method ? "'" . $this->db->escape($method) . "'" : "null") . ",";
2143
                            $sql .= ($command ? "'" . $this->db->escape($command) . "'" : "null") . ",";
2144
                            $sql .= ($parameters ? "'" . $this->db->escape($parameters) . "'" : "null") . ",";
2145
                            $sql .= ($comment ? "'" . $this->db->escape($comment) . "'" : "null") . ",";
2146
                            if (is_int($frequency)) {
2147
                                $sql .= "'" . $this->db->escape($frequency) . "', ";
2148
                            }
2149
                            if (is_int($unitfrequency)) {
2150
                                $sql .= "'" . $this->db->escape($unitfrequency) . "', ";
2151
                            }
2152
                            if (is_int($priority)) {
2153
                                $sql .= "'" . $this->db->escape($priority) . "', ";
2154
                            }
2155
                            if (is_int($status)) {
2156
                                $sql .= ((int)$status) . ", ";
2157
                            }
2158
                            $sql .= $entity . ",";
2159
                            $sql .= "'" . $this->db->escape($test) . "'";
2160
                            $sql .= ")";
2161
2162
                            $resql = $this->db->query($sql);
2163
                            if (!$resql) {
2164
                                $err++;
2165
                            }
2166
                        }
2167
2168
                        if (!$err) {
2169
                            $this->db->commit();
2170
                        } else {
2171
                            $this->error = $this->db->lasterror();
2172
                            $this->db->rollback();
2173
                        }
2174
                    }
2175
                    // else box already registered into database
2176
                } else {
2177
                    $this->error = $this->db->lasterror();
2178
                    $err++;
2179
                }
2180
            }
2181
        }
2182
2183
        return $err;
2184
    }
2185
2186
2187
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2188
2189
    /**
2190
     * Adds menu entries
2191
     *
2192
     * @return int     Error count (0 if OK)
2193
     */
2194
    public function insert_menus()
2195
    {
2196
        // phpcs:enable
2197
        global $conf, $user;
2198
2199
        if (!is_array($this->menu) || empty($this->menu)) {
2200
            return 0;
2201
        }
2202
2203
        include_once DOL_DOCUMENT_ROOT . '/core/class/menubase.class.php';
2204
2205
        dol_syslog(get_class($this) . "::insert_menus", LOG_DEBUG);
2206
2207
        $err = 0;
2208
2209
        // Common module
2210
        $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
2211
2212
        $this->db->begin();
2213
2214
        foreach ($this->menu as $key => $value) {
2215
            $menu = new Menubase($this->db);
2216
            $menu->menu_handler = 'all';
2217
2218
            //$menu->module=strtolower($this->name);    TODO When right_class will be same than module name
2219
            $menu->module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2220
2221
            if (!$this->menu[$key]['fk_menu']) {
2222
                $menu->fk_menu = 0;
2223
            } else {
2224
                $foundparent = 0;
2225
                $fk_parent = $this->menu[$key]['fk_menu'];
2226
                $reg = array();
2227
                if (preg_match('/^r=/', $fk_parent)) {    // old deprecated method
2228
                    $fk_parent = str_replace('r=', '', $fk_parent);
2229
                    if (isset($this->menu[$fk_parent]['rowid'])) {
2230
                        $menu->fk_menu = $this->menu[$fk_parent]['rowid'];
2231
                        $foundparent = 1;
2232
                    }
2233
                } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+),fk_leftmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2234
                    $menu->fk_menu = -1;
2235
                    $menu->fk_mainmenu = $reg[1];
2236
                    $menu->fk_leftmenu = $reg[2];
2237
                    $foundparent = 1;
2238
                } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2239
                    $menu->fk_menu = -1;
2240
                    $menu->fk_mainmenu = $reg[1];
2241
                    $menu->fk_leftmenu = '';
2242
                    $foundparent = 1;
2243
                }
2244
                if (!$foundparent) {
2245
                    $this->error = "ErrorBadDefinitionOfMenuArrayInModuleDescriptor";
2246
                    dol_syslog(get_class($this) . "::insert_menus " . $this->error . " " . $this->menu[$key]['fk_menu'], LOG_ERR);
2247
                    $err++;
2248
                }
2249
            }
2250
            $menu->type = $this->menu[$key]['type'];
2251
            $menu->mainmenu = isset($this->menu[$key]['mainmenu']) ? $this->menu[$key]['mainmenu'] : (isset($menu->fk_mainmenu) ? $menu->fk_mainmenu : '');
2252
            $menu->leftmenu = isset($this->menu[$key]['leftmenu']) ? $this->menu[$key]['leftmenu'] : '';
2253
            $menu->title = $this->menu[$key]['titre'];
2254
            $menu->prefix = isset($this->menu[$key]['prefix']) ? $this->menu[$key]['prefix'] : '';
2255
            $menu->url = $this->menu[$key]['url'];
2256
            $menu->langs = isset($this->menu[$key]['langs']) ? $this->menu[$key]['langs'] : '';
2257
            $menu->position = $this->menu[$key]['position'];
2258
            $menu->perms = $this->menu[$key]['perms'];
2259
            $menu->target = isset($this->menu[$key]['target']) ? $this->menu[$key]['target'] : '';
2260
            $menu->user = $this->menu[$key]['user'];
2261
            $menu->enabled = isset($this->menu[$key]['enabled']) ? $this->menu[$key]['enabled'] : 0;
2262
            $menu->position = $this->menu[$key]['position'];
2263
            $menu->entity = $entity;
2264
2265
            if (!$err) {
2266
                $result = $menu->create($user); // Save menu entry into table llx_menu
2267
                if ($result > 0) {
2268
                    $this->menu[$key]['rowid'] = $result;
2269
                } else {
2270
                    $this->error = $menu->error;
2271
                    dol_syslog(get_class($this) . '::insert_menus result=' . $result . " " . $this->error, LOG_ERR);
2272
                    $err++;
2273
                    break;
2274
                }
2275
            }
2276
        }
2277
2278
        if (!$err) {
2279
            $this->db->commit();
2280
        } else {
2281
            dol_syslog(get_class($this) . "::insert_menus " . $this->error, LOG_ERR);
2282
            $this->db->rollback();
2283
        }
2284
2285
        return $err;
2286
    }
2287
2288
2289
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2290
2291
    /**
2292
     * Creates directories
2293
     *
2294
     * @return int Error count (0 if OK)
2295
     */
2296
    public function create_dirs()
2297
    {
2298
        // phpcs:enable
2299
        global $langs, $conf;
2300
2301
        $err = 0;
2302
        $name = '';
2303
2304
        if (isset($this->dirs) && is_array($this->dirs)) {
2305
            foreach ($this->dirs as $key => $value) {
2306
                $addtodatabase = 0;
2307
2308
                if (!is_array($value)) {
2309
                    $dir = $value; // Default simple mode
2310
                } else {
2311
                    $constname = $this->const_name . "_DIR_";
2312
                    $dir = $this->dirs[$key][1];
2313
                    $addtodatabase = empty($this->dirs[$key][2]) ? '' : $this->dirs[$key][2]; // Create constante in llx_const
2314
                    $subname = empty($this->dirs[$key][3]) ? '' : strtoupper($this->dirs[$key][3]); // Add submodule name (ex: $conf->module->submodule->dir_output)
2315
                    $forcename = empty($this->dirs[$key][4]) ? '' : strtoupper($this->dirs[$key][4]); // Change the module name if different
2316
2317
                    if (!empty($forcename)) {
2318
                        $constname = 'MAIN_MODULE_' . $forcename . "_DIR_";
2319
                    }
2320
                    if (!empty($subname)) {
2321
                        $constname = $constname . $subname . "_";
2322
                    }
2323
2324
                    $name = $constname . strtoupper($this->dirs[$key][0]);
2325
                }
2326
2327
                // Define directory full path ($dir must start with "/")
2328
                if (!getDolGlobalString('MAIN_MODULE_MULTICOMPANY') || $conf->entity == 1) {
2329
                    $fulldir = DOL_DATA_ROOT . $dir;
2330
                } else {
2331
                    $fulldir = DOL_DATA_ROOT . "/" . $conf->entity . $dir;
2332
                }
2333
                // Create dir if it does not exists
2334
                if (!empty($fulldir) && !file_exists($fulldir)) {
2335
                    if (dol_mkdir($fulldir, DOL_DATA_ROOT) < 0) {
2336
                        $this->error = $langs->trans("ErrorCanNotCreateDir", $fulldir);
2337
                        dol_syslog(get_class($this) . "::_init " . $this->error, LOG_ERR);
2338
                        $err++;
2339
                    }
2340
                }
2341
2342
                // Define the constant in database if requested (not the default mode)
2343
                if (!empty($addtodatabase) && !empty($name)) {
2344
                    $result = $this->insert_dirs($name, $dir);
2345
                    if ($result) {
2346
                        $err++;
2347
                    }
2348
                }
2349
            }
2350
        }
2351
2352
        return $err;
2353
    }
2354
2355
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2356
2357
    /**
2358
     * Adds directories definitions
2359
     *
2360
     * @param string $name Name
2361
     * @param string $dir Directory
2362
     *
2363
     * @return int             Error count (0 if OK)
2364
     */
2365
    public function insert_dirs($name, $dir)
2366
    {
2367
        // phpcs:enable
2368
        global $conf;
2369
2370
        $err = 0;
2371
2372
        $sql = "SELECT count(*)";
2373
        $sql .= " FROM " . MAIN_DB_PREFIX . "const";
2374
        $sql .= " WHERE " . $this->db->decrypt('name') . " = '" . $this->db->escape($name) . "'";
2375
        $sql .= " AND entity = " . $conf->entity;
2376
2377
        dol_syslog(get_class($this) . "::insert_dirs", LOG_DEBUG);
2378
        $result = $this->db->query($sql);
2379
        if ($result) {
2380
            $row = $this->db->fetch_row($result);
2381
2382
            if ($row[0] == 0) {
2383
                $sql = "INSERT INTO " . MAIN_DB_PREFIX . "const (name, type, value, note, visible, entity)";
2384
                $sql .= " VALUES (" . $this->db->encrypt($name) . ", 'chaine', " . $this->db->encrypt($dir) . ", '" . $this->db->escape("Directory for module " . $this->name) . "', '0', " . ((int)$conf->entity) . ")";
2385
2386
                dol_syslog(get_class($this) . "::insert_dirs", LOG_DEBUG);
2387
                $this->db->query($sql);
2388
            }
2389
        } else {
2390
            $this->error = $this->db->lasterror();
2391
            $err++;
2392
        }
2393
2394
        return $err;
2395
    }
2396
2397
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2398
2399
    /**
2400
     * Return Kanban view of a module
2401
     *
2402
     * @param string $codeenabledisable HTML code for button to enable/disable module
2403
     * @param string $codetoconfig HTML code to go to config page
2404
     * @return  string                          HTML code of Kanban view
2405
     */
2406
    public function getKanbanView($codeenabledisable = '', $codetoconfig = '')
2407
    {
2408
        global $langs;
2409
2410
        // Define imginfo
2411
        $imginfo = "info";
2412
        if ($this->isCoreOrExternalModule() == 'external') {
2413
            $imginfo = "info_black";
2414
        }
2415
2416
        $const_name = 'MAIN_MODULE_' . strtoupper(preg_replace('/^mod/i', '', get_class($this)));
2417
2418
        $version = $this->getVersion(0);
2419
        $versiontrans = '';
2420
        if (preg_match('/development/i', $version)) {
2421
            $versiontrans .= 'warning';
2422
        }
2423
        if (preg_match('/experimental/i', $version)) {
2424
            $versiontrans .= 'warning';
2425
        }
2426
        if (preg_match('/deprecated/i', $version)) {
2427
            $versiontrans .= 'warning';
2428
        }
2429
2430
        $return = '
2431
    	<div class="box-flex-item info-box-module'
2432
            . (getDolGlobalString($const_name) ? '' : ' --disabled')
2433
            . ($this->isCoreOrExternalModule() == 'external' ? ' --external' : '')
2434
            . ($this->needUpdate ? ' --need-update' : '')
2435
            . '">
2436
	    <div class="info-box info-box-sm info-box-module">
2437
	    <div class="info-box-icon' . (!getDolGlobalString($const_name) ? '' : ' info-box-icon-module-enabled' . ($versiontrans ? ' info-box-icon-module-warning' : '')) . '">';
2438
2439
        $alttext = '';
2440
        //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
2441
        //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
2442
        if (!empty($this->picto)) {
2443
            if (preg_match('/^\//i', $this->picto)) {
2444
                $return .= img_picto($alttext, $this->picto, 'class="inline-block valignmiddle"', 1);
2445
            } else {
2446
                $return .= img_object($alttext, $this->picto, 'class="inline-block valignmiddle"');
2447
            }
2448
        } else {
2449
            $return .= img_object($alttext, 'generic', 'class="inline-block valignmiddle"');
2450
        }
2451
2452
        if ($this->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
2453
            $versionTitle = $langs->trans("Version") . ' ' . $this->getVersion(1);
2454
            if ($this->needUpdate) {
2455
                $versionTitle .= '<br>' . $langs->trans('ModuleUpdateAvailable') . ' : ' . $this->lastVersion;
2456
            }
2457
2458
            $return .= '<span class="info-box-icon-version' . ($versiontrans ? ' ' . $versiontrans : '') . ' classfortooltip" title="' . dol_escape_js($versionTitle) . '" >';
2459
            $return .= $this->getVersion(1);
2460
            $return .= '</span>';
2461
        }
2462
2463
        $return .= '</div>
2464
	    <div class="info-box-content info-box-text-module' . (!getDolGlobalString($const_name) ? '' : ' info-box-module-enabled' . ($versiontrans ? ' info-box-content-warning' : '')) . '">
2465
	    <span class="info-box-title">' . $this->getName() . '</span>
2466
	    <span class="info-box-desc twolinesmax opacitymedium" title="' . dol_escape_htmltag($this->getDesc()) . '">' . nl2br($this->getDesc()) . '</span>';
2467
2468
        $return .= '<div class="valignmiddle inline-block info-box-more">';
2469
        //if ($versiontrans) print img_warning($langs->trans("Version").' '.$this->getVersion(1)).' ';
2470
        $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>';
2471
        $return .= '</div><br>';
2472
2473
        $return .= '<div class="valignmiddle inline-block info-box-actions">';
2474
        $return .= '<div class="valignmiddle inline-block info-box-setup">';
2475
        $return .= $codetoconfig;
2476
        $return .= '</div>';
2477
        $return .= '<div class="valignmiddle inline-block marginleftonly marginrightonly">';
2478
        $return .= $codeenabledisable;
2479
        $return .= '</div>';
2480
        $return .= '</div>';
2481
2482
        $return .= '
2483
	    </div><!-- /.info-box-content -->
2484
	    </div><!-- /.info-box -->
2485
	    </div>';
2486
2487
        return $return;
2488
    }
2489
2490
    /**
2491
     * Tells if module is core or external.
2492
     * 'dolibarr' and 'dolibarr_deprecated' is core
2493
     * 'experimental' and 'development' is core
2494
     *
2495
     * @return string  'core', 'external' or 'unknown'
2496
     */
2497
    public function isCoreOrExternalModule()
2498
    {
2499
        if ($this->version == 'dolibarr' || $this->version == 'dolibarr_deprecated') {
2500
            return 'core';
2501
        }
2502
        if (!empty($this->version) && !in_array($this->version, array('experimental', 'development'))) {
2503
            return 'external';
2504
        }
2505
        if (!empty($this->editor_name) || !empty($this->editor_url)) {
2506
            return 'external';
2507
        }
2508
        if ($this->numero >= 100000) {
2509
            return 'external';
2510
        }
2511
        return 'unknown';
2512
    }
2513
2514
    /**
2515
     * Gives module version (translated if param $translated is on)
2516
     * For 'experimental' modules, gives 'experimental' translation
2517
     * For 'dolibarr' modules, gives Dolibarr version
2518
     *
2519
     * @param int $translated 1=Special version keys are translated, 0=Special version keys are not translated
2520
     * @return string                       Module version
2521
     */
2522
    public function getVersion($translated = 1)
2523
    {
2524
        global $langs;
2525
        $langs->load("admin");
2526
2527
        $ret = '';
2528
2529
        $newversion = preg_replace('/_deprecated/', '', $this->version);
2530
        if ($newversion == 'experimental') {
2531
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionExperimental") : $newversion);
2532
        } elseif ($newversion == 'development') {
2533
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionDevelopment") : $newversion);
2534
        } elseif ($newversion == 'dolibarr') {
2535
            $ret = DOL_VERSION;
2536
        } elseif ($newversion) {
2537
            $ret = $newversion;
2538
        } else {
2539
            $ret = ($translated ? $langs->transnoentitiesnoconv("VersionUnknown") : 'unknown');
2540
        }
2541
2542
        if (preg_match('/_deprecated/', $this->version)) {
2543
            $ret .= ($translated ? ' (' . $langs->transnoentitiesnoconv("Deprecated") . ')' : $this->version);
2544
        }
2545
        return $ret;
2546
    }
2547
2548
    /**
2549
     * Gives the translated module name if translation exists in admin.lang or into language files of module.
2550
     * Otherwise return the module key name.
2551
     *
2552
     * @return string  Translated module name
2553
     */
2554
    public function getName()
2555
    {
2556
        global $langs;
2557
        $langs->load("admin");
2558
2559
        if ($langs->transnoentitiesnoconv("Module" . $this->numero . "Name") != "Module" . $this->numero . "Name") {
2560
            // If module name translation exists
2561
            return $langs->transnoentitiesnoconv("Module" . $this->numero . "Name");
2562
        } else {
2563
            // If module name translation using it's unique id does not exist, we try to use its name to find translation
2564
            if (is_array($this->langfiles)) {
2565
                foreach ($this->langfiles as $val) {
2566
                    if ($val) {
2567
                        $langs->load($val);
2568
                    }
2569
                }
2570
            }
2571
2572
            if ($langs->trans("Module" . $this->name . "Name") != "Module" . $this->name . "Name") {
2573
                // If module name translation exists
2574
                return $langs->transnoentitiesnoconv("Module" . $this->name . "Name");
2575
            }
2576
2577
            // Last chance with simple label
2578
            return $langs->transnoentitiesnoconv($this->name);
2579
        }
2580
    }
2581
2582
    /**
2583
     * Gives the translated module description if translation exists in admin.lang or the default module description
2584
     *
2585
     * @return string  Translated module description
2586
     */
2587
    public function getDesc()
2588
    {
2589
        global $langs;
2590
        $langs->load("admin");
2591
2592
        if ($langs->transnoentitiesnoconv("Module" . $this->numero . "Desc") != "Module" . $this->numero . "Desc") {
2593
            // If module description translation exists
2594
            return $langs->transnoentitiesnoconv("Module" . $this->numero . "Desc");
2595
        } else {
2596
            // If module description translation does not exist using its unique id, we can use its name to find translation
2597
            if (is_array($this->langfiles)) {
2598
                foreach ($this->langfiles as $val) {
2599
                    if ($val) {
2600
                        $langs->load($val);
2601
                    }
2602
                }
2603
            }
2604
2605
            if ($langs->transnoentitiesnoconv("Module" . $this->name . "Desc") != "Module" . $this->name . "Desc") {
2606
                // If module name translation exists
2607
                return $langs->trans("Module" . $this->name . "Desc");
2608
            }
2609
2610
            // Last chance with simple label
2611
            return $langs->trans($this->description);
2612
        }
2613
    }
2614
2615
    /**
2616
     * Check for module update
2617
     * TODO : store results for $this->url_last_version and $this->needUpdate
2618
     * Add a cron task to monitor for updates
2619
     *
2620
     * @return int Return integer <0 if Error, 0 == no update needed,  >0 if need update
2621
     */
2622
    public function checkForUpdate()
2623
    {
2624
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/geturl.lib.php';
2625
        if (!empty($this->url_last_version)) {
2626
            $lastVersion = getURLContent($this->url_last_version, 'GET', '', 1, array(), array('http', 'https'), 0);    // Accept http or https links on external remote server only
2627
            if (isset($lastVersion['content']) && strlen($lastVersion['content']) < 30) {
2628
                // 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 _ . -
2629
                $this->lastVersion = preg_replace("/[^a-zA-Z0-9_\.\-]+/", "", $lastVersion['content']);
2630
                if (version_compare($this->lastVersion, $this->version) > 0) {
2631
                    $this->needUpdate = true;
2632
                    return 1;
2633
                } else {
2634
                    $this->needUpdate = false;
2635
                    return 0;
2636
                }
2637
            } else {
2638
                return -1;
2639
            }
2640
        }
2641
        return 0;
2642
    }
2643
2644
    /**
2645
     * Create tables and keys required by module:
2646
     * - Files table.sql or table-module.sql with create table instructions
2647
     * - Then table.key.sql or table-module.key.sql with create keys instructions
2648
     * - Then data_xxx.sql (usually provided by external modules only)
2649
     * - Then update_xxx.sql (usually provided by external modules only)
2650
     * Files must be stored in subdirectory 'tables' or 'data' into directory $reldir (Example: '/install/mysql/' or '/module/sql/')
2651
     * This function may also be called by :
2652
     * - _load_tables('/install/mysql/', 'modulename') into the this->init() of core module descriptors.
2653
     * - _load_tables('/mymodule/sql/') into the this->init() of external module descriptors.
2654
     *
2655
     * @param string $reldir Relative directory where to scan files. Example: '/install/mysql/' or '/module/sql/'
2656
     * @param string $onlywithsuffix Only with the defined suffix
2657
     * @return  int<0,1>                        Return integer <=0 if KO, >0 if OK
2658
     */
2659
    protected function _load_tables($reldir, $onlywithsuffix = '')
2660
    {
2661
        // phpcs:enable
2662
        global $conf;
2663
2664
        $error = 0;
2665
        $dirfound = 0;
2666
        $ok = 1;
2667
2668
        if (empty($reldir)) {
2669
            return 1;
2670
        }
2671
2672
        include_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
2673
2674
        foreach ($conf->file->dol_document_root as $dirroot) {
2675
            if ($ok == 1) {
2676
                $dirsql = $dirroot . $reldir;
2677
                $ok = 0;
2678
2679
                // We will loop on xxx/, xxx/tables/, xxx/data/
2680
                $listofsubdir = array('', 'tables/', 'data/');
2681
                if ($this->db->type == 'pgsql') {
2682
                    $listofsubdir[] = '../pgsql/functions/';
2683
                }
2684
2685
                foreach ($listofsubdir as $subdir) {
2686
                    $dir = $dirsql . $subdir;
2687
2688
                    if (!is_dir($dir)) {
2689
                        continue;
2690
                    }
2691
                    $handle = opendir($dir); // Dir may not exists
2692
2693
                    if (is_resource($handle)) {
2694
                        $dirfound++;
2695
2696
                        // Run llx_mytable.sql files, then llx_mytable_*.sql
2697
                        $files = array();
2698
                        while (($file = readdir($handle)) !== false) {
2699
                            $files[] = $file;
2700
                        }
2701
                        sort($files);
2702
                        foreach ($files as $file) {
2703
                            if ($onlywithsuffix) {
2704
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2705
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2706
                                    continue;
2707
                                } else {
2708
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2709
                                }
2710
                            }
2711
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
2712
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2713
                                if ($result <= 0) {
2714
                                    $error++;
2715
                                }
2716
                            }
2717
                        }
2718
2719
                        rewinddir($handle);
2720
2721
                        // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) then then llx_mytable_*.key.sql
2722
                        $files = array();
2723
                        while (($file = readdir($handle)) !== false) {
2724
                            $files[] = $file;
2725
                        }
2726
                        sort($files);
2727
                        foreach ($files as $file) {
2728
                            if ($onlywithsuffix) {
2729
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2730
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2731
                                    continue;
2732
                                } else {
2733
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2734
                                }
2735
                            }
2736
                            if (preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
2737
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2738
                                if ($result <= 0) {
2739
                                    $error++;
2740
                                }
2741
                            }
2742
                        }
2743
2744
                        rewinddir($handle);
2745
2746
                        // Run functions-xxx.sql files (Must be done after llx_mytable.key.sql)
2747
                        $files = array();
2748
                        while (($file = readdir($handle)) !== false) {
2749
                            $files[] = $file;
2750
                        }
2751
                        sort($files);
2752
                        foreach ($files as $file) {
2753
                            if ($onlywithsuffix) {
2754
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2755
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2756
                                    continue;
2757
                                } else {
2758
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2759
                                }
2760
                            }
2761
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 9) == 'functions') {
2762
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2763
                                if ($result <= 0) {
2764
                                    $error++;
2765
                                }
2766
                            }
2767
                        }
2768
2769
                        rewinddir($handle);
2770
2771
                        // Run data_xxx.sql files (Must be done after llx_mytable.key.sql)
2772
                        $files = array();
2773
                        while (($file = readdir($handle)) !== false) {
2774
                            $files[] = $file;
2775
                        }
2776
                        sort($files);
2777
                        foreach ($files as $file) {
2778
                            if ($onlywithsuffix) {
2779
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2780
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2781
                                    continue;
2782
                                } else {
2783
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2784
                                }
2785
                            }
2786
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'data') {
2787
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2788
                                if ($result <= 0) {
2789
                                    $error++;
2790
                                }
2791
                            }
2792
                        }
2793
2794
                        rewinddir($handle);
2795
2796
                        // Run update_xxx.sql files
2797
                        $files = array();
2798
                        while (($file = readdir($handle)) !== false) {
2799
                            $files[] = $file;
2800
                        }
2801
                        sort($files);
2802
                        foreach ($files as $file) {
2803
                            if ($onlywithsuffix) {
2804
                                if (!preg_match('/\-' . preg_quote($onlywithsuffix, '/') . '\./i', $file)) {
2805
                                    //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
2806
                                    continue;
2807
                                } else {
2808
                                    //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
2809
                                }
2810
                            }
2811
                            if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 6) == 'update') {
2812
                                $result = run_sql($dir . $file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, '', 1);
2813
                                if ($result <= 0) {
2814
                                    $error++;
2815
                                }
2816
                            }
2817
                        }
2818
2819
                        closedir($handle);
2820
                    }
2821
                }
2822
2823
                if ($error == 0) {
2824
                    $ok = 1;
2825
                }
2826
            }
2827
        }
2828
2829
        if (!$dirfound) {
2830
            dol_syslog("A module wants to load sql files from " . $reldir . " but this directory was not found.", LOG_WARNING);
2831
        }
2832
        return $ok;
2833
    }
2834
2835
    /**
2836
     * Helper method to declare dictionaries one at a time (rather than declaring dictionaries property by property).
2837
     *
2838
     * @param array $dictionaryArray Array describing one dictionary. Keys are:
2839
     *                               'name',        table name (without prefix)
2840
     *                               'lib',         dictionary label
2841
     *                               'sql',         query for select
2842
     *                               'sqlsort',     sort order
2843
     *                               'field',       comma-separated list of fields to select
2844
     *                               'fieldvalue',  list of columns used for editing existing rows
2845
     *                               'fieldinsert', list of columns used for inserting new rows
2846
     *                               'rowid',       name of the technical ID (primary key) column, usually 'rowid'
2847
     *                               'cond',        condition for the dictionary to be shown / active
2848
     *                               'help',        optional array of translation keys by column for tooltips
2849
     *                               'fieldcheck'   (appears to be unused)
2850
     * @param string $langs Optional translation file to include (appears to be unused)
2851
     * @return void
2852
     */
2853
    protected function declareNewDictionary($dictionaryArray, $langs = '')
2854
    {
2855
        $fields = array('name', 'lib', 'sql', 'sqlsort', 'field', 'fieldvalue', 'fieldinsert', 'rowid', 'cond', 'help', 'fieldcheck');
2856
2857
        foreach ($fields as $field) {
2858
            if (!empty($dictionaryArray[$field])) {
2859
                $this->dictionaries['tab' . $field][] = $dictionaryArray[$field];
2860
            }
2861
        }
2862
        if ($langs && !in_array($langs, $this->dictionaries[$langs])) $this->dictionaries['langs'][] = $langs;
2863
    }
2864
}
2865