Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

Categorie::get_filles()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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