Passed
Pull Request — dev (#8)
by Rafael
58:47
created

Categorie::getObjectsInCateg()   F

Complexity

Conditions 19
Paths 704

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 40
nc 704
nop 8
dl 0
loc 59
rs 0.7611
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* Copyright (C) 2005       Matthieu Valleton           <[email protected]>
4
 * Copyright (C) 2005       Davoleau Brice              <[email protected]>
5
 * Copyright (C) 2005       Rodolphe Quiedeville        <[email protected]>
6
 * Copyright (C) 2006-2012  Regis Houssin               <[email protected]>
7
 * Copyright (C) 2006-2012  Laurent Destailleur         <[email protected]>
8
 * Copyright (C) 2007       Patrick Raguin              <[email protected]>
9
 * Copyright (C) 2013-2016  Juanjo Menent               <[email protected]>
10
 * Copyright (C) 2013-2018  Philippe Grand              <[email protected]>
11
 * Copyright (C) 2015       Marcos García               <[email protected]>
12
 * Copyright (C) 2015       Raphaël Doursenaud          <[email protected]>
13
 * Copyright (C) 2016       Charlie Benke               <[email protected]>
14
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
15
 * Copyright (C) 2023-2024	Benjamin Falière		    <[email protected]>
16
 * Copyright (C) 2024		MDW	                        <[email protected]>
17
 * Copyright (C) 2024       Rafael San José             <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31
 */
32
33
namespace Dolibarr\Code\Categories\Classes;
34
35
use Dolibarr\Code\User\Classes\User;
36
use Dolibarr\Core\Base\CommonObject;
37
use Dolibarr\Lib\Misc;
38
use DoliDB;
39
40
/**
41
 *  \file       htdocs/categories/class/categorie.class.php
42
 *  \ingroup    categorie
43
 *  \brief      File of class to manage categories
44
 */
45
46
/**
47
 *  Class to manage categories
48
 */
49
class Categorie extends CommonObject
50
{
51
    // Categories types (we use string because we want to accept any modules/types in a future)
52
    const TYPE_PRODUCT = 'product';
53
    const TYPE_SUPPLIER = 'supplier';
54
    const TYPE_CUSTOMER = 'customer';
55
    const TYPE_MEMBER = 'member';
56
    const TYPE_CONTACT = 'contact';
57
    const TYPE_USER = 'user';
58
    const TYPE_PROJECT = 'project';
59
    const TYPE_ACCOUNT = 'bank_account';
60
    const TYPE_BANK_LINE = 'bank_line';
61
    const TYPE_WAREHOUSE = 'warehouse';
62
    const TYPE_ACTIONCOMM = 'actioncomm';
63
    const TYPE_WEBSITE_PAGE = 'website_page';
64
    const TYPE_TICKET = 'ticket';
65
    const TYPE_KNOWLEDGEMANAGEMENT = 'knowledgemanagement';
66
    /**
67
     * @var array Code mapping from ID
68
     *
69
     * @note This array should be removed in future, once previous constants are moved to the string value. Deprecated
70
     */
71
    public static $MAP_ID_TO_CODE = array(
72
        0 => 'product',
73
        1 => 'supplier',
74
        2 => 'customer',
75
        3 => 'member',
76
        4 => 'contact',
77
        5 => 'bank_account',
78
        6 => 'project',
79
        7 => 'user',
80
        8 => 'bank_line',
81
        9 => 'warehouse',
82
        10 => 'actioncomm',
83
        11 => 'website_page',
84
        12 => 'ticket',
85
        13 => 'knowledgemanagement'
86
    );
87
    /**
88
     * @var array Title Area mapping from type string
89
     *
90
     * @note Move to const array when PHP 5.6 will be our minimum target
91
     */
92
    public static $MAP_TYPE_TITLE_AREA = array(
93
        'product' => 'ProductsCategoriesArea',
94
        'customer' => 'CustomersCategoriesArea',
95
        'supplier' => 'SuppliersCategoriesArea',
96
        'member' => 'MembersCategoriesArea',
97
        'contact' => 'ContactsCategoriesArea',
98
        'user' => 'UsersCategoriesArea',
99
        'account' => 'AccountsCategoriesArea', // old for bank account
100
        'bank_account' => 'AccountsCategoriesArea',
101
        'project' => 'ProjectsCategoriesArea',
102
        'warehouse' => 'StocksCategoriesArea',
103
        'actioncomm' => 'ActioncommCategoriesArea',
104
        'website_page' => 'WebsitePageCategoriesArea'
105
    );
106
    /**
107
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
108
     */
109
    public $picto = 'category';
110
    /**
111
     * @var array Foreign keys mapping from type string when value does not match
112
     *
113
     * @todo Move to const array when PHP 5.6 will be our minimum target
114
     */
115
    public $MAP_CAT_FK = array(
116
        'customer' => 'soc',
117
        'supplier' => 'soc',
118
        'contact' => 'socpeople',
119
        'bank_account' => 'account',
120
    );
121
122
    /**
123
     * @var array Category tables mapping from type string (llx_categorie_...) when value does not match
124
     *
125
     * @note Move to const array when PHP 5.6 will be our minimum target
126
     */
127
    public $MAP_CAT_TABLE = array(
128
        'customer' => 'societe',
129
        'supplier' => 'fournisseur',
130
        'bank_account' => 'account',
131
    );
132
133
    /**
134
     * @var array Object class mapping from type string
135
     *
136
     * @note Move to const array when PHP 5.6 will be our minimum target
137
     */
138
    public $MAP_OBJ_CLASS = array(
139
        'product' => 'Product',
140
        'customer' => 'Societe',
141
        'supplier' => 'Fournisseur',
142
        'member' => 'Adherent',
143
        'contact' => 'Contact',
144
        'user' => 'User',
145
        'account' => 'Account', // old for bank account
146
        'bank_account' => 'Account',
147
        'project' => 'Project',
148
        'warehouse' => 'Entrepot',
149
        'actioncomm' => 'ActionComm',
150
        'website_page' => 'WebsitePage',
151
        'ticket' => 'Ticket',
152
        'knowledgemanagement' => 'KnowledgeRecord'
153
    );
154
    /**
155
     * @var array   Object table mapping from type string (table llx_...) when value of key does not match table name.
156
     *              This array may be completed by external modules with hook "constructCategory"
157
     */
158
    public $MAP_OBJ_TABLE = array(
159
        'customer' => 'societe',
160
        'supplier' => 'societe',
161
        'member' => 'adherent',
162
        'contact' => 'socpeople',
163
        'account' => 'bank_account', // old for bank account
164
        'project' => 'projet',
165
        'warehouse' => 'entrepot',
166
        'knowledgemanagement' => 'knowledgemanagement_knowledgerecord'
167
    );
168
    /**
169
     * @var string ID to identify managed object
170
     */
171
    public $element = 'category';
172
    /**
173
     * @var string Name of table without prefix where object is stored
174
     */
175
    public $table_element = 'categorie';
176
    /**
177
     * @var int ID
178
     */
179
    public $fk_parent;
180
    /**
181
     * @var string Category label
182
     */
183
    public $label;
184
    /**
185
     * @var string description
186
     */
187
    public $description;
188
    /**
189
     * @var string     Color
190
     */
191
    public $color;
192
    /**
193
     * @var int Position
194
     */
195
    public $position;
196
    /**
197
     * @var int Visible
198
     */
199
    public $visible;
200
    /**
201
     * @var int       Id of thirdparty when CATEGORY_ASSIGNED_TO_A_CUSTOMER is set
202
     */
203
    public $socid;
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_PROJECT
214
     * @see Categorie::TYPE_ACCOUNT
215
     * @see Categorie::TYPE_BANK_LINE
216
     * @see Categorie::TYPE_WAREHOUSE
217
     * @see Categorie::TYPE_ACTIONCOMM
218
     * @see Categorie::TYPE_WEBSITE_PAGE
219
     * @see Categorie::TYPE_TICKET
220
     */
221
    public $type;
222
    /**
223
     * @var array<int,array{rowid:int,id:int,fk_parent:int,label:string,description:string,color:string,position:string,visible:int,ref_ext:string,picto:string,fullpath:string,fulllabel:string}>  Categories table in memory
224
     */
225
    public $cats = array();
226
    /**
227
     * @var array Mother of table
228
     */
229
    public $motherof = array();
230
    /**
231
     * @var array children
232
     */
233
    public $childs = array();
234
    /**
235
     * @var ?array{string,array{label:string,description:string,note?:string}} multilangs
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?array{string,array{labe...n:string,note?:string}} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
236
     */
237
    public $multilangs;
238
    /**
239
     * @var int imgWidth
240
     */
241
    public $imgWidth;
242
    /**
243
     * @var int imgHeight
244
     */
245
    public $imgHeight;
246
    /**
247
     * @var array Table of mapping between type string and ID used for field 'type' in table llx_categories
248
     */
249
    protected $MAP_ID = array(
250
        'product' => 0,
251
        'supplier' => 1,
252
        'customer' => 2,
253
        'member' => 3,
254
        'contact' => 4,
255
        'bank_account' => 5,
256
        'project' => 6,
257
        'user' => 7,
258
        'bank_line' => 8,
259
        'warehouse' => 9,
260
        'actioncomm' => 10,
261
        'website_page' => 11,
262
        'ticket' => 12,
263
        'knowledgemanagement' => 13
264
    );
265
266
    /**
267
     *  Constructor
268
     *
269
     * @param DoliDB $db Database handler
270
     */
271
    public function __construct($db)
272
    {
273
        global $hookmanager;
274
275
        $this->db = $db;
276
277
        if (is_object($hookmanager)) {
278
            $hookmanager->initHooks(array('category'));
279
            $parameters = array();
280
            $reshook = $hookmanager->executeHooks('constructCategory', $parameters, $this); // Note that $action and $object may have been modified by some hooks
281
            if ($reshook >= 0 && !empty($hookmanager->resArray)) {
282
                foreach ($hookmanager->resArray as $mapList) {
283
                    $mapId = $mapList['id'];
284
                    $mapCode = $mapList['code'];
285
                    self::$MAP_ID_TO_CODE[$mapId] = $mapCode;
286
                    $this->MAP_ID[$mapCode] = $mapId;
287
                    $this->MAP_CAT_FK[$mapCode] = isset($mapList['cat_fk']) ? $mapList['cat_fk'] : null;
288
                    $this->MAP_CAT_TABLE[$mapCode] = isset($mapList['cat_table']) ? $mapList['cat_table'] : null;
289
                    $this->MAP_OBJ_CLASS[$mapCode] = $mapList['obj_class'];
290
                    $this->MAP_OBJ_TABLE[$mapCode] = $mapList['obj_table'];
291
                }
292
            }
293
        }
294
    }
295
296
    /**
297
     * Function used to replace a thirdparty id with another one.
298
     *
299
     * @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
300
     * @param int $origin_id Old thirdparty id
301
     * @param int $dest_id New thirdparty id
302
     * @return  bool
303
     */
304
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
305
    {
306
        $tables = array(
307
            'categorie_societe'
308
        );
309
310
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables, 1);
311
    }
312
313
    /**
314
     * Return the additional SQL JOIN query for filtering a list by a category
315
     *
316
     * @param string $type The category type (e.g Categorie::TYPE_WAREHOUSE)
317
     * @param string $rowIdName The name of the row id inside the whole sql query (e.g. "e.rowid")
318
     * @return string                   A additional SQL JOIN query
319
     * @deprecated  search on some categories must be done using a WHERE EXISTS or NOT EXISTS and not a LEFT JOIN. @TODO Replace with getWhereQuery($type, $searchCategoryList)
320
     */
321
    public static function getFilterJoinQuery($type, $rowIdName)
322
    {
323
        if ($type == 'bank_account') {
324
            $type = 'account';
325
        }
326
327
        return " LEFT JOIN " . MAIN_DB_PREFIX . "categorie_" . $type . " as cp ON " . $rowIdName . " = cp.fk_" . $type;
328
    }
329
330
    /**
331
     * Return the additional SQL SELECT query for filtering a list by a category
332
     *
333
     * @param string $type The category type (e.g Categorie::TYPE_WAREHOUSE)
334
     * @param string $rowIdName The name of the row id inside the whole sql query (e.g. "e.rowid")
335
     * @param Array $searchList A list with the selected categories
336
     * @return string                   A additional SQL SELECT query
337
     * @deprecated  search on some categories must be done using a WHERE EXISTS or NOT EXISTS and not a LEFT JOIN
338
     */
339
    public static function getFilterSelectQuery($type, $rowIdName, $searchList)
340
    {
341
        if ($type == 'bank_account') {
342
            $type = 'account';
343
        }
344
        if ($type == 'customer') {
345
            $type = 'societe';
346
        }
347
        if ($type == 'supplier') {
348
            $type = 'fournisseur';
349
        }
350
351
        if (empty($searchList) && !is_array($searchList)) {
352
            return "";
353
        }
354
355
        $searchCategorySqlList = array();
356
        foreach ($searchList as $searchCategory) {
357
            if (intval($searchCategory) == -2) {
358
                $searchCategorySqlList[] = " cp.fk_categorie IS NULL";
359
            } elseif (intval($searchCategory) > 0) {
360
                $searchCategorySqlList[] = " " . $rowIdName . " IN (SELECT fk_" . $type . " FROM " . MAIN_DB_PREFIX . "categorie_" . $type . " WHERE fk_categorie = " . ((int)$searchCategory) . ")";
361
            }
362
        }
363
364
        if (!empty($searchCategorySqlList)) {
365
            return " AND (" . implode(' AND ', $searchCategorySqlList) . ")";
366
        } else {
367
            return "";
368
        }
369
    }
370
371
    /**
372
     * Get map list
373
     *
374
     * @return  array
375
     */
376
    public function getMapList()
377
    {
378
        $mapList = array();
379
380
        foreach ($this->MAP_ID as $mapCode => $mapId) {
381
            $mapList[] = array(
382
                'id' => $mapId,
383
                'code' => $mapCode,
384
                'cat_fk' => (empty($this->MAP_CAT_FK[$mapCode]) ? $mapCode : $this->MAP_CAT_FK[$mapCode]),
385
                'cat_table' => (empty($this->MAP_CAT_TABLE[$mapCode]) ? $mapCode : $this->MAP_CAT_TABLE[$mapCode]),
386
                'obj_class' => (empty($this->MAP_OBJ_CLASS[$mapCode]) ? $mapCode : $this->MAP_OBJ_CLASS[$mapCode]),
387
                'obj_table' => (empty($this->MAP_OBJ_TABLE[$mapCode]) ? $mapCode : $this->MAP_OBJ_TABLE[$mapCode])
388
            );
389
        }
390
391
        return $mapList;
392
    }
393
394
    /**
395
     *  Add category into database
396
     *
397
     * @param User $user Object user
398
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
399
     * @return int                 -1 : SQL error
400
     *                              -2 : new ID unknown
401
     *                              -3 : Invalid category
402
     *                              -4 : category already exists
403
     */
404
    public function create($user, $notrigger = 0)
405
    {
406
        global $conf, $langs, $hookmanager;
407
        $langs->load('categories');
408
409
        $type = $this->type;
410
411
        if (!is_numeric($type)) {
412
            $type = $this->MAP_ID[$type];
413
        }
414
415
        $error = 0;
416
417
        dol_syslog(get_class($this) . '::create', LOG_DEBUG);
418
419
        // Clean parameters
420
        $this->label = trim($this->label);
421
        $this->description = trim($this->description);
422
        $this->color = trim($this->color);
423
        $this->position = (int)$this->position;
424
        $this->import_key = trim($this->import_key);
425
        $this->ref_ext = trim($this->ref_ext);
426
        if (empty($this->visible)) {
427
            $this->visible = 0;
428
        }
429
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
430
431
        if ($this->already_exists()) {
432
            $this->error = $langs->trans("ImpossibleAddCat", $this->label);
433
            $this->error .= " : " . $langs->trans("CategoryExistsAtSameLevel");
434
            dol_syslog($this->error, LOG_WARNING);
435
            return -4;
436
        }
437
438
        $this->db->begin();
439
        $now = dol_now();
440
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "categorie (";
441
        $sql .= "fk_parent,";
442
        $sql .= " label,";
443
        $sql .= " description,";
444
        $sql .= " color,";
445
        $sql .= " position,";
446
        if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
447
            $sql .= "fk_soc,";
448
        }
449
        $sql .= " visible,";
450
        $sql .= " type,";
451
        $sql .= " import_key,";
452
        $sql .= " ref_ext,";
453
        $sql .= " entity,";
454
        $sql .= " date_creation,";
455
        $sql .= " fk_user_creat";
456
        $sql .= ") VALUES (";
457
        $sql .= (int)$this->fk_parent . ",";
458
        $sql .= "'" . $this->db->escape($this->label) . "', ";
459
        $sql .= "'" . $this->db->escape($this->description) . "', ";
460
        $sql .= "'" . $this->db->escape($this->color) . "', ";
461
        $sql .= (int)$this->position . ",";
462
        if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
463
            $sql .= ($this->socid > 0 ? $this->socid : 'null') . ", ";
464
        }
