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

Categorie::getObjectsInCateg()   F

Complexity

Conditions 19
Paths 704

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* Copyright (C) 2005       Matthieu Valleton           <[email protected]>
4
 * Copyright (C) 2005       Davoleau Brice              <[email protected]>
5
 * Copyright (C) 2005       Rodolphe Quiedeville        <[email protected]>
6
 * Copyright (C) 2006-2012  Regis Houssin               <[email protected]>
7
 * Copyright (C) 2006-2012  Laurent Destailleur         <[email protected]>
8
 * Copyright (C) 2007       Patrick Raguin              <[email protected]>
9
 * Copyright (C) 2013-2016  Juanjo Menent               <[email protected]>
10
 * Copyright (C) 2013-2018  Philippe Grand              <[email protected]>
11
 * Copyright (C) 2015       Marcos García               <[email protected]>
12
 * Copyright (C) 2015       Raphaël Doursenaud          <[email protected]>
13
 * Copyright (C) 2016       Charlie Benke               <[email protected]>
14
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
15
 * Copyright (C) 2023-2024	Benjamin Falière		    <[email protected]>
16
 * Copyright (C) 2024		MDW	                        <[email protected]>
17
 * Copyright (C) 2024       Rafael San José             <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31
 */
32
33
namespace Dolibarr\Code\Categories\Classes;
34
35
/**
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