dolibarr 23.0.4
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-2025 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-2025 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
76
80 public $picto = 'category';
81
82
86 public $MAP_ID = array(
87 'product' => 0,
88 'service' => 0,
89 'supplier' => 1,
90 'customer' => 2,
91 'member' => 3,
92 'contact' => 4,
93 'bank_account' => 5,
94 'project' => 6,
95 'user' => 7,
96 'bank_line' => 8,
97 'warehouse' => 9,
98 'actioncomm' => 10,
99 'website_page' => 11,
100 'ticket' => 12,
101 'knowledgemanagement' => 13,
102 'fichinter' => 14,
103 'order' => 16,
104 'invoice' => 17,
105 'supplier_order' => 20,
106 'supplier_invoice' => 21,
107 'supplier_proposal' => 22,
108 'propal' => 23,
109 'project_task' => 24,
110 );
111
117 /*
118 public static $MAP_ID_TO_CODE = array(
119 0 => 'product',
120 1 => 'supplier',
121 2 => 'customer',
122 3 => 'member',
123 4 => 'contact',
124 5 => 'bank_account',
125 6 => 'project',
126 7 => 'user',
127 8 => 'bank_line',
128 9 => 'warehouse',
129 10 => 'actioncomm',
130 11 => 'website_page',
131 12 => 'ticket',
132 13 => 'knowledgemanagement',
133 14 => 'fichinter',
134 16 => 'order',
135 17 => 'invoice',
136 20 => 'supplier_order',
137 21 => 'supplier_invoice',
138 22 => 'supplier_proposal',
139 23 => 'propal',
140 24 => 'project_task'
141 );
142 */
143
147 public $MAP_CAT_FK = array(
148 'customer' => 'soc',
149 'supplier' => 'soc',
150 'contact' => 'socpeople',
151 'bank_account' => 'account',
152 );
153
157 public $MAP_CAT_TABLE = array(
158 'customer' => 'societe',
159 'supplier' => 'fournisseur',
160 'bank_account' => 'account',
161 );
162
166 public $MAP_OBJ_CLASS = array(
167 'product' => 'Product',
168 'service' => 'Product',
169 'customer' => 'Societe',
170 'supplier' => 'Fournisseur',
171 'member' => 'Adherent',
172 'contact' => 'Contact',
173 'user' => 'User',
174 'account' => 'Account', // old for bank account
175 'bank_account' => 'Account',
176 'project' => 'Project',
177 'warehouse' => 'Entrepot',
178 'actioncomm' => 'ActionComm',
179 'website_page' => 'WebsitePage',
180 'ticket' => 'Ticket',
181 'knowledgemanagement' => 'KnowledgeRecord',
182 'fichinter' => 'Fichinter',
183 'order' => 'Commande',
184 'invoice' => 'Facture',
185 'supplier_order' => 'CommandeFournisseur',
186 'supplier_invoice' => 'FactureFournisseur',
187 'supplier_proposal' => 'SupplierProposal',
188 'propal' => 'Propal',
189 'project_task' => 'Task',
190 );
191
195 public static $MAP_TYPE_TITLE_AREA = array(
196 'product' => 'Products',
197 'service' => 'Services',
198 'customer' => 'ProspectsOrCustomers',
199 'supplier' => 'Suppliers',
200 'member' => 'Members',
201 'contact' => 'Contacts',
202 'user' => 'Users',
203 'account' => 'Accounts', // old for bank account
204 'bank_account' => 'BankAccounts',
205 'bank_line' => 'BankTransactions',
206 'project' => 'Projects',
207 'warehouse' => 'Warehouse',
208 'actioncomm' => 'AgendaEvents',
209 'website_page' => 'WebsitePages',
210 'ticket' => 'Tickets',
211 'knowledgemanagement' => 'KnowledgeRecords',
212 'fichinter' => 'Interventions',
213 'order' => 'Orders',
214 'invoice' => 'Invoices',
215 'supplier_order' => 'SuppliersOrders',
216 'supplier_invoice' => 'SuppliersInvoices',
217 'propal' => 'Proposals',
218 'supplier_proposal' => 'SupplierProposals',
219 'project_task' => 'Tasks'
220 );
221
226 public $MAP_OBJ_TABLE = array(
227 'customer' => 'societe',
228 'supplier' => 'societe',
229 'member' => 'adherent',
230 'contact' => 'socpeople',
231 'account' => 'bank_account', // old for bank account
232 'project' => 'projet',
233 'warehouse' => 'entrepot',
234 'knowledgemanagement' => 'knowledgemanagement_knowledgerecord',
235 'fichinter' => 'fichinter',
236 'order' => 'commande',
237 'invoice' => 'facture',
238 'supplier_order' => 'commande_fournisseur',
239 'supplier_invoice' => 'facture_fourn',
240 'project_task' => 'projet_task'
241 );
242
246 public $element = 'category';
247
251 public $table_element = 'categorie';
252
256 public $fk_parent;
257
261 public $label;
262
266 public $description;
267
271 public $color;
272
276 public $position;
277
281 public $visible;
282
286 public $socid;
287
311 public $type;
312
316 public $cats = array();
317
321 public $motherof = array();
322
326 public $childs = array();
327
331 public $multilangs = array();
332
336 public $imgWidth;
337
341 public $imgHeight;
342
367 public $fields = array(
368 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'position' => 10, 'notnull' => 1, 'visible' => 0,),
369 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
370 'fk_parent' => array('type' => 'integer', 'label' => 'ParentCategory', 'enabled' => 1, 'position' => 20, 'notnull' => 1, 'visible' => 0, 'css' => 'maxwidth500 widthcentpercentminusxx',),
371 '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),
372 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'position' => 30, 'notnull' => 0, 'visible' => 0, 'alwayseditable' => 1,),
373 'type' => array('type' => 'integer', 'label' => 'Type', 'enabled' => 1, 'position' => 35, 'notnull' => 1, 'visible' => 0, 'alwayseditable' => 1,),
374 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'position' => 40, 'notnull' => 0, 'visible' => 1, 'alwayseditable' => 1,),
375 'color' => array('type' => 'varchar(8)', 'label' => 'Color', 'enabled' => 1, 'position' => 45, 'notnull' => 0, 'visible' => 1, 'alwayseditable' => 1,),
376 'position' => array('type' => 'integer', 'label' => 'Position', 'enabled' => 1, 'position' => 50, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,),
377 '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',),
378 'visible' => array('type' => 'integer', 'label' => 'Visible', 'enabled' => 1, 'position' => 60, 'notnull' => 1, 'visible' => 0, 'alwayseditable' => 1,),
379 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'position' => 900, 'notnull' => 0, 'visible' => -2, 'alwayseditable' => 1,),
380 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'position' => 70, 'notnull' => 0, 'visible' => -1, 'alwayseditable' => 1,),
381 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'position' => 75, 'notnull' => 1, 'visible' => -1, 'alwayseditable' => 1,),
382 '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',),
383 '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',),
384 );
385
391 public function __construct($db)
392 {
393 global $hookmanager;
394
395 $this->db = $db;
396
397 $this->ismultientitymanaged = 1;
398 $this->isextrafieldmanaged = 1;
399
400 if (is_object($hookmanager)) {
401 $hookmanager->initHooks(array('category'));
402 $parameters = array();
403 $reshook = $hookmanager->executeHooks('constructCategory', $parameters, $this); // Note that $action and $object may have been modified by some hooks
404 if ($reshook >= 0 && !empty($hookmanager->resArray)) {
405 foreach ($hookmanager->resArray as $mapList) {
407 $mapId = $mapList['id'];
408 $mapCode = $mapList['code'];
409 //self::$MAP_ID_TO_CODE[$mapId] = $mapCode;
410 $this->MAP_ID[$mapCode] = $mapId;
411 $this->MAP_CAT_FK[$mapCode] = isset($mapList['cat_fk']) ? $mapList['cat_fk'] : null;
412 $this->MAP_CAT_TABLE[$mapCode] = isset($mapList['cat_table']) ? $mapList['cat_table'] : null;
413 $this->MAP_OBJ_CLASS[$mapCode] = $mapList['obj_class'];
414 $this->MAP_OBJ_TABLE[$mapCode] = $mapList['obj_table'];
415 self::$MAP_TYPE_TITLE_AREA[$mapCode] = isset($mapList['label']) ? $mapList['label'] : null;
416 }
417 }
418 }
419 }
420
426 public function getMapList()
427 {
428 $mapList = array();
429
430 foreach ($this->MAP_ID as $mapCode => $mapId) {
431 $mapList[] = array(
432 'id' => $mapId,
433 'code' => $mapCode,
434 'cat_fk' => (empty($this->MAP_CAT_FK[$mapCode]) ? $mapCode : $this->MAP_CAT_FK[$mapCode]),
435 'cat_table' => (empty($this->MAP_CAT_TABLE[$mapCode]) ? $mapCode : $this->MAP_CAT_TABLE[$mapCode]),
436 'obj_class' => (empty($this->MAP_OBJ_CLASS[$mapCode]) ? $mapCode : $this->MAP_OBJ_CLASS[$mapCode]),
437 'obj_table' => (empty($this->MAP_OBJ_TABLE[$mapCode]) ? $mapCode : $this->MAP_OBJ_TABLE[$mapCode]),
438 );
439 }
440
441 return $mapList;
442 }
443
449 public function getMapId()
450 {
451 return $this->MAP_ID;
452 }
453
463 public function fetch($id, $label = '', $type = null, $ref_ext = '')
464 {
465 // Check parameters
466 if (empty($id) && empty($label) && empty($ref_ext)) {
467 $this->error = "No category to search for";
468 return -1;
469 }
470 if (!is_null($type) && !is_numeric($type)) {
471 $type = $this->MAP_ID[$type];
472 }
473
474 $sql = "SELECT rowid, fk_parent, entity, label, description, color, position, fk_soc, visible, type, ref_ext";
475 $sql .= ", date_creation, tms, fk_user_creat, fk_user_modif, import_key";
476 $sql .= " FROM ".MAIN_DB_PREFIX."categorie";
477 if ($id) {
478 $sql .= " WHERE rowid = ".((int) $id);
479 } elseif (!empty($ref_ext)) {
480 $sql .= " WHERE ref_ext LIKE '".$this->db->escape($ref_ext)."'";
481 } else {
482 $sql .= " WHERE label = '".$this->db->escape($label)."' AND entity IN (".getEntity('category').")";
483 if (!is_null($type)) {
484 $sql .= " AND type = ".((int) $type);
485 }
486 }
487
488 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
489 $resql = $this->db->query($sql);
490 if ($resql) {
491 if ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
492 $this->id = $res['rowid'];
493 //$this->ref = $res['rowid'];
494 $this->fk_parent = (int) $res['fk_parent'];
495 $this->label = $res['label'];
496 $this->description = $res['description'];
497 $this->color = $res['color'];
498 $this->position = $res['position'];
499 $this->socid = (int) $res['fk_soc'];
500 $this->visible = (int) $res['visible'];
501 $this->type = $res['type'];
502 $this->ref_ext = $res['ref_ext'];
503 $this->entity = (int) $res['entity'];
504 $this->date_creation = $this->db->jdate($res['date_creation']);
505 $this->date_modification = $this->db->jdate($res['tms']);
506 $this->user_creation_id = (int) $res['fk_user_creat'];
507 $this->user_modification_id = (int) $res['fk_user_modif'];
508 $this->import_key = $res['import_key'];
509
510 // Retrieve all extrafields
511 // fetch optionals attributes and labels
512 $this->fetch_optionals();
513
514 $this->db->free($resql);
515
516 // multilangs
517 if (getDolGlobalInt('MAIN_MULTILANGS')) {
518 $this->getMultiLangs();
519 }
520
521 return 1;
522 } else {
523 $this->error = "No category found";
524 return 0;
525 }
526 } else {
527 dol_print_error($this->db);
528 $this->error = $this->db->lasterror;
529 $this->errors[] = $this->db->lasterror;
530 return -1;
531 }
532 }
533
544 public function create($user, $notrigger = 0)
545 {
546 global $conf, $langs;
547 $langs->load('categories');
548
549 $type = $this->type;
550
551 if (!is_numeric($type)) {
552 $type = $this->MAP_ID[$type];
553 }
554
555 $error = 0;
556
557 dol_syslog(get_class($this).'::create', LOG_DEBUG);
558
559 // Clean parameters
560 $this->label = trim((string) $this->label);
561 $this->description = trim((string) $this->description);
562 $this->color = trim((string) $this->color);
563 $this->position = (int) $this->position;
564 if (isset($this->import_key)) {
565 $this->import_key = trim($this->import_key);
566 }
567 $this->ref_ext = trim((string) $this->ref_ext);
568 if (empty($this->visible)) {
569 $this->visible = 0;
570 }
571 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
572
573 if ($this->already_exists()) {
574 $this->error = $langs->trans("ImpossibleAddCat", $this->label);
575 $this->error .= " : ".$langs->trans("CategoryExistsAtSameLevel");
576 dol_syslog($this->error, LOG_WARNING);
577 return -4;
578 }
579
580 $this->db->begin();
581 $now = dol_now();
582 $sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie (";
583 $sql .= "fk_parent,";
584 $sql .= " label,";
585 $sql .= " description,";
586 $sql .= " color,";
587 $sql .= " position,";
588 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
589 $sql .= "fk_soc,";
590 }
591 $sql .= " visible,";
592 $sql .= " type,";
593 $sql .= " import_key,";
594 $sql .= " ref_ext,";
595 $sql .= " entity,";
596 $sql .= " date_creation,";
597 $sql .= " fk_user_creat";
598 $sql .= ") VALUES (";
599 $sql .= (int) $this->fk_parent.",";
600 $sql .= "'".$this->db->escape($this->label)."', ";
601 $sql .= "'".$this->db->escape($this->description)."', ";
602 $sql .= "'".$this->db->escape($this->color)."', ";
603 $sql .= (int) $this->position.",";
604 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
605 $sql .= ($this->socid > 0 ? $this->socid : 'null').", ";
606 }
607 $sql .= "'".$this->db->escape((string) $this->visible)."', ";
608 $sql .= ((int) $type).", ";
609 $sql .= (!empty($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : 'null').", ";
610 $sql .= (!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : 'null').", ";
611 $sql .= (int) $conf->entity.", ";
612 $sql .= "'".$this->db->idate($now)."', ";
613 $sql .= (int) $user->id;
614 $sql .= ")";
615
616 $res = $this->db->query($sql);
617 if ($res) {
618 $id = $this->db->last_insert_id(MAIN_DB_PREFIX."categorie");
619
620 if ($id > 0) {
621 $this->id = $id;
622
623 $action = 'create';
624
625 // Actions on extra fields
626 $result = $this->insertExtraFields();
627 if ($result < 0) {
628 $error++;
629 }
630
631 if (!$error && !$notrigger) {
632 // Call trigger
633 $result = $this->call_trigger('CATEGORY_CREATE', $user);
634 if ($result < 0) {
635 $error++;
636 }
637 // End call triggers
638 }
639
640 if (!$error) {
641 $this->db->commit();
642 return $id;
643 } else {
644 $this->db->rollback();
645 return -3;
646 }
647 } else {
648 $this->db->rollback();
649 return -2;
650 }
651 } else {
652 $this->error = $this->db->error();
653 $this->db->rollback();
654 return -1;
655 }
656 }
657
667 public function update(User $user, $notrigger = 0)
668 {
669 global $langs;
670
671 $error = 0;
672
673 // Clean parameters
674 $this->label = trim((string) $this->label);
675 $this->description = trim((string) $this->description);
676 $this->ref_ext = trim((string) $this->ref_ext);
677 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
678 $this->visible = ($this->visible != "" ? intval($this->visible) : 0);
679
680 if ($this->fk_parent > 0 && $this->fk_parent == $this->id) {
681 $langs->load('categories');
682 $this->error = $langs->trans("ErrorCategoryCannotBeItsOwnParent");
683 dol_syslog($this->error, LOG_WARNING);
684 return -1;
685 }
686 // Nor one of its descendants, which would detach both from the tree and make a loop: go up from the new parent
687 // (only when the parent changes, or when we do not know the previous parent)
688 if (empty($this->oldcopy) || $this->fk_parent != $this->oldcopy->fk_parent) {
689 $ancestorid = $this->fk_parent;
690 $protection = 1000;
691 while ($ancestorid > 0 && $protection-- > 0) {
692 $resql = $this->db->query("SELECT fk_parent FROM ".MAIN_DB_PREFIX."categorie WHERE rowid = ".((int) $ancestorid));
693 $obj = $resql ? $this->db->fetch_object($resql) : null;
694 $ancestorid = $obj ? (int) $obj->fk_parent : 0;
695 if ($ancestorid == $this->id) {
696 $langs->load('categories');
697 $this->error = $langs->trans("ErrorCategoryCannotBeMovedIntoItsDescendant");
698 dol_syslog($this->error, LOG_WARNING);
699 return -1;
700 }
701 }
702 }
703
704 if ($this->already_exists()) {
705 $this->error = $langs->trans("ImpossibleUpdateCat");
706 $this->error .= " : ".$langs->trans("CategoryExistsAtSameLevel");
707 return -1;
708 }
709
710 $this->db->begin();
711
712 $sql = "UPDATE ".MAIN_DB_PREFIX."categorie";
713 $sql .= " SET label = '".$this->db->escape($this->label)."',";
714 $sql .= " description = '".$this->db->escape($this->description)."',";
715 $sql .= " ref_ext = '".$this->db->escape($this->ref_ext)."',";
716 $sql .= " color = '".$this->db->escape($this->color)."'";
717 $sql .= ", position = ".(int) $this->position;
718 if (getDolGlobalString('CATEGORY_ASSIGNED_TO_A_CUSTOMER')) {
719 $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : 'null');
720 }
721 $sql .= ", visible = ".(int) $this->visible;
722 $sql .= ", fk_parent = ".(int) $this->fk_parent;
723 $sql .= ", fk_user_modif = ".(int) $user->id;
724 $sql .= " WHERE rowid = ".((int) $this->id);
725
726 dol_syslog(get_class($this)."::update", LOG_DEBUG);
727 if ($this->db->query($sql)) {
728 $action = 'update';
729
730 // Actions on extra fields
731 $result = $this->insertExtraFields();
732 if ($result < 0) {
733 $error++;
734 }
735
736 if (!$error && !$notrigger) {
737 // Call trigger
738 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
739 if ($result < 0) {
740 $error++;
741 }
742 // End call triggers
743 }
744
745 if (!$error) {
746 $this->db->commit();
747 return 1;
748 } else {
749 $this->db->rollback();
750 return -1;
751 }
752 } else {
753 $this->db->rollback();
754 dol_print_error($this->db);
755 return -1;
756 }
757 }
758
766 public function delete($user, $notrigger = 0)
767 {
768 $error = 0;
769
770 // Clean parameters
771 $this->fk_parent = ($this->fk_parent != "" ? intval($this->fk_parent) : 0);
772
773 dol_syslog(get_class($this)."::remove");
774
775 $this->db->begin();
776
777 if (/* !$error && */ !$notrigger) {
778 // Call trigger
779 $result = $this->call_trigger('CATEGORY_DELETE', $user);
780 if ($result < 0) {
781 $error++;
782 }
783 // End call triggers
784 }
785
786 /* FIX #1317 : Check for child category and move up 1 level*/
787 if (!$error) {
788 $sql = "UPDATE ".MAIN_DB_PREFIX."categorie";
789 $sql .= " SET fk_parent = ".((int) $this->fk_parent);
790 $sql .= " WHERE fk_parent = ".((int) $this->id);
791
792 if (!$this->db->query($sql)) {
793 $this->error = $this->db->lasterror();
794 $error++;
795 }
796 }
797
798 $arraydelete = array(
799 'categorie_account' => 'fk_categorie',
800 'categorie_actioncomm' => 'fk_categorie',
801 'categorie_contact' => 'fk_categorie',
802 'categorie_fournisseur' => 'fk_categorie',
803 'categorie_knowledgemanagement' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('knowledgemanagement')),
804 'categorie_fichinter' => array('field' => 'fk_categorie', 'enabled' => false ), // not yet fully implemented (wait 22) isModEnabled('intervention')),
805 'categorie_member' => 'fk_categorie',
806 'categorie_user' => 'fk_categorie',
807 'categorie_product' => 'fk_categorie',
808 'categorie_project' => 'fk_categorie',
809 'categorie_project_task' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('project')),
810 'categorie_societe' => 'fk_categorie',
811 'categorie_ticket' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('ticket')),
812 'categorie_warehouse' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('stock')),
813 'categorie_website_page' => array('field' => 'fk_categorie', 'enabled' => isModEnabled('website')),
814 'category_bankline' => 'fk_categ',
815 'categorie_lang' => 'fk_category',
816 'categorie' => 'rowid',
817 );
818 // The links of the category are in the table of its own type: make sure that table is purged even when it is
819 // missing from the list above or disabled there, otherwise its foreign key refuses to delete the category.
820 $typecode = is_numeric($this->type) ? array_search((int) $this->type, $this->MAP_ID) : $this->type;
821 if (!empty($typecode) && $typecode != 'bank_line') { // the links of bank lines are in category_bankline, already in the list
822 $linktable = 'categorie_'.(empty($this->MAP_CAT_TABLE[$typecode]) ? $typecode : $this->MAP_CAT_TABLE[$typecode]);
823 if (array_key_exists($linktable, $arraydelete)) {
824 $arraydelete[$linktable] = 'fk_categorie';
825 } else {
826 $arraydelete = array($linktable => 'fk_categorie') + $arraydelete;
827 }
828 }
829 foreach ($arraydelete as $key => $value) {
830 if (is_array($value)) {
831 if (empty($value['enabled'])) {
832 continue;
833 }
834 $value = $value['field'];
835 }
836 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$key;
837 $sql .= " WHERE ".$value." = ".((int) $this->id);
838 if (!$this->db->query($sql)) {
839 $this->errors[] = $this->db->lasterror();
840 dol_syslog("Error sql=".$sql." ".$this->error, LOG_ERR);
841 $error++;
842 }
843 }
844
845 // Removed extrafields
846 if (!$error) {
847 $result = $this->deleteExtraFields();
848 if ($result < 0) {
849 $error++;
850 dol_syslog(get_class($this)."::delete erreur ".$this->error, LOG_ERR);
851 }
852 }
853
854 if (!$error) {
855 $this->db->commit();
856 return 1;
857 } else {
858 $this->db->rollback();
859 return -1;
860 }
861 }
862
863 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
872 public function add_type($obj, $type = '')
873 {
874 // phpcs:enable
875 global $user;
876
877 $error = 0;
878
879 if ($this->id == -1) {
880 return -2;
881 }
882
883 if (empty($type)) {
884 $type = $obj->element;
885 }
886
887 dol_syslog(get_class($this).'::add_type', LOG_DEBUG);
888
889 $this->db->begin();
890
891 $sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
892 $sql .= " (fk_categorie, fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]).")";
893 $sql .= " VALUES (".((int) $this->id).", ".((int) $obj->id).")";
894
895 if ($this->db->query($sql)) {
896 if (getDolGlobalString('CATEGORIE_RECURSIV_ADD')) {
897 $sql = 'SELECT fk_parent FROM '.MAIN_DB_PREFIX.'categorie';
898 $sql .= " WHERE rowid = ".((int) $this->id);
899
900 dol_syslog(get_class($this)."::add_type", LOG_DEBUG);
901 $resql = $this->db->query($sql);
902 if ($resql) {
903 if ($this->db->num_rows($resql) > 0) {
904 $objparent = $this->db->fetch_object($resql);
905
906 if (!empty($objparent->fk_parent)) {
907 $cat = new Categorie($this->db);
908 $cat->id = $objparent->fk_parent;
909 if (!$cat->containsObject($type, $obj->id)) {
910 $result = $cat->add_type($obj, $type);
911 if ($result < 0) {
912 $this->error = $cat->error;
913 $error++;
914 }
915 }
916 }
917 }
918 } else {
919 $error++;
920 $this->error = $this->db->lasterror();
921 }
922
923 if ($error) {
924 $this->db->rollback();
925 return -1;
926 }
927 }
928
929 // Call trigger
930 $this->context = array('linkto' => $obj); // Save object we want to link category to into category instance to provide information to trigger
931 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
932 if ($result < 0) {
933 $error++;
934 }
935 // End call triggers
936
937 if (!$error) {
938 $this->db->commit();
939 return 1;
940 } else {
941 $this->db->rollback();
942 return -2;
943 }
944 } else {
945 $this->db->rollback();
946 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
947 $this->error = $this->db->lasterrno();
948 return -3;
949 } else {
950 $this->error = $this->db->lasterror();
951 }
952 return -1;
953 }
954 }
955
956 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
965 public function del_type($obj, $type)
966 {
967 // phpcs:enable
968 global $user;
969
970 $error = 0;
971
972 // For backward compatibility
973 if ($type == 'societe') {
974 $type = 'customer';
975 dol_syslog(get_class($this)."::del_type(): type 'societe' is deprecated, please use 'customer' instead", LOG_WARNING);
976 } elseif ($type == 'fournisseur') {
977 $type = 'supplier';
978 dol_syslog(get_class($this)."::del_type(): type 'fournisseur' is deprecated, please use 'supplier' instead", LOG_WARNING);
979 }
980
981 $this->db->begin();
982
983 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
984 $sql .= " WHERE fk_categorie = ".((int) $this->id);
985 $sql .= " AND fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".((int) $obj->id);
986
987 dol_syslog(get_class($this).'::del_type', LOG_DEBUG);
988 if ($this->db->query($sql)) {
989 // Call trigger
990 $this->context = array('unlinkoff' => $obj); // Save object we want to link category to into category instance to provide information to trigger
991 $result = $this->call_trigger('CATEGORY_MODIFY', $user);
992 if ($result < 0) {
993 $error++;
994 }
995 // End call triggers
996
997 if (!$error) {
998 $this->db->commit();
999 return 1;
1000 } else {
1001 $this->db->rollback();
1002 return -2;
1003 }
1004 } else {
1005 $this->db->rollback();
1006 $this->error = $this->db->lasterror();
1007 return -1;
1008 }
1009 }
1010
1029 public function getObjectsInCateg($type, $onlyids = 0, $limit = 0, $offset = 0, $sortfield = '', $sortorder = 'ASC', $filter = '', $filtermode = 'AND', $filterlang = '')
1030 {
1031 global $user;
1032
1033 if (empty($onlyids)) {
1034 dol_syslog("getObjectsInCateg: This method used with parameter onlyids=0 is deprecated. Try by using instead getListForItem().", LOG_WARNING);
1035 }
1036
1037 $objs = array();
1038
1039 $classnameforobj = $this->MAP_OBJ_CLASS[$type];
1040 if (!empty($classnameforobj) && class_exists($classnameforobj)) {
1041 $tmpobj = new $classnameforobj($this->db);
1044 $sql = "SELECT c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." as fk_object";
1045 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type])." as c";
1046 $sql .= ", ".MAIN_DB_PREFIX.(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])." as o";
1047 if (!empty($filterlang)) {
1048 $sql .= ", ".MAIN_DB_PREFIX.(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])."_lang as ol";
1049 }
1050 $sql .= " WHERE o.entity IN (".getEntity($tmpobj->element).")";
1051 $sql .= " AND c.fk_categorie = ".((int) $this->id);
1052 // Compatibility with actioncomm table which has id instead of rowid
1053 if ((array_key_exists($type, $this->MAP_OBJ_TABLE) && $this->MAP_OBJ_TABLE[$type] == "actioncomm") || $type == "actioncomm") {
1054 $sql .= " AND c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = o.id";
1055 } else {
1056 $sql .= " AND c.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = o.rowid";
1057 }
1058 if (!empty($filterlang)) {
1059 $sql .= " AND ol.fk_".(empty($this->MAP_OBJ_TABLE[$type]) ? $type : $this->MAP_OBJ_TABLE[$type])." = o.rowid";
1060 $sql .= " AND ol.lang = '".$this->db->escape($filterlang)."'";
1061 }
1062 // Protection for external users
1063 if (($type == 'customer' || $type == 'supplier') && $user->socid > 0) {
1064 $sql .= " AND o.rowid = ".((int) $user->socid);
1065 }
1066
1067 $errormessage = '';
1068 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1069 if ($errormessage) {
1070 $this->errors[] = $errormessage;
1071 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1072 return -1;
1073 }
1074
1075 $sql .= $this->db->order($sortfield, $sortorder);
1076 if ($limit > 0 || $offset > 0) {
1077 $sql .= $this->db->plimit($limit + 1, $offset);
1078 }
1079
1080 dol_syslog(get_class($this)."::getObjectsInCateg", LOG_DEBUG);
1081
1082 $resql = $this->db->query($sql);
1083 if ($resql) {
1084 while ($rec = $this->db->fetch_array($resql)) {
1085 if ($onlyids) {
1086 $objs[] = $rec['fk_object'];
1087 } else {
1088 $tmpobj = new $classnameforobj($this->db);
1089 $tmpobj->fetch($rec['fk_object']); // The fetch will set $tmpobj->id only if it succeed.
1090 // @phpstan-ignore-next-line
1091 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.
1092 $objs[] = $tmpobj;
1093 }
1094 }
1095 }
1096 } else {
1097 $this->error = $this->db->error().' sql='.$sql;
1098 return -1;
1099 }
1100 }
1101
1102 return $objs;
1103 }
1104
1113 public function containsObject($type, $object_id)
1114 {
1115 $sql = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
1116 $sql .= " WHERE fk_categorie = ".((int) $this->id)." AND fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".((int) $object_id);
1117
1118 dol_syslog(get_class($this)."::containsObject", LOG_DEBUG);
1119
1120 $resql = $this->db->query($sql);
1121 if ($resql) {
1122 return $this->db->fetch_object($resql)->nb;
1123 } else {
1124 $this->error = $this->db->error();
1125 return -1;
1126 }
1127 }
1128
1141 public function getListForItem($id, $type = 'customer', $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
1142 {
1143 $categories = array();
1144
1145 $type = sanitizeVal($type, 'aZ09');
1146
1147 $sub_type = $type;
1148 $subcol_name = "fk_".$type;
1149 if ($type == "customer") {
1150 $sub_type = "societe";
1151 $subcol_name = "fk_soc";
1152 }
1153 if ($type == "supplier") {
1154 $sub_type = "fournisseur";
1155 $subcol_name = "fk_soc";
1156 }
1157 if ($type == "contact") {
1158 $subcol_name = "fk_socpeople";
1159 }
1160
1161 $idoftype = (int) (array_key_exists($type, $this->MAP_ID) ? $this->MAP_ID[$type] : -1);
1162
1163 $sql = "SELECT s.rowid";
1164 $sqlfields = $sql; // $sql fields to remove for count total
1165 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as s, ".MAIN_DB_PREFIX."categorie_".$this->db->sanitize($sub_type)." as sub";
1166 $sql .= ' WHERE s.entity IN ('.getEntity('category').')';
1167 $sql .= ' AND s.type = '.((int) $idoftype);
1168 $sql .= ' AND s.rowid = sub.fk_categorie';
1169 $sql .= " AND sub.".$this->db->sanitize($subcol_name)." = ".((int) $id);
1170
1171 $offset = 0;
1172 $nbtotalofrecords = '';
1173 if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) {
1174 $sqlforcount = preg_replace('/^'.preg_quote($sqlfields, '/').'/', 'SELECT COUNT(*) as nbtotalofrecords', $sql);
1175 $sqlforcount = preg_replace('/GROUP BY .*$/', '', $sqlforcount);
1176
1177 $resql = $this->db->query($sqlforcount);
1178 if ($resql) {
1179 $objforcount = $this->db->fetch_object($resql);
1180 $nbtotalofrecords = $objforcount->nbtotalofrecords;
1181 } else {
1182 dol_print_error($this->db);
1183 }
1184
1185 if (($limit >= (int) $nbtotalofrecords) && $page > 0) {
1186 return [];
1187 }
1188
1189 if (($page * $limit) >= (int) $nbtotalofrecords) { // if total resultset is smaller or equal then paging size (filtering), goto and load page 0
1190 $page = 0;
1191 $offset = 0;
1192 }
1193 $this->db->free($resql);
1194 }
1195
1196 $sql .= $this->db->order($sortfield, $sortorder);
1197 if ($limit) {
1198 if ($page < 0) {
1199 $page = 0;
1200 }
1201 $offset = $limit * $page;
1202
1203 $sql .= $this->db->plimit($limit + 1, $offset);
1204 }
1205
1206 $result = $this->db->query($sql);
1207 if ($result) {
1208 $i = 0;
1209 $num = $this->db->num_rows($result);
1210 $min = min($num, ($limit <= 0 ? $num : $limit));
1211 while ($i < $min) {
1212 $obj = $this->db->fetch_object($result);
1213 $category_static = new Categorie($this->db);
1214 if ($category_static->fetch($obj->rowid)) {
1215 $categories[$i]['id'] = $category_static->id;
1216 $categories[$i]['fk_parent'] = $category_static->fk_parent;
1217 $categories[$i]['label'] = $category_static->label;
1218 $categories[$i]['description'] = $category_static->description;
1219 $categories[$i]['color'] = $category_static->color;
1220 $categories[$i]['position'] = $category_static->position;
1221 $categories[$i]['socid'] = $category_static->socid;
1222 $categories[$i]['ref_ext'] = $category_static->ref_ext;
1223 $categories[$i]['visible'] = $category_static->visible;
1224 $categories[$i]['type'] = $category_static->type;
1225 $categories[$i]['entity'] = $category_static->entity;
1226 $categories[$i]['array_options'] = $category_static->array_options;
1227
1228 // multilangs
1229 if (getDolGlobalInt('MAIN_MULTILANGS') && isset($category_static->multilangs)) {
1230 $categories[$i]['multilangs'] = $category_static->multilangs;
1231 }
1232 }
1233 $i++;
1234 }
1235 } else {
1236 $this->error = $this->db->lasterror();
1237 return -1;
1238 }
1239 if (!count($categories)) {
1240 return [];
1241 }
1242
1243 return $categories;
1244 }
1245
1246 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1252 public function get_filles()
1253 {
1254 // phpcs:enable
1255 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1256 $sql .= " WHERE fk_parent = ".((int) $this->id);
1257 $sql .= " AND entity IN (".getEntity('category').")";
1258
1259 $res = $this->db->query($sql);
1260 if ($res) {
1261 $cats = array();
1262 while ($rec = $this->db->fetch_array($res)) {
1263 $cat = new Categorie($this->db);
1264 $cat->fetch($rec['rowid']);
1265 $cats[] = $cat;
1266 }
1267 return $cats;
1268 } else {
1269 dol_print_error($this->db);
1270 return -1;
1271 }
1272 }
1273
1274 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1281 protected function load_motherof()
1282 {
1283 // phpcs:enable
1284 $this->motherof = array();
1285
1286 // Load array[child]=parent
1287 $sql = "SELECT fk_parent as id_parent, rowid as id_son";
1288 $sql .= " FROM ".MAIN_DB_PREFIX."categorie";
1289 $sql .= " WHERE fk_parent <> 0";
1290 $sql .= " AND entity IN (".getEntity('category').")";
1291
1292 dol_syslog(get_class($this)."::load_motherof", LOG_DEBUG);
1293 $resql = $this->db->query($sql);
1294 if ($resql) {
1295 while ($obj = $this->db->fetch_object($resql)) {
1296 $this->motherof[$obj->id_son] = $obj->id_parent;
1297 }
1298 return 1;
1299 } else {
1300 dol_print_error($this->db);
1301 return -1;
1302 }
1303 }
1304
1305 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1326 public function get_full_arbo($type, $fromid = 0, $include = 0, $forcelangcode = '')
1327 {
1328 // phpcs:enable
1329 global $langs;
1330
1331 if (!is_numeric($type)) {
1332 $type = $this->MAP_ID[$type];
1333 }
1334 if (is_null($type)) {
1335 $this->error = 'BadValueForParameterType';
1336 return -1;
1337 }
1338
1339 if (is_string($fromid)) {
1340 $fromid = explode(',', $fromid);
1341 } elseif (is_numeric($fromid)) {
1342 if ($fromid > 0) {
1343 $fromid = array($fromid);
1344 } else {
1345 $fromid = array();
1346 }
1347 } elseif (!is_array($fromid)) {
1348 $fromid = array();
1349 }
1350
1351 $this->cats = array();
1352 $nbcateg = 0;
1353
1354 // Init this->motherof that is array(id_son=>id_parent, ...)
1355 $this->load_motherof();
1356
1357 if ($forcelangcode) {
1358 $current_lang = $forcelangcode;
1359 } else {
1360 $current_lang = $langs->getDefaultLang();
1361 }
1362
1363 // Init $this->cats array
1364 // Note: The DISTINCT reduces pb with old tables with duplicates but should not be used
1365 $sql = "SELECT DISTINCT c.rowid, c.label, c.ref_ext, c.description, c.color, c.position, c.fk_parent, c.visible";
1366 if (getDolGlobalInt('MAIN_MULTILANGS') && $current_lang !== 'none') {
1367 $sql .= ", t.label as label_trans, t.description as description_trans";
1368 }
1369 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as c";
1370 if (getDolGlobalInt('MAIN_MULTILANGS') && $current_lang !== 'none') {
1371 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."categorie_lang as t ON t.fk_category = c.rowid AND t.lang = '".$this->db->escape($current_lang)."'";
1372 }
1373 $sql .= " WHERE c.entity IN (".getEntity('category').")";
1374 $sql .= " AND c.type = ".(int) $type;
1375
1376 dol_syslog(get_class($this)."::get_full_arbo get category list", LOG_DEBUG);
1377
1378 $resql = $this->db->query($sql);
1379 if ($resql) {
1380 $i = 0;
1381 $nbcateg = $this->db->num_rows($resql);
1382
1383 while ($obj = $this->db->fetch_object($resql)) {
1384 $this->cats[(int) $obj->rowid]
1385 = array(
1386 'rowid' => (int) $obj->rowid,
1387 'id' => (int) $obj->rowid,
1388 'fk_parent' => (int) $obj->fk_parent,
1389 'label' => !empty($obj->label_trans) ? (string) $obj->label_trans : (string) $obj->label,
1390 'description' => !empty($obj->description_trans) ? (string) $obj->description_trans : (string) $obj->description,
1391 'color' => (string) $obj->color,
1392 'position' => (string) $obj->position,
1393 'visible' => (int) $obj->visible,
1394 'ref_ext' => (string) $obj->ref_ext,
1395 'picto' => 'category',
1396 // fields are filled with buildPathFromId later
1397 'fullpath' => '',
1398 'fulllabel' => '',
1399 'level' => null,
1400 );
1401 $i++;
1402 }
1403 } else {
1404 dol_print_error($this->db);
1405 return -1;
1406 }
1407
1408 // We add the fullpath property to each elements of first level (no parent exists)
1409 dol_syslog(get_class($this)."::get_full_arbo call to buildPathFromId", LOG_DEBUG);
1410
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) and adds level to $this->cats items
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]); // @phpstan-ignore-line
1432 }
1433 }
1434 }
1435
1436 dol_syslog(get_class($this)."::get_full_arbo dol_sort_array", LOG_DEBUG);
1437
1438 $this->cats = dol_sort_array($this->cats, 'fulllabel', 'asc', 1, 0, 1); // Sort on full label like "Label 1 >> Sublabel a >> Subsublabel"
1439
1440 return $this->cats;
1441 }
1442
1453 private function buildPathFromId($id_categ, $protection = 1000)
1454 {
1455 //dol_syslog(get_class($this)."::buildPathFromId id_categ=".$id_categ." protection=".$protection, LOG_DEBUG);
1456
1457 if (!empty($this->cats[$id_categ]['fullpath'])) {
1458 // Already defined
1459 dol_syslog(get_class($this)."::buildPathFromId fullpath and fulllabel already defined", LOG_WARNING);
1460 return -1;
1461 }
1462
1463 // $this->cats[$id_categ] is supposed to be already an array. We just want to complete it with property fullpath and fulllabel
1464
1465 // Define fullpath and fulllabel
1466 $this->cats[$id_categ]['fullpath'] = '_'.$id_categ;
1467 $this->cats[$id_categ]['fulllabel'] = $this->cats[$id_categ]['label'];
1468 $i = 0;
1469 $cursor_categ = $id_categ;
1470 //print 'Work for id_categ='.$id_categ.'<br>'."\n";
1471 while ((empty($protection) || $i < $protection) && !empty($this->motherof[$cursor_categ])) {
1472 //print '&nbsp; cursor_categ='.$cursor_categ.' i='.$i.' '.$this->motherof[$cursor_categ].'<br>'."\n";
1473 $this->cats[$id_categ]['fullpath'] = '_'.$this->motherof[$cursor_categ].$this->cats[$id_categ]['fullpath'];
1474 $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'];
1475 //print '&nbsp; Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].' '.$this->cats[$id_categ]['fulllabel'].'<br>'."\n";
1476 $i++;
1477 $cursor_categ = $this->motherof[$cursor_categ];
1478 }
1479 //print 'Result for id_categ='.$id_categ.' : '.$this->cats[$id_categ]['fullpath'].'<br>'."\n";
1480
1481 // We count number of _ to have level
1482 $nbunderscore = substr_count($this->cats[$id_categ]['fullpath'], '_');
1483 $this->cats[$id_categ]['level'] = ($nbunderscore ? $nbunderscore : null);
1484
1485 return 1;
1486 }
1487
1488
1489 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1497 public function get_all_categories($type = null, $parent = false)
1498 {
1499 // phpcs:enable
1500 if (!is_numeric($type) && !is_null($type)) {
1501 $type = $this->MAP_ID[$type];
1502 }
1503
1504 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1505 $sql .= " WHERE entity IN (".getEntity('category').")";
1506 if (!is_null($type)) {
1507 $sql .= " AND type = ".(int) $type;
1508 }
1509 if ($parent) {
1510 $sql .= " AND fk_parent = 0";
1511 }
1512
1513 $res = $this->db->query($sql);
1514 if ($res) {
1515 $cats = array();
1516 while ($rec = $this->db->fetch_array($res)) {
1517 $cat = new Categorie($this->db);
1518 $cat->fetch($rec['rowid']);
1519 $cats[$rec['rowid']] = $cat;
1520 }
1521 return $cats;
1522 } else {
1523 dol_print_error($this->db);
1524 return -1;
1525 }
1526 }
1527
1528 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1535 public function get_main_categories($type = null)
1536 {
1537 // phpcs:enable
1538 return $this->get_all_categories($type, true);
1539 }
1540
1541 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1547 public function already_exists()
1548 {
1549 // phpcs:enable
1550 $type = $this->type;
1551
1552 if (!is_numeric($type)) {
1553 $type = $this->MAP_ID[$type];
1554 }
1555
1556 /* We have to select any rowid from llx_categorie which category's mother and label
1557 * are equals to those of the calling category
1558 */
1559 $sql = "SELECT c.rowid";
1560 $sql .= " FROM ".MAIN_DB_PREFIX."categorie as c ";
1561 $sql .= " WHERE c.entity IN (".getEntity('category').")";
1562 $sql .= " AND c.type = ".((int) $type);
1563 $sql .= " AND c.fk_parent = ".((int) $this->fk_parent);
1564 $sql .= " AND c.label = '".$this->db->escape($this->label)."'";
1565
1566 dol_syslog(get_class($this)."::already_exists", LOG_DEBUG);
1567
1568 $resql = $this->db->query($sql);
1569 if ($resql) {
1570 if ($this->db->num_rows($resql) > 0) { // Checking for empty resql
1571 $obj = $this->db->fetch_object($resql);
1572 /* If object called create, obj cannot have is id.
1573 * If object called update, he mustn't have the same label as an other category for this mother.
1574 * 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.
1575 */
1576 if (!empty($obj) && $obj->rowid > 0 && $obj->rowid != $this->id) {
1577 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);
1578 return 1;
1579 }
1580 }
1581 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);
1582 return 0;
1583 } else {
1584 $this->error = $this->db->error();
1585 return -1;
1586 }
1587 }
1588
1589
1590 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1602 public function print_all_ways($sep = 'auto', $url = '', $nocolor = 0, $addpicto = 0, $notrunc = 0)
1603 {
1604 // phpcs:enable
1605 $ways = array();
1606
1607 if ($sep == 'auto') {
1608 $sep = '&gt;';
1609 }
1610
1611 $all_ways = $this->get_all_ways(); // Load array of categories to reach this->id
1612
1613 foreach ($all_ways as $way) { // It seems we always have 1 entry in this array.
1614 $w = array();
1615 $i = 0;
1616 $forced_color = '';
1617 foreach ($way as $cat) { // Loop on each successive categories to reach the target of current category
1618 $i++;
1619
1620 if (empty($nocolor)) {
1621 $forced_color = 'colortoreplace';
1622 if ($i == count($way)) { // Last category in hierarchy
1623 // Check contrast with background and correct text color
1624 $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.
1625 if ($cat->color) {
1626 if (colorIsLight($cat->color)) {
1627 $forced_color = 'categtextblack';
1628 }
1629 }
1630 }
1631 }
1632
1633 if ($url == '') {
1634 if (($i < count($way) && empty($notrunc)) && $i > 1) {
1635 $link = '';
1636 $linkend = '';
1637 } else {
1638 $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.'">';
1639 $linkend = '</a>';
1640 }
1641 $s = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '');
1642 $s .= (($i < count($way) && empty($notrunc)) ? ($i == 1 ? dol_trunc($cat->label, 3) : '') : $cat->label);
1643 $s .= $linkend;
1644 $w[] = $s;
1645 } elseif ($url == 'none') {
1646 if (($i < count($way) && empty($notrunc)) && $i > 1) {
1647 $link = '';
1648 $linkend = '';
1649 } else {
1650 $link = '<span class="valignmiddle '.($i < count($way) ? 'small ' : '').$forced_color.'">';
1651 $linkend = '</span>';
1652 }
1653 $s = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '');
1654 $s .= (($i < count($way) && empty($notrunc)) ? ($i == 1 ? dol_trunc($cat->label, 3) : '') : $cat->label);
1655 $s .= $linkend;
1656 $w[] = $s;
1657 } else {
1658 $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>';
1659 }
1660 }
1661 $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode('<span class="inline-block valignmiddle paddingleft paddingright small '.$forced_color.'">'.$sep.'</span>', $w));
1662
1663 $ways[] = $newcategwithpath;
1664 }
1665
1666 return $ways;
1667 }
1668
1669
1670 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1677 public function get_meres()
1678 {
1679 // phpcs:enable
1680 $parents = array();
1681
1682 $sql = "SELECT fk_parent FROM ".MAIN_DB_PREFIX."categorie";
1683 $sql .= " WHERE rowid = ".((int) $this->id);
1684
1685 $res = $this->db->query($sql);
1686
1687 if ($res) {
1688 while ($rec = $this->db->fetch_array($res)) {
1689 if ($rec['fk_parent'] > 0) {
1690 $cat = new Categorie($this->db);
1691 $cat->fetch($rec['fk_parent']);
1692 $parents[] = $cat;
1693 }
1694 }
1695 return $parents;
1696 } else {
1697 dol_print_error($this->db);
1698 return -1;
1699 }
1700 }
1701
1702 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1709 public function get_all_ways()
1710 {
1711 // phpcs:enable
1712 $ways = array();
1713
1714 $parents = $this->get_meres();
1715 if (is_array($parents)) {
1716 foreach ($parents as $parent) {
1717 $all_ways = $parent->get_all_ways(); // recursivity. TODO Add a protection for infinite loop
1718 foreach ($all_ways as $way) {
1719 $w = $way;
1720 $w[] = $this;
1721 $ways[] = $w;
1722 }
1723 }
1724 }
1725
1726 if (count($ways) == 0) {
1727 $ways[0][0] = $this;
1728 }
1729
1730 return $ways;
1731 }
1732
1743 public function containing($id, $type, $mode = 'object')
1744 {
1745 $cats = array();
1746
1747 if (is_numeric($type)) {
1748 $type = array_search($type, $this->MAP_ID);
1749 }
1750
1751 if ($type === Categorie::TYPE_BANK_LINE) { // TODO Remove this after migration of llx_category_bankline into llx_categorie_bankline
1752 // Load bank categories
1753 $sql = "SELECT c.label, c.rowid";
1754 $sql .= " FROM ".MAIN_DB_PREFIX."category_bankline as a, ".MAIN_DB_PREFIX."categorie as c";
1755 $sql .= " WHERE a.lineid=".((int) $id)." AND a.fk_categ = c.rowid";
1756 $sql .= " AND c.entity IN (".getEntity('category').")";
1757 $sql .= " ORDER BY c.label";
1758
1759 $res = $this->db->query($sql);
1760 if ($res) {
1761 while ($obj = $this->db->fetch_object($res)) {
1762 if ($mode == 'id') {
1763 $cats[] = $obj->rowid;
1764 } elseif ($mode == 'label') {
1765 $cats[] = $obj->label;
1766 } else {
1767 $cat = new Categorie($this->db);
1768 $cat->id = $obj->rowid;
1769 $cat->label = $obj->label;
1770 $cats[] = $cat;
1771 }
1772 }
1773 } else {
1774 dol_print_error($this->db);
1775 return -1;
1776 }
1777 } else {
1778 $sql = "SELECT ct.fk_categorie, c.label, c.rowid";
1779 $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";
1780 $sql .= " WHERE ct.fk_categorie = c.rowid AND ct.fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type])." = ".(int) $id;
1781 // This seems useless because the table already contains id of category of 1 unique type. So commented.
1782 // So now it works also with external added categories.
1783 //$sql .= " AND c.type = ".((int) $this->MAP_ID[$type]);
1784 $sql .= " AND c.entity IN (".getEntity('category').")";
1785
1786 $res = $this->db->query($sql);
1787 if ($res) {
1788 while ($obj = $this->db->fetch_object($res)) {
1789 if ($mode == 'id') {
1790 $cats[] = $obj->rowid;
1791 } elseif ($mode == 'label') {
1792 $cats[] = $obj->label;
1793 } else {
1794 $cat = new Categorie($this->db);
1795 $cat->fetch($obj->fk_categorie);
1796 $cats[] = $cat;
1797 }
1798 }
1799 } else {
1800 dol_print_error($this->db);
1801 return -1;
1802 }
1803 }
1804
1805 return $cats;
1806 }
1807
1819 public function rechercher($id, $nom, $type, $exact = false, $case = false)
1820 {
1821 // Deprecation warning
1822 if (is_numeric($type)) {
1823 dol_syslog(__METHOD__.': using numeric types is deprecated.', LOG_WARNING);
1824 }
1825
1826 $cats = array();
1827
1828 // For backward compatibility
1829 if (is_numeric($type)) {
1830 // We want to reverse lookup
1831 $map_type = array_flip($this->MAP_ID);
1832 $type = $map_type[$type];
1833 dol_syslog(get_class($this)."::rechercher(): numeric types are deprecated, please use string instead", LOG_WARNING);
1834 }
1835
1836 // Generation requete recherche
1837 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."categorie";
1838 $sql .= " WHERE type = ".((int) $this->MAP_ID[$type]);
1839 $sql .= " AND entity IN (".getEntity('category').")";
1840 if ($nom) {
1841 if (!$exact) {
1842 $nom = '%'.$this->db->escape(str_replace('*', '%', $nom)).'%';
1843 }
1844 if (!$case) {
1845 $sql .= " AND label LIKE '".$this->db->escape($nom)."'";
1846 } else {
1847 $sql .= " AND label LIKE BINARY '".$this->db->escape($nom)."'";
1848 }
1849 }
1850 if ($id) {
1851 $sql .= " AND rowid = ".((int) $id);
1852 }
1853
1854 $res = $this->db->query($sql);
1855 if ($res) {
1856 while ($rec = $this->db->fetch_array($res)) {
1857 $cat = new Categorie($this->db);
1858 $cat->fetch($rec['rowid']);
1859 $cats[] = $cat;
1860 }
1861
1862 return $cats;
1863 } else {
1864 $this->error = $this->db->error().' sql='.$sql;
1865 return -1;
1866 }
1867 }
1868
1875 public function isAnyPhotoAvailable($sdir)
1876 {
1877 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1878 include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
1879
1880 $sdir .= '/' . get_exdir($this->id, 2, 0, 0, $this, 'category') . $this->id . "/photos/";
1881
1882 $dir_osencoded = dol_osencode($sdir);
1883 if (file_exists($dir_osencoded)) {
1884 $handle = opendir($dir_osencoded);
1885 if (is_resource($handle)) {
1886 while (($file = readdir($handle)) !== false) {
1887 if (!utf8_check($file)) {
1888 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1889 }
1890 if (dol_is_file($sdir . $file) && image_format_supported($file) >= 0) {
1891 return true;
1892 }
1893 }
1894 }
1895 }
1896 return false;
1897 }
1898
1905 public function getTooltipContentArray($params)
1906 {
1907 global $langs;
1908
1909 $langs->load('categories');
1910
1911 $datas = [];
1912
1913 $datas['label'] = $langs->trans("ShowCategory").': '.($this->ref ? $this->ref : $this->label);
1914
1915 return $datas;
1916 }
1917
1931 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $moreparam = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = 0)
1932 {
1933 global $conf, $langs, $hookmanager;
1934
1935 if (!empty($conf->dol_no_mouse_hover)) {
1936 $notooltip = 1; // Force disable tooltips
1937 }
1938
1939 $result = '';
1940 $params = [
1941 'id' => $this->id,
1942 'objecttype' => $this->element,
1943 'option' => $option,
1944 ];
1945 $classfortooltip = 'classfortooltip';
1946 $dataparams = '';
1947 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1948 $classfortooltip = 'classforajaxtooltip';
1949 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1950 $label = '';
1951 } else {
1952 $label = implode($this->getTooltipContentArray($params));
1953 }
1954
1955 $url = DOL_URL_ROOT.'/categories/viewcat.php?id='.$this->id.'&type='.$this->type.$moreparam.'&backtopage='.urlencode($_SERVER['PHP_SELF'].($moreparam ? '?'.$moreparam : ''));
1956
1957 if ($option !== 'nolink') {
1958 // Add param to save lastsearch_values or not
1959 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1960 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1961 $add_save_lastsearch_values = 1;
1962 }
1963 if (/* $url && */ $add_save_lastsearch_values) {
1964 $url .= '&save_lastsearch_values=1';
1965 }
1966 }
1967
1968 // Check contrast with background and correct text color
1969 $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.
1970 if ($this->color) {
1971 if (colorIsLight($this->color)) { // If color is light, we force color to dark
1972 $forced_color = 'categtextblack';
1973 }
1974 }
1975
1976 $linkclose = '';
1977 if (empty($notooltip)) {
1978 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1979 $label = $langs->trans("ShowCategory");
1980 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1981 }
1982 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1983 $linkclose .= $dataparams.' class="'.$classfortooltip.' '.$forced_color.($morecss ? ' '.$morecss : '').'"';
1984 } else {
1985 $linkclose = ' class="'.$forced_color.($morecss ? ' '.$morecss : '').'"';
1986 }
1987
1988 if ($option == 'nolink' /* || empty($url) */) {
1989 $linkstart = '<span';
1990 } else {
1991 $linkstart = '<a href="'.$url.'"';
1992 }
1993 $linkstart .= $linkclose.'>';
1994 if ($option == 'nolink' /* || empty($url) */) {
1995 $linkend = '</span>';
1996 } else {
1997 $linkend = '</a>';
1998 }
1999
2000 $result .= $linkstart;
2001
2002 if ($withpicto) {
2003 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2004 }
2005
2006 if ($withpicto != 2) {
2007 $result .= dol_trunc(($this->ref ? $this->ref : $this->label), $maxlength);
2008 }
2009
2010 $result .= $linkend;
2011
2012 global $action;
2013 $hookmanager->initHooks(array($this->element . 'dao'));
2014 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2015 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2016 if ($reshook > 0) {
2017 $result = $hookmanager->resPrint;
2018 } else {
2019 $result .= $hookmanager->resPrint;
2020 }
2021 return $result;
2022 }
2023
2024
2025 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2033 public function add_photo($sdir, $file)
2034 {
2035 // phpcs:enable
2036 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2037
2038 $dir = $sdir.'/'.get_exdir($this->id, 2, 0, 0, $this, 'category').$this->id."/";
2039 $dir .= "photos/";
2040
2041 if (!file_exists($dir)) {
2042 dol_mkdir($dir);
2043 }
2044
2045 if (file_exists($dir)) {
2046 if (is_array($file['name'])) {
2047 $nbfile = count($file['name']);
2048 for ($i = 0; $i < $nbfile; $i++) {
2049 $originImage = $dir.$file['name'][$i];
2050
2051 // Cree fichier en taille origine
2052 dol_move_uploaded_file($file['tmp_name'][$i], $originImage, 1, 0, 0);
2053
2054 if (file_exists($originImage)) {
2055 // Create thumbs
2056 $this->addThumbs($originImage);
2057 }
2058 }
2059 } else {
2060 $originImage = $dir.$file['name'];
2061
2062 // Cree fichier en taille origine
2063 dol_move_uploaded_file($file['tmp_name'], $originImage, 1, 0, 0);
2064
2065 if (file_exists($originImage)) {
2066 // Create thumbs
2067 $this->addThumbs($originImage);
2068 }
2069 }
2070 }
2071 }
2072
2073 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2081 public function liste_photos($dir, $nbmax = 0)
2082 {
2083 // phpcs:enable
2084 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2085
2086 $nbphoto = 0;
2087 $tabobj = array();
2088
2089 $dirthumb = $dir.'thumbs/';
2090
2091 if (file_exists($dir)) {
2092 $handle = opendir($dir);
2093 if (is_resource($handle)) {
2094 while (($file = readdir($handle)) !== false) {
2095 if (dol_is_file($dir.$file) && preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $dir.$file)) {
2096 $nbphoto++;
2097 $photo = $file;
2098
2099 // On determine nom du fichier vignette
2100 $photo_vignette = '';
2101 $regs = array();
2102 if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $photo, $regs)) {
2103 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
2104 }
2105
2106 // Object
2107 $obj = array();
2108 $obj['photo'] = $photo;
2109 if ($photo_vignette && is_file($dirthumb.$photo_vignette)) {
2110 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
2111 } else {
2112 $obj['photo_vignette'] = "";
2113 }
2114
2115 $tabobj[$nbphoto - 1] = $obj;
2116
2117 // On continue ou on arrete de boucler
2118 if ($nbmax && $nbphoto >= $nbmax) {
2119 break;
2120 }
2121 }
2122 }
2123
2124 closedir($handle);
2125 }
2126 }
2127
2128 return $tabobj;
2129 }
2130
2131 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2138 public function delete_photo($file)
2139 {
2140 // phpcs:enable
2141 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2142
2143 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
2144 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
2145 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
2146
2147 // On efface l'image d'origine
2148 dol_delete_file($file, 0); // do not use disableglob, ecmfiles will not be deleted
2149
2150 // Si elle existe, on efface la vignette
2151 $regs = array();
2152 if (preg_match('/(\.jpeg|\.jpg|\.bmp|\.gif|\.png|\.tiff)$/i', $filename, $regs)) {
2153 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
2154 if (file_exists($dirthumb.$photo_vignette)) {
2155 dol_delete_file($dirthumb.$photo_vignette, 1);
2156 }
2157 }
2158 }
2159
2160 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2167 public function get_image_size($file)
2168 {
2169 // phpcs:enable
2170 $infoImg = getimagesize($file); // Recuperation des infos de l'image
2171 $this->imgWidth = $infoImg[0]; // Largeur de l'image
2172 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
2173 }
2174
2183 public function setMultiLangs(User $user, $notrigger = 0)
2184 {
2185 global $langs;
2186
2187 $langs_available = $langs->get_available_languages();
2188 $current_lang = $langs->getDefaultLang();
2189
2190 foreach ($langs_available as $key => $value) {
2191 $sql = "SELECT rowid";
2192 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_lang";
2193 $sql .= " WHERE fk_category=".((int) $this->id);
2194 $sql .= " AND lang = '".$this->db->escape($key)."'";
2195
2196 $result = $this->db->query($sql);
2197
2198 if ($key == $current_lang) {
2199 $sql2 = '';
2200 if ($this->db->num_rows($result)) { // if no line in database
2201 $sql2 = "UPDATE ".MAIN_DB_PREFIX."categorie_lang";
2202 $sql2 .= " SET label = '".$this->db->escape($this->label)."',";
2203 $sql2 .= " description = '".$this->db->escape($this->description)."'";
2204 $sql2 .= " WHERE fk_category = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2205 } elseif (isset($this->multilangs[$key])) {
2206 $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."categorie_lang (fk_category, lang, label, description)";
2207 $sql2 .= " VALUES(".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($this->label)."'";
2208 $sql2 .= ", '".$this->db->escape($this->multilangs[$key]["description"])."')";
2209 }
2210 dol_syslog(get_class($this).'::setMultiLangs', LOG_DEBUG);
2211 if ($sql2 && !$this->db->query($sql2)) {
2212 $this->error = $this->db->lasterror();
2213 return -1;
2214 }
2215 } elseif (isset($this->multilangs[$key])) {
2216 if ($this->db->num_rows($result)) { // if no line in database
2217 $sql2 = "UPDATE ".MAIN_DB_PREFIX."categorie_lang";
2218 $sql2 .= " SET label='".$this->db->escape($this->multilangs[$key]["label"])."',";
2219 $sql2 .= " description='".$this->db->escape($this->multilangs[$key]["description"])."'";
2220 $sql2 .= " WHERE fk_category=".((int) $this->id)." AND lang='".$this->db->escape($key)."'";
2221 } else {
2222 $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."categorie_lang (fk_category, lang, label, description)";
2223 $sql2 .= " VALUES(".((int) $this->id).", '".$this->db->escape($key)."', '".$this->db->escape($this->multilangs[$key]["label"])."'";
2224 $sql2 .= ",'".$this->db->escape($this->multilangs[$key]["description"])."')";
2225 }
2226
2227 // on ne sauvegarde pas des champs vides
2228 if ($this->multilangs[$key]["label"] || $this->multilangs[$key]["description"] || $this->multilangs[$key]["note"]) {
2229 dol_syslog(get_class($this).'::setMultiLangs', LOG_DEBUG);
2230 }
2231 if (!$this->db->query($sql2)) {
2232 $this->error = $this->db->lasterror();
2233 return -1;
2234 }
2235 }
2236 }
2237
2238 // Call trigger
2239 if (!$notrigger) {
2240 $result = $this->call_trigger('CATEGORY_SET_MULTILANGS', $user);
2241 if ($result < 0) {
2242 $this->error = $this->db->lasterror();
2243 return -1;
2244 }
2245 }
2246 // End call triggers
2247
2248 return 1;
2249 }
2250
2259 public function delMultiLangs($langtodelete, $user)
2260 {
2261 $sql = "DELETE FROM ".$this->db->prefix()."categorie_lang";
2262 $sql .= " WHERE fk_category = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2263
2264 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2265 $result = $this->db->query($sql);
2266 if ($result) {
2267 // Call trigger
2268 $result = $this->call_trigger('CATEGORY_DEL_MULTILANGS', $user);
2269 if ($result < 0) {
2270 $this->error = $this->db->lasterror();
2271 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2272 return -1;
2273 }
2274 // End call triggers
2275 return 1;
2276 } else {
2277 $this->error = $this->db->lasterror();
2278 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2279 return -1;
2280 }
2281 }
2282
2288 public function getMultiLangs()
2289 {
2290 global $langs;
2291
2292 $current_lang = $langs->getDefaultLang();
2293
2294 $sql = "SELECT lang, label, description";
2295 $sql .= " FROM ".MAIN_DB_PREFIX."categorie_lang";
2296 $sql .= " WHERE fk_category=".((int) $this->id);
2297
2298 $result = $this->db->query($sql);
2299 if ($result) {
2300 while ($obj = $this->db->fetch_object($result)) {
2301 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2302 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2303 $this->label = $obj->label;
2304 $this->description = $obj->description;
2305 }
2306 $this->multilangs[$obj->lang]["label"] = $obj->label;
2307 $this->multilangs[$obj->lang]["description"] = $obj->description;
2308 }
2309 return 1;
2310 } else {
2311 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
2312 return -1;
2313 }
2314 }
2315
2322 public function getLibStatut($mode)
2323 {
2324 return '';
2325 }
2326
2327
2335 public function initAsSpecimen()
2336 {
2337 dol_syslog(get_class($this)."::initAsSpecimen");
2338
2339 // Initialise parameters
2340 $this->id = 0;
2341 $this->fk_parent = 0;
2342 $this->label = 'SPECIMEN';
2343 $this->specimen = 1;
2344 $this->description = 'This is a description';
2345 $this->socid = 1;
2346 $this->type = self::TYPE_PRODUCT;
2347
2348 return 1;
2349 }
2350
2359 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2360 {
2361 $tables = array(
2362 'categorie_societe'
2363 );
2364
2365 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables, 1);
2366 }
2367
2376 public static function getFilterJoinQuery($type, $rowIdName)
2377 {
2378 if ($type == 'bank_account') {
2379 $type = 'account';
2380 }
2381
2382 return " LEFT JOIN ".MAIN_DB_PREFIX."categorie_".$type." as cp ON ".$rowIdName." = cp.fk_".$type;
2383 }
2384
2394 public static function getFilterSelectQuery($type, $rowIdName, $searchList)
2395 {
2396 if ($type == 'bank_account') {
2397 $type = 'account';
2398 }
2399 if ($type == 'customer') {
2400 $type = 'societe';
2401 }
2402 if ($type == 'supplier') {
2403 $type = 'fournisseur';
2404 }
2405
2406 if (empty($searchList) && !is_array($searchList)) {
2407 return "";
2408 }
2409
2410 $searchCategorySqlList = array();
2411 foreach ($searchList as $searchCategory) {
2412 if (intval($searchCategory) == -2) {
2413 $searchCategorySqlList[] = " cp.fk_categorie IS NULL";
2414 } elseif (intval($searchCategory) > 0) {
2415 $searchCategorySqlList[] = " ".$rowIdName." IN (SELECT fk_".$type." FROM ".MAIN_DB_PREFIX."categorie_".$type." WHERE fk_categorie = ".((int) $searchCategory).")";
2416 }
2417 }
2418
2419 if (!empty($searchCategorySqlList)) {
2420 return " AND (".implode(' AND ', $searchCategorySqlList).")";
2421 } else {
2422 return "";
2423 }
2424 }
2425
2431 public function countNbOfCategories()
2432 {
2433 dol_syslog(get_class($this)."::count_all_categories", LOG_DEBUG);
2434 $sql = "SELECT COUNT(rowid) FROM ".MAIN_DB_PREFIX."categorie";
2435 $sql .= " WHERE entity IN (".getEntity('category').")";
2436
2437 $res = $this->db->query($sql);
2438 if ($res) {
2439 $obj = $this->db->fetch_object($res);
2440 return $obj->count;
2441 } else {
2442 dol_print_error($this->db);
2443 return -1;
2444 }
2445 }
2446}
print $object position
Definition edit.php:207
$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.
delMultiLangs($langtodelete, $user)
Delete a language for this 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.
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.
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.
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)
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
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...
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.
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:132