465
        $sql .= "'" . $this->db->escape($this->visible) . "', ";
466
        $sql .= ((int)$type) . ", ";
467
        $sql .= (!empty($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : 'null') . ", ";
468
        $sql .= (!empty($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : 'null') . ", ";
469
        $sql .= (int)$conf->entity . ", ";
470
        $sql .= "'" . $this->db->idate($now) . "', ";
471
        $sql .= (int)$user->id;
472
        $sql .= ")";
473
474
        $res = $this->db->query($sql);
475
        if ($res) {
476
            $id = $this->db->last_insert_id(MAIN_DB_PREFIX . "categorie");
477
478
            if ($id > 0) {
479
                $this->id = $id;
480
481
                $action = 'create';
482
483
                // Actions on extra fields
484
                if (!$error) {
485
                    $result = $this->insertExtraFields();
486
                    if ($result < 0) {
487
                        $error++;
488
                    }
489
                }
490
491
                if (!$error && !$notrigger) {
492
                    // Call trigger
493
                    $result = $this->call_trigger('CATEGORY_CREATE', $user);
494
                    if ($result < 0) {
495
                        $error++;
496
                    }
497
                    // End call triggers
498
                }
499
500
                if (!$error) {
501
                    $this->db->commit();
502
                    return $id;
503
                } else {
504
                    $this->db->rollback();
505
                    return -3;
506
                }
507
            } else {
508
                $this->db->rollback();
509
                return -2;
510
            }
511
        } else {
512
            $this->error = $this->db->error();
513
            $this->db->rollback();
514
            return -1;
515
        }
516
    }
517
518
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
519
520
    /**
521
     *  Check if a category with same label already exists for this cat's parent or root and for this cat's type
522
     *
523
     * @return     integer     1 if record already exist, 0 otherwise, -1 if error
524
     */
525
    public function already_exists()
526
    {
527
        // phpcs:enable
528
        $type = $this->type;
529
530
        if (!is_numeric($type)) {
531
            $type = $this->MAP_ID[$type];
532
        }
533
534
        /* We have to select any rowid from llx_categorie which category's mother and label
535
         * are equals to those of the calling category
536
         */
537
        $sql = "SELECT c.rowid";
538
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as c ";
539
        $sql .= " WHERE c.entity IN (" . getEntity('category') . ")";
540
        $sql .= " AND c.type = " . ((int)$type);
541
        $sql .= " AND c.fk_parent = " . ((int)$this->fk_parent);
542
        $sql .= " AND c.label = '" . $this->db->escape($this->label) . "'";
543
544
        dol_syslog(get_class($this) . "::already_exists", LOG_DEBUG);
545
546
        $resql = $this->db->query($sql);
547
        if ($resql) {
548
            if ($this->db->num_rows($resql) > 0) {                      // Checking for empty resql
549
                $obj = $this->db->fetch_object($resql);
550
                /* If object called create, obj cannot have is id.
551
                 * If object called update, he mustn't have the same label as an other category for this mother.
552
                 * So if the result has the same id, update is not for label, and if result has an other one, update may be for label.
553
                 */
554
                if (!empty($obj) && $obj->rowid > 0 && $obj->rowid != $this->id) {
555
                    dol_syslog(get_class($this) . "::already_exists category with name=" . $this->label . " and parent " . $this->fk_parent . " exists: rowid=" . $obj->rowid . " current_id=" . $this->id, LOG_DEBUG);
556
                    return 1;
557
                }
558
            }
559
            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);
560
            return 0;
561
        } else {
562
            $this->error = $this->db->error();
563
            return -1;
564
        }
565
    }
