dolibarr 25.0.0-alpha
contact.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
4 * Copyright (C) 2004-2013 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
6 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerker@telenet.be>
7 * Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr>
8 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
9 * Copyright (C) 2013 Alexandre Spangaro <aspangaro@open-dsi.fr>
10 * Copyright (C) 2013 Juanjo Menent <jmenent@2byte.es>
11 * Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
12 * Copyright (C) 2019 Nicolas ZABOURI <info@inovea-conseil.com>
13 * Copyright (C) 2020 Open-Dsi <support@open-dsi.fr>
14 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <https://www.gnu.org/licenses/>.
29 */
30
36require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
37require_once DOL_DOCUMENT_ROOT.'/core/class/commonsocialnetworks.class.php';
38require_once DOL_DOCUMENT_ROOT.'/core/class/commonpeople.class.php';
39
40
44class Contact extends CommonObject
45{
47 use CommonPeople;
48
53 public $TRIGGER_PREFIX = 'CONTACT';
54
62 public const MERGE_FIELDS_FILL_IF_EMPTY = array(
63 'civility_code', 'lastname', 'firstname', 'name_alias', 'address', 'zip', 'town',
64 'state_id', 'country_id', 'poste', 'phone_pro', 'phone_perso', 'phone_mobile', 'fax',
65 'email', 'socialnetworks', 'birthday', 'default_lang', 'ref_ext',
66 'fk_prospectlevel', 'stcomm_id', 'socid'
67 );
68
72 public const MERGE_FIELDS_CONCAT = array('note_public', 'note_private');
73
78 public const MERGE_MAX_PARENT_DEPTH = 100;
79
83 public $element = 'contact';
84
88 public $table_element = 'socpeople';
89
93 public $picto = 'contact';
94
119 // BEGIN MODULEBUILDER PROPERTIES
123 public $fields = array(
124 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'noteditable' => 1, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id', 'css' => 'left'),
125 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 30, 'index' => 1),
126 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 35),
127 'civility' => array('type' => 'varchar(6)', 'label' => 'Civility', 'enabled' => 1, 'visible' => 3, 'position' => 40),
128 'lastname' => array('type' => 'varchar(50)', 'label' => 'Lastname', 'enabled' => 1, 'visible' => 1, 'position' => 45, 'showoncombobox' => 1, 'searchall' => 1),
129 'name_alias' => array('type' => 'varchar(255)', 'label' => 'Name alias', 'enabled' => 1, 'visible' => -1, 'position' => 46, 'searchall' => 1),
130 'firstname' => array('type' => 'varchar(50)', 'label' => 'Firstname', 'enabled' => 1, 'visible' => 1, 'position' => 50, 'showoncombobox' => 1, 'searchall' => 1),
131 'poste' => array('type' => 'varchar(80)', 'label' => 'PostOrFunction', 'enabled' => 1, 'visible' => -1, 'position' => 52),
132 'address' => array('type' => 'varchar(255)', 'label' => 'Address', 'enabled' => 1, 'visible' => -1, 'position' => 55),
133 'zip' => array('type' => 'varchar(25)', 'label' => 'Zip', 'enabled' => 1, 'visible' => -1, 'position' => 60),
134 'town' => array('type' => 'varchar(50)', 'label' => 'Town', 'enabled' => 1, 'visible' => -1, 'position' => 65),
135 'fk_departement' => array('type' => 'integer', 'label' => 'Fk departement', 'enabled' => 1, 'visible' => 3, 'position' => 70),
136 'fk_pays' => array('type' => 'integer', 'label' => 'Fk pays', 'enabled' => 1, 'visible' => 3, 'position' => 75),
137 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 1, 'visible' => 1, 'position' => 77, 'searchall' => 1),
138 'birthday' => array('type' => 'date', 'label' => 'Birthday', 'enabled' => 1, 'visible' => -1, 'position' => 80),
139 'phone' => array('type' => 'varchar(30)', 'label' => 'Phone', 'enabled' => 1, 'visible' => 1, 'position' => 90, 'searchall' => 1),
140 'phone_perso' => array('type' => 'varchar(30)', 'label' => 'PhonePerso', 'enabled' => 1, 'visible' => -1, 'position' => 95, 'searchall' => 1),
141 'phone_mobile' => array('type' => 'varchar(30)', 'label' => 'PhoneMobile', 'enabled' => 1, 'visible' => 1, 'position' => 100, 'searchall' => 1),
142 'fax' => array('type' => 'varchar(30)', 'label' => 'Fax', 'enabled' => 1, 'visible' => -1, 'position' => 105, 'searchall' => 1),
143 'email' => array('type' => 'varchar(255)', 'label' => 'Email', 'enabled' => 1, 'visible' => 1, 'position' => 110, 'searchall' => 1),
144 'socialnetworks' => array('type' => 'text', 'label' => 'SocialNetworks', 'enabled' => 1, 'visible' => 3, 'position' => 115),
145 'photo' => array('type' => 'varchar(255)', 'label' => 'Photo', 'enabled' => 1, 'visible' => 3, 'position' => 170),
146 'priv' => array('type' => 'smallint(6)', 'label' => 'ContactVisibility', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 175),
147 'fk_stcommcontact' => array('type' => 'integer', 'label' => 'ProspectStatus', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 220),
148 'fk_prospectcontactlevel' => array('type' => 'varchar(12)', 'label' => 'ProspectLevel', 'enabled' => 1, 'visible' => -1, 'position' => 255),
149 //no more used. Replace by a scan of email into mailing_unsubscribe. 'no_email' =>array('type'=>'smallint(6)', 'label'=>'No_Email', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>180),
150 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 195, 'searchall' => 1),
151 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 200, 'searchall' => 1),
152 'default_lang' => array('type' => 'varchar(6)', 'label' => 'Default lang', 'enabled' => 1, 'visible' => 3, 'position' => 205),
153 'canvas' => array('type' => 'varchar(32)', 'label' => 'Canvas', 'enabled' => 1, 'visible' => 3, 'position' => 210),
154 'ip' => array('type' => 'ip', 'label' => 'IPAddress', 'enabled' => '1', 'position' => 700, 'notnull' => 0, 'visible' => '-2', 'comment' => 'ip used to create record (for public submission page)'),
155 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 300),
156 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 305),
157 'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 310),
158 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'position' => 315),
159 'statut' => array('type' => 'tinyint(4)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 500),
160 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 1000),
161 );
162
166 public $civility_id; // In fact we store civility_code
170 public $civility_code;
174 public $civility;
175
179 public $gender;
180
184 public $birthday_alert;
185
191 public $civilite;
192
196 public $fullname;
197
201 public $name_alias;
202
206 public $address;
207
211 public $zip;
212
216 public $town;
217
221 public $state_id;
222
226 public $state_code;
227
231 public $state;
232
236 public $poste;
237
241 public $socid; // both socid and fk_soc are used
245 public $fk_soc; // both socid and fk_soc are used
246
250 public $socname;
251
256 public $statut;
257
261 public $code;
262
267 public $email;
268
275 public $mail;
276
281 public $url;
282
288 public $no_email;
289
294 public $socialnetworks;
295
299 public $photo;
300
304 public $phone_pro;
305
309 public $phone_perso;
310
314 public $phone_mobile;
315
319 public $fax;
320
325 public $priv;
326
330 public $birthday;
331
335 public $default_lang;
336
340 public $ref_facturation;
341
345 public $ref_contrat;
346
350 public $ref_commande;
351
355 public $ref_propal;
356
360 public $user_id;
361
365 public $user_login;
366
370 public $ip;
371 // END MODULEBUILDER PROPERTIES
372
376 public $roles;
377
381 public $cacheprospectstatus = array();
382
386 public $fk_prospectlevel;
387
391 public $stcomm_id;
392
396 public $statut_commercial;
397
401 public $stcomm_picto;
402
403
409 public function __construct($db)
410 {
411 $this->db = $db;
412 $this->statut = 1; // By default, status is enabled
413 $this->status = 1; // By default, status is enabled
414 $this->ismultientitymanaged = 1;
415 $this->isextrafieldmanaged = 1;
416
417 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
418
419 if (!isModEnabled('mailing')) {
420 $this->fields['no_email']['enabled'] = 0;
421 }
422 // typical ['s.nom'] is used for third-parties
423 if (!getDolGlobalString('SOCIETE_DISABLE_CONTACTS')) {
424 $this->fields['fk_soc']['enabled'] = 0;
425 $this->fields['fk_soc']['searchall'] = 0;
426 }
427
428 // If THIRDPARTY_ENABLE_PROSPECTION_ON_ALTERNATIVE_ADRESSES not set, there is no prospect level on contact level, only on thirdparty
429 if (getDolGlobalString('SOCIETE_DISABLE_PROSPECTS') || !getDolGlobalString('THIRDPARTY_ENABLE_PROSPECTION_ON_ALTERNATIVE_ADRESSES')) { // Default behaviour
430 $this->fields['fk_stcommcontact']['enabled'] = 0;
431 $this->fields['fk_prospectcontactlevel']['enabled'] = 0;
432 }
433
434 // Unset fields that are disabled
435 foreach ($this->fields as $key => $val) {
436 if (isset($val['enabled']) && empty($val['enabled'])) {
437 unset($this->fields[$key]);
438 }
439 }
440
441 // Translate some data of arrayofkeyval
442 /*if (is_object($langs))
443 {
444 foreach($this->fields as $key => $val)
445 {
446 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval']))
447 {
448 foreach($val['arrayofkeyval'] as $key2 => $val2)
449 {
450 $this->fields[$key]['arrayofkeyval'][$key2]=$langs->trans($val2);
451 }
452 }
453 }
454 }*/
455 }
456
462 public function loadStateBoard()
463 {
464 global $user, $hookmanager;
465
466 $this->nb = array();
467 $clause = "WHERE";
468
469 $sql = "SELECT count(sp.rowid) as nb";
470 $sql .= " FROM ".MAIN_DB_PREFIX."socpeople as sp";
471 if (!$user->hasRight('societe', 'client', 'voir')) {
472 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
473 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
474 $sql .= " WHERE sp.fk_soc = s.rowid AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
475 $clause = "AND";
476 }
477 $sql .= " ".$clause." sp.entity IN (".getEntity($this->element).")";
478 $sql .= " AND (sp.priv='0' OR (sp.priv='1' AND sp.fk_user_creat = ".((int) $user->id)."))";
479 if ($user->socid > 0) {
480 $sql .= " AND sp.fk_soc = ".((int) $user->socid);
481 }
482 // Add where from hooks
483 if (is_object($hookmanager)) {
484 $parameters = array();
485 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
486 $sql .= $hookmanager->resPrint;
487 }
488
489 $resql = $this->db->query($sql);
490 if ($resql) {
491 while ($obj = $this->db->fetch_object($resql)) {
492 $this->nb["contacts"] = $obj->nb;
493 }
494 $this->db->free($resql);
495 return 1;
496 } else {
497 dol_print_error($this->db);
498 $this->error = $this->db->lasterror();
499 return -1;
500 }
501 }
502
510 public function create($user, $notrigger = 0)
511 {
512 global $conf;
513
514 $error = 0;
515 $now = dol_now();
516
517 if (empty($this->date_creation)) {
518 $this->date_creation = $now;
519 }
520
521 $this->db->begin();
522
523 // Clean parameters
524 $this->name_alias = trim($this->name_alias);
525 $this->lastname = $this->lastname ? trim($this->lastname) : trim($this->name);
526 $this->firstname = trim($this->firstname);
527 $this->setUpperOrLowerCase();
528 if (empty($this->socid)) {
529 $this->socid = 0;
530 }
531 if (empty($this->priv)) {
532 $this->priv = 0;
533 }
534 if (!empty($this->statut) && empty($this->status)) {
535 $this->status = 1;
536 }
537 if (empty($this->status)) {
538 $this->status = 0; // This is to convert '' into '0' to avoid bad sql request
539 $this->statut = 0; // This is to convert '' into '0' to avoid bad sql request
540 }
541
542 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
543 $this->entity = setEntity($this);
544
545 $sql = "INSERT INTO ".MAIN_DB_PREFIX."socpeople (";
546 $sql .= " datec";
547 $sql .= ", fk_soc";
548 $sql .= ", name_alias";
549 $sql .= ", lastname";
550 $sql .= ", firstname";
551 $sql .= ", fk_user_creat";
552 $sql .= ", priv";
553 $sql .= ", fk_stcommcontact";
554 $sql .= ", statut";
555 $sql .= ", canvas";
556 $sql .= ", entity";
557 $sql .= ", ref_ext";
558 $sql .= ", import_key";
559 $sql .= ", ip";
560 $sql .= ") VALUES (";
561 $sql .= "'".$this->db->idate($now)."',";
562 if ($this->socid > 0) {
563 $sql .= " ".((int) $this->socid).",";
564 } else {
565 $sql .= "null,";
566 }
567 $sql .= "'".$this->db->escape($this->name_alias)."',";
568 $sql .= "'".$this->db->escape($this->lastname)."',";
569 $sql .= "'".$this->db->escape($this->firstname)."',";
570 $sql .= " ".($user->id > 0 ? ((int) $user->id) : "null").",";
571 $sql .= " ".((int) $this->priv).",";
572 $sql .= " 0,";
573 $sql .= " ".((int) $this->status).",";
574 $sql .= " ".(!empty($this->canvas) ? "'".$this->db->escape($this->canvas)."'" : "null").",";
575 $sql .= " ".((int) $this->entity).",";
576 $sql .= "'".$this->db->escape($this->ref_ext)."',";
577 $sql .= " ".(!empty($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
578 $sql .= " ".(!empty($this->ip) ? "'".$this->db->escape($this->ip)."'" : "null");
579 $sql .= ")";
580
581 dol_syslog(get_class($this)."::create", LOG_DEBUG);
582 $resql = $this->db->query($sql);
583 if ($resql) {
584 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."socpeople");
585
586 $result = $this->update($this->id, $user, 1, 'add'); // This include updateRoles(), ...
587 if ($result < 0) {
588 $error++;
589 $this->error = $this->db->lasterror();
590 }
591
592 if (!$error) {
593 $result = $this->update_perso($this->id, $user, 1); // TODO Remove function update_perso, should be same than update
594 if ($result < 0) {
595 $error++;
596 $this->error = $this->db->lasterror();
597 }
598 }
599
600 if (!$error && !$notrigger) {
601 // Call trigger
602 $result = $this->call_trigger('CONTACT_CREATE', $user);
603 if ($result < 0) {
604 $error++;
605 }
606 // End call triggers
607 }
608
609 if (!$error) {
610 $this->db->commit();
611 return $this->id;
612 } else {
613 $this->db->rollback();
614 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
615 return -2;
616 }
617 } else {
618 $this->error = $this->db->lasterror();
619
620 $this->db->rollback();
621 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
622 return -1;
623 }
624 }
625
636 public function update($id, $user = null, $notrigger = 0, $action = 'update', $nosyncuser = 0)
637 {
638 global $conf;
639
640 if (empty($this->country_id) && !empty($this->country_code)) {
641 $country_id = getCountry($this->country_code, '3');
642 $this->country_id = is_int($country_id) ? $country_id : 0;
643 }
644
645 $error = 0;
646
647 $this->id = $id;
648
649 $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
650
651 // Clean parameters
652 $this->ref_ext = (empty($this->ref_ext) ? '' : trim($this->ref_ext));
653 $this->name_alias = trim($this->name_alias);
654 $this->lastname = trim($this->lastname) ? trim($this->lastname) : trim($this->lastname);
655 $this->firstname = trim($this->firstname);
656 $this->email = trim($this->email ?? '');
657 $this->phone_pro = trim($this->phone_pro);
658 $this->phone_perso = trim($this->phone_perso);
659 $this->phone_mobile = trim($this->phone_mobile);
660 $this->photo = trim($this->photo);
661 $this->fax = trim($this->fax);
662 $this->zip = (empty($this->zip) ? '' : trim($this->zip));
663 $this->town = (empty($this->town) ? '' : trim($this->town));
664 $this->country_id = (empty($this->country_id) || $this->country_id < 0) ? 0 : $this->country_id;
665 if (!empty($this->statut) && empty($this->status)) {
666 $this->status = 1;
667 }
668 if (empty($this->status)) {
669 $this->status = 0;
670 $this->statut = 0;
671 }
672 if (empty($this->civility_code) && !is_numeric($this->civility_id)) {
673 $this->civility_code = $this->civility_id; // For backward compatibility
674 }
675 $this->setUpperOrLowerCase();
676
677 $this->db->begin();
678
679 $sql = "UPDATE ".MAIN_DB_PREFIX."socpeople SET";
680 if ($this->socid > 0) {
681 $sql .= " fk_soc = ".((int) $this->socid).",";
682 } elseif ($this->socid == -1) {
683 $sql .= " fk_soc = NULL,";
684 }
685 $sql .= " civility='".$this->db->escape($this->civility_code)."'";
686 $sql .= ", name_alias='".$this->db->escape($this->name_alias)."'";
687 $sql .= ", lastname='".$this->db->escape($this->lastname)."'";
688 $sql .= ", firstname='".$this->db->escape($this->firstname)."'";
689 $sql .= ", address='".$this->db->escape((string) $this->address)."'";
690 $sql .= ", zip='".$this->db->escape($this->zip)."'";
691 $sql .= ", town='".$this->db->escape($this->town)."'";
692 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "NULL");
693 $sql .= ", fk_pays=".($this->country_id > 0 ? ((int) $this->country_id) : 'NULL');
694 $sql .= ", fk_departement=".($this->state_id > 0 ? ((int) $this->state_id) : 'NULL');
695 $sql .= ", poste='".$this->db->escape($this->poste)."'";
696 $sql .= ", fax='".$this->db->escape($this->fax)."'";
697 $sql .= ", email='".$this->db->escape($this->email)."'";
698 $sql .= ", socialnetworks = '".$this->db->escape(json_encode($this->socialnetworks))."'";
699 $sql .= ", photo='".$this->db->escape($this->photo)."'";
700 $sql .= ", birthday=".($this->birthday ? "'".$this->db->idate($this->birthday)."'" : "null");
701 $sql .= ", note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "NULL");
702 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "NULL");
703 $sql .= ", phone = ".(isset($this->phone_pro) ? "'".$this->db->escape($this->phone_pro)."'" : "NULL");
704 $sql .= ", phone_perso = ".(isset($this->phone_perso) ? "'".$this->db->escape($this->phone_perso)."'" : "NULL");
705 $sql .= ", phone_mobile = ".(isset($this->phone_mobile) ? "'".$this->db->escape($this->phone_mobile)."'" : "NULL");
706 $sql .= ", priv = ".((int) $this->priv);
707 $sql .= ", fk_prospectlevel = '".$this->db->escape($this->fk_prospectlevel)."'";
708 if (isset($this->stcomm_id)) {
709 $sql .= ", fk_stcommcontact = ".($this->stcomm_id > 0 || $this->stcomm_id == -1 ? ((int) $this->stcomm_id) : "0");
710 }
711 $sql .= ", statut = ".((int) $this->status);
712 $sql .= ", fk_user_modif=".($user->id > 0 ? "'".$this->db->escape((string) $user->id)."'" : "NULL");
713 $sql .= ", default_lang=".($this->default_lang ? "'".$this->db->escape($this->default_lang)."'" : "NULL");
714 $sql .= ", entity = ".((int) $this->entity);
715 $sql .= " WHERE rowid = ".((int) $id);
716
717 dol_syslog(get_class($this)."::update", LOG_DEBUG);
718 $result = $this->db->query($sql);
719 if ($result) {
720 unset($this->country_code);
721 unset($this->country);
722 unset($this->state_code);
723 unset($this->state);
724
725 $action = 'update';
726
727 // Actions on extra fields
728 $result = $this->insertExtraFields();
729 if ($result < 0) {
730 $error++;
731 }
732
733 if (!$error) {
734 $result = $this->updateRoles();
735 if ($result < 0) {
736 $error++;
737 }
738 }
739
740 if (!$error && $this->user_id > 0) {
741 // If contact is linked to a user
742 $tmpobj = new User($this->db);
743 $tmpobj->fetch($this->user_id);
744 $usermustbemodified = 0;
745 if ($tmpobj->office_phone != $this->phone_pro) {
746 $tmpobj->office_phone = $this->phone_pro;
747 $usermustbemodified++;
748 }
749 if ($tmpobj->office_fax != $this->fax) {
750 $tmpobj->office_fax = $this->fax;
751 $usermustbemodified++;
752 }
753 if ($tmpobj->address != $this->address) {
754 $tmpobj->address = $this->address;
755 $usermustbemodified++;
756 }
757 if ($tmpobj->town != $this->town) {
758 $tmpobj->town = $this->town;
759 $usermustbemodified++;
760 }
761 if ($tmpobj->zip != $this->zip) {
762 $tmpobj->zip = $this->zip;
763 $usermustbemodified++;
764 }
765 if ($tmpobj->zip != $this->zip) {
766 $tmpobj->state_id = $this->state_id;
767 $usermustbemodified++;
768 }
769 if ($tmpobj->country_id != $this->country_id) {
770 $tmpobj->country_id = $this->country_id;
771 $usermustbemodified++;
772 }
773 if ($tmpobj->email != $this->email) {
774 $tmpobj->email = $this->email;
775 $usermustbemodified++;
776 }
777 if (!empty(array_diff($tmpobj->socialnetworks, $this->socialnetworks))) {
778 $tmpobj->socialnetworks = $this->socialnetworks;
779 $usermustbemodified++;
780 }
781 if ($usermustbemodified) {
782 $result = $tmpobj->update($user, 0, 1, 1, 1);
783 if ($result < 0) {
784 $error++;
785 }
786 }
787 }
788
789 if (!$error && !$notrigger) {
790 // Call trigger
791 $result = $this->call_trigger('CONTACT_MODIFY', $user);
792 if ($result < 0) {
793 $error++;
794 }
795 // End call triggers
796 }
797
798 if (!$error) {
799 $this->db->commit();
800 return 1;
801 } else {
802 dol_syslog(get_class($this)."::update Error ".$this->error, LOG_ERR);
803 $this->db->rollback();
804 return -$error;
805 }
806 } else {
807 $this->error = $this->db->lasterror().' sql='.$sql;
808 $this->db->rollback();
809 return -1;
810 }
811 }
812
813
814 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
815 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
825 public function _load_ldap_dn($info, $mode = 0)
826 {
827 // phpcs:enable
828 global $conf;
829 $dn = '';
830 if ($mode == 0) {
831 $dn = getDolGlobalString('LDAP_KEY_CONTACTS') . "=".$info[getDolGlobalString('LDAP_KEY_CONTACTS')]."," . getDolGlobalString('LDAP_CONTACT_DN');
832 } elseif ($mode == 1) {
833 $dn = getDolGlobalString('LDAP_CONTACT_DN');
834 } elseif ($mode == 2) {
835 $dn = getDolGlobalString('LDAP_KEY_CONTACTS') . "=".$info[getDolGlobalString('LDAP_KEY_CONTACTS')];
836 }
837 return $dn;
838 }
839
840
841 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
848 public function _load_ldap_info()
849 {
850 // phpcs:enable
851 global $conf, $langs;
852
853 $info = array();
854
855 // Object classes
856 $info["objectclass"] = explode(',', getDolGlobalString('LDAP_CONTACT_OBJECT_CLASS'));
857
858 $this->fullname = $this->getFullName($langs);
859
860 // Fields
861 if ($this->fullname && getDolGlobalString('LDAP_CONTACT_FIELD_FULLNAME')) {
862 $info[getDolGlobalString('LDAP_CONTACT_FIELD_FULLNAME')] = $this->fullname;
863 }
864 if ($this->lastname && getDolGlobalString('LDAP_CONTACT_FIELD_NAME')) {
865 $info[getDolGlobalString('LDAP_CONTACT_FIELD_NAME')] = $this->lastname;
866 }
867 if ($this->firstname && getDolGlobalString('LDAP_CONTACT_FIELD_FIRSTNAME')) {
868 $info[getDolGlobalString('LDAP_CONTACT_FIELD_FIRSTNAME')] = $this->firstname;
869 }
870
871 if ($this->poste) {
872 $info["title"] = $this->poste;
873 }
874 if ($this->socid > 0) {
875 $soc = new Societe($this->db);
876 $soc->fetch($this->socid);
877
878 $info[getDolGlobalString('LDAP_CONTACT_FIELD_COMPANY')] = $soc->name;
879 if ($soc->client == 1) {
880 $info["businessCategory"] = "Customers";
881 }
882 if ($soc->client == 2) {
883 $info["businessCategory"] = "Prospects";
884 }
885 if ($soc->fournisseur == 1) {
886 $info["businessCategory"] = "Suppliers";
887 }
888 }
889 if ($this->address && getDolGlobalString('LDAP_CONTACT_FIELD_ADDRESS')) {
890 $info[getDolGlobalString('LDAP_CONTACT_FIELD_ADDRESS')] = $this->address;
891 }
892 if ($this->zip && getDolGlobalString('LDAP_CONTACT_FIELD_ZIP')) {
893 $info[getDolGlobalString('LDAP_CONTACT_FIELD_ZIP')] = $this->zip;
894 }
895 if ($this->town && getDolGlobalString('LDAP_CONTACT_FIELD_TOWN')) {
896 $info[getDolGlobalString('LDAP_CONTACT_FIELD_TOWN')] = $this->town;
897 }
898 if ($this->country_code && getDolGlobalString('LDAP_CONTACT_FIELD_COUNTRY')) {
899 $info[getDolGlobalString('LDAP_CONTACT_FIELD_COUNTRY')] = $this->country_code;
900 }
901 if ($this->phone_pro && getDolGlobalString('LDAP_CONTACT_FIELD_PHONE')) {
902 $info[getDolGlobalString('LDAP_CONTACT_FIELD_PHONE')] = $this->phone_pro;
903 }
904 if ($this->phone_perso && getDolGlobalString('LDAP_CONTACT_FIELD_HOMEPHONE')) {
905 $info[getDolGlobalString('LDAP_CONTACT_FIELD_HOMEPHONE')] = $this->phone_perso;
906 }
907 if ($this->phone_mobile && getDolGlobalString('LDAP_CONTACT_FIELD_MOBILE')) {
908 $info[getDolGlobalString('LDAP_CONTACT_FIELD_MOBILE')] = $this->phone_mobile;
909 }
910 if ($this->fax && getDolGlobalString('LDAP_CONTACT_FIELD_FAX')) {
911 $info[getDolGlobalString('LDAP_CONTACT_FIELD_FAX')] = $this->fax;
912 }
913 if ($this->note_private && getDolGlobalString('LDAP_CONTACT_FIELD_DESCRIPTION')) {
914 $info[getDolGlobalString('LDAP_CONTACT_FIELD_DESCRIPTION')] = dol_string_nohtmltag($this->note_private, 2);
915 }
916 if ($this->email && getDolGlobalString('LDAP_CONTACT_FIELD_MAIL')) {
917 $info[getDolGlobalString('LDAP_CONTACT_FIELD_MAIL')] = $this->email;
918 }
919
920 if (getDolGlobalString('LDAP_SERVER_TYPE') == 'egroupware') {
921 $info["objectclass"][4] = "phpgwContact"; // compatibilite egroupware
922
923 $info['uidnumber'] = $this->id;
924
925 $info['phpgwTz'] = 0;
926 $info['phpgwMailType'] = 'INTERNET';
927 $info['phpgwMailHomeType'] = 'INTERNET';
928
929 $info["phpgwContactTypeId"] = 'n';
930 $info["phpgwContactCatId"] = 0;
931 $info["phpgwContactAccess"] = "public";
932
933 $info["phpgwContactOwner"] = 1;
934
935 if ($this->email) {
936 $info["rfc822Mailbox"] = $this->email;
937 }
938 if ($this->phone_mobile) {
939 $info["phpgwCellTelephoneNumber"] = $this->phone_mobile;
940 }
941 }
942
943 return $info;
944 }
945
946
947 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
956 public function update_perso($id, $user = null, $notrigger = 0)
957 {
958 // phpcs:enable
959 $error = 0;
960 $result = false;
961
962 $this->db->begin();
963
964 // Update the contact
965 $sql = "UPDATE ".MAIN_DB_PREFIX."socpeople SET";
966 $sql .= " birthday = ".($this->birthday ? "'".$this->db->idate($this->birthday)."'" : "null");
967 $sql .= ", photo = ".($this->photo ? "'".$this->db->escape($this->photo)."'" : "null");
968 if ($user) {
969 $sql .= ", fk_user_modif = ".((int) $user->id);
970 }
971 $sql .= " WHERE rowid = ".((int) $id);
972
973 dol_syslog(get_class($this)."::update_perso this->birthday=".$this->birthday." -", LOG_DEBUG);
974 $resql = $this->db->query($sql);
975 if (!$resql) {
976 $error++;
977 $this->error = $this->db->lasterror();
978 }
979
980 if ($user) {
981 // Update birthday alert
982 if (!empty($this->birthday_alert)) {
983 //check existing
984 $sql_check = "SELECT rowid FROM " . MAIN_DB_PREFIX . "user_alert WHERE type = 1 AND fk_contact = " . ((int) $id) . " AND fk_user = " . ((int) $user->id);
985 $result_check = $this->db->query($sql_check);
986 if (!$result_check || ($this->db->num_rows($result_check) < 1)) {
987 //insert
988 $sql = "INSERT INTO " . MAIN_DB_PREFIX . "user_alert(type, fk_contact, fk_user) ";
989 $sql .= "VALUES (1," . ((int) $id) . "," . ((int) $user->id) . ")";
990 $result = $this->db->query($sql);
991 if (!$result) {
992 $error++;
993 $this->error = $this->db->lasterror();
994 }
995 } else {
996 $result = true;
997 }
998 } else {
999 $sql = "DELETE FROM " . MAIN_DB_PREFIX . "user_alert ";
1000 $sql .= "WHERE type=1 AND fk_contact=" . ((int) $id) . " AND fk_user=" . ((int) $user->id);
1001 $result = $this->db->query($sql);
1002 if (!$result) {
1003 $error++;
1004 $this->error = $this->db->lasterror();
1005 }
1006 }
1007
1008 if (!$error && !$notrigger) {
1009 // Call trigger
1010 $result = $this->call_trigger('CONTACT_MODIFY', $user);
1011 if ($result < 0) {
1012 $error++;
1013 }
1014 // End call triggers
1015 }
1016 }
1017
1018 if (!$error) {
1019 $this->db->commit();
1020 return 1;
1021 } else {
1022 dol_syslog(get_class($this)."::update Error ".$this->error, LOG_ERR);
1023 $this->db->rollback();
1024 return -$error;
1025 }
1026 }
1027
1028
1040 public function fetch($id, $user = null, $ref_ext = '', $email = '', $loadalsoroles = 0, $socid = 0)
1041 {
1042 global $langs;
1043
1044 dol_syslog(get_class($this)."::fetch id=".$id." ref_ext=".$ref_ext." email=".$email, LOG_DEBUG);
1045
1046 if (empty($id) && empty($ref_ext) && empty($email)) {
1047 $this->error = 'BadParameter';
1048 return -1;
1049 }
1050
1051 $langs->loadLangs(array("dict", "companies"));
1052
1053 $sql = "SELECT c.rowid, c.entity, c.fk_soc, c.ref_ext, c.civility as civility_code, c.name_alias, c.lastname, c.firstname,";
1054 $sql .= " c.address, c.statut as status, c.zip, c.town,";
1055 $sql .= " c.fk_pays as country_id,";
1056 $sql .= " c.fk_departement as state_id,";
1057 $sql .= " c.birthday,";
1058 $sql .= " c.poste, c.phone, c.phone_perso, c.phone_mobile, c.fax, c.email,";
1059 $sql .= " c.socialnetworks,";
1060 $sql .= " c.photo,";
1061 $sql .= " c.priv, c.note_private, c.note_public, c.default_lang, c.canvas,";
1062 $sql .= " c.fk_prospectlevel, c.fk_stcommcontact, st.libelle as stcomm, st.picto as stcomm_picto,";
1063 $sql .= " c.import_key,";
1064 $sql .= " c.datec as date_creation, GREATEST(c.tms, cef.tms) as date_modification, c.fk_user_creat, c.fk_user_modif,";
1065 $sql .= " co.label as country, co.code as country_code,";
1066 $sql .= " d.nom as state, d.code_departement as state_code,";
1067 $sql .= " u.rowid as user_id, u.login as user_login,";
1068 $sql .= " s.nom as socname, s.address as socaddress, s.zip as soccp, s.town as soccity, s.default_lang as socdefault_lang";
1069 $sql .= " FROM ".MAIN_DB_PREFIX."socpeople as c";
1070 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople_extrafields as cef ON cef.fk_object=c.rowid";
1071 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_country as co ON c.fk_pays = co.rowid";
1072 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_departements as d ON c.fk_departement = d.rowid";
1073 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON c.rowid = u.fk_socpeople";
1074 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid";
1075 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_stcommcontact as st ON c.fk_stcommcontact = st.id';
1076 if ($id) {
1077 $sql .= " WHERE c.rowid = ".((int) $id);
1078 } else {
1079 $sql .= " WHERE c.entity IN (".getEntity($this->element).")";
1080 if ($ref_ext) {
1081 $sql .= " AND c.ref_ext = '".$this->db->escape($ref_ext)."'";
1082 }
1083 if ($email) {
1084 $sql .= " AND c.email = '".$this->db->escape($email)."'";
1085 }
1086 if ($socid) {
1087 $sql .= " AND c.fk_soc = ".((int) $socid);
1088 }
1089 }
1090
1091 $resql = $this->db->query($sql);
1092 if ($resql) {
1093 $num = $this->db->num_rows($resql);
1094 if ($num > 1) {
1095 $this->error = 'Fetch found several records. Rename one of contact to avoid duplicate.';
1096 dol_syslog($this->error, LOG_ERR);
1097
1098 return 2;
1099 } elseif ($num) { // $num = 1
1100 $obj = $this->db->fetch_object($resql);
1101
1102 $this->id = $obj->rowid;
1103 $this->entity = $obj->entity;
1104 $this->ref = $obj->rowid;
1105 $this->ref_ext = $obj->ref_ext;
1106
1107 $this->civility_code = $obj->civility_code;
1108 $this->civility = $obj->civility_code ? ($langs->trans("Civility".$obj->civility_code) != "Civility".$obj->civility_code ? $langs->trans("Civility".$obj->civility_code) : $obj->civility_code) : '';
1109
1110 $this->name_alias = $obj->name_alias;
1111 $this->lastname = $obj->lastname;
1112 $this->firstname = $obj->firstname;
1113 $this->address = $obj->address;
1114 $this->zip = $obj->zip;
1115 $this->town = $obj->town;
1116
1117 $this->date_creation = $this->db->jdate($obj->date_creation);
1118 $this->date_modification = $this->db->jdate($obj->date_modification);
1119 $this->user_creation_id = $obj->fk_user_creat;
1120 $this->user_modification_id = $obj->fk_user_modif;
1121
1122 $this->state_id = $obj->state_id;
1123 $this->state_code = $obj->state_code;
1124 $this->state = $obj->state;
1125
1126 $this->country_id = $obj->country_id;
1127 $this->country_code = $obj->country_id ? $obj->country_code : '';
1128 $this->country = $obj->country_id ? ($langs->trans('Country'.$obj->country_code) != 'Country'.$obj->country_code ? $langs->transnoentities('Country'.$obj->country_code) : $obj->country) : '';
1129
1130 $this->fk_soc = $obj->fk_soc; // Both fk_soc and socid are used
1131 $this->socid = $obj->fk_soc; // Both fk_soc and socid are used
1132 $this->socname = $obj->socname;
1133 $this->poste = $obj->poste;
1134 $this->status = $obj->status;
1135 $this->statut = $obj->status; // deprecated
1136
1137 $this->fk_prospectlevel = $obj->fk_prospectlevel;
1138
1139 $transcode = $langs->trans('StatusProspect'.$obj->fk_stcommcontact);
1140 $label_sale_status = ($transcode != 'StatusProspect'.$obj->fk_stcommcontact ? $transcode : $obj->stcomm);
1141 $this->stcomm_id = $obj->fk_stcommcontact; // id statut commercial
1142 $this->statut_commercial = $label_sale_status; // libelle statut commercial
1143 $this->stcomm_picto = $obj->stcomm_picto; // Picto statut commercial
1144
1145 $this->phone_pro = trim($obj->phone);
1146 $this->fax = trim($obj->fax);
1147 $this->phone_perso = trim($obj->phone_perso);
1148 $this->phone_mobile = trim($obj->phone_mobile);
1149
1150 $this->email = $obj->email;
1151 $this->socialnetworks = ($obj->socialnetworks ? (array) json_decode($obj->socialnetworks, true) : array());
1152 $this->photo = $obj->photo;
1153 $this->priv = $obj->priv;
1154 $this->mail = $obj->email;
1155
1156 $this->birthday = $this->db->jdate($obj->birthday);
1157 $this->note = $obj->note_private; // deprecated
1158 $this->note_private = $obj->note_private;
1159 $this->note_public = $obj->note_public;
1160 $this->default_lang = $obj->default_lang;
1161 $this->user_id = $obj->user_id;
1162 $this->user_login = $obj->user_login;
1163 $this->canvas = $obj->canvas;
1164
1165 $this->import_key = $obj->import_key;
1166
1167 // Define gender according to civility
1168 $this->setGenderFromCivility();
1169
1170 // Search Dolibarr user linked to this contact
1171 $sql = "SELECT u.rowid ";
1172 $sql .= " FROM ".MAIN_DB_PREFIX."user as u";
1173 $sql .= " WHERE u.fk_socpeople = ".((int) $this->id);
1174
1175 $resql = $this->db->query($sql);
1176 if ($resql) {
1177 if ($this->db->num_rows($resql)) {
1178 $uobj = $this->db->fetch_object($resql);
1179
1180 $this->user_id = $uobj->rowid;
1181 }
1182 $this->db->free($resql);
1183 } else {
1184 $this->error = $this->db->error();
1185 return -1;
1186 }
1187
1188 // Retrieve all extrafield
1189 // fetch optionals attributes and labels
1190 $this->fetch_optionals();
1191
1192 // Load also alerts of this user
1193 if ($user) {
1194 $sql = "SELECT fk_user";
1195 $sql .= " FROM ".MAIN_DB_PREFIX."user_alert";
1196 $sql .= " WHERE fk_user = ".((int) $user->id)." AND fk_contact = ".((int) $id);
1197
1198 $resql = $this->db->query($sql);
1199 if ($resql) {
1200 if ($this->db->num_rows($resql)) {
1201 $obj = $this->db->fetch_object($resql);
1202
1203 $this->birthday_alert = 1;
1204 }
1205 $this->db->free($resql);
1206 } else {
1207 $this->error = $this->db->error();
1208 return -1;
1209 }
1210 }
1211
1212 // Load also roles of this address
1213 if ($loadalsoroles) {
1214 $resultRole = $this->fetchRoles();
1215 if ($resultRole < 0) {
1216 return $resultRole;
1217 }
1218 }
1219
1220 return $this->id;
1221 } else {
1222 $langs->load('errors');
1223 $this->error = $langs->trans("ErrorRecordNotFound");
1224 return 0;
1225 }
1226 } else {
1227 $this->error = $this->db->error();
1228 return -1;
1229 }
1230 }
1231
1245 public function findNearest($id = 0, $lastname = '', $firstname = '', $ref_ext = '', $email = '', $ref_alias = '', $socid = 0)
1246 {
1247 // A rowid is known, it is a unique key so we found it
1248 if ($id) {
1249 return $id;
1250 }
1251
1252 // We try to find the contact with exact matching on all fields
1253 // TODO Replace this with step by step search
1254 // Then search on email
1255 // Then search on lastname + firstname
1256 // Then search ref_ext or alias with a OR
1257 $tmpcontact = new Contact($this->db);
1258 $result = $tmpcontact->fetch($id, null, $ref_ext, $email, 0, $socid);
1259
1260 return $result;
1261 }
1262
1263
1264
1271 public function setGenderFromCivility()
1272 {
1273 unset($this->gender);
1274
1275 if (in_array($this->civility_id, array('MR')) || in_array($this->civility_code, array('MR'))) {
1276 $this->gender = 'man';
1277 } elseif (in_array($this->civility_id, array('MME', 'MLE')) || in_array($this->civility_code, array('MME', 'MLE'))) {
1278 $this->gender = 'woman';
1279 }
1280 }
1281
1282 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1292 public function load_ref_elements()
1293 {
1294 // phpcs:enable
1295 // Count the elements for which it is contact
1296 $sql = "SELECT tc.element, count(ec.rowid) as nb";
1297 $sql .= " FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as tc";
1298 $sql .= " WHERE ec.fk_c_type_contact = tc.rowid";
1299 $sql .= " AND fk_socpeople = ".((int) $this->id);
1300 $sql .= " AND tc.source = 'external'";
1301 $sql .= " GROUP BY tc.element";
1302
1303 dol_syslog(get_class($this)."::load_ref_elements", LOG_DEBUG);
1304
1305 $resql = $this->db->query($sql);
1306 if ($resql) {
1307 while ($obj = $this->db->fetch_object($resql)) {
1308 if ($obj->nb) {
1309 if ($obj->element == 'facture') {
1310 $this->ref_facturation = $obj->nb;
1311 } elseif ($obj->element == 'contrat') {
1312 $this->ref_contrat = $obj->nb;
1313 } elseif ($obj->element == 'commande') {
1314 $this->ref_commande = $obj->nb;
1315 } elseif ($obj->element == 'propal') {
1316 $this->ref_propal = $obj->nb;
1317 }
1318 }
1319 }
1320 $this->db->free($resql);
1321 return 0;
1322 } else {
1323 $this->error = $this->db->lasterror();
1324 return -1;
1325 }
1326 }
1327
1335 public function delete($user, $notrigger = 0)
1336 {
1337 $error = 0;
1338
1339 $this->db->begin();
1340
1341 if (!$notrigger) {
1342 // Call trigger
1343 $result = $this->call_trigger('CONTACT_DELETE', $user);
1344 if ($result < 0) {
1345 $error++;
1346 }
1347 // End call triggers
1348 }
1349
1350 if (!$error) {
1351 // Get all rowid of element_contact linked to a type that is link to llx_socpeople
1352 $sql = "SELECT ec.rowid";
1353 $sql .= " FROM ".MAIN_DB_PREFIX."element_contact ec,";
1354 $sql .= " ".MAIN_DB_PREFIX."c_type_contact tc";
1355 $sql .= " WHERE ec.fk_socpeople=".((int) $this->id);
1356 $sql .= " AND ec.fk_c_type_contact=tc.rowid";
1357 $sql .= " AND tc.source='external'";
1358 dol_syslog(__METHOD__, LOG_DEBUG);
1359 $resql = $this->db->query($sql);
1360 if ($resql) {
1361 $num = $this->db->num_rows($resql);
1362
1363 $i = 0;
1364 while ($i < $num && !$error) {
1365 $obj = $this->db->fetch_object($resql);
1366
1367 $sqldel = "DELETE FROM ".MAIN_DB_PREFIX."element_contact";
1368 $sqldel .= " WHERE rowid = ".((int) $obj->rowid);
1369 dol_syslog(__METHOD__, LOG_DEBUG);
1370 $result = $this->db->query($sqldel);
1371 if (!$result) {
1372 $error++;
1373 $this->error = $this->db->error().' sql='.$sqldel;
1374 }
1375
1376 $i++;
1377 }
1378 } else {
1379 $error++;
1380 $this->error = $this->db->error().' sql='.$sql;
1381 }
1382 }
1383
1384 if (!$error) {
1385 // Remove Roles
1386 $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_contacts WHERE fk_socpeople = ".((int) $this->id);
1387 dol_syslog(__METHOD__, LOG_DEBUG);
1388 $resql = $this->db->query($sql);
1389 if (!$resql) {
1390 $error++;
1391 $this->error .= $this->db->lasterror();
1392 $errorflag = -1;
1393 }
1394 }
1395
1396 if (!$error) {
1397 // Remove Notifications
1398 $sql = "DELETE FROM ".MAIN_DB_PREFIX."notify_def WHERE fk_contact = ".((int) $this->id);
1399 dol_syslog(__METHOD__, LOG_DEBUG);
1400 $resql = $this->db->query($sql);
1401 if (!$resql) {
1402 $error++;
1403 $this->error .= $this->db->lasterror();
1404 $errorflag = -1;
1405 }
1406 }
1407
1408 if (!$error) {
1409 // Remove category
1410 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_contact WHERE fk_socpeople = ".((int) $this->id);
1411 dol_syslog(__METHOD__, LOG_DEBUG);
1412 $resql = $this->db->query($sql);
1413 if (!$resql) {
1414 $error++;
1415 $this->error .= $this->db->lasterror();
1416 $errorflag = -1;
1417 }
1418 }
1419
1420 if (!$error) {
1421 $sql = "DELETE FROM ".MAIN_DB_PREFIX."socpeople";
1422 $sql .= " WHERE rowid = ".((int) $this->id);
1423 dol_syslog(__METHOD__, LOG_DEBUG);
1424 $result = $this->db->query($sql);
1425 if (!$result) {
1426 $error++;
1427 $this->error = $this->db->error().' sql='.$sql;
1428 }
1429 }
1430
1431 // Remove extrafields
1432 if (!$error) {
1433 // For avoid conflicts if trigger used
1434 $result = $this->deleteExtraFields();
1435 if ($result < 0) {
1436 $error++;
1437 }
1438 }
1439
1440 if (!$error) {
1441 $this->db->commit();
1442 return 1;
1443 } else {
1444 $this->db->rollback();
1445 dol_syslog("Error ".$this->error, LOG_ERR);
1446 return -1;
1447 }
1448 }
1449
1450
1457 public function info($id)
1458 {
1459 $sql = "SELECT c.rowid, c.datec as datec, c.fk_user_creat,";
1460 $sql .= " GREATEST(c.tms, cef.tms) as tms, c.fk_user_modif";
1461 $sql .= " FROM ".MAIN_DB_PREFIX."socpeople as c";
1462 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople_extrafields as cef ON cef.fk_object=c.rowid";
1463 $sql .= " WHERE c.rowid = ".((int) $id);
1464
1465 $resql = $this->db->query($sql);
1466 if ($resql) {
1467 if ($this->db->num_rows($resql)) {
1468 $obj = $this->db->fetch_object($resql);
1469
1470 $this->id = $obj->rowid;
1471
1472 $this->user_creation_id = $obj->fk_user_creat;
1473 $this->user_modification_id = $obj->fk_user_modif;
1474 $this->date_creation = $this->db->jdate($obj->datec);
1475 $this->date_modification = $this->db->jdate($obj->tms);
1476 }
1477
1478 $this->db->free($resql);
1479 } else {
1480 print $this->db->error();
1481 }
1482 }
1483
1489 public function getNbOfEMailings()
1490 {
1491 $sql = "SELECT count(mc.email) as nb";
1492 $sql .= " FROM ".MAIN_DB_PREFIX."mailing_cibles as mc, ".MAIN_DB_PREFIX."mailing as m";
1493 $sql .= " WHERE mc.fk_mailing=m.rowid AND mc.email = '".$this->db->escape($this->email)."' ";
1494 $sql .= " AND m.entity IN (".getEntity($this->element).") AND mc.statut NOT IN (-1,0)"; // -1 error, 0 not sent, 1 sent with success
1495
1496 $resql = $this->db->query($sql);
1497 if ($resql) {
1498 $obj = $this->db->fetch_object($resql);
1499 $nb = (int) $obj->nb;
1500
1501 $this->db->free($resql);
1502 return $nb;
1503 } else {
1504 $this->error = $this->db->error();
1505 return -1;
1506 }
1507 }
1508
1515 public function getTooltipContentArray($params)
1516 {
1517 global $conf, $langs, $user;
1518
1519 $datas = [];
1520
1521 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1522 return ['optimize' => $langs->trans("ShowContact")];
1523 }
1524 if (!empty($this->photo) && class_exists('Form')) {
1525 $photo = '<div class="photointooltip floatright">';
1526 $photo .= Form::showphoto('contact', $this, 0, 40, 0, 'photoref', 'mini', 0); // Important, we must force height so image will have height tags and if image is inside a tooltip, the tooltip manager can calculate height and position correctly the tooltip.
1527 $photo .= '</div>';
1528 $datas['photo'] = $photo;
1529 }
1530
1531 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Contact").'</u> ' . $this->getLibStatut(4);
1532 $datas['name'] = '<br><b>'.$langs->trans("Name").':</b> '.$this->getFullName($langs);
1533 // if ($this->civility_id) $datas['civility'] = '<br><b>' . $langs->trans("Civility") . ':</b> '.$this->civility_id; // TODO Translate civilty_id code
1534 if (!empty($this->poste)) {
1535 $datas['job'] = '<br><b>'.$langs->trans("Poste").':</b> '.$this->poste;
1536 }
1537 $datas['email'] = '<br><b>'.$langs->trans("EMail").':</b> '.$this->email;
1538 $phonelist = array();
1539 $country_code = empty($this->country_code) ? '' : $this->country_code;
1540 if ($this->phone_pro) {
1541 $phonelist[] = dol_print_phone($this->phone_pro, $country_code, $this->id, 0, '', '&nbsp;', 'phone');
1542 }
1543 if ($this->phone_mobile) {
1544 $phonelist[] = dol_print_phone($this->phone_mobile, $country_code, $this->id, 0, '', '&nbsp;', 'mobile');
1545 }
1546 if ($this->phone_perso) {
1547 $phonelist[] = dol_print_phone($this->phone_perso, $country_code, $this->id, 0, '', '&nbsp;', 'phone');
1548 }
1549 $datas['phonelist'] = '<br><b>'.$langs->trans("Phone").':</b> '.implode('&nbsp;', $phonelist);
1550 $datas['address'] = '<br><b>'.$langs->trans("Address").':</b> '.dol_format_address($this, 1, ' ', $langs);
1551
1552 return $datas;
1553 }
1554
1568 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $moreparam = '', $save_lastsearch_value = -1, $maxlen = 0, $morecss = 'valignmiddle')
1569 {
1570 global $conf, $langs, $hookmanager;
1571
1572 if (!empty($conf->dol_no_mouse_hover)) {
1573 $notooltip = 1; // Force disable tooltips
1574 }
1575
1576 $result = '';
1577 $params = [
1578 'id' => $this->id,
1579 'objecttype' => $this->element,
1580 'option' => $option,
1581 ];
1582 $classfortooltip = 'classfortooltip';
1583 $dataparams = '';
1584 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1585 $classfortooltip = 'classforajaxtooltip';
1586 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1587 $label = '';
1588 } else {
1589 $label = implode($this->getTooltipContentArray($params));
1590 }
1591
1592 $baseurl = DOL_URL_ROOT . '/contact/card.php';
1593 $query = ['id' => $this->id];
1594 if ($option !== 'nolink') {
1595 // Add param to save lastsearch_values or not
1596 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1597 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1598 $add_save_lastsearch_values = 1;
1599 }
1600 if ($add_save_lastsearch_values) {
1601 $query = array_merge($query, ['save_lastsearch_values' => 1]);
1602 }
1603 }
1604 $url = dolBuildUrl($baseurl, $query);
1605
1606 $url .= $moreparam;
1607
1608 $linkclose = '';
1609 if (empty($notooltip)) {
1610 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1611 $label = $langs->trans("ShowContact");
1612 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1613 }
1614 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1615 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1616 } else {
1617 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1618 }
1619
1620 if ($option == 'nolink') {
1621 $linkstart = '<span';
1622 } else {
1623 $linkstart = '<a href="'.$url.'"';
1624 }
1625 $linkstart .= $linkclose.'>';
1626 if ($option == 'nolink') {
1627 $linkend = '</span>';
1628 } else {
1629 $linkend = '</a>';
1630 }
1631
1632 $result .= $linkstart;
1633
1634 if ($withpicto) {
1635 if ($withpicto < 0) {
1636 $result .= '<!-- picto photo contact --><span class="nopadding userimg'.($morecss ? ' '.$morecss : '').'">'.Form::showphoto('contact', $this, 0, 0, 0, 'userphoto'.($withpicto == -3 ? 'small' : ''), 'mini', 0, 1).'</span>';
1637 if ($withpicto != 2 && $withpicto != -2) {
1638 $result .= ' ';
1639 }
1640 } else {
1641 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="pictofixedwidth valignmiddle"' : '') : 'class="'.(($withpicto != 2) ? 'pictofixedwidth valignmiddle' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1642 }
1643 }
1644 if ($withpicto != 2 && $withpicto != -2) {
1645 $result .= '<span class="valignmiddle">'.($maxlen ? dol_trunc($this->getFullName($langs), $maxlen) : $this->getFullName($langs)).'</span>';
1646 }
1647
1648 $result .= $linkend;
1649
1650 global $action;
1651 $hookmanager->initHooks(array('contactdao'));
1652 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1653 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1654 if ($reshook > 0) {
1655 $result = $hookmanager->resPrint;
1656 } else {
1657 $result .= $hookmanager->resPrint;
1658 }
1659
1660 return $result;
1661 }
1662
1668 public function getCivilityLabel()
1669 {
1670 global $langs;
1671
1672 $code = ($this->civility_code ? $this->civility_code : (!empty($this->civility_id) ? $this->civility : (!empty($this->civilite) ? $this->civilite : '')));
1673 if (empty($code)) {
1674 return '';
1675 }
1676
1677 $langs->load("dict");
1678 return $langs->getLabelFromKey($this->db, "Civility".$code, "c_civility", "code", "label", $code);
1679 }
1680
1687 public function getLibStatut($mode)
1688 {
1689 return $this->LibStatut($this->status, $mode);
1690 }
1691
1692 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1700 public function LibStatut($status, $mode)
1701 {
1702 // phpcs:enable
1703 global $langs;
1704
1705 $labelStatus = array(
1706 0 => 'ActivityCeased',
1707 1 => 'InActivity',
1708 4 => 'InActivity',
1709 5 => 'ActivityCeased',
1710 );
1711 $labelStatusShort = array(
1712 0 => 'ActivityCeased',
1713 1 => 'InActivity',
1714 4 => 'InActivity',
1715 5 => 'ActivityCeased',
1716 );
1717
1718 $statusType = 'status4';
1719 if ($status == 0 || $status == 5) {
1720 $statusType = 'status5';
1721 }
1722
1723 $label = $langs->transnoentitiesnoconv($labelStatus[$status]);
1724 $labelshort = $langs->transnoentitiesnoconv($labelStatusShort[$status]);
1725
1726 return dolGetStatus($label, $labelshort, '', $statusType, $mode);
1727 }
1728
1729
1730 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1738 public function LibPubPriv($status, $decorate = 0)
1739 {
1740 // phpcs:enable
1741 global $langs;
1742 if ($status == '1') {
1743 $s = ($decorate == 2 ? '' : $langs->trans('ContactPrivate'));
1744 if ($decorate) {
1745 $s = '<span title="'.$langs->trans('ContactPrivateDesc').'">'.img_picto('', 'private', 'class="paddingrightonly"').$s.'</span>';
1746 }
1747 return $s;
1748 } else {
1749 $s = ($decorate == 2 ? '' : $langs->trans('ContactPublic'));
1750 if ($decorate) {
1751 $s = '<span title="'.$langs->trans('ContactPublicDesc').'">'.img_picto('', 'public', 'class="paddingrightonly"').$s.'</span>';
1752 }
1753 return $s;
1754 }
1755 }
1756
1757
1765 public function initAsSpecimen()
1766 {
1767 // Get first id of existing company and save it into $socid
1768 $socid = 0;
1769 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."societe ORDER BY rowid LIMIT 1";
1770 $resql = $this->db->query($sql);
1771 if ($resql) {
1772 $obj = $this->db->fetch_object($resql);
1773 if ($obj) {
1774 $socid = $obj->rowid;
1775 }
1776 }
1777
1778 // Initialise parameters
1779 $this->id = 0;
1780 $this->entity = 1;
1781 $this->specimen = 1;
1782 $this->lastname = 'DOLIBARR';
1783 $this->firstname = 'SPECIMEN';
1784 $this->address = '21 jump street';
1785 $this->zip = '99999';
1786 $this->town = 'MyTown';
1787 $this->country_id = 1;
1788 $this->country_code = 'FR';
1789 $this->country = 'France';
1790 $this->email = 'specimen@specimen.com';
1791 $this->socialnetworks = array(
1792 'skype' => 'tom.hanson',
1793 'twitter' => 'tomhanson',
1794 'linkedin' => 'tomhanson',
1795 );
1796 $this->phone_pro = '0909090901';
1797 $this->phone_perso = '0909090902';
1798 $this->phone_mobile = '0909090903';
1799 $this->fax = '0909090909';
1800
1801 $this->note_public = 'This is a comment (public)';
1802 $this->note_private = 'This is a comment (private)';
1803
1804 $this->socid = $socid;
1805 $this->status = 1;
1806
1807 return 1;
1808 }
1809
1816 public function setstatus($status)
1817 {
1818 global $conf, $langs, $user;
1819
1820 $error = 0;
1821
1822 // Check parameters
1823 if (!empty($this->statut) && empty($this->status)) {
1824 $this->status = 1;
1825 }
1826 if ($this->status == $status) {
1827 return 0;
1828 } else {
1829 $this->status = $status;
1830 $this->statut = $status;
1831 }
1832
1833 $this->db->begin();
1834
1835 // User disable
1836 $sql = "UPDATE ".MAIN_DB_PREFIX."socpeople";
1837 $sql .= " SET statut = ".((int) $this->status);
1838 $sql .= ", fk_user_modif = ".((int) $user->id);
1839 $sql .= " WHERE rowid = ".((int) $this->id);
1840 $result = $this->db->query($sql);
1841
1842 dol_syslog(get_class($this)."::setstatus", LOG_DEBUG);
1843 if ($result) {
1844 // Call trigger
1845 $result = $this->call_trigger('CONTACT_ENABLEDISABLE', $user);
1846 if ($result < 0) {
1847 $error++;
1848 }
1849 // End call triggers
1850 }
1851
1852 if ($error) {
1853 $this->db->rollback();
1854 return -$error;
1855 } else {
1856 $this->db->commit();
1857 return 1;
1858 }
1859 }
1860
1872 public function setCategories($categories, $remove_existing = true)
1873 {
1874 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
1875 return parent::setCategoriesCommon($categories, Categorie::TYPE_CONTACT, $remove_existing);
1876 }
1877
1886 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
1887 {
1888 $tables = array(
1889 'socpeople', 'societe_contacts'
1890 );
1891
1892 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
1893 }
1894
1907 public static function replaceContact(DoliDB $dbs, $origin_id, $dest_id)
1908 {
1909 // llx_societe_contacts: UNIQUE(entity, fk_soc, fk_c_type_contact, fk_socpeople).
1910 // Delete the roles the target contact already has, then move the remaining ones.
1911 $sql = 'DELETE FROM '.$dbs->prefix().'societe_contacts WHERE rowid IN (';
1912 $sql .= ' SELECT x.rowid FROM (';
1913 $sql .= ' SELECT origin.rowid FROM '.$dbs->prefix().'societe_contacts as origin';
1914 $sql .= ' INNER JOIN '.$dbs->prefix().'societe_contacts as dest ON dest.entity = origin.entity';
1915 $sql .= ' AND dest.fk_soc = origin.fk_soc AND dest.fk_c_type_contact = origin.fk_c_type_contact';
1916 $sql .= ' WHERE origin.fk_socpeople = '.((int) $origin_id).' AND dest.fk_socpeople = '.((int) $dest_id);
1917 $sql .= ' ) as x)';
1918 if (!$dbs->query($sql)) {
1919 return false;
1920 }
1921
1922 if (!CommonObject::commonReplaceContact($dbs, $origin_id, $dest_id, array('societe_contacts'))) {
1923 return false;
1924 }
1925
1926 // llx_element_contact.fk_socpeople points to llx_socpeople only when c_type_contact.source is
1927 // 'external'. It points to llx_user when source is 'internal', so both queries below MUST filter
1928 // on it, otherwise internal (user) assignments would be moved to the merged contact.
1929
1930
1931 // llx_element_contact: UNIQUE(element_id, fk_c_type_contact, fk_socpeople)
1932 $sql = 'DELETE FROM '.$dbs->prefix().'element_contact WHERE rowid IN (';
1933 $sql .= ' SELECT x.rowid FROM (';
1934 $sql .= ' SELECT origin.rowid FROM '.$dbs->prefix().'element_contact as origin';
1935 $sql .= ' INNER JOIN '.$dbs->prefix().'element_contact as dest ON dest.element_id = origin.element_id';
1936 $sql .= ' AND dest.fk_c_type_contact = origin.fk_c_type_contact';
1937 $sql .= ' WHERE origin.fk_socpeople = '.((int) $origin_id).' AND dest.fk_socpeople = '.((int) $dest_id);
1938 $sql .= " AND origin.fk_c_type_contact IN (SELECT rowid FROM ".$dbs->prefix()."c_type_contact WHERE source = 'external')";
1939 $sql .= ' ) as x)';
1940 if (!$dbs->query($sql)) {
1941 return false;
1942 }
1943
1944 $sql = 'UPDATE '.$dbs->prefix().'element_contact SET fk_socpeople = '.((int) $dest_id);
1945 $sql .= ' WHERE fk_socpeople = '.((int) $origin_id);
1946 $sql .= " AND fk_c_type_contact IN (SELECT rowid FROM ".$dbs->prefix()."c_type_contact WHERE source = 'external')";
1947 if (!$dbs->query($sql)) {
1948 return false;
1949 }
1950
1951 // References to a contact stored as a (type, id) couple. All the names below are literals, so
1952 // they are safe to concatenate. 'unique' lists the other columns of the unique index of the
1953 // table, if any, so the rows the target contact already has can be dropped before the update.
1954 $polymorphic = array(
1955 array('table' => 'object_lang', 'id' => 'fk_object', 'type' => 'type_object',
1956 'values' => array('contact', 'socpeople'), 'unique' => array('property', 'lang')),
1957 array('table' => 'links', 'id' => 'objectid', 'type' => 'objecttype',
1958 'values' => array('contact'), 'unique' => array('label')),
1959 array('table' => 'element_element', 'id' => 'fk_source', 'type' => 'sourcetype',
1960 'values' => array('contact'), 'unique' => array('fk_target', 'targettype')),
1961 array('table' => 'element_element', 'id' => 'fk_target', 'type' => 'targettype',
1962 'values' => array('contact'), 'unique' => array('fk_source', 'sourcetype')),
1963 // An event can be linked to a contact as its related object
1964 array('table' => 'actioncomm', 'id' => 'fk_element', 'type' => 'elementtype',
1965 'values' => array('contact'), 'unique' => array()),
1966 // dol_move() updates the path of the indexed files but never their source object, so the
1967 // index rows have to be moved here or they would point to the deleted contact. The unique
1968 // index of the table is on (filepath, filename, entity), which is left untouched.
1969 array('table' => 'ecm_files', 'id' => 'src_object_id', 'type' => 'src_object_type',
1970 'values' => array('contact', 'socpeople'), 'unique' => array()),
1971 array('table' => 'quickmemo_memo', 'id' => 'fk_element', 'type' => 'element_type',
1972 'values' => array('contact'), 'unique' => array()),
1973 array('table' => 'comment', 'id' => 'fk_element', 'type' => 'element_type',
1974 'values' => array('contact'), 'unique' => array()),
1975 );
1976
1977 foreach ($polymorphic as $ref) {
1978 // Some of these tables are provided by modules that may not be installed
1979 $sanitizedtable = $dbs->sanitize($ref['table']);
1980 $sanitizedidcol = $dbs->sanitize($ref['id']);
1981 $sanitizedtypecol = $dbs->sanitize($ref['type']);
1982 if (!$dbs->DDLListTables((string) $dbs->database_name, $dbs->prefix().$sanitizedtable)) {
1983 continue;
1984 }
1985 // Each value is escaped on its own: sanitize() removes the quotes inside the string it is
1986 // given, so sanitizing an already assembled list would collapse it into a single value
1987 $quotedvalues = array();
1988 foreach ($ref['values'] as $refvalue) {
1989 $quotedvalues[] = "'".$dbs->escape($refvalue)."'";
1990 }
1991 $sanitizedvalues = implode(', ', $quotedvalues); // @phan-suppress-current-line SqlInjection
1992 $sanitizedtypefilter = $sanitizedtypecol." IN (".$sanitizedvalues.")";
1993
1994 if (!empty($ref['unique'])) {
1995 $sql = "DELETE FROM ".$dbs->prefix().$sanitizedtable." WHERE rowid IN (";
1996 $sql .= " SELECT x.rowid FROM (";
1997 $sql .= " SELECT origin.rowid FROM ".$dbs->prefix().$sanitizedtable." as origin";
1998 $sql .= " INNER JOIN ".$dbs->prefix().$sanitizedtable." as dest";
1999 $sql .= " ON dest.".$sanitizedtypecol." = origin.".$sanitizedtypecol;
2000 foreach ($ref['unique'] as $uniquecol) {
2001 $sanitizeduniquecol = $dbs->sanitize($uniquecol);
2002 $sql .= " AND dest.".$sanitizeduniquecol." = origin.".$sanitizeduniquecol;
2003 }
2004 $sql .= " WHERE origin.".$sanitizedidcol." = ".((int) $origin_id);
2005 $sql .= " AND dest.".$sanitizedidcol." = ".((int) $dest_id);
2006 $sql .= " AND origin.".$sanitizedtypefilter;
2007 $sql .= " ) as x)";
2008 if (!$dbs->query($sql)) {
2009 return false;
2010 }
2011 }
2012
2013 $sql = "UPDATE ".$dbs->prefix().$sanitizedtable." SET ".$sanitizedidcol." = ".((int) $dest_id);
2014 $sql .= " WHERE ".$sanitizedtypefilter;
2015 $sql .= " AND ".$sanitizedidcol." = ".((int) $origin_id);
2016 if (!$dbs->query($sql)) {
2017 return false;
2018 }
2019 }
2020
2021 // A link between the two contacts became a link of the target contact to itself, which the
2022 // linked objects box would then display. There is no unique index violation, so nothing failed.
2023 $sql = "DELETE FROM ".$dbs->prefix()."element_element WHERE fk_source = fk_target";
2024 $sql .= " AND sourcetype = targettype AND fk_source = ".((int) $dest_id);
2025 $sql .= " AND sourcetype = 'contact'";
2026 if (!$dbs->query($sql)) {
2027 return false;
2028 }
2029
2030 return true;
2031 }
2032
2044 public function mergeContact($contact_origin_id)
2045 {
2046 global $langs, $hookmanager, $user, $action;
2047
2048 $error = 0;
2049 $langs->loadLangs(array('errors', 'companies'));
2050
2051 // The target contact must have been loaded: update() would silently update no row and the
2052 // satellite data would then be moved to the contact id 0.
2053 if (!($this->id > 0) || empty($this->entity)) {
2054 $this->error = $langs->trans('ErrorBadParameters');
2055 dol_syslog(__METHOD__.' Called on a contact that was not loaded', LOG_ERR);
2056 return -1;
2057 }
2058 if ($contact_origin_id <= 0 || $contact_origin_id == $this->id) {
2059 $this->error = $langs->trans('ErrorBadParameters');
2060 return -1;
2061 }
2062 // A merge deletes a contact, so it requires the permission to delete one, whatever the caller
2063 if (!$user->hasRight('societe', 'contact', 'creer') || !$user->hasRight('societe', 'contact', 'supprimer')) {
2064 $this->error = $langs->trans('ErrorForbidden');
2065 return -1;
2066 }
2067 // An external user never merges anything, as on the third party card
2068 if ($user->socid > 0) {
2069 $this->error = $langs->trans('ErrorForbidden');
2070 return -1;
2071 }
2072
2073 $contact_origin = new Contact($this->db); // The contact that we will delete
2074 $resultfetch = $contact_origin->fetch($contact_origin_id);
2075 // fetch() returns the id when found, 2 when several records were found, 0 when not found and -1 on error
2076 if ($resultfetch != $contact_origin_id) {
2077 $this->error = $langs->trans('ErrorRecordNotFound');
2078 dol_syslog(__METHOD__.' Cannot fetch contact id='.$contact_origin_id.', result='.$resultfetch, LOG_ERR);
2079 return -1;
2080 }
2081
2082 // Access guards. fetch() by rowid applies neither an entity filter nor a permission filter, so
2083 // the two contacts are revalidated here, including the current one: an id coming from a POST is
2084 // not to be trusted, and the checks must also protect the callers that are not the contact card.
2085 $entities = explode(',', getEntity('contact'));
2086 foreach (array($this, $contact_origin) as $tmpcontact) {
2087 if (!in_array($tmpcontact->entity, $entities) || $tmpcontact->entity != $this->entity) {
2088 $this->error = $langs->trans('ErrorContactsMergeDifferentEntity');
2089 return -1;
2090 }
2091 if (!empty($tmpcontact->priv) && $tmpcontact->user_creation_id != $user->id) {
2092 $this->error = $langs->trans('ErrorContactsMergePrivate');
2093 return -1;
2094 }
2095 // A contact without a third party is shared, so the perimeter of the sales representatives
2096 // does not apply to it, as in restrictedArea()
2097 if ($tmpcontact->socid > 0 && !$user->hasRight('societe', 'client', 'voir')
2098 && !$this->isSalesRepresentativeOf($tmpcontact->socid)) {
2099 $this->error = $langs->trans('ErrorForbidden');
2100 return -1;
2101 }
2102 }
2103 // Absorbing a shared contact into a private one would hide its data from everybody else,
2104 // including the administrators, and the merged contact is deleted so it is not reversible
2105 if (!empty($this->priv) && empty($contact_origin->priv)) {
2106 $this->error = $langs->trans('ErrorContactsMergeIntoPrivate');
2107 return -1;
2108 }
2109 $originlinked = $this->isLinkedToUser($contact_origin->id);
2110 $destlinked = $this->isLinkedToUser($this->id);
2111 if ($originlinked < 0 || $destlinked < 0) {
2112 // The guard below is a security one, so it must refuse and not let the merge through
2113 $this->error = $langs->trans('ErrorContactsMerge');
2114 return -1;
2115 }
2116 // Moving llx_user.fk_socpeople would give the contact of a user account to another contact,
2117 // and Contact::update() then propagates the email of that contact to the user, which is a way
2118 // to take over the account. Changing the contact of a user requires the permission to do so.
2119 if (($originlinked > 0 || $destlinked > 0) && !$user->hasRight('user', 'user', 'creer')) {
2120 $this->error = $langs->trans('ErrorContactsMergeLinkedToUser');
2121 return -1;
2122 }
2123 // llx_user.fk_socpeople has a unique key: refuse rather than silently break a user link
2124 if ($originlinked > 0 && $destlinked > 0) {
2125 $this->error = $langs->trans('ErrorContactsMergeBothLinkedToUser');
2126 return -1;
2127 }
2128
2129 dol_syslog(__METHOD__.' merge contact id='.$contact_origin->id.' (will be deleted) into the contact id='.$this->id);
2130
2131 $this->db->begin();
2132
2133 // Recopy some data
2134 foreach (self::MERGE_FIELDS_FILL_IF_EMPTY as $property) {
2135 if (empty($this->$property) && !empty($contact_origin->$property)) {
2136 $this->$property = $contact_origin->$property;
2137 }
2138 }
2139
2140 // Concat some data, with a dated mention so a targeted erasure stays possible later
2141 $mention = '['.$langs->transnoentitiesnoconv('MergedFromContact', dol_print_date(dol_now(), 'day'), (string) $contact_origin->id).']';
2142 foreach (self::MERGE_FIELDS_CONCAT as $property) {
2143 if (!empty($contact_origin->$property)) {
2144 $this->$property = dol_concatdesc($this->$property, $mention."\n".$contact_origin->$property);
2145 }
2146 }
2147
2148 // A merge must never make the data of a private contact visible to everybody
2149 if (!empty($contact_origin->priv)) {
2150 $this->priv = 1;
2151 }
2152
2153 // If alias name is not defined on target contact, we can store in it the old name of the contact
2154 if (empty($this->name_alias) && $this->getFullName($langs) != $contact_origin->getFullName($langs)) {
2155 $this->name_alias = $contact_origin->getFullName($langs);
2156 }
2157
2158 // Merge extrafields. They are saved by the update() below.
2159 if (is_array($contact_origin->array_options)) {
2160 foreach ($contact_origin->array_options as $key => $val) {
2161 if (empty($this->array_options[$key])) {
2162 $this->array_options[$key] = $val;
2163 }
2164 }
2165 }
2166
2167 // updateRoles(), called by update(), deletes then reinserts every societe_contacts row of the
2168 // contact from $this->roles, which would wipe the roles we are about to move. It is a no-op
2169 // when roles is not set. Set it to null instead of using unset(): roles is a declared property
2170 // and unset() would make any later access emit an "Undefined property" warning.
2171 $this->roles = null;
2172
2173 // Update. The trigger is called once at the end of the merge, hence $notrigger = 1.
2174 if ($this->update($this->id, $user, 1) <= 0) {
2175 $error++;
2176 dol_syslog(__METHOD__.' Failed to update the target contact: '.$this->errorsToString(), LOG_ERR);
2177 }
2178
2179 // Merge categories, before the deletion below: llx_categorie_contact has a foreign key on
2180 // llx_socpeople without ON DELETE.
2181 if (!$error) {
2182 include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2183 $static_cat = new Categorie($this->db);
2184 $cats_origin = $static_cat->containing($contact_origin->id, 'contact', 'id');
2185 $cats_dest = $static_cat->containing($this->id, 'contact', 'id');
2186 // containing() returns the int -1 on SQL error. Reading it as an empty list would replace
2187 // the categories of the target contact by the ones of the merged contact only.
2188 if (!is_array($cats_origin) || !is_array($cats_dest)) {
2189 $this->error = $static_cat->error;
2190 dol_syslog(__METHOD__.' Cannot read the categories of the contacts: '.$this->error, LOG_ERR);
2191 $error++;
2192 } else {
2193 // array_merge() must be used here: the + operator on arrays is a union on keys, it
2194 // would silently drop categories.
2195 $cats = array_merge($cats_origin, $cats_dest);
2196 if ($this->setCategories(array_values(array_unique($cats))) < 0) {
2197 $error++;
2198 }
2199 }
2200 }
2201
2202 // Children contacts
2203 if (!$error) {
2204 $error += $this->mergeContactChildren($contact_origin);
2205 }
2206
2207 // Move links
2208 if (!$error) {
2209 $objects = array(
2210 'ActionComm' => '/comm/action/class/actioncomm.class.php',
2211 'Contact' => '/contact/class/contact.class.php',
2212 'User' => '/user/class/user.class.php',
2213 );
2214 foreach ($objects as $object_name => $object_file) {
2215 require_once DOL_DOCUMENT_ROOT.$object_file;
2216
2217 if (!$object_name::replaceContact($this->db, $contact_origin->id, $this->id)) {
2218 $error++;
2219 $this->error = $this->db->lasterror();
2220 dol_syslog(__METHOD__.' '.$object_name.'::replaceContact failed: '.$this->error, LOG_ERR);
2221 break;
2222 }
2223 }
2224 }
2225
2226 // Tables of the optional modules
2227 if (!$error) {
2228 $error += $this->mergeContactOptionalTables($contact_origin);
2229 }
2230
2231 // External modules should update their ones too
2232 if (!$error) {
2233 $parameters = array('contact_origin' => $contact_origin->id, 'contact_dest' => $this->id);
2234 $reshook = $hookmanager->executeHooks('replaceContact', $parameters, $this, $action);
2235
2236 if ($reshook < 0) {
2237 $this->error = $hookmanager->error;
2238 $this->errors = $hookmanager->errors;
2239 $error++;
2240 }
2241 }
2242
2243 if (!$error) {
2244 $this->context = array(
2245 'merge' => 1,
2246 'mergefromid' => $contact_origin->id,
2247 'mergefromname' => $contact_origin->getFullName($langs)
2248 );
2249
2250 // Call trigger
2251 $result = $this->call_trigger('CONTACT_MODIFY', $user);
2252 if ($result < 0) {
2253 $error++;
2254 }
2255 // End call triggers
2256 }
2257
2258 if (!$error) {
2259 // We finally remove the old contact
2260 if ($contact_origin->delete($user) < 1) {
2261 $this->error = $contact_origin->error;
2262 $this->errors = $contact_origin->errors;
2263 $error++;
2264 }
2265 }
2266
2267 if ($error) {
2268 $this->error = $langs->trans('ErrorContactsMerge').' '.$this->error;
2269 $this->db->rollback();
2270 // The object still holds the merged values in memory, reload it so the caller does not
2271 // display data that was rolled back
2272 $this->fetch($this->id);
2273 return -1;
2274 }
2275
2276 $this->db->commit();
2277
2278 // Files are moved once the transaction is committed: dol_move() is not transactional, and
2279 // Contact::delete() does not remove the directory of the contact, so the files are still there.
2280 $this->mergeContactFiles($contact_origin->id);
2281
2282 return 0;
2283 }
2284
2293 private function isLinkedToUser($contactid)
2294 {
2295 $sql = "SELECT rowid FROM ".$this->db->prefix()."user WHERE fk_socpeople = ".((int) $contactid);
2296
2297 $resql = $this->db->query($sql);
2298 if (!$resql) {
2299 $this->error = $this->db->lasterror();
2300 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2301 return -1;
2302 }
2303 $found = ($this->db->num_rows($resql) > 0 ? 1 : 0);
2304 $this->db->free($resql);
2305
2306 return $found;
2307 }
2308
2316 private function isSalesRepresentativeOf($socid)
2317 {
2318 global $user;
2319
2320 if (empty($socid)) {
2321 return false; // A shared contact with no third party is out of any portfolio
2322 }
2323
2324 $sql = "SELECT fk_soc FROM ".$this->db->prefix()."societe_commerciaux";
2325 $sql .= " WHERE fk_soc = ".((int) $socid)." AND fk_user = ".((int) $user->id);
2326
2327 $resql = $this->db->query($sql);
2328 if (!$resql) {
2329 $this->error = $this->db->lasterror();
2330 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2331 return false;
2332 }
2333 $found = ($this->db->num_rows($resql) > 0);
2334 $this->db->free($resql);
2335
2336 return $found;
2337 }
2338
2349 private function mergeContactChildren($contact_origin)
2350 {
2351 $error = 0;
2352
2353 // fk_parent is not loaded by fetch()
2354 $parentofdest = $this->getParentId($this->id);
2355
2356 // The target contact is a child of the merged one: its parent is about to be deleted
2357 if ($parentofdest == $contact_origin->id) {
2358 $newparent = $this->getParentId($contact_origin->id);
2359 // A dedicated query is used rather than setValueFrom(): the 'int' format of the latter
2360 // casts null to 0, while fk_parent is nullable, and its trigger key would fetch the
2361 // record again and overwrite the values merged into memory.
2362 $sql = 'UPDATE '.$this->db->prefix().'socpeople';
2363 $sql .= ' SET fk_parent = '.($newparent > 0 ? ((int) $newparent) : 'NULL');
2364 $sql .= ' WHERE rowid = '.((int) $this->id);
2365 if (!$this->db->query($sql)) {
2366 $this->error = $this->db->lasterror();
2367 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2368 return 1;
2369 }
2370 }
2371
2372 // Collect the ancestors of the target contact, they must not become its children
2373 $ancestors = array();
2374 $currentid = $this->getParentId($this->id);
2375 $depth = 0;
2376 while ($currentid > 0 && $depth < self::MERGE_MAX_PARENT_DEPTH) {
2377 if (in_array($currentid, $ancestors)) {
2378 break; // The hierarchy already contains a cycle, stop walking it
2379 }
2380 $ancestors[] = (int) $currentid;
2381 $currentid = $this->getParentId($currentid);
2382 $depth++;
2383 }
2384
2385 $sql = 'UPDATE '.$this->db->prefix().'socpeople SET fk_parent = '.((int) $this->id);
2386 $sql .= ' WHERE fk_parent = '.((int) $contact_origin->id);
2387 $sql .= ' AND rowid <> '.((int) $this->id);
2388 if (!empty($ancestors)) {
2389 // $ancestors only contains ids already cast to int
2390 $sanitizedancestors = implode(',', $ancestors); // @phan-suppress-current-line SqlInjection
2391 $sql .= " AND rowid NOT IN (".$sanitizedancestors.")";
2392 }
2393 if (!$this->db->query($sql)) {
2394 $this->error = $this->db->lasterror();
2395 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2396 $error++;
2397 }
2398
2399 // The children excluded above, being ancestors of the target contact, still point to the
2400 // contact about to be deleted. Detach them rather than leave a dangling parent.
2401 if (!$error) {
2402 $sql = 'UPDATE '.$this->db->prefix().'socpeople SET fk_parent = NULL';
2403 $sql .= ' WHERE fk_parent = '.((int) $contact_origin->id);
2404 if (!$this->db->query($sql)) {
2405 $this->error = $this->db->lasterror();
2406 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2407 $error++;
2408 }
2409 }
2410
2411 return $error;
2412 }
2413
2421 private function getParentId($contactid)
2422 {
2423 $sql = "SELECT fk_parent FROM ".$this->db->prefix()."socpeople WHERE rowid = ".((int) $contactid);
2424
2425 $resql = $this->db->query($sql);
2426 if (!$resql) {
2427 $this->error = $this->db->lasterror();
2428 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2429 return 0;
2430 }
2431 $parentid = 0;
2432 if ($obj = $this->db->fetch_object($resql)) {
2433 $parentid = (empty($obj->fk_parent) ? 0 : (int) $obj->fk_parent);
2434 }
2435 $this->db->free($resql);
2436
2437 return $parentid;
2438 }
2439
2448 private function mergeContactOptionalTables($contact_origin)
2449 {
2450 $error = 0;
2451
2452 // Notifications. llx_notify_def has no unique key, but a duplicated row means the same
2453 // notification sent twice, so it must be deduplicated as well.
2454 $sql = 'DELETE FROM '.$this->db->prefix().'notify_def WHERE rowid IN (';
2455 $sql .= ' SELECT x.rowid FROM (';
2456 $sql .= ' SELECT origin.rowid FROM '.$this->db->prefix().'notify_def as origin';
2457 $sql .= ' INNER JOIN '.$this->db->prefix().'notify_def as dest ON dest.fk_action = origin.fk_action';
2458 // A row is a duplicate only if the whole definition matches, recipient included: fk_soc,
2459 // entity, type, threshold, context, fk_user and email are nullable, hence the NULL safe
2460 // comparisons, the MySQL <=> operator not being portable to PostgreSQL.
2461 $sql .= ' AND (dest.fk_soc = origin.fk_soc OR (dest.fk_soc IS NULL AND origin.fk_soc IS NULL))';
2462 $sql .= ' AND (dest.entity = origin.entity OR (dest.entity IS NULL AND origin.entity IS NULL))';
2463 $sql .= ' AND (dest.type = origin.type OR (dest.type IS NULL AND origin.type IS NULL))';
2464 $sql .= ' AND (dest.threshold = origin.threshold OR (dest.threshold IS NULL AND origin.threshold IS NULL))';
2465 $sql .= ' AND (dest.context = origin.context OR (dest.context IS NULL AND origin.context IS NULL))';
2466 $sql .= ' AND (dest.fk_user = origin.fk_user OR (dest.fk_user IS NULL AND origin.fk_user IS NULL))';
2467 $sql .= ' AND (dest.email = origin.email OR (dest.email IS NULL AND origin.email IS NULL))';
2468 $sql .= ' WHERE origin.fk_contact = '.((int) $contact_origin->id).' AND dest.fk_contact = '.((int) $this->id);
2469 $sql .= ' ) as x)';
2470 if (!$this->db->query($sql)) {
2471 $this->error = $this->db->lasterror();
2472 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2473 return 1;
2474 }
2475
2476 if (!CommonObject::commonReplaceContact($this->db, $contact_origin->id, $this->id, array('notify', 'notify_def'), 'fk_contact')) {
2477 dol_syslog(__METHOD__.' Failed to move the notifications: '.$this->db->lasterror(), LOG_ERR);
2478 return 1;
2479 }
2480
2481 // Mass emailing targets
2482 if ($this->db->DDLListTables((string) $this->db->database_name, $this->db->prefix().'mailing_cibles')) {
2483 // No deduplication here: uk_mailing_cibles is (fk_mailing, email), it does not contain
2484 // fk_contact, so moving fk_contact cannot violate it, and the rows hold the send history.
2485 if (!CommonObject::commonReplaceContact($this->db, $contact_origin->id, $this->id, array('mailing_cibles'), 'fk_contact')) {
2486 dol_syslog(__METHOD__.' Failed to move the mass emailing targets: '.$this->db->lasterror(), LOG_ERR);
2487 return 1;
2488 }
2489
2490 // The source of a target is also stored as a (source_type, source_id) couple
2491 $sql = 'UPDATE '.$this->db->prefix().'mailing_cibles SET source_id = '.((int) $this->id);
2492 $sql .= " WHERE source_type = 'contact' AND source_id = ".((int) $contact_origin->id);
2493 if (!$this->db->query($sql)) {
2494 $this->error = $this->db->lasterror();
2495 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
2496 return 1;
2497 }
2498 }
2499
2500 return $error;
2501 }
2502
2511 private function mergeContactFiles($contact_origin_id)
2512 {
2513 global $conf, $langs;
2514
2515 if (empty($conf->societe->multidir_output[$this->entity])) {
2516 return;
2517 }
2518
2519 // files.lib.php is not loaded when the merge is called outside of a web context
2520 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2521
2522 // The id is used and not $this->ref, which is null unless the contact was loaded by fetch()
2523 $srcdir = $conf->societe->multidir_output[$this->entity].'/contact/'.((int) $contact_origin_id);
2524 $destdir = $conf->societe->multidir_output[$this->entity].'/contact/'.((int) $this->id);
2525
2526 if (!dol_is_dir($srcdir)) {
2527 return;
2528 }
2529
2530 $failed = array();
2531 $dirlist = dol_dir_list($srcdir, 'files', 1);
2532 foreach ($dirlist as $filetomove) {
2533 $destfile = $destdir.'/'.$filetomove['relativename'];
2534 // dol_move() is called below with $overwriteifexists = 0, so a file already existing on
2535 // the target contact is renamed rather than lost
2536 if (dol_is_file($destfile)) {
2537 $info = pathinfo($filetomove['relativename']);
2538 $suffix = (empty($info['extension']) ? '' : '.'.$info['extension']);
2539 $destfile = $destdir.'/'.(empty($info['dirname']) || $info['dirname'] == '.' ? '' : $info['dirname'].'/');
2540 $destfile .= $info['filename'].'-'.((int) $contact_origin_id).$suffix;
2541 }
2542 // dol_move() does not create the target directory, and the target contact usually has
2543 // none yet, so it has to be created for every level of the source tree
2544 dol_mkdir(dirname($destfile));
2545 if (!dol_move($filetomove['fullname'], $destfile, '0', 0, 0, 1)) {
2546 $failed[] = $filetomove['relativename'];
2547 }
2548 }
2549
2550 if (!empty($failed)) {
2551 dol_syslog(__METHOD__.' Failed to move '.count($failed).' file(s) from '.$srcdir, LOG_ERR);
2552 // The merge itself is committed, so this is reported as a warning and not as a failure
2553 $this->warnings[] = $langs->trans('WarningContactsMergeFilesNotMoved', implode(', ', $failed));
2554 }
2555 }
2556
2564 public function fetchRoles()
2565 {
2566 global $langs;
2567 $error = 0;
2568 $num = 0;
2569
2570 $sql = "SELECT tc.rowid, tc.element, tc.source, tc.code, tc.libelle as label, sc.rowid as contactroleid, sc.fk_soc as socid";
2571 $sql .= " FROM ".MAIN_DB_PREFIX."societe_contacts as sc, ".MAIN_DB_PREFIX."c_type_contact as tc";
2572 $sql .= " WHERE tc.rowid = sc.fk_c_type_contact";
2573 $sql .= " AND tc.source = 'external' AND tc.active=1";
2574 $sql .= " AND sc.fk_socpeople = ".((int) $this->id);
2575 $sql .= " AND sc.entity IN (".getEntity('societe').')';
2576
2577 $resql = $this->db->query($sql);
2578 if ($resql) {
2579 $this->roles = array();
2580
2581 $num = $this->db->num_rows($resql);
2582 if ($num > 0) {
2583 while ($obj = $this->db->fetch_object($resql)) {
2584 $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
2585 $label_element = $langs->trans('ContactDefault_'.$obj->element);
2586 $this->roles[$obj->contactroleid] = array('id' => $obj->rowid, 'socid' => $obj->socid, 'element' => $obj->element, 'source' => $obj->source, 'code' => $obj->code, 'label' => $label_element.' - '.($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->label));
2587 }
2588 }
2589 } else {
2590 $error++;
2591 $this->error = $this->db->lasterror();
2592 $this->errors[] = $this->db->lasterror();
2593 }
2594
2595 if (empty($error)) {
2596 return $num;
2597 } else {
2598 return $error * -1;
2599 }
2600 }
2601
2609 public function getContactRoles($element = '')
2610 {
2611 $tab = array();
2612
2613 if ($element == 'action') {
2614 $element = 'agenda';
2615 }
2616
2617 $sql = "SELECT sc.fk_socpeople as id, sc.fk_c_type_contact";
2618 $sql .= " FROM ".MAIN_DB_PREFIX."c_type_contact tc";
2619 $sql .= ", ".MAIN_DB_PREFIX."societe_contacts sc";
2620 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."socpeople sp";
2621 $sql .= " ON sc.fk_socpeople = sp.rowid AND sp.statut = 1";
2622 $sql .= " WHERE sc.fk_soc = ".((int) $this->socid);
2623 $sql .= " AND sc.fk_c_type_contact=tc.rowid";
2624 $sql .= " AND tc.element = '".$this->db->escape($element)."'";
2625 $sql .= " AND sp.entity IN (".getEntity('contact').")";
2626 $sql .= " AND tc.active = 1";
2627
2628 $resql = $this->db->query($sql);
2629 if ($resql) {
2630 $num = $this->db->num_rows($resql);
2631 $i = 0;
2632 while ($i < $num) {
2633 $obj = $this->db->fetch_object($resql);
2634 $tab[$obj->id] = array('fk_socpeople' => $obj->id, 'type_contact' => $obj->fk_c_type_contact);
2635
2636 $i++;
2637 }
2638
2639 return $tab;
2640 } else {
2641 $this->error = $this->db->error();
2642 dol_print_error($this->db);
2643 return -1;
2644 }
2645 }
2646
2654 public function updateRoles()
2655 {
2656 global $conf;
2657
2658 $error = 0;
2659
2660 if (!isset($this->roles)) {
2661 return 0; // Avoid to loose roles when property not set
2662 }
2663
2664 $this->db->begin();
2665
2666 $sql = "DELETE FROM ".MAIN_DB_PREFIX."societe_contacts WHERE fk_socpeople=".((int) $this->id)." AND entity IN (".getEntity("contact").")";
2667
2668 $result = $this->db->query($sql);
2669 if (!$result) {
2670 $this->errors[] = $this->db->lasterror().' sql='.$sql;
2671 $error++;
2672 } else {
2673 if (count($this->roles) > 0) {
2674 foreach ($this->roles as $keyRoles => $valRoles) {
2675 if (empty($valRoles)) {
2676 continue;
2677 }
2678 $idrole = 0;
2679 if (is_array($valRoles)) {
2680 $idrole = $valRoles['id'];
2681 } else {
2682 $idrole = $valRoles;
2683 }
2684
2685 $socid = 0;
2686 if (is_array($valRoles)) {
2687 $socid = $valRoles['socid'];
2688 } else {
2689 $socid = $this->socid;
2690 }
2691
2692 if ($socid > 0) {
2693 $sql = "INSERT INTO ".MAIN_DB_PREFIX."societe_contacts";
2694 $sql .= " (entity,";
2695 $sql .= "date_creation,";
2696 $sql .= "fk_soc,";
2697 $sql .= "fk_c_type_contact,";
2698 $sql .= "fk_socpeople) ";
2699 $sql .= " VALUES (".((int) $conf->entity).",";
2700 $sql .= "'".$this->db->idate(dol_now())."',";
2701 $sql .= ((int) $socid).", ";
2702 $sql .= ((int) $idrole)." , ";
2703 $sql .= ((int) $this->id);
2704 $sql .= ")";
2705
2706 $result = $this->db->query($sql);
2707 if (!$result) {
2708 $this->errors[] = $this->db->lasterror().' sql='.$sql;
2709 $error++;
2710 }
2711 }
2712 }
2713 }
2714 }
2715 if (empty($error)) {
2716 $this->db->commit();
2717 return 1;
2718 } else {
2719 $this->error = implode(' ', $this->errors);
2720 $this->db->rollback();
2721 return $error * -1;
2722 }
2723 }
2724
2731 public function loadCacheOfProspStatus($active = 1)
2732 {
2733 global $langs;
2734
2735 $sql = "SELECT id, code, libelle as label, picto FROM ".MAIN_DB_PREFIX."c_stcommcontact";
2736 if ($active >= 0) {
2737 $sql .= " WHERE active = ".((int) $active);
2738 }
2739 $resql = $this->db->query($sql);
2740 $num = $this->db->num_rows($resql);
2741 $i = 0;
2742 while ($i < $num) {
2743 $obj = $this->db->fetch_object($resql);
2744 $this->cacheprospectstatus[$obj->id] = array('id' => $obj->id, 'code' => $obj->code, 'label' => ($langs->trans("ST_".strtoupper($obj->code)) == "ST_".strtoupper($obj->code)) ? $obj->label : $langs->trans("ST_".strtoupper($obj->code)), 'picto' => $obj->picto);
2745 $i++;
2746 }
2747 return 1;
2748 }
2749
2755 public function getLibProspLevel()
2756 {
2757 return $this->libProspLevel($this->fk_prospectlevel);
2758 }
2759
2766 public function libProspLevel($fk_prospectlevel)
2767 {
2768 global $langs;
2769
2770 $lib = $langs->trans("ProspectLevel".$fk_prospectlevel);
2771 // If lib not found in language file, we get label from cache/database
2772 if ($lib == "ProspectLevel".$fk_prospectlevel) {
2773 $lib = $langs->getLabelFromKey($this->db, $fk_prospectlevel, 'c_prospectlevel', 'code', 'label');
2774 }
2775 return $lib;
2776 }
2777
2778
2786 public function setProspectLevel(User $user)
2787 {
2788 return $this->update($this->id, $user);
2789 }
2790
2798 public function getLibProspCommStatut($mode = 0, $label = '')
2799 {
2800 return $this->libProspCommStatut($this->stcomm_id, $mode, $label, $this->stcomm_picto);
2801 }
2802
2816 public function libProspCommStatut($statut, $mode = 0, $label = '', $picto = '')
2817 {
2818 global $langs;
2819 $langs->load('customers');
2820
2821 if ($mode == 2) {
2822 if ($statut == '-1' || $statut == 'ST_NO') {
2823 return img_action($langs->trans("StatusProspect-1"), '-1', $picto).' '.$langs->trans("StatusProspect-1");
2824 } elseif ($statut == '0' || $statut == 'ST_NEVER') {
2825 return img_action($langs->trans("StatusProspect0"), '0', $picto).' '.$langs->trans("StatusProspect0");
2826 } elseif ($statut == '1' || $statut == 'ST_TODO') {
2827 return img_action($langs->trans("StatusProspect1"), '1', $picto).' '.$langs->trans("StatusProspect1");
2828 } elseif ($statut == '2' || $statut == 'ST_PEND') {
2829 return img_action($langs->trans("StatusProspect2"), '2', $picto).' '.$langs->trans("StatusProspect2");
2830 } elseif ($statut == '3' || $statut == 'ST_DONE') {
2831 return img_action($langs->trans("StatusProspect3"), '3', $picto).' '.$langs->trans("StatusProspect3");
2832 } else {
2833 return img_action(($langs->trans("StatusProspect".$statut) != "StatusProspect".$statut) ? $langs->trans("StatusProspect".$statut) : $label, '0', $picto).' '.(($langs->trans("StatusProspect".$statut) != "StatusProspect".$statut) ? $langs->trans("StatusProspect".$statut) : $label);
2834 }
2835 }
2836 if ($mode == 3) {
2837 if ($statut == '-1' || $statut == 'ST_NO') {
2838 return img_action($langs->trans("StatusProspect-1"), '-1', $picto);
2839 } elseif ($statut == '0' || $statut == 'ST_NEVER') {
2840 return img_action($langs->trans("StatusProspect0"), '0', $picto);
2841 } elseif ($statut == '1' || $statut == 'ST_TODO') {
2842 return img_action($langs->trans("StatusProspect1"), '1', $picto);
2843 } elseif ($statut == '2' || $statut == 'ST_PEND') {
2844 return img_action($langs->trans("StatusProspect2"), '2', $picto);
2845 } elseif ($statut == '3' || $statut == 'ST_DONE') {
2846 return img_action($langs->trans("StatusProspect3"), '3', $picto);
2847 } else {
2848 return img_action(($langs->trans("StatusProspect".$statut) != "StatusProspect".$statut) ? $langs->trans("StatusProspect".$statut) : $label, '0', $picto);
2849 }
2850 }
2851 if ($mode == 4) {
2852 if ($statut == '-1' || $statut == 'ST_NO') {
2853 return img_action($langs->trans("StatusProspect-1"), '-1', $picto).' '.$langs->trans("StatusProspect-1");
2854 } elseif ($statut == '0' || $statut == 'ST_NEVER') {
2855 return img_action($langs->trans("StatusProspect0"), '0', $picto).' '.$langs->trans("StatusProspect0");
2856 } elseif ($statut == '1' || $statut == 'ST_TODO') {
2857 return img_action($langs->trans("StatusProspect1"), '1', $picto).' '.$langs->trans("StatusProspect1");
2858 } elseif ($statut == '2' || $statut == 'ST_PEND') {
2859 return img_action($langs->trans("StatusProspect2"), '2', $picto).' '.$langs->trans("StatusProspect2");
2860 } elseif ($statut == '3' || $statut == 'ST_DONE') {
2861 return img_action($langs->trans("StatusProspect3"), '3', $picto).' '.$langs->trans("StatusProspect3");
2862 } else {
2863 return img_action(($langs->trans("StatusProspect".$statut) != "StatusProspect".$statut) ? $langs->trans("StatusProspect".$statut) : $label, '0', $picto).' '.(($langs->trans("StatusProspect".$statut) != "StatusProspect".$statut) ? $langs->trans("StatusProspect".$statut) : $label);
2864 }
2865 }
2866
2867 return "Error, mode/status not found";
2868 }
2869
2870
2877 public function setNoEmail($no_email)
2878 {
2879 $error = 0;
2880
2881 // Update mass emailing flag into table mailing_unsubscribe
2882 if ($this->email) {
2883 $this->db->begin();
2884
2885 if ($no_email) {
2886 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE entity IN (".getEntity('mailing', 0).") AND email = '".$this->db->escape($this->email)."'";
2887 $resql = $this->db->query($sql);
2888 if ($resql) {
2889 $obj = $this->db->fetch_object($resql);
2890 $noemail = $obj->nb;
2891 if (empty($noemail)) {
2892 $sql = "INSERT INTO ".MAIN_DB_PREFIX."mailing_unsubscribe(email, entity, date_creat) VALUES ('".$this->db->escape($this->email)."', ".getEntity('mailing', 0).", '".$this->db->idate(dol_now())."')";
2893 $resql = $this->db->query($sql);
2894 if (!$resql) {
2895 $error++;
2896 $this->error = $this->db->lasterror();
2897 $this->errors[] = $this->error;
2898 }
2899 }
2900 } else {
2901 $error++;
2902 $this->error = $this->db->lasterror();
2903 $this->errors[] = $this->error;
2904 }
2905 } else {
2906 $sql = "DELETE FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE email = '".$this->db->escape($this->email)."' AND entity IN (".getEntity('mailing', 0).")";
2907 $resql = $this->db->query($sql);
2908 if (!$resql) {
2909 $error++;
2910 $this->error = $this->db->lasterror();
2911 $this->errors[] = $this->error;
2912 }
2913 }
2914
2915 if (empty($error)) {
2916 $this->no_email = $no_email;
2917 $this->db->commit();
2918 return 1;
2919 } else {
2920 $this->db->rollback();
2921 return $error * -1;
2922 }
2923 }
2924
2925 return 0;
2926 }
2927
2934 public function getNoEmail()
2935 {
2936 if ($this->email) {
2937 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."mailing_unsubscribe WHERE entity IN (".getEntity('mailing').") AND email = '".$this->db->escape($this->email)."'";
2938 $resql = $this->db->query($sql);
2939 if ($resql) {
2940 $obj = $this->db->fetch_object($resql);
2941 $this->no_email = $obj->nb;
2942 return 1;
2943 } else {
2944 $this->error = $this->db->lasterror();
2945 $this->errors[] = $this->error;
2946 return -1;
2947 }
2948 }
2949 return 0;
2950 }
2951
2952
2960 public function getKanbanView($option = '', $arraydata = null)
2961 {
2962 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2963
2964 $return = '<div class="box-flex-item box-flex-grow-zero">';
2965 $return .= '<div class="info-box info-box-sm">';
2966 $return .= '<span class="info-box-icon bg-infobox-action">';
2967 if (!is_null($this->photo)) {
2968 $return .= Form::showphoto('contact', $this, 0, 60, 0, 'photokanban photoref photowithmargin photologintooltip', 'small', 0, 1);
2969 } else {
2970 $return .= img_picto('', $this->picto);
2971 }
2972 $return .= '</span>';
2973 $return .= '<div class="info-box-content">';
2974 $return .= '<div class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(0) . '</div>';
2975 if ($selected >= 0) {
2976 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2977 }
2978 if (is_object($this->thirdparty)) {
2979 $return .= '<div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2980 }
2981 /*if (property_exists($this, 'phone_pro') && !empty($this->phone_pro)) {
2982 $return .= '<br>'.img_picto($langs->trans("Phone"), 'phone');
2983 $return .= ' <span class="info-box-label">'.$this->phone_pro.'</span>';
2984 }*/
2985 /*if (method_exists($this, 'LibPubPriv')) {
2986 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Visibility").'</span>';
2987 $return .= '<span> : '.$this->LibPubPriv($this->priv).'</span>';
2988 }*/
2989 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2990 $return .= '</div>';
2991 $return .= '</div>';
2992 $return .= '</div>';
2993 return $return;
2994 }
2995}
$object ref
Definition info.php:90
Class to manage categories.
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.
static commonReplaceContact(DoliDB $dbs, $origin_id, $dest_id, array $tables, $fieldname='fk_socpeople', $ignoreerrors=0)
Function used to replace a contact id with another one.
Class to manage contact/addresses.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
__construct($db)
Constructor.
update_perso($id, $user=null, $notrigger=0)
Update field alert birthday.
getLibProspCommStatut($mode=0, $label='')
Return status of prospect.
getParentId($contactid)
Return the id of the parent contact of a contact, 0 if none.
$fields
'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter...
create($user, $notrigger=0)
Add a contact into database.
loadStateBoard()
Load indicators into this->nb for board.
static replaceContact(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a contact id with another one when merging two contacts.
getNoEmail()
get "blacklist" mailing status set no_email attribute to 1 or 0
getNomUrl($withpicto=0, $option='', $notooltip=0, $moreparam='', $save_lastsearch_value=-1, $maxlen=0, $morecss='valignmiddle')
Return name of contact with link (and eventually picto) Use $this->id, $this->lastname,...
getNbOfEMailings()
Return number of mass Emailing received by these contacts with its email.
isLinkedToUser($contactid)
Tell whether a user account is linked to the given contact.
libProspCommStatut($statut, $mode=0, $label='', $picto='')
Return label of a given status.
getTooltipContentArray($params)
getTooltipContentArray
mergeContactOptionalTables($contact_origin)
Move the data stored by the optional modules and by the notification system.
libProspLevel($fk_prospectlevel)
Return label of prospect level.
getCivilityLabel()
Return civility label of contact.
setProspectLevel(User $user)
Set prospect level.
fetch($id, $user=null, $ref_ext='', $email='', $loadalsoroles=0, $socid=0)
Load object contact.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
load_ref_elements()
Load number of elements the contact is used as a link for ref_facturation ref_contrat ref_commande (f...
getLibProspLevel()
Return prospect level.
fetchRoles()
Fetch roles (default contact of some companies) for the current contact.
isSalesRepresentativeOf($socid)
Tell whether the current user is a sales representative of the given third party.
setCategories($categories, $remove_existing=true)
Sets object to supplied categories.
getContactRoles($element='')
Get thirdparty contact roles of a given contact.
_load_ldap_dn($info, $mode=0)
Return DN string complete in the LDAP directory for the object.
LibStatut($status, $mode)
Return the label of a given status.
setGenderFromCivility()
Set the property "gender" of this class, based on the property "civility_id" or use property "civilit...
update($id, $user=null, $notrigger=0, $action='update', $nosyncuser=0)
Update information into database.
LibPubPriv($status, $decorate=0)
Return translated label of Public or Private.
setstatus($status)
Change status of a user.
loadCacheOfProspStatus($active=1)
Load array of prospect status.
mergeContact($contact_origin_id)
Merge a contact with the current one, deleting the given contact $contact_origin_id.
_load_ldap_info()
Initialize info table (LDAP attributes table)
mergeContactFiles($contact_origin_id)
Move the files of the merged contact into the directory of the target contact.
updateRoles()
Updates all roles (default contact for companies) according to values inside the ->roles array.
info($id)
Load contact information from the database.
initAsSpecimen()
Initialise an instance with random values.
getLibStatut($mode)
Return the label of the status.
setNoEmail($no_email)
Set "blacklist" mailing status.
mergeContactChildren($contact_origin)
Move the children of the merged contact to the target contact.
findNearest($id=0, $lastname='', $firstname='', $ref_ext='', $email='', $ref_alias='', $socid=0)
Search the contact that match the most the provided parameters.
Class to manage Dolibarr database access.
prefix()
Return the DB prefix found into prefix_db (if it was set manually by doing $dbhandler->prefix_db=....
sanitize($stringtosanitize, $allowsimplequote=0, $allowsequals=0, $allowsspace=0, $allowschars=1)
Sanitize a string for SQL forging.
static showphoto($modulepart, $object, $width=100, $height=0, $caneditfield=0, $cssclass='photowithmargin', $imagesize='', $addlinktofullsize=1, $cache=0, $forcecapture='', $noexternsourceoverwrite=0, $usesharelinkifavailable=0)
Return HTML code to output a photo.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
getFullName($langs, $option=0, $nameorder=-1, $maxlen=0)
Return full name (civility+' '+name+' '+lastname)
setUpperOrLowerCase()
Set to upper or ucwords/lower if needed.
trait CommonSocialNetworks
Superclass for social networks.
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as p label as s rowid as s nom as s email
Sender: Who sends the email ("Sender" has sent emails on behalf of "From").
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_move($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=1, $moreinfo=array(), $entity=null)
Move a file into another name.
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dol_is_dir($folder)
Test if filename is a directory.
dol_now($mode='gmt')
Return date for now.
setEntity($currentobject)
Set entity id to use when to create an object.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
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.
isModEnabled($module)
Is Dolibarr module enabled.
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)
div refaddress div address
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_action($titlealt, $numaction, $picto='', $moreatt='')
Show logo action.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
DDLListTables($database, $table='')
List tables into a database.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133