Passed
Push — master ( 0f9140...c4489d )
by Alxarafe
22:27
created

Base/AlCategorie.php (1 issue)

Labels
Severity
1
<?php
2
/* Copyright (C) 2005       Matthieu Valleton       <[email protected]>
3
 * Copyright (C) 2005       Davoleau Brice          <[email protected]>
4
 * Copyright (C) 2005       Rodolphe Quiedeville    <[email protected]>
5
 * Copyright (C) 2006-2012  Regis Houssin           <[email protected]>
6
 * Copyright (C) 2006-2012  Laurent Destailleur     <[email protected]>
7
 * Copyright (C) 2007       Patrick Raguin          <[email protected]>
8
 * Copyright (C) 2013-2016  Juanjo Menent           <[email protected]>
9
 * Copyright (C) 2013-2018  Philippe Grand          <[email protected]>
10
 * Copyright (C) 2015       Marcos García           <[email protected]>
11
 * Copyright (C) 2015       Raphaël Doursenaud      <[email protected]>
12
 * Copyright (C) 2016       Charlie Benke           <[email protected]>
13
 * Copyright (C) 2018       Frédéric France         <[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 <http://www.gnu.org/licenses/>.
27
 */
28
namespace Alixar\Base;
29
30
/**
31
 * 	\file       htdocs/categories/class/categorie.class.php
32
 * 	\ingroup    categorie
33
 * 	\brief      File of class to manage categories
34
 */
35
/*
36
 * require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
37
 * require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
38
 * require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.class.php';
39
 * require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
40
 */
41
42
use Alxarafe\Helpers\Config;
43
use Alxarafe\Helpers\Debug;
44
use Alixar\Helpers\Globals;
45
use Alixar\Base\AlCommonObject;
46
use Alixar\Helpers\AlDolUtils;
47
48
/**
49
 * 	Class to manage categories
50
 */