566
567
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
568
569
    /**
570
     *  Update category
571
     *
572
     * @param User $user Object user
573
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
574
     * @return int                 1 : OK
575
     *                              -1 : SQL error
576
     *                              -2 : invalid category
577
     */
578
    public function update(User $user, $notrigger = 0)
579
    {
580
        global $langs;
581
582
        $error = 0;
583
584
        // Clean parameters
585
        $this->label = trim($this->label);
586
        $this->description = trim($this->description);
587
        $this->ref_ext = trim($this->ref_ext);
588
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
589
        $this->visible = ($this->visible != "" ? intval($this->visible) : 0);
590
591
        if ($this->already_exists()) {
592
            $this->error = $langs->trans("ImpossibleUpdateCat");
593
            $this->error .= " : " . $langs->trans("CategoryExistsAtSameLevel");
594
            return -1;
595
        }
596
597
        $this->db->begin();
598
599
        $sql = "UPDATE " . MAIN_DB_PREFIX . "categorie";
600
        $sql .= " SET label = '" . $this->db->escape($this->label) . "',";
601
        $sql .= " description = '" . $this->db->escape($this->description) . "',";
602
        $sql .= " ref_ext = '" . $this->db->escape($this->ref_ext) . "',";
603
        $sql .= " color = '" . $this->db->escape($this->color) . "'";
604
        $sql .= ", position = " . (int)$this->position;
605
        if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
606
            $sql .= ", fk_soc = " . ($this->socid > 0 ? $this->socid : 'null');
607
        }
608
        $sql .= ", visible = " . (int)$this->visible;
609
        $sql .= ", fk_parent = " . (int)$this->fk_parent;
610
        $sql .= ", fk_user_modif = " . (int)$user->id;
611
        $sql .= " WHERE rowid = " . ((int)$this->id);
612
613
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
614
        if ($this->db->query($sql)) {
615
            $action = 'update';
616
617
            // Actions on extra fields
618
            if (!$error) {
619
                $result = $this->insertExtraFields();
620
                if ($result < 0) {
621
                    $error++;
622
                }
623
            }
624
625
            if (!$error && !$notrigger) {
626
                // Call trigger
627
                $result = $this->call_trigger('CATEGORY_MODIFY', $user);
628
                if ($result < 0) {
629
                    $error++;
630
                }
631
                // End call triggers
632
            }
633
634
            if (!$error) {
635
                $this->db->commit();
636
                return 1;
637
            } else {
638
                $this->db->rollback();
639
                return -1;
640
            }
641
        } else {
642
            $this->db->rollback();
643
            dol_print_error($this->db);
644
            return -1;
645
        }
646
    }
647
648
    /**
649
     *  Delete a category from database
650
     *
651
     * @param User $user Object user that ask to delete
652
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
653
     * @return int                 Return integer <0 KO >0 OK
654
     */
655
    public function delete($user, $notrigger = 0)
656
    {
657
        $error = 0;
658
659
        // Clean parameters
660
        $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
661
662
        dol_syslog(get_class($this) . "::remove");
663
664
        $this->db->begin();
665
666
        if (!$error && !$notrigger) {
667
            // Call trigger
668
            $result = $this->call_trigger('CATEGORY_DELETE', $user);
669
            if ($result < 0) {
670
                $error++;
671
            }
672
            // End call triggers
673
        }
674
675
        /* FIX #1317 : Check for child category and move up 1 level*/
676
        if (!$error) {
677
            $sql = "UPDATE " . MAIN_DB_PREFIX . "categorie";
678
            $sql .= " SET fk_parent = " . ((int)$this->fk_parent);
679
            $sql .= " WHERE fk_parent = " . ((int)$this->id);
680
681
            if (!$this->db->query($sql)) {
682
                $this->error = $this->db->lasterror();
683
                $error++;
684
            }
685
        }
686
687
        $arraydelete = array(
688
            'categorie_account' => 'fk_categorie',
689
            'categorie_actioncomm' => 'fk_categorie',
690
            'categorie_contact' => 'fk_categorie',
691
            'categorie_fournisseur' => 'fk_categorie',
692
            'categorie_knowledgemanagement' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('knowledgemanagement')),
693
            'categorie_member' => 'fk_categorie',
694
            'categorie_user' => 'fk_categorie',
695
            'categorie_product' => 'fk_categorie',
696
            'categorie_project' => 'fk_categorie',
697
            'categorie_societe' => 'fk_categorie',
698
            'categorie_ticket' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('ticket')),
699
            'categorie_warehouse' => 'fk_categorie',
700
            'categorie_website_page' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('website')),
701
            'bank_class' => 'fk_categ',
702
            'categorie_lang' => 'fk_category',
703
            'categorie' => 'rowid',
704
        );
705
        foreach ($arraydelete as $key => $value) {
706
            if (is_array($value)) {
707
                if (empty($value['enabled'])) {
708
                    continue;
709
                }
710
                $value = $value['field'];
711
            }
712
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $key;
713
            $sql .= " WHERE " . $value . " = " . ((int)$this->id);
714
            if (!$this->db->query($sql)) {
715
                $this->errors[] = $this->db->lasterror();
716
                dol_syslog("Error sql=" . $sql . " " . $this->error, LOG_ERR);
717
                $error++;
718
            }
719
        }
720
721
        // Removed extrafields
722
        if (!$error) {
723
            $result = $this->deleteExtraFields();
724
            if ($result < 0) {
725
                $error++;
726
                dol_syslog(get_class($this) . "::delete erreur " . $this->error, LOG_ERR);
727
            }
728
        }
729
730
        if (!$error) {
731
            $this->db->commit();
732
            return 1;
733
        } else {
734
            $this->db->rollback();
735
            return -1;
736
        }
737
    }
738
739
    /**
740
     * Link an object to the category
741
     *
742
     * @param CommonObject $obj Object to link to category
743
     * @param string $type Type of category ('product', ...). Use '' to take $obj->element.
744
     * @return  int                     1 : OK, -1 : erreur SQL, -2 : id not defined, -3 : Already linked
745
     * @see del_type()
746
     */
747
    public function add_type($obj, $type = '')
748
    {
749
        // phpcs:enable
750
        global $user;
751
752
        $error = 0;
753
754
        if ($this->id == -1) {
755
            return -2;
756
        }
757
758
        if (empty($type)) {
759
            $type = $obj->element;
760
        }
761
762
        dol_syslog(get_class($this) . '::add_type', LOG_DEBUG);
763
764
        $this->db->begin();
765
766
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_" . (empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
767
        $sql .= " (fk_categorie, fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . ")";
768
        $sql .= " VALUES (" . ((int)$this->id) . ", " . ((int)$obj->id) . ")";
769
770
        if ($this->db->query($sql)) {
771
            if (getDolGlobalString('CATEGORIE_RECURSIV_ADD')) {
772
                $sql = 'SELECT fk_parent FROM ' . MAIN_DB_PREFIX . 'categorie';
773
                $sql .= " WHERE rowid = " . ((int)$this->id);
774
775
                dol_syslog(get_class($this) . "::add_type", LOG_DEBUG);
776
                $resql = $this->db->query($sql);
777
                if ($resql) {
778
                    if ($this->db->num_rows($resql) > 0) {
779
                        $objparent = $this->db->fetch_object($resql);
780
781
                        if (!empty($objparent->fk_parent)) {
782
                            $cat = new Categorie($this->db);
783
                            $cat->id = $objparent->fk_parent;
784
                            if (!$cat->containsObject($type, $obj->id)) {
785
                                $result = $cat->add_type($obj, $type);
786
                                if ($result < 0) {
787
                                    $this->error = $cat->error;
788
                                    $error++;
789
                                }
790
                            }
791
                        }
792
                    }
793
                } else {
794
                    $error++;
795
                    $this->error = $this->db->lasterror();
796
                }
797
798
                if ($error) {
799
                    $this->db->rollback();
800
                    return -1;
801
                }
802
            }
803
804
            // Call trigger
805
            $this->context = array('linkto' => $obj); // Save object we want to link category to into category instance to provide information to trigger
806
            $result = $this->call_trigger('CATEGORY_LINK', $user);
807
            if ($result < 0) {
808
                $error++;
809
            }
810
            // End call triggers
811
812
            if (!$error) {
813
                $this->db->commit();
814
                return 1;
815
            } else {
816
                $this->db->rollback();
817
                return -2;
818
            }
819
        } else {
820
            $this->db->rollback();
821
            if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
822
                $this->error = $this->db->lasterrno();
823
                return -3;
824
            } else {
825
                $this->error = $this->db->lasterror();
826
            }
827
            return -1;
828
        }
829
    }
830
831
    /**
832
     * Check for the presence of an object in a category
833
     *
834
     * @param string $type Type of category ('customer', 'supplier', 'contact', 'product', 'member')
835
     * @param int $object_id Id of the object to search
836
     * @return  int                     Number of occurrences
837
     * @see getObjectsInCateg()
838
     */
839
    public function containsObject($type, $object_id)
