dolibarr 24.0.2
categorie.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2005 Matthieu Valleton <mv@seeschloss.org>
3 * Copyright (C) 2005 Davoleau Brice <brice.davoleau@gmail.com>
4 * Copyright (C) 2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
5 * Copyright (C) 2006-2012 Regis Houssin <regis.houssin@inodbox.com>
6 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
7 * Copyright (C) 2007 Patrick Raguin <patrick.raguin@gmail.com>
8 * Copyright (C) 2013-2016 Juanjo Menent <jmenent@2byte.es>
9 * Copyright (C) 2013-2018 Philippe Grand <philippe.grand@atoo-net.com>
10 * Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
12 * Copyright (C) 2016-2025 Charlene Benke <charlene@patas-monkey.com>
13 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
14 * Copyright (C) 2022-2023 Solution Libre SAS <contact@solution-libre.fr>
15 * Copyright (C) 2023-2024 Benjamin Falière <benjamin.faliere@altairis.fr>
16 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
17 * Copyright (C) 2025 Alexandre Spangaro <alexandre@inovea-conseil.com>
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
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
40require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
41require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php';
42require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.class.php';
43require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
44require_once DOL_DOCUMENT_ROOT.'/knowledgemanagement/class/knowledgerecord.class.php';
45
46
51{
52 // Categories types (we use string because we want to accept any modules/types in a future)
53 const TYPE_PRODUCT = 'product';
54 const TYPE_SUPPLIER = 'supplier';
55 const TYPE_CUSTOMER = 'customer';
56 const TYPE_MEMBER = 'member';
57 const TYPE_CONTACT = 'contact';
58 const TYPE_USER = 'user';
59 const TYPE_PROJECT = 'project';
60 const TYPE_ACCOUNT = 'bank_account';
61 const TYPE_BANK_LINE = 'bank_line';
62 const TYPE_WAREHOUSE = 'warehouse';
63 const TYPE_ACTIONCOMM = 'actioncomm';
64 const TYPE_WEBSITE_PAGE = 'website_page';
65 const TYPE_TICKET = 'ticket';
66 const TYPE_KNOWLEDGEMANAGEMENT = 'knowledgemanagement';
67 const TYPE_FICHINTER = 'fichinter';
68 const TYPE_ORDER = 'order';
69 const TYPE_INVOICE = 'invoice';
70 const TYPE_SUPPLIER_ORDER = 'supplier_order';
71 const TYPE_SUPPLIER_INVOICE = 'supplier_invoice';
72 const TYPE_SUPPLIER_PROPOSAL = 'supplier_proposal';
73 const TYPE_PROPOSAL = 'propal';
74 const TYPE_PROJECT_TASK = 'project_task';
75 const TYPE_MO = 'mo';
76
77
81 public $picto = 'category';
82
83
87 public $MAP_ID = array(
88 'product' => 0,
89 'service' => 0,
90 'supplier' => 1,
91 'customer' => 2,
92 'member' => 3,
93 'contact' => 4,
94 'bank_account' => 5,
95 'project' => 6,
96 'user' => 7,
97 'bank_line' => 8,
98 'warehouse' => 9,
99 'actioncomm' => 10,
100 'website_page' => 11,
101 'ticket' => 12,
102 'knowledgemanagement' => 13,
103 'fichinter' => 14,
104 'order' => 16,
105 'invoice' => 17,
106 'supplier_order' => 20,
107 'supplier_invoice' => 21,
108 'supplier_proposal' => 22,
109 'propal' => 23,
110 'project_task' => 24,
111 'mo' => 25,
112 );
113
119 /*
120 public static $MAP_ID_TO_CODE = array(
121 0 => 'product',
122 1 => 'supplier',
123 2 => 'customer',
124 3 => 'member',
125 4 => 'contact',
126 5 => 'bank_account',
127 6 => 'project',
128 7 => 'user',
129 8 => 'bank_line',
130 9 => 'warehouse',
131 10 => 'actioncomm',
132 11 => 'website_page',
133 12 => 'ticket',
134 13 => 'knowledgemanagement',
135 14 => 'fichinter',
136 16 => 'order',
137 17 => 'invoice',
138 20 => 'supplier_order',
139 21 => 'supplier_invoice',
140 22 => 'supplier_proposal',
141 23 => 'propal',
142 24 => 'project_task'
143 );
144 */
145
149 public $MAP_CAT_FK = array(
150 'customer' => 'soc',
151 'supplier' => 'soc',
152 'contact' => 'socpeople',
153 'bank_account' => 'account',
154 );
155
159 public $MAP_CAT_TABLE = array(
160 'customer' => 'societe',
161 'supplier' => 'fournisseur',
162 'bank_account' => 'account',
163 );
164
168 public $MAP_OBJ_CLASS = array(
169 'product' => 'Product',
170 'service' => 'Product',
171 'customer' => 'Societe',
172 'supplier' => 'Fournisseur',
173 'member' => 'Adherent',
174 'contact' => 'Contact',
175 'user' => 'User',
176 'account' => 'Account', // old for bank account
177 'bank_account' => 'Account',
178 'project' => 'Project',
179 'warehouse' => 'Entrepot',
180 'actioncomm' => 'ActionComm',
181 'website_page' => 'WebsitePage',
182 'ticket' => 'Ticket',
183 'knowledgemanagement' => 'KnowledgeRecord',
184 'fichinter' => 'Fichinter',
185 'order' => 'Commande',
186 'invoice' => 'Facture',
187 'supplier_order' => 'CommandeFournisseur',
188 'supplier_invoice' => 'FactureFournisseur',
189 'supplier_proposal' => 'SupplierProposal',
190 'propal' => 'Propal',
191 'project_task' => 'Task',
192 'mo' => 'Mo',
193 );
194
198 public static $MAP_TYPE_TITLE_AREA = array(
199 'product' => 'Products',
200 'service' => 'Services',
201 'customer' => 'ProspectsOrCustomers',
202 'supplier' => 'Suppliers',
203 'member' => 'Members',
204 'contact' => 'Contacts',
205 'user' => 'Users',
206 'account' => 'Accounts', // old for bank account
207 'bank_account' => 'BankAccounts',
208 'bank_line' => 'BankTransactions',
209 'project' => 'Projects',
210 'warehouse' => 'Warehouse',
211 'actioncomm' => 'AgendaEvents',
212 'website_page' => 'WebsitePages',
213 'ticket' => 'Tickets',
214 'knowledgemanagement' => 'KnowledgeRecords',
215 'fichinter' => 'Interventions',
216 'order' => 'Orders',
217 'invoice' => 'Invoices',
218 'supplier_order' => 'SuppliersOrders',
219 'supplier_invoice' => 'SuppliersInvoices',
220 'propal' => 'Proposals',
221 'supplier_proposal' => 'SupplierProposals',
222 'project_task' => 'Tasks',
223 'mo' => 'MOs'
224 );
225
230 public $MAP_OBJ_TABLE = array(
231 'customer' => 'societe',
232 'supplier' => 'societe',
233 'member' => 'adherent',
234 'contact' => 'socpeople',
235 'account' => 'bank_account', // old for bank account
236 'project' => 'projet',
237 'warehouse' => 'entrepot',
238 'knowledgemanagement' => 'knowledgemanagement_knowledgerecord',
239 'fichinter' => 'fichinter',
240 'order' => 'commande',
241 'invoice' => 'facture',
242 'supplier_order' => 'commande_fournisseur',
243 'supplier_invoice' => 'facture_fourn',
244 'project_task' => 'projet_task',
245 'mo' => 'mrp_mo'
246 );
247
251 public $element = 'category';
252
256 public $table_element = 'categorie';
257
261 public $fk_parent;
262
266 public $label;
267
271 public $description;
272
276 public $color;
277
281 public $position;
282
286 public $visible;
287
291 public $socid;
292
316 public $type;
317
321 public $cats = array();
322
326 public $motherof = array();
327
331 public $childs = array();
332
336 public $multilangs = array();
337
341 public $imgWidth;
342
346 public $imgHeight;
347
372 public $fields = array(
373 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'position' => 10, 'notnull' => 1, 'visible' => 0,),
374 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
375 'fk_parent' => array('type' => 'integer', 'label' => 'ParentCategory', 'enabled' => 1, 'position' => 20, 'notnull' => 1, 'visible' => 0, 'css' => 'maxwidth500 widthcentpercentminusxx',),
376 'label' => array('type' => 'varchar(180)', 'label' => 'Ref', 'enabled' => 1, 'position' => 25, 'notnull' => 1, 'visible' => 1, 'alwayseditable' => 1, 'css' => 'minwidth300', 'cssview' => 'wordbreak', 'csslist' => 'tdoverflowmax150', 'showoncombobox' => 1),
377 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'position' => 30, 'notnull' => 0, 'visible' => 0, 'alwayseditable' => 1,),
378 'type' => array('type' => 'integer', 'label' => 'Type', 'enabled' => 1, 'position' => 35, 'notnull' => 1, 'visible' => 0, 'alwayseditable' => 1,),
379 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'position' => 40, 'notnull' => 0, 'visible' => 1, 'alwayseditable' => 1,),
380 'color' => array('type' => 'varchar(8)', 'label' => 'Color', 'enabled' => 1, 'position' => 45, 'notnull' => 0, 'visible' => 1, 'alwayseditable' => 1,),
381 'position' => array('type' => 'integer', 'label' => 'Position', 'enabled' => 1, 'position' => 50, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,),
382 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'picto' => 'company', 'enabled' => 1, 'position' => 55, 'notnull' => 0, 'visible' => 0, 'alwayseditable' => 1, 'css' => 'maxwidth500 widthcentpercentminusxx', 'csslist' => 'tdoverflowmax150',),
383 'visible' => array('type' => 'integer', 'label' => 'Visible', 'enabled' => 1, 'position' => 60, 'notnull' => 1, 'visible' => 0, 'alwayseditable' => 1,),
384 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'position' => 900, 'notnull' => 0, 'visible' => -2, 'alwayseditable' => 1,),
385 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'position' => 70, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,),
386 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'position' => 75, 'notnull' => 1, 'visible' => -1, 'alwayseditable' => 1,),
387 'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'position' => 80, 'notnull' => 0, 'visible' => -2, 'alwayseditable' => 1, 'css' => 'maxwidth500 widthcentpercentminusxx', 'csslist' => 'tdoverflowmax150',),
388 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'position' => 85, 'notnull' => -1, 'visible' => -2, 'alwayseditable' => 1, 'css' => 'maxwidth500 widthcentpercentminusxx', 'csslist' => 'tdoverflowmax150',),
389 );
390
396 public function __construct($db)
397 {
398 global $hookmanager;
399
400 $this->db = $db;
401
402 $this->ismultientitymanaged = 1;
403 $this->isextrafieldmanaged = 1;
404
405 if (is_object($hookmanager)) {
406 $hookmanager->initHooks(array('category'));
407 $parameters = array();
408 $reshook = $hookmanager->executeHooks('constructCategory', $parameters, $this); // Note that $action and $object may have been modified by some hooks
409 if ($reshook >= 0 && !empty($hookmanager->resArray)) {
410 foreach ($hookmanager->resArray as $mapList) {
412 $mapId = $mapList['id'];
413 $mapCode = $mapList['code'];
414 //self::$MAP_ID_TO_CODE[$mapId] = $mapCode;
415 $this->MAP_ID[$mapCode] = $mapId;
416 $this->MAP_CAT_FK[$mapCode] = isset($mapList['cat_fk']) ? $mapList['cat_fk'] : null; // @phan-suppress-current-line SqlInjection
417 $this->MAP_CAT_TABLE[$mapCode] = isset($mapList['cat_table']) ? $mapList['cat_table'] : null; // @phan-suppress-current-line SqlInjection
418 $this->MAP_OBJ_CLASS[$mapCode] = $mapList['obj_class']; // @phan-suppress-current-line SqlInjection
419 $this->MAP_OBJ_TABLE[$mapCode] = $mapList['obj_table']; // @phan-suppress-current-line SqlInjection
420 self::$MAP_TYPE_TITLE_AREA[$mapCode] = isset($mapList['label']) ? $mapList['label'] : null;
421 }
422 }
423 }
424 }
425
431 public function getMapList()
432 {
433 $mapList = array();
434
435 foreach ($this->MAP_ID as $mapCode => $mapId) {
436 $mapList[] = array(
437 'id' => $mapId,
438 'code' => $mapCode,
439 'cat_fk' => (empty($this->MAP_CAT_FK[$mapCode]) ? $mapCode : $this->MAP_CAT_FK[$mapCode]),
440 'cat_table' => (empty($this->MAP_CAT_TABLE[$mapCode]) ? $mapCode : $this->MAP_CAT_TABLE[$mapCode]),
441 'obj_class' => (empty($this->MAP_OBJ_CLASS[$mapCode]) ? $mapCode : $this->MAP_OBJ_CLASS[$mapCode]),
442 'obj_table' => (empty($this->MAP_OBJ_TABLE[$mapCode]) ? $mapCode : $this->MAP_OBJ_TABLE[$mapCode]),
443 );
444 }
445
446 return $mapList;
447 }
448
454 public function getMapId()
455 {
456 return $this->MAP_ID;
457 }
458
468 public function fetch($id, $label = '', $type = null, $ref_ext = '')
469 {
470 // Check parameters
471 if (empty($id) && empty($label) && empty($ref_ext)) {
472 $this->error = "No category to search for";
473 return -1;
474 }
475 if (!is_null($type) && !is_numeric($type)) {
476 $type = $this->MAP_ID[$type];
477 }
478
479 $sql = "SELECT rowid, fk_parent, entity, label, description, color, position, fk_soc, visible, type, ref_ext";
480 $sql .= ", date_creation, tms, fk_user_creat, fk_user_modif, import_key";
481 $sql .= " FROM ".MAIN_DB_PREFIX."categorie";
482 if ($id) {
483 $sql .= " WHERE rowid = ".((int) $id);
484 } elseif (!empty($ref_ext)) {
485 $sql .= " WHERE ref_ext LIKE '".$this->db->escape($ref_ext)."'";
486 } else {
487 $sql .= " WHERE label = '".$this->db->escape($label)."' AND entity IN (".getEntity('category').")";
488 if (!is_null($type)) {
489 $sql .= " AND type = ".((int) $type);
490 }
491 }
492
493 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
494 $resql = $this->db->query($sql);
495 if ($resql) {
496 if ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
497 $this->id = $res['rowid'];
498 //$this->ref = $res['rowid'];
499 $this->fk_parent = (int) $res['fk_parent'];
500 $this->label = $res['label'];
501 $this->description = $res['description'];
502 $this->color = $res['color'];
503 $this->position = $res['position'];
504 $this->socid = (int) $res['fk_soc'];
505 $this->visible = (int) $res['visible'];
506 $this->type = $res['type'];
507 $this->ref_ext = $res['ref_ext'];
508 $this->entity = (int) $res['entity'];
509 $this->date_creation = $this->db->jdate($res['date_creation']);
510 $this->date_modification = $this->db->jdate($res['tms']);
511 $this->user_creation_id = (int) $res['fk_user_creat'];
512 $this->user_modification_id = (int) $res['fk_user_modif'];
513 $this->import_key = $res['import_key'];
514
515 // Retrieve all extrafields
516 // fetch optionals attributes and labels
517 $this->fetch_optionals();
518
519 $this->db->free($resql);
520
521 // multilangs
522 if (getDolGlobalInt('MAIN_MULTILANGS')) {
523 $this->getMultiLangs();
524 }
525
526 return 1;
527 } else {
528 $this->error = "No category found";
529 return 0;
530 }
531 } else {
532 dol_print_error($this->db);
533 $this->error = $this->db->lasterror;
534 $this->errors[] = $this->db->lasterror;
535 return -1;
536 }
537 }
538
549 public function create($user, $notrigger = 0)
550 {
551 global $conf, $langs;
552 $langs->load('categories');
553
554 $type = $this->type;
555
556 if (!is_numeric($type)) {
557 $type = $this->MAP_ID[$type];
558 }
559
560 $error = 0;
561
562 dol_syslog(get_class($this).'::create', LOG_DEBUG);
563
564 // Clean parameters
565 $this->label = trim((string) $this->label);
566 $this->description = trim((string) $this->description);
567 $this->color = trim((string) $this->color);
568 $this->position = (int) $this->position;
569 if (isset($this->import_key)) {
570 $this->import_key = trim($this->import_key);
571 }
572 $this->ref_ext = trim((string) $this->ref_ext);
573 if (empty($this->visible)) {
574 $this->visible = 0;
575 }
576 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
577
578 if ($this->already_exists()) {
579 $this->error = $langs->trans("ImpossibleAddCat", $this->label);
580 $this->error .= " : ".$langs->trans("CategoryExistsAtSameLevel");
581 dol_syslog($this->error, LOG_WARNING);
582 return -4;
583 }
584
585 $this->db->begin();
586 $now = dol_now();
587 $sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie (";
588 $sql .= "fk_parent,";
589 $sql .= " label,";
590 $sql .= " description,";
591 $sql .= " color,";
592 $sql .= " position,";
593 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
594 $sql .= "fk_soc,";
595 }
596 $sql .= " visible,";
597 $sql .= " type,";
598 $sql .= " import_key,";
599 $sql .= " ref_ext,";
600 $sql .= " entity,";
601 $sql .= " date_creation,";
602 $sql .= " fk_user_creat";
603 $sql .= ") VALUES (";
604 $sql .= (int) $this->fk_parent.",";
605 $sql .= "'".$this->db->escape($this->label)."', ";
606 $sql .= "'".$this->db->escape($this->description)."', ";
607 $sql .= "'".$this->db->escape($this->color)."', ";
608 $sql .= (int) $this->position.",";
609 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
610 $sql .= ($this->socid > 0 ? ((int) $this->socid) : 'null').", ";
611 }
612 $sql .= "'".$this->db->escape((string) $this->visible)."', ";
613 $sql .= ((int) $type).", ";
614 $sql .= (!empty($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : 'null').", ";
615 $sql .= (!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : 'null').", ";
616 $sql .= (int) $conf->entity.", ";
617 $sql .= "'".$this->db->idate($now)."', ";
618 $sql .= (int) $user->id;
619 $sql .= ")";
620
621 $res = $this->db->query($sql);
622 if ($res) {
623 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."categorie");
624
625 if ($id > 0) {
626 $this->id = $id;
627
628 $action = 'create';
629
630 // Actions on extra fields
631 $result = $this->insertExtraFields();
632 if ($result < 0) {
633 $error++;
634 }
635
636 if (!$error && !$notrigger) {
637 // Call trigger
638 $result = $this->call_trigger('CATEGORY_CREATE', $user);
639 if ($result < 0) {
640 $error++;
641 }
642 // End call triggers
643 }
644
645 if (!$error) {
646 $this->db->commit();
647 return $id;
648 } else {
649 $this->db->rollback();
650 return -3;
651 }
652 } else {
653 $this->db->rollback();
654 return -2;
655 }
656 } else {
657 $this->error = $this->db->error();
658 $this->db->rollback();
659 return -1;
660 }
661 }
662
672 public function update(User $user, $notrigger = 0)
673 {
674 global $langs;
675
676 $error = 0;
677
678 // Clean parameters
679 $this->label = trim((string) $this->label);
680 $this->description = trim((string) $this->description);
681 $this->ref_ext = trim((string) $this->ref_ext);
682 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
683 $this->visible = ($this->visible != "" ? intval($this->visible) : 0);
684
685 if ($this->fk_parent > 0 && $this->fk_parent == $this->id) {
686 $langs->load('categories');
687 $this->error = $langs->trans("ErrorCategoryCannotBeItsOwnParent");
688 dol_syslog($this->error, LOG_WARNING);
689 return -1;
690 }
691 // Nor one of its descendants, which would detach both from the tree and make a loop: go up from the new parent
692 // (only when the parent changes, or when we do not know the previous parent)
693 if (empty($this->oldcopy) || $this->fk_parent != $this->oldcopy->fk_parent) {
694 $ancestorid = $this->fk_parent;
695 $protection = 1000;
696 while ($ancestorid > 0 && $protection-- > 0) {
697 $resql = $this->db->query("SELECT fk_parent FROM ".MAIN_DB_PREFIX."categorie WHERE rowid = ".((int) $ancestorid));
698 $obj = $resql ? $this->db->fetch_object($resql) : null;
699 $ancestorid = $obj ? (int) $obj->fk_parent : 0;
700 if ($ancestorid == $this->id) {
701 $langs->load('categories');
702 $this->error = $langs->trans("ErrorCategoryCannotBeMovedIntoItsDescendant");
703 dol_syslog($this->error, LOG_WARNING);
704 return -1;
705 }
706 }
707 }
708
709 if ($this->already_exists()) {
710 $this->error = $langs->trans("ImpossibleUpdateCat");
711 $this->error .= " : ".$langs->trans("CategoryExistsAtSameLevel");
712 return -1;
713 }
714
715 $this->db->begin();
716
717 $sql = "UPDATE ".MAIN_DB_PREFIX."categorie";
718 $sql .= " SET label = '".$this->db->escape($this->label)."',";
719 $sql .= " description = '".$this->db->escape($this->description)."',";
720 $sql .= " ref_ext = '".$this->db->escape($this->ref_ext)."',";
721 $sql .= " color = '".$this->db->escape($this->color)."'";
722 $sql .= ", position = ".(int) $this->position;
723 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
724 $sql .= ", fk_soc = ".($this->socid > 0 ? ((int) $this->socid) : 'null');
725 }
726 $sql .= ", visible = ".(int) $this->visible;
727 $sql .= ", fk_parent = ".(int) $this->fk_parent;
728 $sql .= ", fk_user_modif = ".(int) $user->id;
729 $sql .= " WHERE rowid = ".((int) $this->id);
730
731 dol_syslog(get_class($this)."::update", LOG_DEBUG);
732 if ($this->db->query($sql)) {
733 // Multilangs
734 if (getDolGlobalInt('MAIN_MULTILANGS')) {
735 if ($this->setMultiLangs($user, $notrigger) < 0) {
736 $this->db->rollback();
737 return -2;
738 }
739 }
740
741 $action = 'update';
742
743 // Actions on extra fields
744 $result = $this->insertExtraFields();
745 if ($result < 0) {
746 $error++;
747 }
748
749 if (!$error && !$notrigger) {
750 // Call trigger
751 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
752 if ($result < 0) {
753 $error++;
754 }
755 // End call triggers
756 }
757
758 if (!$error) {
759 $this->db->commit();
760 return 1;
761 } else {
762 $this->db->rollback();
763 return -1;
764 }
765 } else {
766 $this->db->rollback();
767 dol_print_error($this->db);
768 return -1;
769 }
770 }
771
779 public function delete($user, $notrigger = 0)
780 {
781 $error = 0;
782
783 // Clean parameters
784 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
785
786 dol_syslog(get_class($this)."::remove");
787
788 $this->db->begin();
789
790 if (/* !$error && */ !$notrigger) {
791 // Call trigger
792 $result = $this->call_trigger('CATEGORY_DELETE', $user);
793 if ($result < 0) {
794 $error++;
795 }
796 // End call triggers
797 }
798
799 /* FIX #1317 : Check for child category and move up 1 level*/
800 if (!$error) {
801 $sql = "UPDATE ".MAIN_DB_PREFIX."categorie";
802 $sql .= " SET fk_parent = ".((int) $this->fk_parent);
803 $sql .= " WHERE fk_parent = ".((int) $this->id);
804
805 if (!$this->db->query($sql)) {
806 $this->error = $this->db->lasterror();
807 $error++;
808 }
809 }
810
811 $arraydelete = array(
812 'categorie_account' => 'fk_categorie',
813 'categorie_actioncomm' => 'fk_categorie',
814 'categorie_contact' => 'fk_categorie',
815 'categorie_fournisseur' => 'fk_categorie',
816 'categorie_knowledgemanagement' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('knowledgemanagement')),
817 'categorie_fichinter' => array('field' => 'fk_categorie', 'enabled' => false ), // not yet fully implemented (wait 22) isModEnabled('intervention')),
818 'categorie_member' => 'fk_categorie',
819 'categorie_user' => 'fk_categorie',
820 'categorie_product' => 'fk_categorie',
821 'categorie_project' => 'fk_categorie',
822 'categorie_project_task' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('project')),
823 'categorie_societe' => 'fk_categorie',
824 'categorie_ticket' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('ticket')),
825 'categorie_warehouse' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('stock')),
826 'categorie_website_page' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('website')),
827 'category_bankline' => 'fk_categ',
828 'categorie_lang' => 'fk_category',
829 'categorie' => 'rowid',
830 );
831 // The links of the category are in the table of its own type: make sure that table is purged even when it is
832 // missing from the list above or disabled there, otherwise its foreign key refuses to delete the category.
833 $typecode = is_numeric($this->type) ? array_search((int) $this->type, $this->MAP_ID) : $this->type;
834 if (!empty($typecode) && $typecode != 'bank_line') { // the links of bank lines are in category_bankline, already in the list
835 $linktable = 'categorie_'.(empty($this->MAP_CAT_TABLE[$typecode]) ? $typecode : $this->MAP_CAT_TABLE[$typecode]);
836 if (array_key_exists($linktable, $arraydelete)) {
837 $arraydelete[$linktable] = 'fk_categorie';
838 } else {
839 $arraydelete = array($linktable => 'fk_categorie') + $arraydelete;
840 }
841 }
842 foreach ($arraydelete as $key => $value) {
843 $sanitizedvalue = $value;
844 if (is_array($value)) {
845 if (empty($value['enabled'])) {
846 continue;
847 }
848 $sanitizedvalue = $value['field']; // From explicit array above @phan-suppress-current-line SqlInjection
849 }
850
851 $sql = "DELETE FROM ".$this->db->sanitize(MAIN_DB_PREFIX.$key);
852 $sql .= " WHERE ".$this->db->sanitize($sanitizedvalue)." = ".((int) $this->id);
853 if (!$this->db->query($sql)) {
854 $this->errors[] = $this->db->lasterror();
855 dol_syslog("Error sql=".$sql." ".$this->error, LOG_ERR);
856 $error++;
857 }
858 }
859
860 // Removed extrafields
861 if (!$error) {
862 $result = $this->deleteExtraFields();
863 if ($result < 0) {
864 $error++;
865 dol_syslog(get_class($this)."::delete erreur ".$this->error, LOG_ERR);
866 }
867 }
868
869 if (!$error) {
870 $this->db->commit();
871 return 1;
872 } else {
873 $this->db->rollback();
874 return -1;
875 }
876 }
877
878 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
887 public function add_type($obj, $type = '')
888 {
889 // phpcs:enable
890 global $user;
891
892 $error = 0;
893
894 if ($this->id == -1) {
895 return -2;
896 }
897
898 if (empty($type)) {
899 $type = $obj->element;
900 }
901
902 dol_syslog(get_class($this).'::add_type', LOG_DEBUG);
903
904 $this->db->begin();
905
906 $sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
907 $sql .= " (fk_categorie, fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]).")";
908 $sql .= " VALUES (".((int) $this->id).", ".((int) $obj->id).")";
909
910 if ($this->db->query($sql)) {
911 if (getDolGlobalString('CATEGORIE_RECURSIV_ADD')) {
912 $sql = 'SELECT fk_parent FROM '.MAIN_DB_PREFIX.'categorie';
913 $sql .= " WHERE rowid = ".((int) $this->id);
914
915 dol_syslog(get_class($this)."::add_type", LOG_DEBUG);
916 $resql = $this->db->query($sql);
917 if ($resql) {
918 if ($this->db->num_rows($resql) > 0) {
919 $objparent = $this->db->fetch_object($resql);
920
921 if (!empty($objparent->fk_parent)) {
922 $cat = new Categorie($this->db);
923 $cat->id = $objparent->fk_parent;
924 if (!$cat->containsObject($type, $obj->id)) {
925 $result = $cat->add_type($obj, $type);
926 if ($result < 0) {
927 $this->error = $cat->error;
928 $error++;
929 }
930 }
931 }
932 }
933 } else {
934 $error++;
935 $this->error = $this->db->lasterror();
936 }
937
938 if ($error) {
939 $this->db->rollback();
940 return -1;
941 }
942 }
943
944 // Call trigger
945 $this->context = array('linkto' => $obj); // Save object we want to link category to into category instance to provide information to trigger
946 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
947 if ($result < 0) {
948 $error++;
949 }
950 // End call triggers
951
952 if (!$error) {
953 $this->db->commit();
954 return 1;
955 } else {
956 $this->db->rollback();
957 return -2;
958 }
959 } else {
960 $this->db->rollback();
961 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
962 $this->error = $this->db->lasterrno();
963 return -3;
964 } else {
965 $this->error = $this->db->lasterror();
966 }
967 return -1;
968 }
969 }
970
971 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
980 public function del_type($obj, $type)
981 {
982 // phpcs:enable
983 global $user;
984
985 $error = 0;
986
987 // For backward compatibility
988 if ($type == 'societe') {
989 $type = 'customer';
990 dol_syslog(get_class($this)."::del_type(): type 'societe' is deprecated, please use 'customer' instead", LOG_WARNING);
991 } elseif ($type == 'fournisseur') {
992 $type = 'supplier';
993 dol_syslog(get_class($this)."::del_type(): type 'fournisseur' is deprecated, please use 'supplier' instead", LOG_WARNING);
994 }
995
996 $this->db->begin();
997
998 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
999 $sql .= " WHERE fk_categorie = ".((int) $this->id);
1000 $sql .= " AND fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".((int) $obj->id);
1001
1002 dol_syslog(get_class($this).'::del_type', LOG_DEBUG);
1003 if ($this->db->query($sql)) {
1004 // Call trigger
1005 $this->context = array('unlinkoff' => $obj); // Save object we want to link category to into category instance to provide information to trigger
1006 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
1007 if ($result < 0) {
1008 $error++;
1009 }
1010 // End call triggers
1011
1012 if (!$error) {
1013 $this->db->commit();
1014 return 1;
1015 } else {
1016 $this->db->rollback();
1017 return -2;
1018 }
1019 } else {
1020 $this->db->rollback();
1021 $this->error = $this->db->lasterror();
1022 return -1;
1023 }
1024 }
1025
1044 public function getObjectsInCateg($type, $onlyids = 0, $limit = 0, $offset = 0, $sortfield = '', $sortorder = 'ASC', $filter = '', $filtermode = 'AND', $filterlang = '')
1045 {
1046 global $user;
1047
1048 if (empty($onlyids)) {
1049 dol_syslog("getObjectsInCateg: This method used with parameter onlyids=0 is deprecated. Try by using instead getListForItem().", LOG_WARNING);
1050 }
1051
1052 $objs = array();
1053
1054 $classnameforobj = $this->MAP_OBJ_CLASS[$type];
1055 if (!empty($classnameforobj) && class_exists($classnameforobj)) {
1056 $tmpobj = new $classnameforobj($this->db);
1059 $sql = "SELECT c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." as fk_object";
1060 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type])." as c";
1061 $sql .= ", ".MAIN_DB_PREFIX.(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])." as o";
1062 if (!empty($filterlang)) {
1063 $sql .= ", ".MAIN_DB_PREFIX.(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])."_lang as ol";
1064 }
1065 $sql .= " WHERE o.entity IN (".getEntity($tmpobj->element).")";
1066 $sql .= " AND c.fk_categorie = ".((int) $this->id);
1067 // Compatibility with actioncomm table which has id instead of rowid
1068 if ((array_key_exists($type, $this->MAP_OBJ_TABLE) && $this->MAP_OBJ_TABLE[$type] == "actioncomm") || $type == "actioncomm") {
1069 $sql .= " AND c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = o.id";
1070 } else {
1071 $sql .= " AND c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = o.rowid";
1072 }
1073 if (!empty($filterlang)) {
1074 $sql .= " AND ol.fk_".(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])." = o.rowid";
1075 $sql .= " AND ol.lang = '".$this->db->escape($filterlang)."'";
1076 }
1077 // Protection for external users
1078 if (($type == 'customer' || $type == 'supplier') && $user->socid > 0) {
1079 $sql .= " AND o.rowid = ".((int) $user->socid);
1080 }
1081
1082 // Add where from hooks (for example to restrict to objects an external module shares
1083 // with the current entity by a granularity finer than this category's own)
1084 global $hookmanager;
1085 if (is_object($hookmanager)) {
1086 $parameters = array('type' => $type, 'alias' => 'o');
1087 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this);
1088 $sql .= $hookmanager->resPrint;
1089 }
1090
1091 $errormessage = '';
1092 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1093 if ($errormessage) {
1094 $this->errors[] = $errormessage;
1095 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1096 return -1;
1097 }
1098
1099 $sql .= $this->db->order($sortfield, $sortorder);
1100 if ($limit > 0 || $offset > 0) {
1101 $sql .= $this->db->plimit($limit + 1, $offset);
1102 }
1103
1104 dol_syslog(get_class($this)."::getObjectsInCateg", LOG_DEBUG);
1105
1106 $resql = $this->db->query($sql);
1107 if ($resql) {
1108 while ($rec = $this->db->fetch_array($resql)) {
1109 if ($onlyids) {
1110 $objs[] = $rec['fk_object'];
1111 } else {
1112 $tmpobj = new $classnameforobj($this->db);
1113 $tmpobj->fetch($rec['fk_object']); // The fetch will set $tmpobj->id only if it succeed.
1114 // @phpstan-ignore-next-line
1115 if ($tmpobj->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.
1116 $objs[] = $tmpobj;
1117 }
1118 }
1119 }
1120 } else {
1121 $this->error = $this->db->error().' sql='.$sql;
1122 return -1;
1123 }
1124 }
1125
1126 return $objs;
1127 }
1128
1137 public function containsObject($type, $object_id)
1138 {
1139 $sql = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
1140 $sql .= " WHERE fk_categorie = ".((int) $this->id)." AND fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".((int) $object_id);
1141
1142 dol_syslog(get_class($this)."::containsObject", LOG_DEBUG);
1143
1144 $resql = $this->db->query($sql);
1145 if ($resql) {
1146 return $this->db->fetch_object($resql)->nb;
1147 } else {
1148 $this->error = $this->db->error();
1149 return -1;
1150 }
1151 }
1152
1165 public function getListForItem($id, $type = 'customer', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
1166 {
1167 $categories = array();
1168
1169 $type = sanitizeVal($type, 'aZ09');
1170
1171 $sub_type = $type;
1172 $subcol_name = "fk_".$type;
1173 if ($type == "customer") {
1174 $sub_type = "societe";
1175 $subcol_name = "fk_soc";
1176 }
1177 if ($type == "supplier") {
1178 $sub_type = "fournisseur";
1179 $subcol_name = "fk_soc";
1180 }
1181 if ($type == "contact") {
1182 $subcol_name = "fk_socpeople";
1183 }
1184
1185 $idoftype = (int) (array_key_exists($type, $this->MAP_ID) ? $this->MAP_ID[$type] : -1);
1186
1187 $sql = "SELECT s.rowid";
1188 $sqlfields = $sql; // $sql fields to remove for count total
1189 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as s, ".MAIN_DB_PREFIX."categorie_".$this->db->sanitize($sub_type)." as sub";
1190 $sql .= ' WHERE s.entity IN ('.getEntity('category').')';
1191 $sql .= ' AND s.type = '.((int) $idoftype);
1192 $sql .= ' AND s.rowid = sub.fk_categorie';
1193 $sql .= " AND sub.".$this->db->sanitize($subcol_name)." = ".((int) $id);
1194
1195 $offset = 0;
1196 $nbtotalofrecords = '';
1197 if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
1198 $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
1199 $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
1200
1201 $resql = $this->db->query($sqlforcount);
1202 if ($resql) {
1203 $objforcount = $this->db->fetch_object($resql);
1204 $nbtotalofrecords = $objforcount->nbtotalofrecords;
1205 } else {
1206 dol_print_error($this->db);
1207 }
1208
1209 if (($limit >= (int) $nbtotalofrecords) && $page > 0) {
1210 return [];
1211 }
1212
1213 if (($page * $limit) >= (int) $nbtotalofrecords) { // if total resultset is smaller or equal then paging size (filtering), goto and load page 0
1214 $page = 0;
1215 $offset = 0;
1216 }
1217 $this->db->free($resql);
1218 }
1219
1220 $sql .= $this->db->order($sortfield, $sortorder);
1221 if ($limit) {
1222 if ($page < 0) {
1223 $page = 0;
1224 }
1225 $offset = $limit * $page;
1226
1227 $sql .= $this->db->plimit($limit + 1, $offset);
1228 }
1229
1230 $result = $this->db->query($sql);
1231 if ($result) {
1232 $i = 0;
1233 $num = $this->db->num_rows($result);
1234 $min = min($num, ($limit <= 0 ? $num : $limit));
1235 while ($i < $min) {
1236 $obj = $this->db->fetch_object($result);
1237 $category_static = new Categorie($this->db);
1238 if ($category_static->fetch($obj->rowid)) {
1239 $categories[$i]['id'] = $category_static->id;
1240 $categories[$i]['fk_parent'] = $category_static->fk_parent;
1241 $categories[$i]['label'] = $category_static->label;
1242 $categories[$i]['description'] = $category_static->description;
1243 $categories[$i]['color'] = $category_static->color;
1244 $categories[$i]['position'] = $category_static->position;
1245 $categories[$i]['socid'] = $category_static->socid;
1246 $categories[$i]['ref_ext'] = $category_static->ref_ext;
1247 $categories[$i]['visible'] = $category_static->visible;
1248 $categories[$i]['type'] = $category_static->type;
1249 $categories[$i]['entity'] = $category_static->entity;
1250 $categories[$i]['array_options'] = $category_static->array_options;
1251
1252 // multilangs
1253 if (getDolGlobalInt('MAIN_MULTILANGS') && isset($category_static->multilangs)) {
1254 $categories[$i]['multilangs'] = $category_static->multilangs;
1255 }
1256 }
1257 $i++;
1258 }
1259 } else {
1260 $this->error = $this->db->lasterror();
1261 return -1;
1262 }
1263 if (!count($categories)) {
1264 return [];
1265 }
1266
1267 return $categories;
1268 }
1269
1270 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1276 public function get_filles()
1277 {
1278 // phpcs:enable
1279 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1280 $sql .= " WHERE fk_parent = ".((int) $this->id);
1281 $sql .= " AND entity IN (".getEntity('category').")";
1282
1283 $res = $this->db->query($sql);
1284 if ($res) {
1285 $cats = array();
1286 while ($rec = $this->db->fetch_array($res)) {
1287 $cat = new Categorie($this->db);
1288 $cat->fetch($rec['rowid']);
1289 $cats[] = $cat;
1290 }
1291 return $cats;
1292 } else {
1293 dol_print_error($this->db);
1294 return -1;
1295 }
1296 }
1297
1298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1305 protected function load_motherof()
1306 {
1307 // phpcs:enable
1308 $this->motherof = array();
1309
1310 // Load array[child]=parent
1311 $sql = "SELECT fk_parent as id_parent, rowid as id_son";
1312 $sql .= " FROM ".MAIN_DB_PREFIX."categorie";
1313 $sql .= " WHERE fk_parent <> 0";
1314 $sql .= " AND entity IN (".getEntity('category').")";
1315
1316 dol_syslog(get_class($this)."::load_motherof", LOG_DEBUG);
1317 $resql = $this->db->query($sql);
1318 if ($resql) {
1319 while ($obj = $this->db->fetch_object($resql)) {
1320 $this->motherof[$obj->id_son] = $obj->id_parent;
1321 }
1322 return 1;
1323 } else {
1324 dol_print_error($this->db);
1325 return -1;
1326 }
1327 }
1328
1329 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1350 public function get_full_arbo($type, $fromid = 0, $include = 0, $forcelangcode = '')
1351 {
1352 // phpcs:enable
1353 global $langs;
1354
1355 if (!is_numeric($type)) {
1356 $type = $this->MAP_ID[$type];
1357 }
1358 if (is_null($type)) {
1359 $this->error = 'BadValueForParameterType';
1360 return -1;
1361 }
1362
1363 if (is_string($fromid)) {
1364 $fromid = explode(',', $fromid);
1365 } elseif (is_numeric($fromid)) {
1366 if ($fromid > 0) {
1367 $fromid = array($fromid);
1368 } else {
1369 $fromid = array();
1370 }
1371 } elseif (!is_array($fromid)) {
1372 $fromid = array();
1373 }
1374
1375 $this->cats = array();
1376 $nbcateg = 0;
1377
1378 // Init this->motherof that is array(id_son=>id_parent, ...)
1379 $this->load_motherof();
1380
1381 if ($forcelangcode) {
1382 $current_lang = $forcelangcode;
1383 } else {
1384 $current_lang = $langs->getDefaultLang();
1385 }
1386
1387 // Init $this->cats array
1388 // Note: The DISTINCT reduces pb with old tables with duplicates but should not be used
1389 $sql = "SELECT DISTINCT c.rowid, c.label, c.ref_ext, c.description, c.color, c.position, c.fk_parent, c.visible, c.entity";
1390 if (getDolGlobalInt('MAIN_MULTILANGS') && $current_lang !== 'none') {
1391 $sql .= ", t.label as label_trans, t.description as description_trans";
1392 }
1393 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as c";
1394 if (getDolGlobalInt('MAIN_MULTILANGS') && $current_lang !== 'none') {
1395 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."categorie_lang as t ON t.fk_category = c.rowid AND t.lang = '".$this->db->escape($current_lang)."'";
1396 }
1397 $sql .= " WHERE c.entity IN (".getEntity('category').")";
1398 $sql .= " AND c.type = ".(int) $type;
1399
1400 dol_syslog(get_class($this)."::get_full_arbo get category list", LOG_DEBUG);
1401
1402 $resql = $this->db->query($sql);
1403 if ($resql) {
1404 $i = 0;
1405 $nbcateg = $this->db->num_rows($resql);
1406
1407 while ($obj = $this->db->fetch_object($resql)) {
1408 $this->cats[(int) $obj->rowid]
1409 = array(
1410 'rowid' => (int) $obj->rowid,
1411 'id' => (int) $obj->rowid,
1412 'fk_parent' => (int) $obj->fk_parent,
1413 'label' => !empty($obj->label_trans) ? (string) $obj->label_trans : (string) $obj->label,
1414 'description' => !empty($obj->description_trans) ? (string) $obj->description_trans : (string) $obj->description,
1415 'color' => (string) $obj->color,
1416 'position' => (string) $obj->position,
1417 'visible' => (int) $obj->visible,
1418 'ref_ext' => (string) $obj->ref_ext,
1419 'entity' => (int) $obj->entity,
1420 'picto' => 'category',
1421 // fields are filled with buildPathFromId later
1422 'fullpath' => '',
1423 'fulllabel' => '',
1424 'level' => null,
1425 );
1426 $i++;
1427 }
1428 } else {
1429 dol_print_error($this->db);
1430 return -1;
1431 }
1432
1433 // Let external modules complete or filter the tree (for example to restrict categories
1434 // coming from other entities according to a sharing granularity they manage)
1435 global $hookmanager;
1436 if (is_object($hookmanager)) {
1437 $parameters = array('cats' => &$this->cats, 'motherof' => &$this->motherof, 'type' => $type);
1438 $reshook = $hookmanager->executeHooks('completeCategoryFullTree', $parameters, $this);
1439 if ($reshook < 0) {
1440 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1441 }
1442 }
1443
1444 // We add the fullpath property to each elements of first level (no parent exists)
1445 dol_syslog(get_class($this)."::get_full_arbo call to buildPathFromId", LOG_DEBUG);
1446
1447 foreach ($this->cats as $key => $val) {
1448 //print 'key='.$key.'<br>'."\n";
1449 $this->buildPathFromId($key, $nbcateg); // Process a branch from the root category key (this category has no parent) and adds level to $this->cats items
1450 }
1451
1452 // Include or exclude leaf (including $fromid) from tree
1453 if (count($fromid) > 0) {
1454 $keyfiltercatid = '('.implode('|', $fromid).')';
1455
1456 //print "Look to discard category ".$fromid."\n";
1457 $keyfilter1 = '^'.$keyfiltercatid.'$';
1458 $keyfilter2 = '_'.$keyfiltercatid.'$';
1459 $keyfilter3 = '^'.$keyfiltercatid.'_';
1460 $keyfilter4 = '_'.$keyfiltercatid.'_';
1461 foreach (array_keys($this->cats) as $key) {
1462 $fullpath = (string) $this->cats[$key]['fullpath'];
1463 $test = (preg_match('/'.$keyfilter1.'/', $fullpath) || preg_match('/'.$keyfilter2.'/', $fullpath)
1464 || preg_match('/'.$keyfilter3.'/', $fullpath) || preg_match('/'.$keyfilter4.'/', $fullpath));
1465
1466 if (($test && !$include) || (!$test && $include)) {
1467 unset($this->cats[$key]); // @phpstan-ignore-line
1468 }
1469 }
1470 }
1471
1472 dol_syslog(get_class($this)."::get_full_arbo dol_sort_array", LOG_DEBUG);
1473
1474 $this->cats = dol_sort_array($this->cats, 'fulllabel', 'asc', 1, 0, 1); // Sort on full label like "Label 1 >> Sublabel a >> Subsublabel"
1475
1476 return $this->cats;
1477 }
1478
1489 private function buildPathFromId($id_categ, $protection = 1000)
1490 {
1491 //dol_syslog(get_class($this)."::buildPathFromId id_categ=".$id_categ." protection=".$protection, LOG_DEBUG);
1492
1493 if (!empty($this->cats[$id_categ]['fullpath'])) {
1494 // Already defined
1495 dol_syslog(get_class($this)."::buildPathFromId fullpath and fulllabel already defined", LOG_WARNING);
1496 return -1;
1497 }
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 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1533 public function get_all_categories($type = null, $parent = false)
1534 {
1535 // phpcs:enable
1536 if (!is_numeric($type) && !is_null($type)) {
1537 $type = $this->MAP_ID[$type];
1538 }
1539
1540 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1541 $sql .= " WHERE entity IN (".getEntity('category').")";
1542 if (!is_null($type)) {
1543 $sql .= " AND type = ".(int) $type;
1544 }
1545 if ($parent) {
1546 $sql .= " AND fk_parent = 0";
1547 }
1548
1549 $res = $this->db->query($sql);
1550 if ($res) {
1551 $cats = array();
1552 while ($rec = $this->db->fetch_array($res)) {
1553 $cat = new Categorie($this->db);
1554 $cat->fetch($rec['rowid']);
1555 $cats[$rec['rowid']] = $cat;
1556 }
1557 return $cats;
1558 } else {
1559 dol_print_error($this->db);
1560 return -1;
1561 }
1562 }
1563
1564 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1571 public function get_main_categories($type = null)
1572 {
1573 // phpcs:enable
1574 return $this->get_all_categories($type, true);
1575 }
1576
1577 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1583 public function already_exists()
1584 {
1585 // phpcs:enable
1586 $type = $this->type;
1587
1588 if (!is_numeric($type)) {
1589 $type = $this->MAP_ID[$type];
1590 }
1591
1592 /* We have to select any rowid from llx_categorie which category's mother and label
1593 * are equals to those of the calling category
1594 */
1595 $sql = "SELECT c.rowid";
1596 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as c ";
1597 $sql .= " WHERE c.entity IN (".getEntity('category').")";
1598 $sql .= " AND c.type = ".((int) $type);
1599 $sql .= " AND c.fk_parent = ".((int) $this->fk_parent);
1600 $sql .= " AND c.label = '".$this->db->escape($this->label)."'";
1601
1602 dol_syslog(get_class($this)."::already_exists", LOG_DEBUG);
1603
1604 $resql = $this->db->query($sql);
1605 if ($resql) {
1606 if ($this->db->num_rows($resql) > 0) { // Checking for empty resql
1607 $obj = $this->db->fetch_object($resql);
1608 /* If object called create, obj cannot have is id.
1609 * If object called update, he mustn't have the same label as an other category for this mother.
1610 * 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.
1611 */
1612 if (!empty($obj) && $obj->rowid > 0 && $obj->rowid != $this->id) {
1613 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);
1614 return 1;
1615 }
1616 }
1617 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);
1618 return 0;
1619 } else {
1620 $this->error = $this->db->error();
1621 return -1;
1622 }
1623 }
1624
1625
1626 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1638 public function print_all_ways($sep = 'auto', $url = '', $nocolor = 0, $addpicto = 0, $notrunc = 0)
1639 {
1640 // phpcs:enable
1641 $ways = array();
1642
1643 if ($sep == 'auto') {
1644 $sep = '&gt;';
1645 }
1646
1647 $all_ways = $this->get_all_ways(); // Load array of categories from database to reach this->id
1648
1649 foreach ($all_ways as $way) { // It seems we always have 1 entry in this array.
1650 $w = array();
1651 $i = 0;
1652 $forced_color = '';
1653 foreach ($way as $cat) { // Loop on each successive categories to reach the target of current category
1654 $i++;
1655
1656 if (empty($nocolor)) {
1657 $forced_color = 'colortoreplace';
1658 if ($i == count($way)) { // Last category in hierarchy
1659 // Check contrast with background and correct text color
1660 $forced_color = 'categtextwhite'; // We want color white because the getNomUrl of a tag is always called inside a dark background like '<span color="bbb"></span>' to show it as a tag. TODO Add this in param to force when called outside of span.
1661 if ($cat->color && colorIsLight($cat->color)) {
1662 $forced_color = 'categtextblack';
1663 }
1664 }
1665 }
1666
1667 if ($url == '') {
1668 if (($i < count($way) && empty($notrunc)) && $i > 1) {
1669 $link = '';
1670 $linkend = '';
1671 } else {
1672 $link = '<a href="'.DOL_URL_ROOT.'/categories/viewcat.php?id='.((int) $cat->id).'&type='.urlencode($cat->type).'" class="'.(($i < count($way) && empty($notrunc)) ? 'small ' : '').$forced_color.'">';
1673 $linkend = '</a>';
1674 }
1675 $s = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '');
1676 $s .= (($i < count($way) && empty($notrunc)) ? ($i == 1 ? dol_trunc($cat->label, 3) : '') : $cat->label);
1677 $s .= $linkend;
1678 $w[] = $s;
1679 } elseif ($url == 'none') {
1680 if (($i < count($way) && empty($notrunc)) && $i > 1) {
1681 $link = '';
1682 $linkend = '';
1683 } else {
1684 $link = '<span class="valignmiddle '.($i < count($way) ? 'small ' : '').$forced_color.'">';
1685 $linkend = '</span>';
1686 }
1687 $s = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '');
1688 $s .= (($i < count($way) && empty($notrunc)) ? ($i == 1 ? dol_trunc($cat->label, 3) : '') : $cat->label);
1689 $s .= $linkend;
1690 $w[] = $s;
1691 } else {
1692 $w[] = '<a class="valignmiddle '.($i < count($way) ? 'small ' : '').$forced_color.'" href="'.DOL_URL_ROOT.'/'.$url.'?catid='.((int) $cat->id).'">'.($addpicto ? img_object('', 'category') : '').$cat->label.'</a>';
1693 }
1694 }
1695 $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode('<span class="inline-block valignmiddle paddingleft paddingright small '.$forced_color.'">'.$sep.'</span>', $w));
1696
1697 $ways[] = $newcategwithpath;
1698 }
1699
1700 return $ways;
1701 }
1702
1703
1704 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1711 public function get_meres()
1712 {
1713 // phpcs:enable
1714 $parents = array();
1715
1716 $sql = "SELECT fk_parent FROM ".MAIN_DB_PREFIX."categorie";
1717 $sql .= " WHERE rowid = ".((int) $this->id);
1718
1719 $res = $this->db->query($sql);
1720
1721 if ($res) {
1722 while ($rec = $this->db->fetch_array($res)) {
1723 if ($rec['fk_parent'] > 0) {
1724 $cat = new Categorie($this->db);
1725 $cat->fetch($rec['fk_parent']);
1726 $parents[] = $cat;
1727 }
1728 }
1729 return $parents;
1730 } else {
1731 dol_print_error($this->db);
1732 return -1;
1733 }
1734 }
1735
1736 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1743 public function get_all_ways()
1744 {
1745 // phpcs:enable
1746 $ways = array();
1747
1748 $parents = $this->get_meres();
1749 if (is_array($parents)) {
1750 foreach ($parents as $parent) {
1751 $all_ways = $parent->get_all_ways(); // recursivity. TODO Add a protection for infinite loop
1752 foreach ($all_ways as $way) {
1753 $w = $way;
1754 $w[] = $this;
1755 $ways[] = $w;
1756 }
1757 }
1758 }
1759
1760 if (count($ways) == 0) {
1761 $ways[0][0] = $this;
1762 }
1763
1764 return $ways;
1765 }
1766
1777 public function containing($id, $type, $mode = 'object')
1778 {
1779 $cats = array();
1780
1781 if (is_numeric($type)) {
1782 $type = array_search($type, $this->MAP_ID);
1783 }
1784
1785 if ($type === Categorie::TYPE_BANK_LINE) { // TODO Remove this after migration of llx_category_bankline into llx_categorie_bankline
1786 // Load bank categories
1787 $sql = "SELECT c.label, c.rowid";
1788 $sql .= " FROM ".MAIN_DB_PREFIX."category_bankline as a, ".MAIN_DB_PREFIX."categorie as c";
1789 $sql .= " WHERE a.lineid=".((int) $id)." AND a.fk_categ = c.rowid";
1790 $sql .= " AND c.entity IN (".getEntity('category').")";
1791 $sql .= " ORDER BY c.label";
1792
1793 $res = $this->db->query($sql);
1794 if ($res) {
1795 while ($obj = $this->db->fetch_object($res)) {
1796 if ($mode == 'id') {
1797 $cats[] = $obj->rowid;
1798 } elseif ($mode == 'label') {
1799 $cats[] = $obj->label;
1800 } else {
1801 $cat = new Categorie($this->db);
1802 $cat->id = $obj->rowid;
1803 $cat->label = $obj->label;
1804 $cats[] = $cat;
1805 }
1806 }
1807 } else {
1808 dol_print_error($this->db);
1809 return -1;
1810 }
1811 } else {
1812 $sql = "SELECT ct.fk_categorie, c.label, c.rowid";
1813 $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";
1814 $sql .= " WHERE ct.fk_categorie = c.rowid AND ct.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".(int) $id;
1815 // This seems useless because the table already contains id of category of 1 unique type. So commented.
1816 // So now it works also with external added categories.
1817 //$sql .= " AND c.type = ".((int) $this->MAP_ID[$type]);
1818 $sql .= " AND c.entity IN (".getEntity('category').")";
1819
1820 $res = $this->db->query($sql);
1821 if ($res) {
1822 while ($obj = $this->db->fetch_object($res)) {
1823 if ($mode == 'id') {
1824 $cats[] = $obj->rowid;
1825 } elseif ($mode == 'label') {
1826 $cats[] = $obj->label;
1827 } else {
1828 $cat = new Categorie($this->db);
1829 $cat->fetch($obj->fk_categorie);
1830 $cats[] = $cat;
1831 }
1832 }
1833 } else {
1834 dol_print_error($this->db);
1835 return -1;
1836 }
1837 }
1838
1839 return $cats;
1840 }
1841
1853 public function rechercher($id, $nom, $type, $exact = false, $case = false)
1854 {
1855 // Deprecation warning
1856 if (is_numeric($type)) {
1857 dol_syslog(__METHOD__.': using numeric types is deprecated.', LOG_WARNING);
1858 }
1859
1860 $cats = array();
1861
1862 // For backward compatibility
1863 if (is_numeric($type)) {
1864 // We want to reverse lookup
1865 $map_type = array_flip($this->MAP_ID);
1866 $type = $map_type[$type];
1867 dol_syslog(get_class($this)."::rechercher(): numeric types are deprecated, please use string instead", LOG_WARNING);
1868 }
1869
1870 // Generation requete recherche
1871 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1872 $sql .= " WHERE type = ".((int) $this->MAP_ID[$type]);
1873 $sql .= " AND entity IN (".getEntity('category').")";
1874 if ($nom) {
1875 if (!$exact) {
1876 $nom = '%'.$this->db->escape(str_replace('*', '%', $nom)).'%';
1877 }
1878 if (!$case) {
1879 $sql .= " AND label LIKE '".$this->db->escape($nom)."'";
1880 } else {
1881 $sql .= " AND label LIKE BINARY '".$this->db->escape($nom)."'";
1882 }
1883 }
1884 if ($id) {
1885 $sql .= " AND rowid = ".((int) $id);
1886 }
1887
1888 $res = $this->db->query($sql);
1889 if ($res) {
1890 while ($rec = $this->db->fetch_array($res)) {
1891 $cat = new Categorie($this->db);
1892 $cat->fetch($rec['rowid']);
1893 $cats[] = $cat;
1894 }
1895
1896 return $cats;
1897 } else {
1898 $this->error = $this->db->error().' sql='.$sql;
1899 return -1;
1900 }
1901 }
1902
1909 public function isAnyPhotoAvailable($sdir)
1910 {
1911 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1912 include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
1913
1914 $sdir .= '/' . get_exdir($this->id, 2, 0, 0, $this, 'category') . $this->id . "/photos/";
1915
1916 $dir_osencoded = dol_osencode($sdir);
1917 if (file_exists($dir_osencoded)) {
1918 $handle = opendir($dir_osencoded);
1919 if (is_resource($handle)) {
1920 while (($file = readdir($handle)) !== false) {
1921 if (!utf8_check($file)) {
1922 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1923 }
1924 if (dol_is_file($sdir . $file) && image_format_supported($file) >= 0) {
1925 return true;
1926 }
1927 }
1928 }
1929 }
1930 return false;
1931 }
1932
1939 public function getTooltipContentArray($params)
1940 {
1941 global $langs;
1942
1943 $langs->load('categories');
1944
1945 $datas = [];
1946
1947 $datas['label'] = $langs->trans("ShowCategory").': '.($this->ref ? $this->ref : $this->label);
1948
1949 return $datas;
1950 }
1951
1965 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $moreparam = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = 0)
1966 {
1967 global $conf, $langs, $hookmanager;
1968
1969 if (!empty($conf->dol_no_mouse_hover)) {
1970 $notooltip = 1; // Force disable tooltips
1971 }
1972
1973 $result = '';
1974 $params = [
1975 'id' => $this->id,
1976 'objecttype' => $this->element,
1977 'option' => $option,
1978 ];
1979 $classfortooltip = 'classfortooltip';
1980 $dataparams = '';
1981 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1982 $classfortooltip = 'classforajaxtooltip';
1983 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1984 $label = '';
1985 } else {
1986 $label = implode($this->getTooltipContentArray($params));
1987 }
1988
1989 $url = DOL_URL_ROOT.'/categories/viewcat.php?id='.$this->id.'&type='.$this->type.$moreparam.'&backtopage='.urlencode($_SERVER['PHP_SELF'].($moreparam ? '?'.$moreparam : ''));
1990
1991 if ($option !== 'nolink') {
1992 // Add param to save lastsearch_values or not
1993 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1994 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1995 $add_save_lastsearch_values = 1;
1996 }
1997 if (/* $url && */ $add_save_lastsearch_values) {
1998 $url .= '&save_lastsearch_values=1';
1999 }
2000 }
2001
2002 // Check contrast with background and correct text color
2003 $forced_color = 'categtextwhite'; // We want color white because the getNomUrl of a tag is always called inside a dark background like '<span color="bbb"></span>' to show it as a tag. TODO Add this in param to force when called outside of span.
2004 if ($this->color) {
2005 if (colorIsLight($this->color)) { // If color is light, we force color to dark
2006 $forced_color = 'categtextblack';
2007 }
2008 }
2009
2010 $linkclose = '';
2011 if (empty($notooltip)) {
2012 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2013 $label = $langs->trans("ShowCategory");
2014 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2015 }
2016 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2017 $linkclose .= $dataparams.' class="'.$classfortooltip.' '.$forced_color.($morecss ? ' '.$morecss : '').'"';
2018 } else {
2019 $linkclose = ' class="'.$forced_color.($morecss ? ' '.$morecss : '').'"';
2020 }
2021
2022 if ($option == 'nolink' /* || empty($url) */) {
2023 $linkstart = '<span';
2024 } else {
2025 $linkstart = '<a href="'.$url.'"';
2026 }
2027 $linkstart .= $linkclose.'>';
2028 if ($option == 'nolink' /* || empty($url) */) {
2029 $linkend = '</span>';
2030 } else {
2031 $linkend = '</a>';
2032 }
2033
2034 $result .= $linkstart;
2035
2036 if ($withpicto) {
2037 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2038 }
2039
2040 if ($withpicto != 2) {
2041 $result .= dol_trunc(($this->ref ? $this->ref : $this->label), $maxlength);
2042 }
2043
2044 $result .= $linkend;
2045
2046 global $action;
2047 $hookmanager->initHooks(array($this->element . 'dao'));
2048 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2049 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2050 if ($reshook > 0) {
2051 $result = $hookmanager->resPrint;
2052 } else {
2053 $result .= $hookmanager->resPrint;
2054 }
2055 return $result;
2056 }
2057
2058
2059 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2067 public function add_photo($sdir, $file)
2068 {
2069 // phpcs:enable
2070 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2071
2072 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, 'category').$this->id."/";
2073 $dir .= "photos/";
2074
2075 if (!file_exists($dir)) {
2076 dol_mkdir($dir);
2077 }
2078
2079 if (file_exists($dir)) {
2080 if (is_array($file['name'])) {
2081 $nbfile = count($file['name']);
2082 for ($i = 0; $i < $nbfile; $i++) {
2083 $originImage = $dir.$file['name'][$i];
2084
2085 // Cree fichier en taille origine
2086 dol_move_uploaded_file($file['tmp_name'][$i], $originImage, 1, 0, 0);
2087
2088 if (file_exists($originImage)) {
2089 // Create thumbs
2090 $this->addThumbs($originImage);
2091 }
2092 }
2093 } else {
2094 $originImage = $dir.$file['name'];
2095
2096 // Cree fichier en taille origine
2097 dol_move_uploaded_file($file['tmp_name'], $originImage, 1, 0, 0);
2098
2099 if (file_exists($originImage)) {
2100 // Create thumbs
2101 $this->addThumbs($originImage);
2102 }
2103 }
2104 }
2105 }
2106
2107 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2115 public function liste_photos($dir, $nbmax = 0)
2116 {
2117 // phpcs:enable
2118 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2119
2120 $nbphoto = 0;
2121 $tabobj = array();
2122
2123 $dirthumb = $dir.'thumbs/';
2124
2125 if (file_exists($dir)) {
2126 $handle = opendir($dir);
2127 if (is_resource($handle)) {
2128 while (($file = readdir($handle)) !== false) {
2129 if (dol_is_file($dir.$file) && preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $dir.$file)) {
2130 $nbphoto++;
2131 $photo = $file;
2132
2133 // On determine nom du fichier vignette
2134 $photo_vignette = '';
2135 $regs = array();
2136 if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $photo, $regs)) {
2137 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
2138 }
2139
2140 // Object
2141 $obj = array();
2142 $obj['photo'] = $photo;
2143 if ($photo_vignette && is_file($dirthumb.$photo_vignette)) {
2144 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
2145 } else {
2146 $obj['photo_vignette'] = "";
2147 }
2148
2149 $tabobj[$nbphoto - 1] = $obj;
2150
2151 // On continue ou on arrete de boucler
2152 if ($nbmax && $nbphoto >= $nbmax) {
2153 break;
2154 }
2155 }
2156 }
2157
2158 closedir($handle);
2159 }
2160 }
2161
2162 return $tabobj;
2163 }
2164
2165 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2172 public function delete_photo($file)
2173 {
2174 // phpcs:enable
2175 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2176
2177 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
2178 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
2179 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
2180
2181 // On efface l'image d'origine
2182 dol_delete_file($file, 0); // do not use disableglob, ecmfiles will not be deleted
2183
2184 // Si elle existe, on efface la vignette
2185 $regs = array();
2186 if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $filename, $regs)) {
2187 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
2188 if (file_exists($dirthumb.$photo_vignette)) {
2189 dol_delete_file($dirthumb.$photo_vignette, 1);
2190 }
2191 }
2192 }
2193
2194 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2201 public function get_image_size($file)
2202 {
2203 // phpcs:enable
2204 $infoImg = getimagesize($file); // Recuperation des infos de l'image
2205 $this->imgWidth = $infoImg[0]; // Largeur de l'image
2206 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
2207 }
2208
2217 public function setMultiLangs(User $user, $notrigger = 0)
2218 {
2219 global $langs;
2220
2221 $langs_available = $langs->get_available_languages();
2222 $current_lang = $langs->getDefaultLang();
2223
2224 foreach ($langs_available as $key => $value) {
2225 $sql = "SELECT rowid";
2226 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_lang";
2227 $sql .= " WHERE fk_category=".((int) $this->id);
2228 $sql .= " AND lang = '".$this->db->escape($key)."'";
2229
2230 $result = $this->db->query($sql);
2231
2232 if ($key == $current_lang) {
2233 $sql2 = '';
2234 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2235 $sql2 = "UPDATE ".MAIN_DB_PREFIX."categorie_lang";
2236 $sql2 .= " SET label = '".$this->db->escape($this->label)."',";
2237 $sql2 .= " description = '".$this->db->escape($this->description)."'";
2238 $sql2 .= " WHERE fk_category = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2239 } else { // if no line in database
2240 $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."categorie_lang (fk_category, lang, label, description)";
2241 $sql2 .= " VALUES(".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($this->label)."'";
2242 $sql2 .= ", '".$this->db->escape($this->description)."')";
2243 }
2244 dol_syslog(get_class($this).'::setMultiLangs', LOG_DEBUG);
2245 if (!$this->db->query($sql2)) {
2246 $this->error = $this->db->lasterror();
2247 return -1;
2248 }
2249 $this->multilangs[$key]["label"] = $this->label;
2250 $this->multilangs[$key]["description"] = $this->description;
2251 } elseif (isset($this->multilangs[$key])) { // if there is already a description line for this language
2252 if ($this->db->num_rows($result)) {
2253 $sql2 = "UPDATE ".MAIN_DB_PREFIX."categorie_lang";
2254 $sql2 .= " SET label='".$this->db->escape($this->multilangs[$key]["label"])."',";
2255 $sql2 .= " description='".$this->db->escape($this->multilangs[$key]["description"])."'";
2256 $sql2 .= " WHERE fk_category=".((int) $this->id)." AND lang='".$this->db->escape($key)."'";
2257 } else { // if no line in database
2258 $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."categorie_lang (fk_category, lang, label, description)";
2259 $sql2 .= " VALUES(".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($this->multilangs[$key]["label"])."'";
2260 $sql2 .= ",'".$this->db->escape($this->multilangs[$key]["description"])."')";
2261 }
2262
2263 // We do not save if main fields are empty
2264 if ($this->multilangs[$key]["label"] || $this->multilangs[$key]["description"]) {
2265 dol_syslog(get_class($this).'::setMultiLangs', LOG_DEBUG);
2266 }
2267 if (!$this->db->query($sql2)) {
2268 $this->error = $this->db->lasterror();
2269 return -1;
2270 }
2271 }
2272 }
2273
2274 // Call trigger
2275 if (!$notrigger) {
2276 $result = $this->call_trigger('CATEGORY_SET_MULTILANGS', $user);
2277 if ($result < 0) {
2278 $this->error = $this->db->lasterror();
2279 return -1;
2280 }
2281 }
2282 // End call triggers
2283
2284 return 1;
2285 }
2286
2296 public function delMultiLangs($langtodelete, $user, $notrigger = 0)
2297 {
2298 $sql = "DELETE FROM ".$this->db->prefix()."categorie_lang";
2299 $sql .= " WHERE fk_category = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2300
2301 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2302 $result = $this->db->query($sql);
2303 if ($result) {
2304 if (empty($notrigger)) {
2305 // Call trigger
2306 $result = $this->call_trigger('CATEGORY_DEL_MULTILANGS', $user);
2307 if ($result < 0) {
2308 $this->error = $this->db->lasterror();
2309 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2310 return -1;
2311 }
2312 // End call triggers
2313 }
2314 return 1;
2315 } else {
2316 $this->error = $this->db->lasterror();
2317 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2318 return -1;
2319 }
2320 }
2321
2327 public function getMultiLangs()
2328 {
2329 global $langs;
2330
2331 $current_lang = $langs->getDefaultLang();
2332
2333 $sql = "SELECT lang, label, description";
2334 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_lang";
2335 $sql .= " WHERE fk_category=".((int) $this->id);
2336
2337 $result = $this->db->query($sql);
2338 if ($result) {
2339 while ($obj = $this->db->fetch_object($result)) {
2340 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2341 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2342 $this->label = $obj->label;
2343 $this->description = $obj->description;
2344 }
2345 $this->multilangs[$obj->lang]["label"] = $obj->label;
2346 $this->multilangs[$obj->lang]["description"] = $obj->description;
2347 }
2348 return 1;
2349 } else {
2350 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
2351 return -1;
2352 }
2353 }
2354
2361 public function getLibStatut($mode)
2362 {
2363 return '';
2364 }
2365
2366
2374 public function initAsSpecimen()
2375 {
2376 dol_syslog(get_class($this)."::initAsSpecimen");
2377
2378 // Initialise parameters
2379 $this->id = 0;
2380 $this->fk_parent = 0;
2381 $this->label = 'SPECIMEN';
2382 $this->specimen = 1;
2383 $this->description = 'This is a description';
2384 $this->socid = 1;
2385 $this->type = self::TYPE_PRODUCT;
2386
2387 return 1;
2388 }
2389
2398 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2399 {
2400 $tables = array(
2401 'categorie_societe'
2402 );
2403
2404 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables, 1);
2405 }
2406
2415 public static function getFilterJoinQuery($type, $rowIdName)
2416 {
2417 if ($type == 'bank_account') {
2418 $type = 'account';
2419 }
2420
2421 return " LEFT JOIN ".MAIN_DB_PREFIX."categorie_".$type." as cp ON ".$rowIdName." = cp.fk_".$type;
2422 }
2423
2433 public static function getFilterSelectQuery($type, $rowIdName, $searchList)
2434 {
2435 if ($type == 'bank_account') {
2436 $type = 'account';
2437 }
2438 if ($type == 'customer') {
2439 $type = 'societe';
2440 }
2441 if ($type == 'supplier') {
2442 $type = 'fournisseur';
2443 }
2444
2445 if (empty($searchList) && !is_array($searchList)) {
2446 return "";
2447 }
2448
2449 $searchCategorySqlList = array();
2450 foreach ($searchList as $searchCategory) {
2451 if (intval($searchCategory) == -2) {
2452 $searchCategorySqlList[] = " cp.fk_categorie IS NULL";
2453 } elseif (intval($searchCategory) > 0) {
2454 $searchCategorySqlList[] = " ".$rowIdName." IN (SELECT fk_".$type." FROM ".MAIN_DB_PREFIX."categorie_".$type." WHERE fk_categorie = ".((int) $searchCategory).")";
2455 }
2456 }
2457
2458 if (!empty($searchCategorySqlList)) {
2459 return " AND (".implode(' AND ', $searchCategorySqlList).")";
2460 } else {
2461 return "";
2462 }
2463 }
2464
2470 public function countNbOfCategories()
2471 {
2472 dol_syslog(get_class($this)."::count_all_categories", LOG_DEBUG);
2473 $sql = "SELECT COUNT(rowid) FROM ".MAIN_DB_PREFIX."categorie";
2474 $sql .= " WHERE entity IN (".getEntity('category').")";
2475
2476 $res = $this->db->query($sql);
2477 if ($res) {
2478 $obj = $this->db->fetch_object($res);
2479 return $obj->count;
2480 } else {
2481 dol_print_error($this->db);
2482 return -1;
2483 }
2484 }
2485}
print $object position
Definition edit.php:206
$object ref
Definition info.php:90
Class to manage categories.
getListForItem($id, $type='customer', $sortfield="s.rowid", $sortorder='ASC', $limit=0, $page=0)
Return the list of the categories of a given element (a product, a customer, ...).
get_image_size($file)
Load size of image file.
load_motherof()
Load the array this->motherof that is array(id_son=>id_parent, ...), so array of all child categories...
getLibStatut($mode)
Return label of contact status.
delete_photo($file)
Efface la photo de la categorie et sa vignette.
containing($id, $type, $mode='object')
Return list of categories (object instances or labels) linked to a given object having id $id and typ...
getTooltipContentArray($params)
getTooltipContentArray
countNbOfCategories()
Count all categories.
del_type($obj, $type)
Delete object from category.
buildPathFromId($id_categ, $protection=1000)
For category id_categ and its children available in this->cats, define property fullpath and fulllabe...
static getFilterSelectQuery($type, $rowIdName, $searchList)
Return the additional SQL SELECT query for filtering a list by a category.
create($user, $notrigger=0)
Add category into database.
rechercher($id, $nom, $type, $exact=false, $case=false)
Returns categories whose id or name matches.
getMultiLangs()
Load array this->multilangs.
isAnyPhotoAvailable($sdir)
Return if at least one photo is available.
already_exists()
Check if a category with same label already exists for this cat's parent or root and for this cat's t...
get_filles()
Return direct children ids of a category into an array.
initAsSpecimen()
Initialise an instance with random values.
get_all_categories($type=null, $parent=false)
Returns all categories.
static getFilterJoinQuery($type, $rowIdName)
Return the additional SQL JOIN query for filtering a list by a category.
containsObject($type, $object_id)
Check for the presence of a given object in the current category.
get_main_categories($type=null)
Returns the first level categories (which are not child)
getMapId()
Get MAP_ID.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
setMultiLangs(User $user, $notrigger=0)
Create or Update translations of categories labels.
getMapList()
Get map list.
$fields
'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter...
get_meres()
Returns an array containing the list of parent categories Note: A category can only have one parent b...
get_full_arbo($type, $fromid=0, $include=0, $forcelangcode='')
Rebuilding the category tree as an array Return an array of table('id','id_mere',....
add_type($obj, $type='')
Link an object to the category.
print_all_ways($sep='auto', $url='', $nocolor=0, $addpicto=0, $notrunc=0)
Returns the path of the category, with the names of the categories separated by $sep (" >> " by defau...
update(User $user, $notrigger=0)
Update category.
liste_photos($dir, $nbmax=0)
Return an array with all photos inside the directory.
get_all_ways()
Returns in a array all possible paths to go to the category starting with the major categories repres...
getNomUrl($withpicto=0, $option='', $maxlength=0, $moreparam='', $notooltip=0, $morecss='', $save_lastsearch_value=0)
Return name and link of category (with picto) Use ->id, ->ref, ->label, ->color.
delMultiLangs($langtodelete, $user, $notrigger=0)
Delete a language for this category.
add_photo($sdir, $file)
Add the image uploaded as $file to the directory $sdir/<category>-<id>/photos/.
fetch($id, $label='', $type=null, $ref_ext='')
Load category into memory from database.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
addThumbs($file, $quality=50)
Build thumb.
Class to manage Dolibarr database access.
Class to manage Dolibarr users.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_is_file($pathoffile)
Return if path is a file.
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $keyforsourcefile='addedfile', $upload_dir='', $mode=0)
Check validity of a file upload from an GUI page, and move it to its final destination.
dol_now($mode='gmt')
Return date for now.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
colorIsLight($stringcolor)
Return true if the color is light.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
isModEnabled($module)
Is Dolibarr module enabled.
utf8_check($str)
Check if a string is in UTF8.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:137