51
class AlCategorie extends AlCommonObject
52
{
53
54
    // Categories types (we use string because we want to accept any modules/types in a future)
55
    const TYPE_PRODUCT = 'product';
56
    const TYPE_SUPPLIER = 'supplier';
57
    const TYPE_CUSTOMER = 'customer';
58
    const TYPE_MEMBER = 'member';
59
    const TYPE_CONTACT = 'contact';
60
    const TYPE_USER = 'user';
61
    const TYPE_PROJECT = 'project';
62
    const TYPE_ACCOUNT = 'bank_account';
63
    const TYPE_BANK_LINE = 'bank_line';
64
65
    /**
66
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
67
     */
68
    public $picto = 'category';
69
70
    /**
71
     * @var array ID mapping from type string
72
     *
73
     * @note This array should be remove in future, once previous constants are moved to the string value. Deprecated
74
     */
75
    protected $MAP_ID = array(
76
        'product' => 0,
77
        'supplier' => 1,
78
        'customer' => 2,
79
        'member' => 3,
80
        'contact' => 4,
81
        'bank_account' => 5,
82
        'project' => 6,
83
        'user' => 7,
84
        'bank_line' => 8,
85
    );
86
87
    /**
88
     * @var array Code mapping from ID
89
     */
90
    public static $MAP_ID_TO_CODE = array(
91
        0 => 'product',
92
        1 => 'supplier',
93
        2 => 'customer',
94
        3 => 'member',
95
        4 => 'contact',
96
        5 => 'bank_account',
97
        6 => 'project',
98
        7 => 'user',
99
        8 => 'bank_line',
100
    );
101
102
    /**
103
     * @var array Foreign keys mapping from type string
104
     *
105
     * @note Move to const array when PHP 5.6 will be our minimum target
106
     */
107
    protected $MAP_CAT_FK = array(
108
        'product' => 'product',
109
        'customer' => 'soc',
110
        'supplier' => 'soc',
111
        'member' => 'member',
112
        'contact' => 'socpeople',
113
        'user' => 'user',
114
        'account' => 'account', // old for bank_account
115
        'bank_account' => 'account',
116
        'project' => 'project',
117
    );
118
119
    /**
120
     * @var array Category tables mapping from type string
121
     *
122
     * @note Move to const array when PHP 5.6 will be our minimum target
123
     */
124
    protected $MAP_CAT_TABLE = array(
125
        'product' => 'product',
126
        'customer' => 'societe',
127
        'supplier' => 'fournisseur',
128
        'member' => 'member',
129
        'contact' => 'contact',
130
        'user' => 'user',
131
        'account' => 'account', // old for bank_account
132
        'bank_account' => 'account',
133
        'project' => 'project',
134
    );
135
136
    /**
137
     * @var array Object class mapping from type string
138
     *
139
     * @note Move to const array when PHP 5.6 will be our minimum target
140
     */
141
    protected $MAP_OBJ_CLASS = array(
142
        'product' => 'Product',
143
        'customer' => 'Societe',
144
        'supplier' => 'Fournisseur',
145
        'member' => 'Adherent',
146
        'contact' => 'Contact',
147
        'user' => 'User',
148
        'account' => 'Account', // old for bank account
149
        'bank_account' => 'Account',
150
        'project' => 'Project',
151
    );
152
153
    /**
154
     * @var array Object table mapping from type string
155
     *
156
     * @note Move to const array when PHP 5.6 will be our minimum target
157
     */
158
    protected $MAP_OBJ_TABLE = array(
159
        'product' => 'product',
160
        'customer' => 'societe',
161
        'supplier' => 'societe',
162
        'member' => 'adherent',
163
        'contact' => 'socpeople',
164
        'user' => 'user',
165
        'account' => 'bank_account',
166
        'project' => 'projet',
167
    );
168
169
    /**
170
     * @var string ID to identify managed object
171
     */
172
    public $element = 'category';
173
174
    /**
175
     * @var string Name of table without prefix where object is stored
176
     */
177
    public $table_element = 'categorie';
178
179
    /**
180
     * @var int ID
181
     */
182
    public $fk_parent;
183
184
    /**
185
     * @var string Category label
186
     */
187
    public $label;
188
189
    /**
190
     * @var string description
191
     */
192
    public $description;
193
194
    /**
195
     * @var string     Color
196
     */
197
    public $color;
198
199
    /**
200
     * @var ???
201
     */
202
    public $socid;
203
204
    /**
205
     * @var string	Category type
206
     *
207
     * @see Categorie::TYPE_PRODUCT
208
     * @see Categorie::TYPE_SUPPLIER
209
     * @see Categorie::TYPE_CUSTOMER
210
     * @see Categorie::TYPE_MEMBER
211
     * @see Categorie::TYPE_CONTACT
212
     * @see Categorie::TYPE_USER
213
     * @see Categorie::TYPE_ACCOUNT
214
     * @see Categorie::TYPE_PROJECT
215
     */
216
    public $type;
217
218
    /**
219
     * @var array Categories table in memory
220
     */
221
    public $cats = array();
222
223
    /**
224
     * @var array Mother of table
225
     */
226
    public $motherof = array();
227
228
    /**
229
     * 	Constructor
230
     *
231
     *  @param		DoliDB		$db     Database handler
232
     */
233
    function __construct()
234
    {
235
        // Config::$dbEngine = $db;
236
    }
237
238
    /**
239
     * 	Load category into memory from database
240
     *
241
     * 	@param		int		$id		Id of category
242
     *  @param		string	$label	Label of category
243
     *  @param		string	$type	Type of category ('product', '...') or (0, 1, ...)
244
     * 	@return		int				<0 if KO, >0 if OK
245
     */
246
    function fetch($id, $label = '', $type = null)
247
    {
248
        if (empty($id) && empty($label)) {
249
            return -1;
250
        }
251
252
        if (!is_numeric($type)) {
253
            $type = $this->MAP_ID[$type];
254
        }
255
256
        $sql = "SELECT rowid, fk_parent, entity, label, description, color, fk_soc, visible, type";
257
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie";
258
        if ($id > 0) {
259
            $sql .= " WHERE rowid = " . $id;
260
        } else {
261
            $sql .= " WHERE label = '" . $label . "' AND entity IN (" . DolUtils::getEntity('category') . ")";
262
            if (!is_null($type)) {
263
                $sql .= " AND type = " . $type;
264
            }
265
        }
266
267
        DolUtils::dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
268
        $resql = Config::$dbEngine->select($sql);
269
        if ($resql) {
270
            if (count($resql) > 0) {
271
                $res = json_decode(json_encode($resql));
272
273
                $this->id = $res['rowid'];
274
                //$this->ref			= $res['rowid'];
275
                $this->fk_parent = $res['fk_parent'];
276
                $this->label = $res['label'];
277
                $this->description = $res['description'];
278
                $this->color = $res['color'];
279
                $this->socid = $res['fk_soc'];
280
                $this->visible = $res['visible'];
281
                $this->type = $res['type'];
282
                $this->entity = $res['entity'];
283
284
                // Retreive all extrafield
285
                // fetch optionals attributes and labels
286
                $this->fetch_optionals();
287
288
                // Config::$dbEngine->free($resql);
289
                // multilangs
290
                if (!empty(Globals::$conf->global->MAIN_MULTILANGS)) {
291
                    $this->getMultiLangs();
292
                }
293
294
                return 1;
295
            } else {
296
                return 0;
297
            }
298
        } else {
299
            DolUtils::dol_print_error(Config::$dbEngine);
300
            return -1;
301
        }
302
    }
303
304
    /**
305
     * 	Add category into database
306
     *
307
     * 	@param	User	$user		Object user
308
     * 	@return	int 				-1 : SQL error
309
     *          					-2 : new ID unknown
310
     *          					-3 : Invalid category
311
     * 								-4 : category already exists
312
     */
313
    function create($user)
314
    {
315
        // global $conf,$langs,$hookmanager;
316
        Config::$langs->load('categories');
0 ignored issues
show
The property langs does not exist on Alxarafe\Helpers\Config. Did you mean lang?
Loading history...
317
318
        $type = $this->type;
319
320
        if (!is_numeric($type)) {
321
            $type = $this->MAP_ID[$type];
322
        }
323
324
        $error = 0;
325
326
        DolUtils::dol_syslog(get_class($this) . '::create', LOG_DEBUG);
327
328
        // Clean parameters
329
        $this->label = trim($this->label);
330
        $this->description = trim($this->description);
331
        $this->color = trim($this->color);
332
        $this->import_key = trim($this->import_key);
333
        if (empty($this->visible)) {
334
            $this->visible = 0;
335
        }
336
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
337
338
        if ($this->already_exists()) {
339
            $this->error = $langs->trans("ImpossibleAddCat", $this->label);
340
            $this->error .= " : " . $langs->trans("CategoryExistsAtSameLevel");
341
            dol_syslog($this->error, LOG_WARNING);
342
            return -4;
343
        }
344
345
        Config::$dbEngine->beginTransaction();
346
347
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "categorie (";
348
        $sql .= "fk_parent,";
349
        $sql .= " label,";
350
        $sql .= " description,";
351
        $sql .= " color,";
352
        if (!empty(Globals::$conf->global->CATEGORY_ASSIGNED_TO_A_CUSTOMER)) {
353
            $sql .= "fk_soc,";
354
        }
355
        $sql .= " visible,";
356
        $sql .= " type,";
357
        $sql .= " import_key,";
358
        $sql .= " entity";
359
        $sql .= ") VALUES (";
360
        $sql .= Config::$dbEngine->escape($this->fk_parent) . ",";
361
        $sql .= "'" . Config::$dbEngine->escape($this->label) . "',";
362
        $sql .= "'" . Config::$dbEngine->escape($this->description) . "',";
363
        $sql .= "'" . Config::$dbEngine->escape($this->color) . "',";
364
        if (!empty(Globals::$conf->global->CATEGORY_ASSIGNED_TO_A_CUSTOMER)) {
365
            $sql .= ($this->socid != -1 ? $this->socid : 'null') . ",";
366
        }
367
        $sql .= "'" . Config::$dbEngine->escape($this->visible) . "',";
368
        $sql .= Config::$dbEngine->escape($type) . ",";
369
        $sql .= (!empty($this->import_key) ? "'" . Config::$dbEngine->escape($this->import_key) . "'" : 'null') . ",";
370
        $sql .= Config::$dbEngine->escape(Globals::$conf->entity);
371
        $sql .= ")";
372
373
        $res = Config::$dbEngine->select($sql);
374
        if ($res) {
375
            $id = Config::$dbEngine->last_insert_id(MAIN_DB_PREFIX . "categorie");
376
377
            if ($id > 0) {
378
                $this->id = $id;
379
380
                $action = 'create';
381
382
                // Actions on extra fields
383
                if (empty(Globals::$conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used{
384
                    $result = $this->insertExtraFields();
385
                    if ($result < 0) {
386
                        $error++;
387
                    }
388
                }
389
390
                if (!$error) {
391
                    // Call trigger
392
                    $result = $this->call_trigger('CATEGORY_CREATE', $user);
393
                    if ($result < 0) {
394
                        $error++;
395
                    }
396
                    // End call triggers
397
                }
398
399
                if (!$error) {
400
                    Config::$dbEngine->commit();
401
                    return $id;
402
                } else {
403
                    Config::$dbEngine->rollback();
404
                    return -3;
405
                }
406
            } else {
407
                Config::$dbEngine->rollback();
408
                return -2;
409
            }
410
        } else {
411
            $this->error = Config::$dbEngine->error();
412
            Config::$dbEngine->rollback();
413
            return -1;
414
        }
415
    }
416
417
    /**
418
     * 	Update category
419
     *
420
     * 	@param	User	$user		Object user
421
     * 	@return	int		 			1 : OK
422
     *          					-1 : SQL error
423
     *          					-2 : invalid category
424
     */
425
    function update(User $user)
426
    {
427
        global $conf, $langs, $hookmanager;
428
429
        $error = 0;
430
431
        // Clean parameters
432
        $this->label = trim($this->label);
433
        $this->description = trim($this->description);
434
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
435
        $this->visible = ($this->visible != "" ? intval($this->visible) : 0);
436
437
        if ($this->already_exists()) {
438
            $this->error = $langs->trans("ImpossibleUpdateCat");
439
            $this->error .= " : " . $langs->trans("CategoryExistsAtSameLevel");
440
            return -1;
441
        }
442
443
        Config::$dbEngine->begin();
444
445
        $sql = "UPDATE " . MAIN_DB_PREFIX . "categorie";
446
        $sql .= " SET label = '" . Config::$dbEngine->escape($this->label) . "',";
447
        $sql .= " description = '" . Config::$dbEngine->escape($this->description) . "',";
448
        $sql .= " color = '" . Config::$dbEngine->escape($this->color) . "'";
449
        if (!empty(Globals::$conf->global->CATEGORY_ASSIGNED_TO_A_CUSTOMER)) {
450
            $sql .= ", fk_soc = " . ($this->socid != -1 ? $this->socid : 'null');
451
        }
452
        $sql .= ", visible = '" . Config::$dbEngine->escape($this->visible) . "'";
453
        $sql .= ", fk_parent = " . $this->fk_parent;
454
        $sql .= " WHERE rowid = " . $this->id;
455
456
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
457
        if (Config::$dbEngine->select($sql)) {
458
            $action = 'update';
459
460
            // Actions on extra fields
461
            if (empty(Globals::$conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used{
462
                $result = $this->insertExtraFields();
463
                if ($result < 0) {
464
                    $error++;
465
                }
466
            }
467
468
            if (!$error) {
469
                // Call trigger
470
                $result = $this->call_trigger('CATEGORY_MODIFY', $user);
471
                if ($result < 0) {
472
                    $error++;
473
                    Config::$dbEngine->rollback();
474
                    return -1;
475
                }
476
                // End call triggers
477
            }
478
479
            Config::$dbEngine->commit();
480
481
            return 1;
482
        } else {
483
            Config::$dbEngine->rollback();
484
            dol_print_error(Config::$dbEngine);
485
            return -1;
486
        }
487
    }
488
489
    /**
490
     * 	Delete a category from database
491
     *
492
     * 	@param	User	$user		Object user that ask to delete
493
     * 	@param	int		$notrigger	1=Does not execute triggers, 0= execute triggers
494
     * 	@return	int                 <0 KO >0 OK
495
     */
496
    function delete($user, $notrigger = 0)
497
    {
498
        global $conf, $langs;
499
500
        $error = 0;
501
502
        // Clean parameters
503
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
504
505
        dol_syslog(get_class($this) . "::remove");
506
507
        Config::$dbEngine->begin();
508
509
        if (!$error && !$notrigger) {
510
            // Call trigger
511
            $result = $this->call_trigger('CATEGORY_DELETE', $user);
512
            if ($result < 0)
513
                $error++;
514
            // End call triggers
515
        }
516
517
        /* FIX #1317 : Check for child category and move up 1 level */
518
        if (!$error) {
519
            $sql = "UPDATE " . MAIN_DB_PREFIX . "categorie";
520
            $sql .= " SET fk_parent = " . $this->fk_parent;
521
            $sql .= " WHERE fk_parent = " . $this->id;
522
523
            if (!Config::$dbEngine->select($sql)) {
524
                $this->error = Config::$dbEngine->lasterror();
525
                $error++;
526
            }
527
        }
528
529
        $arraydelete = array(
530
            'categorie_societe' => 'fk_categorie',
531
            'categorie_fournisseur' => 'fk_categorie',
532
            'categorie_product' => 'fk_categorie',
533
            'categorie_member' => 'fk_categorie',
534
            'categorie_contact' => 'fk_categorie',
535
            'categorie_account' => 'fk_categorie',
536
            'bank_class' => 'fk_categ',
537
            'categorie_lang' => 'fk_category',
538
            'categorie' => 'rowid',
539
        );
540
        foreach ($arraydelete as $key => $value) {
541
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $key;
542
            $sql .= " WHERE " . $value . " = " . $this->id;
543
            if (!Config::$dbEngine->select($sql)) {
544
                $this->errors[] = Config::$dbEngine->lasterror();
545
                dol_syslog("Error sql=" . $sql . " " . $this->error, LOG_ERR);
546
                $error++;
547
            }
548
        }
549
550
        // Removed extrafields
551
        if (!$error && empty(Globals::$conf->global->MAIN_EXTRAFIELDS_DISABLED)) { // For avoid conflicts if trigger used{
552
            $result = $this->deleteExtraFields();
553
            if ($result < 0) {
554
                $error++;
555
                dol_syslog(get_class($this) . "::delete erreur " . $this->error, LOG_ERR);
556
            }
557
        }
558
559
        if (!$error) {
560
            Config::$dbEngine->commit();
561
            return 1;
562
        } else {
563
            Config::$dbEngine->rollback();
564
            return -1;
565
        }
566
    }
567
568
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
569
    /**
570
     * Link an object to the category
571
     *
572
     * @param   CommonObject 	$obj  	Object to link to category
573
     * @param   string     		$type 	Type of category ('product', ...)
574
     * @return  int                		1 : OK, -1 : erreur SQL, -2 : id not defined, -3 : Already linked
575
     */
576
    function add_type($obj, $type)
577
    {
578
        // phpcs:enable
579
        global $user, $langs, $conf;
580
581
        $error = 0;
582
583
        if ($this->id == -1)
584
            return -2;
585
586
        Config::$dbEngine->begin();
587
588
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_" . $this->MAP_CAT_TABLE[$type];
589
        $sql .= " (fk_categorie, fk_" . $this->MAP_CAT_FK[$type] . ")";
590
        $sql .= " VALUES (" . $this->id . ", " . $obj->id . ")";
591
592
        dol_syslog(get_class($this) . '::add_type', LOG_DEBUG);
593
        if (Config::$dbEngine->select($sql)) {
594
            if (!empty(Globals::$conf->global->CATEGORIE_RECURSIV_ADD)) {
595
                $sql = 'SELECT fk_parent FROM ' . MAIN_DB_PREFIX . 'categorie';
596
                $sql .= " WHERE rowid = " . $this->id;
597
598
                dol_syslog(get_class($this) . "::add_type", LOG_DEBUG);
599
                $resql = Config::$dbEngine->select($sql);
600
                if ($resql) {
601
                    if (Config::$dbEngine->num_rows($resql) > 0) {
602
                        $objparent = Config::$dbEngine->fetch_object($resql);
603
604
                        if (!empty($objparent->fk_parent)) {
605
                            $cat = new Categorie(Config::$dbEngine);
606
                            $cat->id = $objparent->fk_parent;
607
                            if (!$cat->containsObject($type, $obj->id)) {
608
                                $result = $cat->add_type($obj, $type);
609
                                if ($result < 0) {
610
                                    $this->error = $cat->error;
611
                                    $error++;
612
                                }
613
                            }
614
                        }
615
                    }
616
                } else {
617
                    $error++;
618
                    $this->error = Config::$dbEngine->lasterror();
619
                }
620
621
                if ($error) {
622
                    Config::$dbEngine->rollback();
623
                    return -1;
624
                }
625
            }
626
627
628
629
            // Call trigger
630
            $this->context = array('linkto' => $obj); // Save object we want to link category to into category instance to provide information to trigger
631
            $result = $this->call_trigger('CATEGORY_LINK', $user);
632
            if ($result < 0) {
633
                $error++;
634
            }
635
            // End call triggers
636
637
            if (!$error) {
638
                Config::$dbEngine->commit();
639
                return 1;
640
            } else {
641
                Config::$dbEngine->rollback();
642
                return -2;
643
            }
644
        } else {
645
            Config::$dbEngine->rollback();
646
            if (Config::$dbEngine->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
647
                $this->error = Config::$dbEngine->lasterrno();
648
                return -3;
649
            } else {
650
                $this->error = Config::$dbEngine->lasterror();
651
            }
652
            return -1;
653
        }
654
    }
655
656
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
657
    /**
658
     * Delete object from category
659
     *
660
     * @param   CommonObject $obj  Object
661
     * @param   string       $type Type of category ('customer', 'supplier', 'contact', 'product', 'member')
662
     *
663
     * @return  int          1 if OK, -1 if KO
664
     */
665
    function del_type($obj, $type)
666
    {
667
        // phpcs:enable
668
        global $user, $langs, $conf;
669
670
        $error = 0;
671
672
        // For backward compatibility
673
        if ($type == 'societe') {
674
            $type = 'customer';
675
            dol_syslog(get_class($this) . "::del_type(): type 'societe' is deprecated, please use 'customer' instead", LOG_WARNING);
676
        } elseif ($type == 'fournisseur') {
677
            $type = 'supplier';
678
            dol_syslog(get_class($this) . "::del_type(): type 'fournisseur' is deprecated, please use 'supplier' instead", LOG_WARNING);
679
        }
680
681
        Config::$dbEngine->begin();
682
683
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "categorie_" . $this->MAP_CAT_TABLE[$type];
684
        $sql .= " WHERE fk_categorie = " . $this->id;
685
        $sql .= " AND   fk_" . $this->MAP_CAT_FK[$type] . "  = " . $obj->id;
686
687
        dol_syslog(get_class($this) . '::del_type', LOG_DEBUG);
688
        if (Config::$dbEngine->select($sql)) {
689
            // Call trigger
690
            $this->context = array('unlinkoff' => $obj); // Save object we want to link category to into category instance to provide information to trigger
691
            $result = $this->call_trigger('CATEGORY_UNLINK', $user);
692
            if ($result < 0) {
693
                $error++;
694
            }
695
            // End call triggers
696
697
            if (!$error) {
698
                Config::$dbEngine->commit();
699
                return 1;
700
            } else {
701
                Config::$dbEngine->rollback();
702
                return -2;
703
            }
704
        } else {
705
            Config::$dbEngine->rollback();
706
            $this->error = Config::$dbEngine->lasterror();
707
            return -1;
708
        }
709
    }
710
711
    /**
712
     * Return list of fetched instance of elements having this category
713
     *
714
     * @param   string     $type       Type of category ('customer', 'supplier', 'contact', 'product', 'member')
715
     * @param   int        $onlyids    Return only ids of objects (consume less memory)
716
     * @return  array|int              -1 if KO, array of instance of object if OK
717
     * @see containsObject
718
     */
719
    function getObjectsInCateg($type, $onlyids = 0)
720
    {
721
        $objs = array();
722
723
        $obj = new $this->MAP_OBJ_CLASS[$type](Config::$dbEngine);
724
725
        $sql = "SELECT c.fk_" . $this->MAP_CAT_FK[$type];
726
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_" . $this->MAP_CAT_TABLE[$type] . " as c";
727
        $sql .= ", " . MAIN_DB_PREFIX . $this->MAP_OBJ_TABLE[$type] . " as o";
728
        $sql .= " WHERE o.entity IN (" . getEntity($obj->element) . ")";
729
        $sql .= " AND c.fk_categorie = " . $this->id;
730
        $sql .= " AND c.fk_" . $this->MAP_CAT_FK[$type] . " = o.rowid";
731
732
        dol_syslog(get_class($this) . "::getObjectsInCateg", LOG_DEBUG);
733
        $resql = Config::$dbEngine->select($sql);
734
        if ($resql) {
735
            while ($rec = Config::$dbEngine->fetch_array($resql)) {
736
                if ($onlyids) {
737
                    $objs[] = $rec['fk_' . $this->MAP_CAT_FK[$type]];
738
                } else {
739
                    $obj = new $this->MAP_OBJ_CLASS[$type](Config::$dbEngine);
740
                    $obj->fetch($rec['fk_' . $this->MAP_CAT_FK[$type]]);
741
                    $objs[] = $obj;
742
                }
743
            }
744
            return $objs;
745
        } else {
746
            $this->error = Config::$dbEngine->error() . ' sql=' . $sql;
747
            return -1;
748
        }
749
    }
750
751
    /**
752
     * Check for the presence of an object in a category
753
     *
754
     * @param   string $type      		Type of category ('customer', 'supplier', 'contact', 'product', 'member')
755
     * @param   int    $object_id 		Id of the object to search
756
     * @return  int                     Number of occurrences
757
     * @see getObjectsInCateg
758
     */
759
    function containsObject($type, $object_id)
760
    {
761
        $sql = "SELECT COUNT(*) as nb FROM " . MAIN_DB_PREFIX . "categorie_" . $this->MAP_CAT_TABLE[$type];
762
        $sql .= " WHERE fk_categorie = " . $this->id . " AND fk_" . $this->MAP_CAT_FK[$type] . " = " . $object_id;
763
        dol_syslog(get_class($this) . "::containsObject", LOG_DEBUG);
764
        $resql = Config::$dbEngine->select($sql);
765
        if ($resql) {
766
            return Config::$dbEngine->fetch_object($resql)->nb;
767
        } else {
768
            $this->error = Config::$dbEngine->error() . ' sql=' . $sql;
769
            return -1;
770
        }
771
    }
772
773
    /**
774
     * List categories of an element id
775
     *
776
     * @param	int		$id			Id of element
777
     * @param	string	$type		Type of category ('member', 'customer', 'supplier', 'product', 'contact')
778
     * @param	string	$sortfield	Sort field
779
     * @param	string	$sortorder	Sort order
780
     * @param	int		$limit		Limit for list
781
     * @param	int		$page		Page number
782
     * @return	array|int			Array of categories, 0 if no cat, -1 on error
783
     */
784
    function getListForItem($id, $type = 'customer', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
785
    {
786
        global $conf;
787
788
        $categories = array();
789
790
        $sub_type = $type;
791
        $subcol_name = "fk_" . $type;
792
        if ($type == "customer") {
793
            $sub_type = "societe";
794
            $subcol_name = "fk_soc";
795
        }
796
        if ($type == "supplier") {
797
            $sub_type = "fournisseur";
798
            $subcol_name = "fk_soc";
799
        }
800
        if ($type == "contact") {
801
            $subcol_name = "fk_socpeople";
802
        }
803
        $sql = "SELECT s.rowid";
804
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as s";
805
        $sql .= " , " . MAIN_DB_PREFIX . "categorie_" . $sub_type . " as sub ";
806
        $sql .= ' WHERE s.entity IN (' . getEntity('category') . ')';
807
        $sql .= ' AND s.type=' . array_search($type, self::$MAP_ID_TO_CODE);
808
        $sql .= ' AND s.rowid = sub.fk_categorie';
809
        $sql .= ' AND sub.' . $subcol_name . ' = ' . $id;
810
811
        $sql .= Config::$dbEngine->order($sortfield, $sortorder);
812
813
        $offset = 0;
814
        $nbtotalofrecords = '';
815
        if (empty(Globals::$conf->global->MAIN_DISABLE_FULL_SCANLIST)) {
816
            $result = Config::$dbEngine->select($sql);
817
            $nbtotalofrecords = Config::$dbEngine->num_rows($result);
818
            if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0
819
                $page = 0;
820
                $offset = 0;
821
            }
822
        }
823
824
        $sql .= Config::$dbEngine->plimit($limit + 1, $offset);
825
826
        $result = Config::$dbEngine->select($sql);
827
        if ($result) {
828
            $i = 0;
829
            $num = Config::$dbEngine->num_rows($result);
830
            $min = min($num, ($limit <= 0 ? $num : $limit));
831
            while ($i < $min) {
832
                $obj = Config::$dbEngine->fetch_object($result);
833
                $category_static = new Categorie(Config::$dbEngine);
834
                if ($category_static->fetch($obj->rowid)) {
835
                    $categories[$i]['id'] = $category_static->id;
836
                    $categories[$i]['fk_parent'] = $category_static->fk_parent;
837
                    $categories[$i]['label'] = $category_static->label;
838
                    $categories[$i]['description'] = $category_static->description;
839
                    $categories[$i]['color'] = $category_static->color;
840
                    $categories[$i]['socid'] = $category_static->socid;
841
                    $categories[$i]['visible'] = $category_static->visible;
842
                    $categories[$i]['type'] = $category_static->type;
843
                    $categories[$i]['entity'] = $category_static->entity;
844
                    $categories[$i]['array_options'] = $category_static->array_options;
845
846
                    // multilangs
847
                    if (!empty(Globals::$conf->global->MAIN_MULTILANGS)) {
848
                        $categories[$i]['multilangs'] = $category_static->multilangs;
849
                    }
850
                }
851
                $i++;
852
            }
853
        } else {
854
            $this->error = Config::$dbEngine->lasterror();
855
            return -1;
856
        }
857
        if (!count($categories)) {
858
            return 0;
859
        }
860
861
        return $categories;
862
    }
863
864
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
865
    /**
866
     * Return childs of a category
867
     *
868
     * @return	array|int   <0 KO, array ok
869
     */
870
    function get_filles()
871
    {
872
        // phpcs:enable
873
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
874
        $sql .= " WHERE fk_parent = " . $this->id;
875
876
        $res = Config::$dbEngine->select($sql);
877
        if ($res) {
878
            $cats = array();
879
            while ($rec = Config::$dbEngine->fetch_array($res)) {
880
                $cat = new Categorie(Config::$dbEngine);
881
                $cat->fetch($rec['rowid']);
882
                $cats[] = $cat;
883
            }
884
            return $cats;
885
        } else {
886
            dol_print_error(Config::$dbEngine);
887
            return -1;
888
        }
889
    }
890
891
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
892
    /**
893
     * 	Load this->motherof that is array(id_son=>id_parent, ...)
894
     *
895
     * 	@return		int		<0 if KO, >0 if OK
896
     */
897
    protected function load_motherof()
898
    {
899
        // phpcs:enable
900
        // global $conf;
901
902
        $this->motherof = array();
903
904
        // Load array[child]=parent
905
        $sql = "SELECT fk_parent as id_parent, rowid as id_son";
906
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie";
907
        $sql .= " WHERE fk_parent != 0";
908
        $sql .= " AND entity IN (" . DolUtils::getEntity('category') . ")";
909
910
        DolUtils::dol_syslog(get_class($this) . "::load_motherof", LOG_DEBUG);
911
        $resql = Config::$dbEngine->select($sql);
912
        if (is_array($resql)) {
913
            foreach ($resql as $_obj) {
914
                $obj = json_decode(json_encode($_obj));
915
                $this->motherof[$obj->id_son] = $obj->id_parent;
916
            }
917
            return 1;
918
        } else {
919
            DolUtils::dol_print_error(Config::$dbEngine);
920
            return -1;
921
        }
922
    }
923
924
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
925
    /**
926
     * Rebuilding the category tree as an array
927
     * Return an array of table('id','id_mere',...) trie selon arbre et avec:
928
     *                id = id de la categorie
929
     *                id_mere = id de la categorie mere
930
     *                id_children = tableau des id enfant
931
     *                label = nom de la categorie
932
     *                fulllabel = nom avec chemin complet de la categorie
933
     *                fullpath = chemin complet compose des id
934
     *
935
     * @param   string 	$type        	Type of categories ('customer', 'supplier', 'contact', 'product', 'member') or (0, 1, 2, ...).
936
     * @param   int    	$markafterid 	Removed all categories including the leaf $markafterid in category tree.
937
     *
938
     * @return  array|int               Array of categories. this->cats and this->motherof are set, -1 on error
939
     */
940
    function get_full_arbo($type, $markafterid = 0)
941
    {
942
        // phpcs:enable
943
        // global $conf, $langs;
944
945
        if (!is_numeric($type))
946
        {
947
            $type = $this->MAP_ID[$type];
948
        }
949
950
        $this->cats = array();
951
952
        // Init this->motherof that is array(id_son=>id_parent, ...)
953
        $this->load_motherof();
954
        $current_lang = Globals::$langs->getDefaultLang();
955
956
        // Init $this->cats array
957
        $sql = "SELECT DISTINCT c.rowid, c.label, c.description, c.color, c.fk_parent, c.visible"; // Distinct reduce pb with old tables with duplicates
958
        if (!empty(Globals::$conf->global->MAIN_MULTILANGS))
959
        {
960
            $sql .= ", t.label as label_trans, t.description as description_trans";
961
        }
962
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as c";
963
        if (!empty(Globals::$conf->global->MAIN_MULTILANGS))
964
        {
965
            $sql .= " LEFT  JOIN " . MAIN_DB_PREFIX . "categorie_lang as t ON t.fk_category=c.rowid AND t.lang='" . $current_lang . "'";
966
        }
967
        $sql .= " WHERE c.entity IN (" . DolUtils::getEntity('category') . ")";
968
        $sql .= " AND c.type = " . $type;
969
970
        DolUtils::dol_syslog(get_class($this) . "::get_full_arbo get category list", LOG_DEBUG);
971
        $resql = Config::$dbEngine->select($sql);
972
        if (is_array($resql)) {
973
            foreach ($resql as $_obj) {
974
                $obj = json_decode(json_encode($_obj));
975
976
                $this->cats[$obj->rowid]['rowid'] = $obj->rowid;
977
                $this->cats[$obj->rowid]['id'] = $obj->rowid;
978
                $this->cats[$obj->rowid]['fk_parent'] = $obj->fk_parent;
979
                $this->cats[$obj->rowid]['label'] = !empty($obj->label_trans) ? $obj->label_trans : $obj->label;
980
                $this->cats[$obj->rowid]['description'] = !empty($obj->description_trans) ? $obj->description_trans : $obj->description;
981
                $this->cats[$obj->rowid]['color'] = $obj->color;
982
                $this->cats[$obj->rowid]['visible'] = $obj->visible;
983
            }
984
        } else {
985
            DolUtils::dol_print_error(Config::$dbEngine);
986
            return -1;
987
        }
988
989
        // We add the fullpath property to each elements of first level (no parent exists)
990
        DolUtils::dol_syslog(get_class($this) . "::get_full_arbo call to build_path_from_id_categ", LOG_DEBUG);
991
        foreach ($this->cats as $key => $val) {
992
            //print 'key='.$key.'<br>'."\n";
993
            $this->build_path_from_id_categ($key, 0); // Process a branch from the root category key (this category has no parent)
994
        }
995
996
        // Exclude leaf including $markafterid from tree
997
        if ($markafterid) {
998
            //print "Look to discard category ".$markafterid."\n";
999
            $keyfilter1 = '^' . $markafterid . '$';
1000
            $keyfilter2 = '_' . $markafterid . '$';
1001
            $keyfilter3 = '^' . $markafterid . '_';
1002
            $keyfilter4 = '_' . $markafterid . '_';
1003
            foreach ($this->cats as $key => $val) {
1004
                if (preg_match('/' . $keyfilter1 . '/', $val['fullpath']) || preg_match('/' . $keyfilter2 . '/', $val['fullpath']) || preg_match('/' . $keyfilter3 . '/', $val['fullpath']) || preg_match('/' . $keyfilter4 . '/', $val['fullpath'])) {
1005
                    unset($this->cats[$key]);
1006
                }
1007
            }
1008
        }
1009
1010
        DolUtils::dol_syslog(get_class($this) . "::get_full_arbo dol_sort_array", LOG_DEBUG);
1011
        $this->cats = DolUtils::dol_sort_array($this->cats, 'fulllabel', 'asc', true, false);
1012
1013
        //$this->debug_cats();
1014
1015
        return $this->cats;
1016
    }
1017
1018
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1019
    /**
1020
     * 	For category id_categ and its childs available in this->cats, define property fullpath and fulllabel
1021
     *
1022
     * 	@param		int		$id_categ		id_categ entry to update
1023
     * 	@param		int		$protection		Deep counter to avoid infinite loop
1024
     * 	@return		void
1025
     */
1026
    function build_path_from_id_categ($id_categ, $protection = 1000)
1027
    {
1028
        // phpcs:enable
1029
        dol_syslog(get_class($this) . "::build_path_from_id_categ id_categ=" . $id_categ . " protection=" . $protection, LOG_DEBUG);
1030
1031
        if (!empty($this->cats[$id_categ]['fullpath'])) {
1032
            // Already defined
1033
            dol_syslog(get_class($this) . "::build_path_from_id_categ fullpath and fulllabel already defined", LOG_WARNING);
1034
            return;
1035
        }
1036
1037
        // First build full array $motherof
1038
        //$this->load_motherof();	// Disabled because already done by caller of build_path_from_id_categ
1039
        // Define fullpath and fulllabel
1040
        $this->cats[$id_categ]['fullpath'] = '_' . $id_categ;
1041
        $this->cats[$id_categ]['fulllabel'] = $this->cats[$id_categ]['label'];
1042
        $i = 0;
1043
        $cursor_categ = $id_categ;
1044
        //print 'Work for id_categ='.$id_categ.'<br>'."\n";
1045
        while ((empty($protection) || $i < $protection) && !empty($this->motherof[$cursor_categ])) {
1046
            //print '&nbsp; cursor_categ='.$cursor_categ.' i='.$i.' '.$this->motherof[$cursor_categ].'<br>'."\n";
1047
            $this->cats[$id_categ]['fullpath'] = '_' . $this->motherof[$cursor_categ] . $this->cats[$id_categ]['fullpath'];
1048
            $this->cats[$id_categ]['fulllabel'] = $this->cats[$this->motherof[$cursor_categ]]['label'] . ' >> ' . $this->cats[$id_categ]['fulllabel'];
1049
            //print '&nbsp; Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].' '.$this->cats[$id_categ]['fulllabel'].'<br>'."\n";
1050
            $i++;
1051
            $cursor_categ = $this->motherof[$cursor_categ];
1052
        }
1053
        //print 'Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].'<br>'."\n";
1054
        // We count number of _ to have level
1055
        $this->cats[$id_categ]['level'] = dol_strlen(preg_replace('/[^_]/i', '', $this->cats[$id_categ]['fullpath']));
1056
1057
        return;
1058
    }
1059
1060
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1061
    /**
1062
     * 	Display content of $this->cats
1063
     *
1064
     * 	@return	void
1065
     */
1066
    function debug_cats()
1067
    {
1068
        // phpcs:enable
1069
        // Display $this->cats
1070
        foreach ($this->cats as $key => $val) {
1071
            print 'id: ' . $this->cats[$key]['id'];
1072
            print ' label: ' . $this->cats[$key]['label'];
1073
            print ' mother: ' . $this->cats[$key]['fk_parent'];
1074
            //print ' children: '.(is_array($this->cats[$key]['id_children'])?join(',',$this->cats[$key]['id_children']):'');
1075
            print ' fullpath: ' . $this->cats[$key]['fullpath'];
1076
            print ' fulllabel: ' . $this->cats[$key]['fulllabel'];
1077
            print "<br>\n";
1078
        }
1079
    }
1080
1081
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1082
    /**
1083
     * 	Returns all categories
1084
     *
1085
     * 	@param	int			$type		Type of category (0, 1, ...)
1086
     * 	@param	boolean		$parent		Just parent categories if true
1087
     * 	@return	array|int				Table of Object Category, -1 on error
1088
     */
1089
    function get_all_categories($type = null, $parent = false)
1090
    {
1091
        // phpcs:enable
1092
        if (!is_numeric($type))
1093
            $type = $this->MAP_ID[$type];
1094
1095
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
1096
        $sql .= " WHERE entity IN (" . getEntity('category') . ")";
1097
        if (!is_null($type))
1098
            $sql .= " AND type = " . $type;
1099
        if ($parent)
1100
            $sql .= " AND fk_parent = 0";
1101
1102
        $res = Config::$dbEngine->select($sql);
1103
        if ($res) {
1104
            $cats = array();
1105
            while ($rec = Config::$dbEngine->fetch_array($res)) {
1106
                $cat = new Categorie(Config::$dbEngine);
1107
                $cat->fetch($rec['rowid']);
1108
                $cats[$rec['rowid']] = $cat;
1109
            }
1110
            return $cats;
1111
        } else {
1112
            dol_print_error(Config::$dbEngine);
1113
            return -1;
1114
        }
1115
    }
1116
1117
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1118
    /**
1119
     * 	Check if no category with same label already exists for this cat's parent or root and for this cat's type
1120
     *
1121
     * 	@return		integer		1 if already exist, 0 otherwise, -1 if error
1122
     */
1123
    function already_exists()
1124
    {
1125
        // phpcs:enable
1126
        $type = $this->type;
1127
1128
        if (!is_numeric($type))
1129
            $type = $this->MAP_ID[$type];
1130
1131
        /* We have to select any rowid from llx_categorie which category's mother and label
1132
         * are equals to those of the calling category
1133
         */
1134
        $sql = "SELECT c.rowid";
1135
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as c ";
1136
        $sql .= " WHERE c.entity IN (" . getEntity('category') . ")";
1137
        $sql .= " AND c.type = " . $type;
1138
        $sql .= " AND c.fk_parent = " . $this->fk_parent;
1139
        $sql .= " AND c.label = '" . Config::$dbEngine->escape($this->label) . "'";
1140
1141
        dol_syslog(get_class($this) . "::already_exists", LOG_DEBUG);
1142
        $resql = Config::$dbEngine->select($sql);
1143
        if ($resql) {
1144
            if (Config::$dbEngine->num_rows($resql) > 0) {      // Checking for empty resql{
1145
                $obj = Config::$dbEngine->fetch_array($resql);
1146
                /* If object called create, obj cannot have is id.
1147
                 * If object called update, he mustn't have the same label as an other category for this mother.
1148
                 * So if the result have the same id, update is not for label, and if result have an other one,
1149
                 * update may be for label.
1150
                 */
1151
                if ($obj[0] > 0 && $obj[0] != $this->id) {
1152
                    dol_syslog(get_class($this) . "::already_exists category with name=" . $this->label . " and parent " . $this->fk_parent . " exists: rowid=" . $obj[0] . " current_id=" . $this->id, LOG_DEBUG);
1153
                    return 1;
1154
                }
1155
            }
1156
            dol_syslog(get_class($this) . "::already_exists no category with same name=" . $this->label . " and same parent " . $this->fk_parent . " than category id=" . $this->id, LOG_DEBUG);
1157
            return 0;
1158
        } else {
1159
            $this->error = Config::$dbEngine->error();
1160
            return -1;
1161
        }
1162
    }
1163
1164
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1165
    /**
1166
     * 	Returns the top level categories (which are not girls)
1167
     *
1168
     * 	@param		int		$type		Type of category (0, 1, ...)
1169
     * 	@return		array
1170
     */
1171
    function get_main_categories($type = null)
1172
    {
1173
        // phpcs:enable
1174
        return $this->get_all_categories($type, true);
1175
    }
1176
1177
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1178
    /**
1179
     * Returns the path of the category, with the names of the categories
1180
     * separated by $sep (" >> " by default)
1181
     *
1182
     * @param	string	$sep	     Separator
1183
     * @param	string	$url	     Url
1184
     * @param   int     $nocolor     0
1185
     * @return	array
1186
     */
1187
    function print_all_ways($sep = " &gt;&gt; ", $url = '', $nocolor = 0)
1188
    {
1189
        // phpcs:enable
1190
        $ways = array();
1191
1192
        $allways = $this->get_all_ways(); // Load array of categories
1193
        foreach ($allways as $way) {
1194
            $w = array();
1195
            $i = 0;
1196
            $forced_color = '';
1197
            foreach ($way as $cat) {
1198
                $i++;
1199
1200
                if (empty($nocolor)) {
1201
                    $forced_color = 'toreplace';
1202
                    if ($i == count($way)) {
1203
                        // Check contrast with background and correct text color
1204
                        $forced_color = 'categtextwhite';
1205
                        if ($cat->color) {
1206
                            if (colorIsLight($cat->color))
1207
                                $forced_color = 'categtextblack';
1208
                        }
1209
                    }
1210
                }
1211
1212
                if ($url == '') {
1213
                    $link = '<a href="' . DOL_URL_ROOT . '/categories/viewcat.php?id=' . $cat->id . '&type=' . $cat->type . '" class="' . $forced_color . '">';
1214
                    $linkend = '</a>';
1215
                    $w[] = $link . $cat->label . $linkend;
1216
                } else {
1217
                    $w[] = "<a href='" . DOL_URL_ROOT . "/$url?catid=" . $cat->id . "'>" . $cat->label . "</a>";
1218
                }
1219
            }
1220
            $newcategwithpath = preg_replace('/toreplace/', $forced_color, implode($sep, $w));
1221
1222
            $ways[] = $newcategwithpath;
1223
        }
1224
1225
        return $ways;
1226
    }
1227
1228
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1229
    /**
1230
     * 	Returns an array containing the list of parent categories
1231
     *
1232
     * 	@return	int|array <0 KO, array OK
1233
     */
1234
    function get_meres()
1235
    {
1236
        // phpcs:enable
1237
        $parents = array();
1238
1239
        $sql = "SELECT fk_parent FROM " . MAIN_DB_PREFIX . "categorie";
1240
        $sql .= " WHERE rowid = " . $this->id;
1241
1242
        $res = Config::$dbEngine->select($sql);
1243
1244
        if ($res) {
1245
            while ($rec = Config::$dbEngine->fetch_array($res)) {
1246
                if ($rec['fk_parent'] > 0) {
1247
                    $cat = new Categorie(Config::$dbEngine);
1248
                    $cat->fetch($rec['fk_parent']);
1249
                    $parents[] = $cat;
1250
                }
1251
            }
1252
            return $parents;
1253
        } else {
1254
            dol_print_error(Config::$dbEngine);
1255
            return -1;
1256
        }
1257
    }
1258
1259
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1260
    /**
1261
     * 	Returns in a table all possible paths to get to the category
1262
     * 	starting with the major categories represented by Tables of categories
1263
     *
1264
     * 	@return	array
1265
     */
1266
    function get_all_ways()
1267
    {
1268
        // phpcs:enable
1269
        $ways = array();
1270
1271
        $parents = $this->get_meres();
1272
        if (!empty($parents)) {
1273
            foreach ($parents as $parent) {
1274
                $allways = $parent->get_all_ways();
1275
                foreach ($allways as $way) {
1276
                    $w = $way;
1277
                    $w[] = $this;
1278
                    $ways[] = $w;
1279
                }
1280
            }
1281
        }
1282
1283
        if (count($ways) == 0)
1284
            $ways[0][0] = $this;
1285
1286
        return $ways;
1287
    }
1288
1289
    /**
1290
     * Return list of categories (object instances or labels) linked to element of id $id and type $type
1291
     * Should be named getListOfCategForObject
1292
     *
1293
     * @param   int    		$id     Id of element
1294
     * @param   string|int	$type   Type of category ('customer', 'supplier', 'contact', 'product', 'member') or (0, 1, 2, ...)
1295
     * @param   string 		$mode   'id'=Get array of category ids, 'object'=Get array of fetched category instances, 'label'=Get array of category
1296
     *                      	    labels, 'id'= Get array of category IDs
1297
     * @return  array|int           Array of category objects or < 0 if KO
1298
     */
1299
    function containing($id, $type, $mode = 'object')
1300
    {
1301
        $cats = array();
1302
1303
        if (is_numeric($type))
1304
            $type = Categorie::$MAP_ID_TO_CODE[$type];
1305
1306
        if ($type === Categorie::TYPE_BANK_LINE) {   // TODO Remove this with standard category code
1307
            // Load bank groups
1308
            $sql = "SELECT c.label, c.rowid";
1309
            $sql .= " FROM " . MAIN_DB_PREFIX . "bank_class as a, " . MAIN_DB_PREFIX . "bank_categ as c";
1310
            $sql .= " WHERE a.lineid=" . $id . " AND a.fk_categ = c.rowid";
1311
            $sql .= " ORDER BY c.label";
1312
1313
            $res = Config::$dbEngine->select($sql);
1314
            if ($res) {
1315
                while ($obj = Config::$dbEngine->fetch_object($res)) {
1316
                    if ($mode == 'id') {
1317
                        $cats[] = $obj->rowid;
1318
                    } else if ($mode == 'label') {
1319
                        $cats[] = $obj->label;
1320
                    } else {
1321
                        $cat = new Categorie(Config::$dbEngine);
1322
                        $cat->id = $obj->rowid;
1323
                        $cat->label = $obj->label;
1324
                        $cats[] = $cat;
1325
                    }
1326
                }
1327
            } else {
1328
                dol_print_error(Config::$dbEngine);
1329
                return -1;
1330
            }
1331
        } else {
1332
            $sql = "SELECT ct.fk_categorie, c.label, c.rowid";
1333
            $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_" . $this->MAP_CAT_TABLE[$type] . " as ct, " . MAIN_DB_PREFIX . "categorie as c";
1334
            $sql .= " WHERE ct.fk_categorie = c.rowid AND ct.fk_" . $this->MAP_CAT_FK[$type] . " = " . (int) $id . " AND c.type = " . $this->MAP_ID[$type];
1335
            $sql .= " AND c.entity IN (" . getEntity('category') . ")";
1336
1337
            $res = Config::$dbEngine->select($sql);
1338
            if ($res) {
1339
                while ($obj = Config::$dbEngine->fetch_object($res)) {
1340
                    if ($mode == 'id') {
1341
                        $cats[] = $obj->rowid;
1342
                    } else if ($mode == 'label') {
1343
                        $cats[] = $obj->label;
1344
                    } else {
1345
                        $cat = new Categorie(Config::$dbEngine);
1346
                        $cat->fetch($obj->fk_categorie);
1347
                        $cats[] = $cat;
1348
                    }
1349
                }
1350
            } else {
1351
                dol_print_error(Config::$dbEngine);
1352
                return -1;
1353
            }
1354
        }
1355
1356
        return $cats;
1357
    }
1358
1359
    /**
1360
     * 	Returns categories whose id or name match
1361
     * 	add wildcards in the name unless $exact = true
1362
     *
1363
     * 	@param		int			$id			Id
1364
     * 	@param		string		$nom		Name
1365
     * 	@param		string		$type		Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated.
1366
     * 	@param		boolean		$exact		Exact string search (true/false)
1367
     * 	@param		boolean		$case		Case sensitive (true/false)
1368
     * 	@return		array|int				Array of category id, -1 if error
1369
     */
1370
    function rechercher($id, $nom, $type, $exact = false, $case = false)
1371
    {
1372
        // Deprecation warning
1373
        if (is_numeric($type)) {
1374
            dol_syslog(__METHOD__ . ': using numeric types is deprecated.', LOG_WARNING);
1375
        }
1376
1377
        $cats = array();
1378
1379
        // For backward compatibility
1380
        if (is_numeric($type)) {
1381
            // We want to reverse lookup
1382
            $map_type = array_flip($this->MAP_ID);
1383
            $type = $map_type[$type];
1384
            dol_syslog(get_class($this) . "::rechercher(): numeric types are deprecated, please use string instead", LOG_WARNING);
1385
        }
1386
1387
        // Generation requete recherche
1388
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
1389
        $sql .= " WHERE type = " . $this->MAP_ID[$type];
1390
        $sql .= " AND entity IN (" . getEntity('category') . ")";
1391
        if ($nom) {
1392
            if (!$exact)
1393
                $nom = '%' . str_replace('*', '%', $nom) . '%';
1394
            if (!$case)
1395
                $sql .= " AND label LIKE '" . Config::$dbEngine->escape($nom) . "'";
1396
            else
1397
                $sql .= " AND label LIKE BINARY '" . Config::$dbEngine->escape($nom) . "'";
1398
        }
1399
        if ($id) {
1400
            $sql .= " AND rowid = '" . $id . "'";
1401
        }
1402
1403
        $res = Config::$dbEngine->select($sql);
1404
        if ($res) {
1405
            while ($rec = Config::$dbEngine->fetch_array($res)) {
1406
                $cat = new Categorie(Config::$dbEngine);
1407
                $cat->fetch($rec['rowid']);
1408
                $cats[] = $cat;
1409
            }
1410
1411
            return $cats;
1412
        } else {
1413
            $this->error = Config::$dbEngine->error() . ' sql=' . $sql;
1414
            return -1;
1415
        }
1416
    }
1417
1418
    /**
1419
     * 	Return name and link of category (with picto)
1420
     *  Use ->id, ->ref, ->label, ->color
1421
     *
1422
     * 	@param		int		$withpicto		0=No picto, 1=Include picto into link, 2=Only picto
1423
     * 	@param		string	$option			Sur quoi pointe le lien ('', 'xyz')
1424
     * 	@param		int		$maxlength		Max length of text
1425
     * 	@return		string					Chaine avec URL
1426
     */
1427
    function getNomUrl($withpicto = 0, $option = '', $maxlength = 0)
1428
    {
1429
        global $langs;
1430
1431
        $result = '';
1432
        $label = $langs->trans("ShowCategory") . ': ' . ($this->ref ? $this->ref : $this->label);
1433
1434
        // Check contrast with background and correct text color
1435
        $forced_color = 'categtextwhite';
1436
        if ($this->color) {
1437
            if (colorIsLight($this->color))
1438
                $forced_color = 'categtextblack';
1439
        }
1440
1441
        $link = '<a href="' . DOL_URL_ROOT . '/categories/viewcat.php?id=' . $this->id . '&type=' . $this->type . '&backtopage=' . urlencode($_SERVER['PHP_SELF']) . '" title="' . dol_escape_htmltag($label, 1) . '" class="classfortooltip ' . $forced_color . '">';
1442
        $linkend = '</a>';
1443
1444
        $picto = 'category';
1445
1446
1447
        if ($withpicto)
1448
            $result .= ($link . img_object($label, $picto, 'class="classfortooltip"') . $linkend);
1449
        if ($withpicto && $withpicto != 2)
1450
            $result .= ' ';
1451
        if ($withpicto != 2)
1452
            $result .= $link . dol_trunc(($this->ref ? $this->ref : $this->label), $maxlength) . $linkend;
1453
        return $result;
1454
    }
1455
1456
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1457
    /**
1458
     *  Deplace fichier uploade sous le nom $files dans le repertoire sdir
1459
     *
1460
     *  @param      string	$sdir       Repertoire destination finale
1461
     *  @param      string	$file		Nom du fichier uploade
1462
     * 	@return		void
1463
     */
1464
    function add_photo($sdir, $file)
1465
    {
1466
        // phpcs:enable
1467
        require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1468
1469
        $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, 'category') . $this->id . "/";
1470
        $dir .= "photos/";
1471
1472
        if (!file_exists($dir)) {
1473
            dol_mkdir($dir);
1474
        }
1475
1476
        if (file_exists($dir)) {
1477
            if (is_array($file['name']) && count($file['name']) > 0) {
1478
                $nbfile = count($file['name']);
1479
                for ($i = 0; $i <= $nbfile; $i ++) {
1480
1481
                    $originImage = $dir . $file['name'][$i];
1482
1483
                    // Cree fichier en taille origine
1484
                    dol_move_uploaded_file($file['tmp_name'][$i], $originImage, 1, 0, 0);
1485
1486
                    if (file_exists($originImage)) {
1487
                        // Create thumbs
1488
                        $this->addThumbs($originImage);
1489
                    }
1490
                }
1491
            } else {
1492
                $originImage = $dir . $file['name'];
1493
1494
                // Cree fichier en taille origine
1495
                dol_move_uploaded_file($file['tmp_name'], $originImage, 1, 0, 0);
1496
1497
                if (file_exists($originImage)) {
1498
                    // Create thumbs
1499
                    $this->addThumbs($originImage);
1500
                }
1501
            }
1502
        }
1503
    }
1504
1505
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1506
    /**
1507
     *    Return tableau de toutes les photos de la categorie
1508
     *
1509
     *    @param      string	$dir        Repertoire a scanner
1510
     *    @param      int		$nbmax      Nombre maximum de photos (0=pas de max)
1511
     *    @return     array       			Tableau de photos
1512
     */
1513
    function liste_photos($dir, $nbmax = 0)
1514
    {
1515
        // phpcs:enable
1516
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1517
1518
        $nbphoto = 0;
1519
        $tabobj = array();
1520
1521
        $dirthumb = $dir . 'thumbs/';
1522
1523
        if (file_exists($dir)) {
1524
            $handle = opendir($dir);
1525
            if (is_resource($handle)) {
1526
                while (($file = readdir($handle)) !== false) {
1527
                    if (dol_is_file($dir . $file) && preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $dir . $file)) {
1528
                        $nbphoto++;
1529
                        $photo = $file;
1530
1531
                        // On determine nom du fichier vignette
1532
                        $photo_vignette = '';
1533
                        if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $photo, $regs)) {
1534
                            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $photo) . '_small' . $regs[0];
1535
                        }
1536
1537
                        // Objet
1538
                        $obj = array();
1539
                        $obj['photo'] = $photo;
1540
                        if ($photo_vignette && is_file($dirthumb . $photo_vignette))
1541
                            $obj['photo_vignette'] = 'thumbs/' . $photo_vignette;
1542
                        else
1543
                            $obj['photo_vignette'] = "";
1544
1545
                        $tabobj[$nbphoto - 1] = $obj;
1546
1547
                        // On continue ou on arrete de boucler
1548
                        if ($nbmax && $nbphoto >= $nbmax)
1549
                            break;
1550
                    }
1551
                }
1552
1553
                closedir($handle);
1554
            }
1555
        }
1556
1557
        return $tabobj;
1558
    }