840
    {
841
        $sql = "SELECT COUNT(*) as nb FROM " . MAIN_DB_PREFIX . "categorie_" . (empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
842
        $sql .= " WHERE fk_categorie = " . ((int)$this->id) . " AND fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " = " . ((int)$object_id);
843
844
        dol_syslog(get_class($this) . "::containsObject", LOG_DEBUG);
845
846
        $resql = $this->db->query($sql);
847
        if ($resql) {
848
            return $this->db->fetch_object($resql)->nb;
849
        } else {
850
            $this->error = $this->db->error();
851
            return -1;
852
        }
853
    }
854
855
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
856
857
    /**
858
     * Delete object from category
859
     *
860
     * @param CommonObject $obj Object
861
     * @param string $type Type of category ('customer', 'supplier', 'contact', 'product', 'member')
862
     * @return  int          1 if OK, -1 if KO
863
     * @see add_type()
864
     */
865
    public function del_type($obj, $type)
866
    {
867
        // phpcs:enable
868
        global $user;
869
870
        $error = 0;
871
872
        // For backward compatibility
873
        if ($type == 'societe') {
874
            $type = 'customer';
875
            dol_syslog(get_class($this) . "::del_type(): type 'societe' is deprecated, please use 'customer' instead", LOG_WARNING);
876
        } elseif ($type == 'fournisseur') {
877
            $type = 'supplier';
878
            dol_syslog(get_class($this) . "::del_type(): type 'fournisseur' is deprecated, please use 'supplier' instead", LOG_WARNING);
879
        }
880
881
        $this->db->begin();
882
883
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "categorie_" . (empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
884
        $sql .= " WHERE fk_categorie = " . ((int)$this->id);
885
        $sql .= " AND fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " = " . ((int)$obj->id);
886
887
        dol_syslog(get_class($this) . '::del_type', LOG_DEBUG);
888
        if ($this->db->query($sql)) {
889
            // Call trigger
890
            $this->context = array('unlinkoff' => $obj); // Save object we want to link category to into category instance to provide information to trigger
891
            $result = $this->call_trigger('CATEGORY_UNLINK', $user);
892
            if ($result < 0) {
893
                $error++;
894
            }
895
            // End call triggers
896
897
            if (!$error) {
898
                $this->db->commit();
899
                return 1;
900
            } else {
901
                $this->db->rollback();
902
                return -2;
903
            }
904
        } else {
905
            $this->db->rollback();
906
            $this->error = $this->db->lasterror();
907
            return -1;
908
        }
909
    }
910
911
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
912
913
    /**
914
     * Return list of fetched instance of elements having this category
915
     *
916
     * @param string $type Type of category ('customer', 'supplier', 'contact', 'product', 'member', 'knowledge_management', ...)
917
     * @param int $onlyids Return only ids of objects (consume less memory)
918
     * @param int $limit Limit
919
     * @param int $offset Offset
920
     * @param string $sortfield Sort fields
921
     * @param string $sortorder Sort order ('ASC' or 'DESC');
922
     * @param string $filter Filter as an Universal Search string.
923
     *                                      Example: '((client:=:1) OR ((client:>=:2) AND (client:<=:3))) AND (client:!=:8) AND (nom:like:'a%')'
924
     * @param string $filtermode No more used
925
     * @return  CommonObject[]|int[]|int    Return -1 if KO, array of instance of object if OK
926
     * @see containsObject()
927
     */
928
    public function getObjectsInCateg($type, $onlyids = 0, $limit = 0, $offset = 0, $sortfield = '', $sortorder = 'ASC', $filter = '', $filtermode = 'AND')
929
    {
930
        global $user;
931
932
        $objs = array();
933
934
        $classnameforobj = $this->MAP_OBJ_CLASS[$type];
935
        $obj = Misc::getModuleClass($classnameforobj, $this->db);
936
937
        $sql = "SELECT c.fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " as fk_object";
938
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_" . (empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]) . " as c";
939
        $sql .= ", " . MAIN_DB_PREFIX . (empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type]) . " as o";
940
        $sql .= " WHERE o.entity IN (" . getEntity($obj->element) . ")";
941
        $sql .= " AND c.fk_categorie = " . ((int)$this->id);
942
        // Compatibility with actioncomm table which has id instead of rowid
943
        if ((array_key_exists($type, $this->MAP_OBJ_TABLE) && $this->MAP_OBJ_TABLE[$type] == "actioncomm") || $type == "actioncomm") {
944
            $sql .= " AND c.fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " = o.id";
945
        } else {
946
            $sql .= " AND c.fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " = o.rowid";
947
        }
948
        // Protection for external users
949
        if (($type == 'customer' || $type == 'supplier') && $user->socid > 0) {
950
            $sql .= " AND o.rowid = " . ((int)$user->socid);
951
        }
952
953
        $errormessage = '';
954
        $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
955
        if ($errormessage) {
956
            $this->errors[] = $errormessage;
957
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
958
            return -1;
959
        }
960
961
        $sql .= $this->db->order($sortfield, $sortorder);
962
        if ($limit > 0 || $offset > 0) {
963
            $sql .= $this->db->plimit($limit + 1, $offset);
964
        }
965
966
        dol_syslog(get_class($this) . "::getObjectsInCateg", LOG_DEBUG);
967
968
        $resql = $this->db->query($sql);
969
        if ($resql) {
970
            while ($rec = $this->db->fetch_array($resql)) {
971
                if ($onlyids) {
972
                    $objs[] = $rec['fk_object'];
973
                } else {
974
                    $classnameforobj = $this->MAP_OBJ_CLASS[$type];
975
976
                    $obj = new $classnameforobj($this->db);
977
                    $obj->fetch($rec['fk_object']);
978
                    if ($obj->id > 0) {     // Failing fetch may happen for example when a category supplier was set and third party was moved as customer only. The object supplier can't be loaded.
979
                        $objs[] = $obj;
980
                    }
981
                }
982
            }
983
            return $objs;
984
        } else {
985
            $this->error = $this->db->error() . ' sql=' . $sql;
986
            return -1;
987
        }
988
    }
989
990
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
991
992
    /**
993
     *  Load category into memory from database
994
     *
995
     * @param int $id Id of category
996
     * @param string $label Label of category
997
     * @param string $type Type of category ('product', '...') or (0, 1, ...)
998
     * @param string $ref_ext External reference of object
999
     * @return     int             Return integer <0 if KO, >0 if OK
1000
     */
1001
    public function fetch($id, $label = '', $type = null, $ref_ext = '')
1002
    {
1003
        // Check parameters
1004
        if (empty($id) && empty($label) && empty($ref_ext)) {
1005
            $this->error = "No category to search for";
1006
            return -1;
1007
        }
1008
        if (!is_null($type) && !is_numeric($type)) {
1009
            $type = $this->MAP_ID[$type];
1010
        }
1011
1012
        $sql = "SELECT rowid, fk_parent, entity, label, description, color, position, fk_soc, visible, type, ref_ext";
1013
        $sql .= ", date_creation, tms, fk_user_creat, fk_user_modif";
1014
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie";
1015
        if ($id) {
1016
            $sql .= " WHERE rowid = " . ((int)$id);
1017
        } elseif (!empty($ref_ext)) {
1018
            $sql .= " WHERE ref_ext LIKE '" . $this->db->escape($ref_ext) . "'";
1019
        } else {
1020
            $sql .= " WHERE label = '" . $this->db->escape($label) . "' AND entity IN (" . getEntity('category') . ")";
1021
            if (!is_null($type)) {
1022
                $sql .= " AND type = " . ((int)$type);
1023
            }
1024
        }
1025
1026
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
1027
        $resql = $this->db->query($sql);
1028
        if ($resql) {
1029
            if ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
1030
                $this->id = $res['rowid'];
1031
                //$this->ref = $res['rowid'];
1032
                $this->fk_parent = (int)$res['fk_parent'];
1033
                $this->label = $res['label'];
1034
                $this->description = $res['description'];
1035
                $this->color = $res['color'];
1036
                $this->position = $res['position'];
1037
                $this->socid = (int)$res['fk_soc'];
1038
                $this->visible = (int)$res['visible'];
1039
                $this->type = $res['type'];
1040
                $this->ref_ext = $res['ref_ext'];
1041
                $this->entity = (int)$res['entity'];
1042
                $this->date_creation = $this->db->jdate($res['date_creation']);
1043
                $this->date_modification = $this->db->jdate($res['tms']);
1044
                $this->user_creation_id = (int)$res['fk_user_creat'];
1045
                $this->user_modification_id = (int)$res['fk_user_modif'];
1046
1047
                // Retrieve all extrafield
1048
                // fetch optionals attributes and labels
1049
                $this->fetch_optionals();
1050
1051
                $this->db->free($resql);
1052
1053
                // multilangs
1054
                if (getDolGlobalInt('MAIN_MULTILANGS')) {
1055
                    $this->getMultiLangs();
1056
                }
1057
1058
                return 1;
1059
            } else {
1060
                $this->error = "No category found";
1061
                return 0;
1062
            }
1063
        } else {
1064
            dol_print_error($this->db);
1065
            $this->error = $this->db->lasterror;
1066
            $this->errors[] = $this->db->lasterror;
1067
            return -1;
1068
        }
1069
    }
1070
1071
    /**
1072
     *  Load array this->multilangs
1073
     *
1074
     * @return     int     Return integer <0 if KO, >0 if OK
1075
     */
1076
    public function getMultiLangs()
1077
    {
1078
        global $langs;
1079
1080
        $current_lang = $langs->getDefaultLang();
1081
1082
        $sql = "SELECT lang, label, description";
1083
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_lang";
1084
        $sql .= " WHERE fk_category=" . ((int)$this->id);
1085
1086
        $result = $this->db->query($sql);
1087
        if ($result) {
1088
            while ($obj = $this->db->fetch_object($result)) {
1089
                //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1090
                if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1091
                    $this->label = $obj->label;
1092
                    $this->description = $obj->description;
1093
                }
1094
                $this->multilangs[$obj->lang]["label"] = $obj->label;
1095
                $this->multilangs[$obj->lang]["description"] = $obj->description;
1096
            }
1097
            return 1;
1098
        } else {
1099
            $this->error = $langs->trans("Error") . " : " . $this->db->error() . " - " . $sql;
1100
            return -1;
1101
        }
1102
    }
1103
1104
1105
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1106
1107
    /**
1108
     *  Update ou cree les traductions des infos produits
1109
     *
1110
     * @param User $user Object user
1111
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
1112
     *
1113
     * @return     int     Return integer <0 if KO, >0 if OK
1114
     */
1115
    public function setMultiLangs(User $user, $notrigger = 0)
