Test Setup Failed
Push — dev ( 608138...99eb65 )
by Rafael
61:41 queued 16s
created

DolibarrModules::getLastActivationInfo()   A

Complexity

Conditions 6
Paths 17

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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

$a = canBeFalseAndNull();

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

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

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

$a = canBeFalseAndNull();

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

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

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

$a = canBeFalseAndNull();

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

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

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

$a = canBeFalseAndNull();

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

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

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

$a = canBeFalseAndNull();

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

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