1559
1560
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1561
    /**
1562
     *    Efface la photo de la categorie et sa vignette
1563
     *
1564
     *    @param	string		$file		Path to file
1565
     *    @return	void
1566
     */
1567
    function delete_photo($file)
1568
    {
1569
        // phpcs:enable
1570
        require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1571
1572
        $dir = dirname($file) . '/'; // Chemin du dossier contenant l'image d'origine
1573
        $dirthumb = $dir . '/thumbs/'; // Chemin du dossier contenant la vignette
1574
        $filename = preg_replace('/' . preg_quote($dir, '/') . '/i', '', $file); // Nom du fichier
1575
        // On efface l'image d'origine
1576
        dol_delete_file($file, 1);
1577
1578
        // Si elle existe, on efface la vignette
1579
        if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $filename, $regs)) {
1580
            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $filename) . '_small' . $regs[0];
1581
            if (file_exists($dirthumb . $photo_vignette)) {
1582
                dol_delete_file($dirthumb . $photo_vignette, 1);
1583
            }
1584
        }
1585
    }
1586
1587
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1588
    /**
1589
     *  Load size of image file
1590
     *
1591
     *  @param    	string	$file        Path to file
1592
     *  @return		void
1593
     */
1594
    function get_image_size($file)
1595
    {
1596
        // phpcs:enable
1597
        $infoImg = getimagesize($file); // Recuperation des infos de l'image
1598
        $this->imgWidth = $infoImg[0]; // Largeur de l'image
1599
        $this->imgHeight = $infoImg[1]; // Hauteur de l'image
1600
    }