1116
    {
1117
        global $langs;
1118
1119
        $langs_available = $langs->get_available_languages();
1120
        $current_lang = $langs->getDefaultLang();
1121
1122
        foreach ($langs_available as $key => $value) {
1123
            $sql = "SELECT rowid";
1124
            $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_lang";
1125
            $sql .= " WHERE fk_category=" . ((int)$this->id);
1126
            $sql .= " AND lang = '" . $this->db->escape($key) . "'";
1127
1128
            $result = $this->db->query($sql);
1129
1130
            if ($key == $current_lang) {
1131
                $sql2 = '';
1132
                if ($this->db->num_rows($result)) { // si aucune ligne dans la base
1133
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "categorie_lang";
1134
                    $sql2 .= " SET label = '" . $this->db->escape($this->label) . "',";
1135
                    $sql2 .= " description = '" . $this->db->escape($this->description) . "'";
1136
                    $sql2 .= " WHERE fk_category = " . ((int)$this->id) . " AND lang = '" . $this->db->escape($key) . "'";
1137
                } elseif (isset($this->multilangs[$key])) {
1138
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_lang (fk_category, lang, label, description)";
1139
                    $sql2 .= " VALUES(" . ((int)$this->id) . ", '" . $this->db->escape($key) . "', '" . $this->db->escape($this->label) . "'";
1140
                    $sql2 .= ", '" . $this->db->escape($this->multilangs[$key]["description"]) . "')";
1141
                }
1142
                dol_syslog(get_class($this) . '::setMultiLangs', LOG_DEBUG);
1143
                if ($sql2 && !$this->db->query($sql2)) {
1144
                    $this->error = $this->db->lasterror();
1145
                    return -1;
1146
                }
1147
            } elseif (isset($this->multilangs[$key])) {
1148
                if ($this->db->num_rows($result)) { // si aucune ligne dans la base
1149
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "categorie_lang";
1150
                    $sql2 .= " SET label='" . $this->db->escape($this->multilangs[$key]["label"]) . "',";
1151
                    $sql2 .= " description='" . $this->db->escape($this->multilangs[$key]["description"]) . "'";
1152
                    $sql2 .= " WHERE fk_category=" . ((int)$this->id) . " AND lang='" . $this->db->escape($key) . "'";
1153
                } else {
1154
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "categorie_lang (fk_category, lang, label, description)";
1155
                    $sql2 .= " VALUES(" . ((int)$this->id) . ", '" . $this->db->escape($key) . "', '" . $this->db->escape($this->multilangs[$key]["label"]) . "'";
1156
                    $sql2 .= ",'" . $this->db->escape($this->multilangs[$key]["description"]) . "')";
1157
                }
1158
1159
                // on ne sauvegarde pas des champs vides
1160
                if ($this->multilangs[$key]["label"] || $this->multilangs[$key]["description"] || $this->multilangs[$key]["note"]) {
1161
                    dol_syslog(get_class($this) . '::setMultiLangs', LOG_DEBUG);
1162
                }
1163
                if (!$this->db->query($sql2)) {
1164
                    $this->error = $this->db->lasterror();
1165
                    return -1;
1166
                }
1167
            }
1168
        }
1169
1170
        // Call trigger
1171
        if (!$notrigger) {
1172
            $result = $this->call_trigger('CATEGORY_SET_MULTILANGS', $user);
1173
            if ($result < 0) {
1174
                $this->error = $this->db->lasterror();
1175
                return -1;
1176
            }
1177
        }
1178
        // End call triggers
1179
1180
        return 1;
1181
    }
1182
1183
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1184
1185
    /**
1186
     * List categories of an element id
1187
     *
1188
     * @param int $id Id of element
1189
     * @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact')
1190
     * @param string $sortfield Sort field
1191
     * @param string $sortorder Sort order
1192
     * @param int $limit Limit for list
1193
     * @param int $page Page number
1194
     * @return  int<-1,0>|array
1195
     */
1196
    public function getListForItem($id, $type = 'customer', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
1197
    {
1198
        $categories = array();
1199
1200
        $type = sanitizeVal($type, 'aZ09');
1201
1202
        $sub_type = $type;
1203
        $subcol_name = "fk_" . $type;
1204
        if ($type == "customer") {
1205
            $sub_type = "societe";
1206
            $subcol_name = "fk_soc";
1207
        }
1208
        if ($type == "supplier") {
1209
            $sub_type = "fournisseur";
1210
            $subcol_name = "fk_soc";
1211
        }
1212
        if ($type == "contact") {
1213
            $subcol_name = "fk_socpeople";
1214
        }
1215
1216
        $idoftype = array_search($type, self::$MAP_ID_TO_CODE);
1217
1218
        $sql = "SELECT s.rowid";
1219
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as s, " . MAIN_DB_PREFIX . "categorie_" . $sub_type . " as sub";
1220
        $sql .= ' WHERE s.entity IN (' . getEntity('category') . ')';
1221
        $sql .= ' AND s.type=' . ((int)$idoftype);
1222
        $sql .= ' AND s.rowid = sub.fk_categorie';
1223
        $sql .= " AND sub." . $subcol_name . " = " . ((int)$id);
1224
1225
        $sql .= $this->db->order($sortfield, $sortorder);
1226
1227
        $offset = 0;
1228
        $nbtotalofrecords = '';
1229
        if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
1230
            $result = $this->db->query($sql);
1231
            $nbtotalofrecords = $this->db->num_rows($result);
1232
            if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0
1233
                $page = 0;
1234
                $offset = 0;
1235
            }
1236
        }
1237
1238
        if ($limit) {
1239
            if ($page < 0) {
1240
                $page = 0;
1241
            }
1242
            $offset = $limit * $page;
1243
1244
            $sql .= $this->db->plimit($limit + 1, $offset);
1245
        }
1246
1247
        $result = $this->db->query($sql);
1248
        if ($result) {
1249
            $i = 0;
1250
            $num = $this->db->num_rows($result);
1251
            $min = min($num, ($limit <= 0 ? $num : $limit));
1252
            while ($i < $min) {
1253
                $obj = $this->db->fetch_object($result);
1254
                $category_static = new Categorie($this->db);
1255
                if ($category_static->fetch($obj->rowid)) {
1256
                    $categories[$i]['id'] = $category_static->id;
1257
                    $categories[$i]['fk_parent'] = $category_static->fk_parent;
1258
                    $categories[$i]['label'] = $category_static->label;
1259
                    $categories[$i]['description'] = $category_static->description;
1260
                    $categories[$i]['color'] = $category_static->color;
1261
                    $categories[$i]['position'] = $category_static->position;
1262
                    $categories[$i]['socid'] = $category_static->socid;
1263
                    $categories[$i]['ref_ext'] = $category_static->ref_ext;
1264
                    $categories[$i]['visible'] = $category_static->visible;
1265
                    $categories[$i]['type'] = $category_static->type;
1266
                    $categories[$i]['entity'] = $category_static->entity;
1267
                    $categories[$i]['array_options'] = $category_static->array_options;
1268
1269
                    // multilangs
1270
                    if (getDolGlobalInt('MAIN_MULTILANGS') && isset($category_static->multilangs)) {
1271
                        $categories[$i]['multilangs'] = $category_static->multilangs;
1272
                    }
1273
                }
1274
                $i++;
1275
            }
1276
        } else {
1277
            $this->error = $this->db->lasterror();
1278
            return -1;
1279
        }
1280
        if (!count($categories)) {
1281
            return 0;
1282
        }
1283
1284
        return $categories;
1285
    }
1286
1287
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1288
1289
    /**
1290
     * Return direct children ids of a category into an array
1291
     *
1292
     * @return  array|int   Return integer <0 KO, array ok
1293
     */
1294
    public function get_filles()
1295
    {
1296
        // phpcs:enable
1297
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
1298
        $sql .= " WHERE fk_parent = " . ((int)$this->id);
1299
        $sql .= " AND entity IN (" . getEntity('category') . ")";
1300
1301
        $res = $this->db->query($sql);
1302
        if ($res) {
1303
            $cats = array();
1304
            while ($rec = $this->db->fetch_array($res)) {
1305
                $cat = new Categorie($this->db);
1306
                $cat->fetch($rec['rowid']);
1307
                $cats[] = $cat;
1308
            }
1309
            return $cats;
1310
        } else {
1311
            dol_print_error($this->db);
1312
            return -1;
1313
        }
1314
    }
1315
1316
1317
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1318
1319
    /**
1320
     * Rebuilding the category tree as an array
1321
     * Return an array of table('id','id_mere',...) sorted to have a human readable tree, with
1322
     *                id = id of category
1323
     *                id_mere = id of parent category
1324
     *                id_children = array of child ids
1325
     *                label = name of category
1326
     *                fulllabel = Name with full path for the category
1327
     *                fullpath = Full path built with the id's
1328
     *
1329
     * @param string $type Type of categories ('customer', 'supplier', 'contact', 'product', 'member', ...)
1330
     * @param int|string|array $fromid Keep only or Exclude (depending on $include parameter) all categories (including the leaf $fromid) into the tree after this id $fromid.
1331
     *                                                  $fromid can be an :
1332
     *                                                  - int (id of category)
1333
     *                                                  - string (categories ids separated by comma)
1334
     *                                                  - array (list of categories ids)
1335
     * @param int $include [=0] Removed or 1=Keep only
1336
     * @return  int<-1,-1>|array<int,array{rowid:int,id:int,fk_parent:int,label:string,description:string,color:string,position:string,visible:int,ref_ext:string,picto:string,fullpath:string,fulllabel:string}>                               Array of categories. this->cats and this->motherof are set, -1 on error
1337
     */
1338
    public function get_full_arbo($type, $fromid = 0, $include = 0)
1339
    {
1340
        // phpcs:enable
1341
        global $langs;
1342
1343
        if (!is_numeric($type)) {
1344
            $type = $this->MAP_ID[$type];
1345
        }
1346
        if (is_null($type)) {
1347
            $this->error = 'BadValueForParameterType';
1348
            return -1;
1349
        }
1350
1351
        if (is_string($fromid)) {
1352
            $fromid = explode(',', $fromid);
1353
        } elseif (is_numeric($fromid)) {
1354
            if ($fromid > 0) {
1355
                $fromid = array($fromid);
1356
            } else {
1357
                $fromid = array();
1358
            }
1359
        } elseif (!is_array($fromid)) {
1360
            $fromid = array();
1361
        }
1362
1363
        $this->cats = array();
1364
        $nbcateg = 0;
1365
1366
        // Init this->motherof that is array(id_son=>id_parent, ...)
1367
        $this->load_motherof();
1368
        $current_lang = $langs->getDefaultLang();
1369
1370
        // Init $this->cats array
1371
        $sql = "SELECT DISTINCT c.rowid, c.label, c.ref_ext, c.description, c.color, c.position, c.fk_parent, c.visible"; // Distinct reduce pb with old tables with duplicates
1372
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
1373
            $sql .= ", t.label as label_trans, t.description as description_trans";
1374
        }
1375
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie as c";
1376
        if (getDolGlobalInt('MAIN_MULTILANGS')) {
1377
            $sql .= " LEFT  JOIN " . MAIN_DB_PREFIX . "categorie_lang as t ON t.fk_category=c.rowid AND t.lang='" . $this->db->escape($current_lang) . "'";
1378
        }
1379
        $sql .= " WHERE c.entity IN (" . getEntity('category') . ")";
1380
        $sql .= " AND c.type = " . (int)$type;
1381
1382
        dol_syslog(get_class($this) . "::get_full_arbo get category list", LOG_DEBUG);
1383
        $resql = $this->db->query($sql);
