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

Categorie::isAnyPhotoAvailable()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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