1601
1602
    /**
1603
     * 	Update ou cree les traductions des infos produits
1604
     *
1605
     * 	@param	User	$user		Object user
1606
     *
1607
     * 	@return		int		<0 if KO, >0 if OK
1608
     */
1609
    function setMultiLangs($user)
1610
    {
1611
        global $langs;
1612
1613
        $langs_available = $langs->get_available_languages();
1614
        $current_lang = $langs->getDefaultLang();
1615
1616
        foreach ($langs_available as $key => $value) {
1617
            $sql = "SELECT rowid";
1618
            $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_lang";
1619
            $sql .= " WHERE fk_category=" . $this->id;
1620
            $sql .= " AND lang='" . $key . "'";
1621
1622
            $result = Config::$dbEngine->select($sql);
1623
1624
            if ($key == $current_lang) {
1625
                if (Config::$dbEngine->num_rows($result)) { // si aucune ligne dans la base{
1626
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "categorie_lang";
1627
                    $sql2 .= " SET label='" . Config::$dbEngine->escape($this->label) . "',";
1628
                    $sql2 .= " description='" . Config::$dbEngine->escape($this->description) . "'";
1629
                    $sql2 .= " WHERE fk_category=" . $this->id . " AND lang='" . Config::$dbEngine->escape($key) . "'";
1630
                } else {
1631
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_lang (fk_category, lang, label, description)";
1632
                    $sql2 .= " VALUES(" . $this->id . ",'" . $key . "','" . Config::$dbEngine->escape($this->label);
1633
                    $sql2 .= "','" . Config::$dbEngine->escape($this->multilangs["$key"]["description"]) . "')";
1634
                }
1635
                dol_syslog(get_class($this) . '::setMultiLangs', LOG_DEBUG);
1636
                if (!Config::$dbEngine->query($sql2)) {
1637
                    $this->error = Config::$dbEngine->lasterror();
1638
                    return -1;
1639
                }
1640
            } else if (isset($this->multilangs["$key"])) {
1641
                if (Config::$dbEngine->num_rows($result)) { // si aucune ligne dans la base{
1642
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "categorie_lang";
1643
                    $sql2 .= " SET label='" . Config::$dbEngine->escape($this->multilangs["$key"]["label"]) . "',";
1644
                    $sql2 .= " description='" . Config::$dbEngine->escape($this->multilangs["$key"]["description"]) . "'";
1645
                    $sql2 .= " WHERE fk_category=" . $this->id . " AND lang='" . Config::$dbEngine->escape($key) . "'";
1646
                } else {
1647
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_lang (fk_category, lang, label, description)";
1648
                    $sql2 .= " VALUES(" . $this->id . ",'" . $key . "','" . Config::$dbEngine->escape($this->multilangs["$key"]["label"]);
1649
                    $sql2 .= "','" . Config::$dbEngine->escape($this->multilangs["$key"]["description"]) . "')";
1650
                }
1651
1652
                // on ne sauvegarde pas des champs vides
1653
                if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"] || $this->multilangs["$key"]["note"])
1654
                    dol_syslog(get_class($this) . '::setMultiLangs', LOG_DEBUG);
1655
                if (!Config::$dbEngine->query($sql2)) {
1656
                    $this->error = Config::$dbEngine->lasterror();
1657
                    return -1;
1658
                }
1659
            }
1660
        }
1661
1662
        // Call trigger
1663
        $result = $this->call_trigger('CATEGORY_SET_MULTILANGS', $user);
1664
        if ($result < 0) {
1665
            $this->error = Config::$dbEngine->lasterror();
1666
            return -1;
1667
        }
1668
        // End call triggers
1669
1670
        return 1;
1671
    }