1384
        if ($resql) {
1385
            $i = 0;
1386
            $nbcateg = $this->db->num_rows($resql);
1387
1388
            while ($obj = $this->db->fetch_object($resql)) {
1389
                $this->cats[$obj->rowid]['rowid'] = $obj->rowid;
1390
                $this->cats[$obj->rowid]['id'] = $obj->rowid;
1391
                $this->cats[$obj->rowid]['fk_parent'] = $obj->fk_parent;
1392
                $this->cats[$obj->rowid]['label'] = !empty($obj->label_trans) ? $obj->label_trans : $obj->label;
1393
                $this->cats[$obj->rowid]['description'] = !empty($obj->description_trans) ? $obj->description_trans : $obj->description;
1394
                $this->cats[$obj->rowid]['color'] = $obj->color;
1395
                $this->cats[$obj->rowid]['position'] = $obj->position;
1396
                $this->cats[$obj->rowid]['visible'] = $obj->visible;
1397
                $this->cats[$obj->rowid]['ref_ext'] = $obj->ref_ext;
1398
                $this->cats[$obj->rowid]['picto'] = 'category';
1399
                // fields are filled with buildPathFromId
1400
                $this->cats[$obj->rowid]['fullpath'] = '';
1401
                $this->cats[$obj->rowid]['fulllabel'] = '';
1402
                $i++;
1403
            }
1404
        } else {
1405
            dol_print_error($this->db);
1406
            return -1;
1407
        }
1408
1409
        // We add the fullpath property to each elements of first level (no parent exists)
1410
        dol_syslog(get_class($this) . "::get_full_arbo call to buildPathFromId", LOG_DEBUG);
1411
        foreach ($this->cats as $key => $val) {
1412
            //print 'key='.$key.'<br>'."\n";
1413
            $this->buildPathFromId($key, $nbcateg); // Process a branch from the root category key (this category has no parent)
1414
        }
1415
1416
        // Include or exclude leaf (including $fromid) from tree
1417
        if (count($fromid) > 0) {
1418
            $keyfiltercatid = '(' . implode('|', $fromid) . ')';
1419
1420
            //print "Look to discard category ".$fromid."\n";
1421
            $keyfilter1 = '^' . $keyfiltercatid . '$';
1422
            $keyfilter2 = '_' . $keyfiltercatid . '$';
1423
            $keyfilter3 = '^' . $keyfiltercatid . '_';
1424
            $keyfilter4 = '_' . $keyfiltercatid . '_';
1425
            foreach (array_keys($this->cats) as $key) {
1426
                $fullpath = (string)$this->cats[$key]['fullpath'];
1427
                $test = (preg_match('/' . $keyfilter1 . '/', $fullpath) || preg_match('/' . $keyfilter2 . '/', $fullpath)
1428
                    || preg_match('/' . $keyfilter3 . '/', $fullpath) || preg_match('/' . $keyfilter4 . '/', $fullpath));
1429
1430
                if (($test && !$include) || (!$test && $include)) {
1431
                    unset($this->cats[$key]);
1432
                }
1433
            }
1434
        }
1435
1436
        dol_syslog(get_class($this) . "::get_full_arbo dol_sort_array", LOG_DEBUG);
1437
        $this->cats = dol_sort_array($this->cats, 'fulllabel', 'asc', true, false);
1438
1439
        return $this->cats;
1440
    }
1441
1442
1443
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1444
1445
    /**
1446
     *  Load the array this->motherof that is array(id_son=>id_parent, ...)
1447
     *
1448
     * @return     int     Return integer <0 if KO, >0 if OK
1449
     */
1450
    protected function load_motherof()
1451
    {
1452
        // phpcs:enable
1453
        $this->motherof = array();
1454
1455
        // Load array[child]=parent
1456
        $sql = "SELECT fk_parent as id_parent, rowid as id_son";
1457
        $sql .= " FROM " . MAIN_DB_PREFIX . "categorie";
1458
        $sql .= " WHERE fk_parent != 0";
1459
        $sql .= " AND entity IN (" . getEntity('category') . ")";
1460
1461
        dol_syslog(get_class($this) . "::load_motherof", LOG_DEBUG);
1462
        $resql = $this->db->query($sql);
1463
        if ($resql) {
1464
            while ($obj = $this->db->fetch_object($resql)) {
1465
                $this->motherof[$obj->id_son] = $obj->id_parent;
1466
            }
1467
            return 1;
1468
        } else {
1469
            dol_print_error($this->db);
1470
            return -1;
1471
        }
1472
    }
1473
1474
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1475
1476
    /**
1477
     *  For category id_categ and its children available in this->cats, define property fullpath and fulllabel.
1478
     *  It is called by get_full_arbo()
1479
     *  This function is a memory scan only from $this->cats and $this->motherof, no database access must be done here.
1480
     *
1481
     * @param int $id_categ id_categ entry to update
1482
     * @param int $protection Deep counter to avoid infinite loop
1483
     * @return     int<-1,1>               Return integer <0 if KO, >0 if OK
1484
     * @see get_full_arbo()
1485
     */
1486
    private function buildPathFromId($id_categ, $protection = 1000)
1487
    {
1488
        //dol_syslog(get_class($this)."::buildPathFromId id_categ=".$id_categ." protection=".$protection, LOG_DEBUG);
1489
1490
        if (!empty($this->cats[$id_categ]['fullpath'])) {
1491
            // Already defined
1492
            dol_syslog(get_class($this) . "::buildPathFromId fullpath and fulllabel already defined", LOG_WARNING);
1493
            return -1;
1494
        }
1495
1496
        // First build full array $motherof
1497
        //$this->load_motherof();   // Disabled because already done by caller of buildPathFromId
1498
1499
        // $this->cats[$id_categ] is supposed to be already an array. We just want to complete it with property fullpath and fulllabel
1500
1501
        // Define fullpath and fulllabel
1502
        $this->cats[$id_categ]['fullpath'] = '_' . $id_categ;
1503
        $this->cats[$id_categ]['fulllabel'] = $this->cats[$id_categ]['label'];
1504
        $i = 0;
1505
        $cursor_categ = $id_categ;
1506
        //print 'Work for id_categ='.$id_categ.'<br>'."\n";
1507
        while ((empty($protection) || $i < $protection) && !empty($this->motherof[$cursor_categ])) {
1508
            //print '&nbsp; cursor_categ='.$cursor_categ.' i='.$i.' '.$this->motherof[$cursor_categ].'<br>'."\n";
1509
            $this->cats[$id_categ]['fullpath'] = '_' . $this->motherof[$cursor_categ] . $this->cats[$id_categ]['fullpath'];
1510
            $this->cats[$id_categ]['fulllabel'] = (empty($this->cats[$this->motherof[$cursor_categ]]) ? 'NotFound' : $this->cats[$this->motherof[$cursor_categ]]['label']) . ' >> ' . $this->cats[$id_categ]['fulllabel'];
1511
            //print '&nbsp; Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].' '.$this->cats[$id_categ]['fulllabel'].'<br>'."\n";
1512
            $i++;
1513
            $cursor_categ = $this->motherof[$cursor_categ];
1514
        }
1515
        //print 'Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].'<br>'."\n";
1516
1517
        // We count number of _ to have level
1518
        $nbunderscore = substr_count($this->cats[$id_categ]['fullpath'], '_');
1519
        $this->cats[$id_categ]['level'] = ($nbunderscore ? $nbunderscore : null);
1520
1521
        return 1;
1522
    }
1523
1524
    /**
1525
     *  Returns the top level categories (which are not child)
1526
     *
1527
     * @param int $type Type of category (0, 1, ...)
1528
     * @return     array
1529
     */
1530
    public function get_main_categories($type = null)
1531
    {
1532
        // phpcs:enable
1533
        return $this->get_all_categories($type, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_all_categories($type, true) also could return the type integer which is incompatible with the documented return type array.
Loading history...
1534
    }
1535
1536
    /**
1537
     *  Returns all categories
1538
     *
1539
     * @param int $type Type of category (0, 1, ...)
1540
     * @param boolean $parent Just parent categories if true
1541
     * @return array|int               Table of Object Category, -1 on error
1542
     */
1543
    public function get_all_categories($type = null, $parent = false)
1544
    {
1545
        // phpcs:enable
1546
        if (!is_numeric($type)) {
1547
            $type = $this->MAP_ID[$type];
1548
        }
1549
1550
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
1551
        $sql .= " WHERE entity IN (" . getEntity('category') . ")";
1552
        if (!is_null($type)) {
1553
            $sql .= " AND type = " . (int)$type;
1554
        }
1555
        if ($parent) {
1556
            $sql .= " AND fk_parent = 0";
1557
        }
1558
1559
        $res = $this->db->query($sql);
1560
        if ($res) {
1561
            $cats = array();
1562
            while ($rec = $this->db->fetch_array($res)) {
1563
                $cat = new Categorie($this->db);
1564
                $cat->fetch($rec['rowid']);
1565
                $cats[$rec['rowid']] = $cat;
1566
            }
1567
            return $cats;
1568
        } else {
1569
            dol_print_error($this->db);
1570
            return -1;
1571
        }
1572
    }
1573
1574
    /**
1575
     * Returns the path of the category, with the names of the categories
1576
     * separated by $sep (" >> " by default)
1577
     *
1578
     * @param string $sep Separator
1579
     * @param string $url Url ('', 'none' or 'urltouse')
1580
     * @param int $nocolor 0
1581
     * @param int $addpicto Add picto into link
1582
     * @return  array
1583
     */
1584
    public function print_all_ways($sep = '&gt;&gt;', $url = '', $nocolor = 0, $addpicto = 0)
1585
    {
1586
        // phpcs:enable
1587
        $ways = array();
1588
1589
        $all_ways = $this->get_all_ways(); // Load array of categories
1590
        foreach ($all_ways as $way) {
1591
            $w = array();
1592
            $i = 0;
1593
            $forced_color = '';
1594
            foreach ($way as $cat) {
1595
                $i++;
1596
1597
                if (empty($nocolor)) {
1598
                    $forced_color = 'colortoreplace';
1599
                    if ($i == count($way)) {    // Last category in hierarchy
1600
                        // Check contrast with background and correct text color
1601
                        $forced_color = 'categtextwhite';
1602
                        if ($cat->color) {
1603
                            if (colorIsLight($cat->color)) {
1604
                                $forced_color = 'categtextblack';
1605
                            }
1606
                        }
1607
                    }
1608
                }
1609
1610
                if ($url == '') {
1611
                    $link = '<a href="' . constant('BASE_URL') . '/categories/viewcat.php?id=' . $cat->id . '&type=' . $cat->type . '" class="' . $forced_color . '">';
1612
                    $linkend = '</a>';
1613
                    $w[] = $link . (($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '') . $cat->label . $linkend;
1614
                } elseif ($url == 'none') {
1615
                    $link = '<span class="' . $forced_color . '">';
1616
                    $linkend = '</span>';
1617
                    $w[] = $link . (($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '') . $cat->label . $linkend;
1618
                } else {
1619
                    $w[] = '<a class="' . $forced_color . '" href="' . constant('BASE_URL') . '/' . $url . '?catid=' . $cat->id . '">' . ($addpicto ? img_object('', 'category') : '') . $cat->label . '</a>';
1620
                }
1621
            }
1622
            $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode('<span class="inline-block valignmiddle paddingleft paddingright ' . $forced_color . '">' . $sep . '</span>', $w));
1623
1624
            $ways[] = $newcategwithpath;
1625
        }
1626
1627
        return $ways;
1628
    }
1629
1630
    /**
1631
     *  Returns in a table all possible paths to get to the category
1632
     *  starting with the major categories represented by Tables of categories
1633
     *
1634
     * @return array
1635
     */
1636
    public function get_all_ways()
1637
    {
1638
        // phpcs:enable
1639
        $ways = array();
1640
1641
        $parents = $this->get_meres();
1642
        if (is_array($parents)) {
1643
            foreach ($parents as $parent) {
1644
                $all_ways = $parent->get_all_ways();
1645
                foreach ($all_ways as $way) {
1646
                    $w = $way;
1647
                    $w[] = $this;
1648
                    $ways[] = $w;
1649
                }
1650
            }
1651
        }
1652
1653
        if (count($ways) == 0) {
1654
            $ways[0][0] = $this;
1655
        }
1656
1657
        return $ways;
1658
    }
1659
1660
    /**
1661
     *  Returns an array containing the list of parent categories
1662
     *
1663
     * @return int|array Return integer <0 KO, array OK
1664
     */
1665
    public function get_meres()
1666
    {
1667
        // phpcs:enable
1668
        $parents = array();
1669
1670
        $sql = "SELECT fk_parent FROM " . MAIN_DB_PREFIX . "categorie";
1671
        $sql .= " WHERE rowid = " . ((int)$this->id);
1672
1673
        $res = $this->db->query($sql);
1674
1675
        if ($res) {
1676
            while ($rec = $this->db->fetch_array($res)) {
1677
                if ($rec['fk_parent'] > 0) {
1678
                    $cat = new Categorie($this->db);
1679
                    $cat->fetch($rec['fk_parent']);
1680
                    $parents[] = $cat;
1681
                }
1682
            }
1683
            return $parents;
1684
        } else {
1685
            dol_print_error($this->db);
1686
            return -1;
1687
        }
1688
    }
1689
1690
1691
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1692
1693
    /**
1694
     * Return list of categories (object instances or labels) linked to element of id $id and type $type
1695
     * Should be named getListOfCategForObject
1696
     *
1697
     * @param int $id Id of element
1698
     * @param string|int $type Type of category ('customer', 'supplier', 'contact', 'product', 'member') or (0, 1, 2, ...)
1699
     * @param string $mode 'id'=Get array of category ids, 'object'=Get array of fetched category instances, 'label'=Get array of category
1700
     *                              labels, 'id'= Get array of category IDs
1701
     * @return  Categorie[]|int     Array of category objects or < 0 if KO
1702
     */
1703
    public function containing($id, $type, $mode = 'object')
1704
    {
1705
        $cats = array();
1706
1707
        if (is_numeric($type)) {
1708
            $type = Categorie::$MAP_ID_TO_CODE[$type];
1709
        }
1710
1711
        if ($type === Categorie::TYPE_BANK_LINE) {   // TODO Remove this with standard category code after migration of llx_bank_categ into llx_categorie
1712
            // Load bank categories
1713
            $sql = "SELECT c.label, c.rowid";
1714
            $sql .= " FROM " . MAIN_DB_PREFIX . "bank_class as a, " . MAIN_DB_PREFIX . "bank_categ as c";
1715
            $sql .= " WHERE a.lineid=" . ((int)$id) . " AND a.fk_categ = c.rowid";
1716
            $sql .= " AND c.entity IN (" . getEntity('category') . ")";
1717
            $sql .= " ORDER BY c.label";
1718
1719
            $res = $this->db->query($sql);
1720
            if ($res) {
1721
                while ($obj = $this->db->fetch_object($res)) {
1722
                    if ($mode == 'id') {
1723
                        $cats[] = $obj->rowid;
1724
                    } elseif ($mode == 'label') {
1725
                        $cats[] = $obj->label;
1726
                    } else {
1727
                        $cat = new Categorie($this->db);
1728
                        $cat->id = $obj->rowid;
1729
                        $cat->label = $obj->label;
1730
                        $cats[] = $cat;
1731
                    }
1732
                }
1733
            } else {
1734
                dol_print_error($this->db);
1735
                return -1;
1736
            }
1737
        } else {
1738
            $sql = "SELECT ct.fk_categorie, c.label, c.rowid";
1739
            $sql .= " FROM " . MAIN_DB_PREFIX . "categorie_" . (empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]) . " as ct, " . MAIN_DB_PREFIX . "categorie as c";
1740
            $sql .= " WHERE ct.fk_categorie = c.rowid AND ct.fk_" . (empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]) . " = " . (int)$id;
1741
            // This seems useless because the table already contains id of category of 1 unique type. So commented.
1742
            // So now it works also with external added categories.
1743
            //$sql .= " AND c.type = ".((int) $this->MAP_ID[$type]);
1744
            $sql .= " AND c.entity IN (" . getEntity('category') . ")";
1745
1746
            $res = $this->db->query($sql);
1747
            if ($res) {
1748
                while ($obj = $this->db->fetch_object($res)) {
1749
                    if ($mode == 'id') {
1750
                        $cats[] = $obj->rowid;
1751
                    } elseif ($mode == 'label') {
1752
                        $cats[] = $obj->label;
1753
                    } else {
1754
                        $cat = new Categorie($this->db);
1755
                        $cat->fetch($obj->fk_categorie);
1756
                        $cats[] = $cat;
1757
                    }
1758
                }
1759
            } else {
1760
                dol_print_error($this->db);
1761
                return -1;
1762
            }
1763
        }
1764
1765
        return $cats;
1766
    }
1767
1768
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1769
1770
    /**
1771
     *  Returns categories whose id or name match
1772
     *  add wildcards in the name unless $exact = true
1773
     *
1774
     * @param int $id Id
1775
     * @param string $nom Name
1776
     * @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated.
1777
     * @param boolean $exact Exact string search (true/false)
1778
     * @param boolean $case Case sensitive (true/false)
1779
     * @return     Categorie[]|int         Array of Categorie, -1 if error
1780
     */
1781
    public function rechercher($id, $nom, $type, $exact = false, $case = false)
1782
    {
1783
        // Deprecation warning
1784
        if (is_numeric($type)) {
1785
            dol_syslog(__METHOD__ . ': using numeric types is deprecated.', LOG_WARNING);
1786
        }
1787
1788
        $cats = array();
1789
1790
        // For backward compatibility
1791
        if (is_numeric($type)) {
1792
            // We want to reverse lookup
1793
            $map_type = array_flip($this->MAP_ID);
1794
            $type = $map_type[$type];
1795
            dol_syslog(get_class($this) . "::rechercher(): numeric types are deprecated, please use string instead", LOG_WARNING);
1796
        }
1797
1798
        // Generation requete recherche
1799
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "categorie";
1800
        $sql .= " WHERE type = " . ((int)$this->MAP_ID[$type]);
1801
        $sql .= " AND entity IN (" . getEntity('category') . ")";
1802
        if ($nom) {
1803
            if (!$exact) {
1804
                $nom = '%' . $this->db->escape(str_replace('*', '%', $nom)) . '%';
1805
            }
1806
            if (!$case) {
1807
                $sql .= " AND label LIKE '" . $this->db->escape($nom) . "'";
1808
            } else {
1809
                $sql .= " AND label LIKE BINARY '" . $this->db->escape($nom) . "'";
1810
            }
1811
        }
1812
        if ($id) {
1813
            $sql .= " AND rowid = " . ((int)$id);
1814
        }
1815
1816
        $res = $this->db->query($sql);
1817
        if ($res) {
1818
            while ($rec = $this->db->fetch_array($res)) {
1819
                $cat = new Categorie($this->db);
1820
                $cat->fetch($rec['rowid']);
1821
                $cats[] = $cat;
1822
            }
1823
1824
            return $cats;
1825
        } else {
1826
            $this->error = $this->db->error() . ' sql=' . $sql;
1827
            return -1;
1828
        }
1829
    }
1830
1831
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1832
1833
    /**
1834
     *  Return if at least one photo is available
1835
     *
1836
     * @param string $sdir Directory to scan
1837
     * @return boolean                 True if at least one photo is available, False if not
1838
     */
1839
    public function isAnyPhotoAvailable($sdir)
1840
    {
1841
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1842
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
1843
1844
        $sdir .= '/' . get_exdir($this->id, 2, 0, 0, $this, 'category') . $this->id . "/photos/";
1845
1846
        $dir_osencoded = dol_osencode($sdir);
1847
        if (file_exists($dir_osencoded)) {
1848
            $handle = opendir($dir_osencoded);
1849
            if (is_resource($handle)) {
1850
                while (($file = readdir($handle)) !== false) {
1851
                    if (!utf8_check($file)) {
1852
                        $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1853
                    }
1854
                    if (dol_is_file($sdir . $file) && image_format_supported($file) >= 0) {
1855
                        return true;
1856
                    }
1857
                }
1858
            }
1859
        }
1860
        return false;
1861
    }
1862
1863
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1864
1865
    /**
1866
     *  Return name and link of category (with picto)
1867
     *  Use ->id, ->ref, ->label, ->color
1868
     *
1869
     * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto
1870
     * @param string $option On what the link point to ('nolink', ...)
1871
     * @param int $maxlength Max length of text
1872
     * @param string $moreparam More param on URL link
1873
     * @param int $notooltip 1=Disable tooltip
1874
     * @param string $morecss Add more css on link
1875
     * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1876
     * @return     string                  Chaine avec URL
1877
     */
1878
    public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $moreparam = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = 0)