1672
1673
    /**
1674
     * 	Load array this->multilangs
1675
     *
1676
     * 	@return		int		<0 if KO, >0 if OK
1677
     */
1678
    function getMultiLangs()
1679
    {
1680
        global $langs;
1681
1682
        $current_lang = $langs->getDefaultLang();
1683
1684
        $sql = "SELECT lang, label, description";
1685
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_lang";
1686
        $sql .= " WHERE fk_category=" . $this->id;
1687
1688
        $result = Config::$dbEngine->select($sql);
1689
        if ($result) {
1690
            while ($obj = Config::$dbEngine->fetch_object($result)) {
1691
                //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1692
                if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1693
                    $this->label = $obj->label;
1694
                    $this->description = $obj->description;
1695
                }
1696
                $this->multilangs["$obj->lang"]["label"] = $obj->label;
1697
                $this->multilangs["$obj->lang"]["description"] = $obj->description;
1698
            }
1699
            return 1;
1700
        } else {
1701
            $this->error = $langs->trans("Error") . " : " . Config::$dbEngine->error() . " - " . $sql;
1702
            return -1;
1703
        }
1704
    }
1705
1706
    /**
1707
     * 	Return label of contact status
1708
     *
1709
     * 	@param      int			$mode       0=libelle long, 1=libelle court, 2=Picto + Libelle court, 3=Picto, 4=Picto + Libelle long, 5=Libelle court + Picto
1710
     * 	@return 	string					Label of contact status
1711
     */
1712
    function getLibStatut($mode)
1713
    {
1714
        return '';
1715
    }
1716
1717
    /**
1718
     *  Initialise an instance with random values.
1719
     *  Used to build previews or test instances.
1720
     * 	id must be 0 if object instance is a specimen.
1721
     *
1722
     *  @return	void
1723
     */
1724
    function initAsSpecimen()
1725
    {
1726
        dol_syslog(get_class($this) . "::initAsSpecimen");
1727
1728
        // Initialise parametres
1729
        $this->id = 0;
1730
        $this->fk_parent = 0;
1731
        $this->label = 'SPECIMEN';
1732
        $this->specimen = 1;
1733
        $this->description = 'This is a description';
1734
        $this->socid = 1;
1735
        $this->type = self::TYPE_PRODUCT;
1736
    }
1737
1738
    /**
1739
     * Function used to replace a thirdparty id with another one.
1740
     *
1741
     * @param DoliDB $db Database handler
1742
     * @param int $origin_id Old thirdparty id
1743
     * @param int $dest_id New thirdparty id
1744
     * @return bool
1745
     */
1746
    public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
1747
    {
1748
        $tables = array(
1749
            'categorie_societe'
1750
        );
1751
1752
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables, 1);
1753
    }
1754
}
1755