1879
    {
1880
        global $conf, $langs, $hookmanager;
1881
1882
        if (!empty($conf->dol_no_mouse_hover)) {
1883
            $notooltip = 1; // Force disable tooltips
1884
        }
1885
1886
        $result = '';
1887
        $params = [
1888
            'id' => $this->id,
1889
            'objecttype' => $this->element,
1890
            'option' => $option,
1891
        ];
1892
        $classfortooltip = 'classfortooltip';
1893
        $dataparams = '';
1894
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1895
            $classfortooltip = 'classforajaxtooltip';
1896
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1897
            $label = '';
1898
        } else {
1899
            $label = implode($this->getTooltipContentArray($params));
1900
        }
1901
1902
        $url = constant('BASE_URL') . '/categories/viewcat.php?id=' . $this->id . '&type=' . $this->type . $moreparam . '&backtopage=' . urlencode($_SERVER['PHP_SELF'] . ($moreparam ? '?' . $moreparam : ''));
1903
1904
        if ($option !== 'nolink') {
1905
            // Add param to save lastsearch_values or not
1906
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1907
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1908
                $add_save_lastsearch_values = 1;
1909
            }
1910
            if ($url && $add_save_lastsearch_values) {
1911
                $url .= '&save_lastsearch_values=1';
1912
            }
1913
        }
1914
1915
        // Check contrast with background and correct text color
1916
        $forced_color = 'categtextwhite';
1917
        if ($this->color) {
1918
            if (colorIsLight($this->color)) {
1919
                $forced_color = 'categtextblack';
1920
            }
1921
        }
1922
1923
        $linkclose = '';
1924
        if (empty($notooltip)) {
1925
            if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1926
                $label = $langs->trans("ShowMyObject");
1927
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1928
            }
1929
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1930
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ' ' . $forced_color . ($morecss ? ' ' . $morecss : '') . '"';
1931
        } else {
1932
            $linkclose = ($morecss ? ' class="' . $forced_color . ($morecss ? ' ' . $morecss : '') . '"' : '');
1933
        }
1934
1935
        if ($option == 'nolink' || empty($url)) {
1936
            $linkstart = '<span';
1937
        } else {
1938
            $linkstart = '<a href="' . $url . '"';
1939
        }
1940
        $linkstart .= $linkclose . '>';
1941
        if ($option == 'nolink' || empty($url)) {
1942
            $linkend = '</span>';
1943
        } else {
1944
            $linkend = '</a>';
1945
        }
1946
1947
        $result .= $linkstart;
1948
1949
        if ($withpicto) {
1950
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
1951
        }
1952
1953
        if ($withpicto != 2) {
1954
            $result .= dol_trunc(($this->ref ? $this->ref : $this->label), $maxlength);
1955
        }
1956
1957
        $result .= $linkend;
1958
1959
        global $action;
1960
        $hookmanager->initHooks(array($this->element . 'dao'));
1961
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1962
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1963
        if ($reshook > 0) {
1964
            $result = $hookmanager->resPrint;
1965
        } else {
1966
            $result .= $hookmanager->resPrint;
1967
        }
1968
        return $result;
1969
    }
1970
1971
    /**
1972
     * getTooltipContentArray
1973
     * @param array $params params to construct tooltip data
1974
     * @return array
1975
     * @since v18
1976
     */
1977
    public function getTooltipContentArray($params)
1978
    {
1979
        global $langs;
1980
1981
        $langs->load('categories');
1982
1983
        $datas = [];
1984
1985
        $datas['label'] = $langs->trans("ShowCategory") . ': ' . ($this->ref ? $this->ref : $this->label);
1986
1987
        return $datas;
1988
    }
1989
1990
    /**
1991
     *  Add the image uploaded as $file to the directory $sdir/<category>-<id>/photos/
1992
     *
1993
     * @param string $sdir Root destination directory
1994
     * @param array $file Uploaded file name
1995
     * @return     void
1996
     */
1997
    public function add_photo($sdir, $file)
1998
    {
1999
        // phpcs:enable
2000
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2001
2002
        $dir = $sdir . '/' . get_exdir($this->id, 2, 0, 0, $this, 'category') . $this->id . "/";
2003
        $dir .= "photos/";
2004
2005
        if (!file_exists($dir)) {
2006
            dol_mkdir($dir);
2007
        }
2008
2009
        if (file_exists($dir)) {
2010
            if (is_array($file['name']) && count($file['name']) > 0) {
2011
                $nbfile = count($file['name']);
2012
                for ($i = 0; $i < $nbfile; $i++) {
2013
                    $originImage = $dir . $file['name'][$i];
2014
2015
                    // Cree fichier en taille origine
2016
                    dol_move_uploaded_file($file['tmp_name'][$i], $originImage, 1, 0, 0);
2017
2018
                    if (file_exists($originImage)) {
2019
                        // Create thumbs
2020
                        $this->addThumbs($originImage);
2021
                    }
2022
                }
2023
            } else {
2024
                $originImage = $dir . $file['name'];
2025
2026
                // Cree fichier en taille origine
2027
                dol_move_uploaded_file($file['tmp_name'], $originImage, 1, 0, 0);
2028
2029
                if (file_exists($originImage)) {
2030
                    // Create thumbs
2031
                    $this->addThumbs($originImage);
2032
                }
2033
            }
2034
        }
2035
    }
2036
2037
    /**
2038
     *    Return an array with all photos inside the directory
2039
     *
2040
     * @param string $dir Dir to scan
2041
     * @param int $nbmax Nombre maximum de photos (0=pas de max)
2042
     * @return     array                 Tableau de photos
2043
     */
2044
    public function liste_photos($dir, $nbmax = 0)
2045
    {
2046
        // phpcs:enable
2047
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2048
2049
        $nbphoto = 0;
2050
        $tabobj = array();
2051
2052
        $dirthumb = $dir . 'thumbs/';
2053
2054
        if (file_exists($dir)) {
2055
            $handle = opendir($dir);
2056
            if (is_resource($handle)) {
2057
                while (($file = readdir($handle)) !== false) {
2058
                    if (dol_is_file($dir . $file) && preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $dir . $file)) {
2059
                        $nbphoto++;
2060
                        $photo = $file;
2061
2062
                        // On determine nom du fichier vignette
2063
                        $photo_vignette = '';
2064
                        $regs = array();
2065
                        if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $photo, $regs)) {
2066
                            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $photo) . '_small' . $regs[0];
2067
                        }
2068
2069
                        // Object
2070
                        $obj = array();
2071
                        $obj['photo'] = $photo;
2072
                        if ($photo_vignette && is_file($dirthumb . $photo_vignette)) {
2073
                            $obj['photo_vignette'] = 'thumbs/' . $photo_vignette;
2074
                        } else {
2075
                            $obj['photo_vignette'] = "";
2076
                        }
2077
2078
                        $tabobj[$nbphoto - 1] = $obj;
2079
2080
                        // On continue ou on arrete de boucler
2081
                        if ($nbmax && $nbphoto >= $nbmax) {
2082
                            break;
2083
                        }
2084
                    }
2085
                }
2086
2087
                closedir($handle);
2088
            }
2089
        }
2090
2091
        return $tabobj;
2092
    }
2093
2094
    /**
2095
     *    Efface la photo de la categorie et sa vignette
2096
     *
2097
     * @param string $file Path to file
2098
     * @return   void
2099
     */
2100
    public function delete_photo($file)
2101
    {
2102
        // phpcs:enable
2103
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2104
2105
        $dir = dirname($file) . '/'; // Chemin du dossier contenant l'image d'origine
2106
        $dirthumb = $dir . '/thumbs/'; // Chemin du dossier contenant la vignette
2107
        $filename = preg_replace('/' . preg_quote($dir, '/') . '/i', '', $file); // Nom du fichier
2108
2109
        // On efface l'image d'origine
2110
        dol_delete_file($file, 1);
2111
2112
        // Si elle existe, on efface la vignette
2113
        $regs = array();
2114
        if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $filename, $regs)) {
2115
            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $filename) . '_small' . $regs[0];
2116
            if (file_exists($dirthumb . $photo_vignette)) {
2117
                dol_delete_file($dirthumb . $photo_vignette, 1);
2118
            }
2119
        }
2120
    }
2121
2122
    /**
2123
     *  Load size of image file
2124
     *
2125
     * @param string $file Path to file
2126
     * @return     void
2127
     */
2128
    public function get_image_size($file)
2129
    {
2130
        // phpcs:enable
2131
        $infoImg = getimagesize($file); // Recuperation des infos de l'image
2132
        $this->imgWidth = $infoImg[0]; // Largeur de l'image
2133
        $this->imgHeight = $infoImg[1]; // Hauteur de l'image
2134
    }
2135
2136
    /**
2137
     *  Return label of contact status
2138
     *
2139
     * @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
2140
     * @return     string              Label of contact status
2141
     */
2142
    public function getLibStatut($mode)
2143
    {
2144
        return '';
2145
    }
2146
2147
    /**
2148
     *  Initialise an instance with random values.
2149
     *  Used to build previews or test instances.
2150
     *  id must be 0 if object instance is a specimen.
2151
     *
2152
     * @return int
2153
     */
2154
    public function initAsSpecimen()
2155
    {
2156
        dol_syslog(get_class($this) . "::initAsSpecimen");
2157
2158
        // Initialise parameters
2159
        $this->id = 0;
2160
        $this->fk_parent = 0;
2161
        $this->label = 'SPECIMEN';
2162
        $this->specimen = 1;
2163
        $this->description = 'This is a description';
2164
        $this->socid = 1;
2165
        $this->type = self::TYPE_PRODUCT;
2166
2167
        return 1;
2168
    }
2169
2170
    /**
2171
     *      Count all categories
2172
     *
2173
     * @return int                             Number of categories, -1 on error
2174
     */
2175
    public function countNbOfCategories()
2176
    {
2177
        dol_syslog(get_class($this) . "::count_all_categories", LOG_DEBUG);
2178
        $sql = "SELECT COUNT(rowid) FROM " . MAIN_DB_PREFIX . "categorie";
2179
        $sql .= " WHERE entity IN (" . getEntity('category') . ")";
2180
2181
        $res = $this->db->query($sql);
2182
        if ($res) {
2183
            $obj = $this->db->fetch_object($res);
2184
            return $obj->count;
2185
        } else {
2186
            dol_print_error($this->db);
2187
            return -1;
2188
        }
2189
    }
2190
}
2191