dolibarr 24.0.1
emailcollector.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2026 Vincent de Grandpré <vincent@de-grandpre.quebec>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
27// Put here all includes required by your class file
28include_once DOL_DOCUMENT_ROOT .'/emailcollector/lib/emailcollector.lib.php';
29
30require_once DOL_DOCUMENT_ROOT .'/core/class/commonobject.class.php';
31require_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
32require_once DOL_DOCUMENT_ROOT .'/core/lib/functions2.lib.php';
33
34require_once DOL_DOCUMENT_ROOT .'/comm/propal/class/propal.class.php'; // Customer Proposal
35require_once DOL_DOCUMENT_ROOT .'/commande/class/commande.class.php'; // Sale Order
36require_once DOL_DOCUMENT_ROOT .'/compta/facture/class/facture.class.php'; // Customer Invoice
37require_once DOL_DOCUMENT_ROOT .'/contact/class/contact.class.php'; // Contact / Address
38require_once DOL_DOCUMENT_ROOT .'/expedition/class/expedition.class.php'; // Shipping / Delivery
39require_once DOL_DOCUMENT_ROOT .'/fourn/class/fournisseur.commande.class.php'; // Purchase Order
40require_once DOL_DOCUMENT_ROOT .'/fourn/class/fournisseur.facture.class.php'; // Purchase Invoice
41require_once DOL_DOCUMENT_ROOT .'/projet/class/project.class.php'; // Project
42require_once DOL_DOCUMENT_ROOT .'/reception/class/reception.class.php'; // Reception
43require_once DOL_DOCUMENT_ROOT .'/recruitment/class/recruitmentcandidature.class.php'; // Recruiting
44require_once DOL_DOCUMENT_ROOT .'/societe/class/societe.class.php'; // Third-Party
45require_once DOL_DOCUMENT_ROOT .'/supplier_proposal/class/supplier_proposal.class.php'; // Supplier Proposal
46require_once DOL_DOCUMENT_ROOT .'/ticket/class/ticket.class.php'; // Ticket
47require_once DOL_DOCUMENT_ROOT .'/adherents/class/adherent.class.php'; // Member/Adherent
48//require_once DOL_DOCUMENT_ROOT .'/expensereport/class/expensereport.class.php'; // Expense Report
49//require_once DOL_DOCUMENT_ROOT .'/holiday/class/holiday.class.php'; // Holidays (leave request)
50
51
52use Webklex\PHPIMAP\ClientManager;
53use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
54use Webklex\PHPIMAP\Exceptions\InvalidWhereQueryCriteriaException;
55use OAuth\Common\Storage\DoliStorage;
56use OAuth\Common\Consumer\Credentials;
57
62{
66 public $element = 'emailcollector';
67
71 public $table_element = 'emailcollector_emailcollector';
72
76 public $picto = 'email';
77
81 public $fk_element = 'fk_emailcollector';
82
86 protected $childtables = array();
87
91 protected $childtablesoncascade = array('emailcollector_emailcollectorfilter', 'emailcollector_emailcollectoraction');
92
93
113 // BEGIN MODULEBUILDER PROPERTIES
117 public $fields = array(
118 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'visible' => -2, 'enabled' => 1, 'position' => 1, 'notnull' => 1, 'index' => 1),
119 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 20),
120 'ref' => array('type' => 'varchar(128)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 1, 'index' => 1, 'position' => 10, 'searchall' => 1, 'help' => 'Example: MyCollector1', 'csslist' => 'tdoverflowmax200'),
121 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'visible' => 1, 'enabled' => 1, 'position' => 30, 'notnull' => -1, 'searchall' => 1, 'help' => 'Example: My Email collector', 'csslist' => 'tdoverflowmax150 small', 'tdcss' => 'titlefieldmiddle'),
122 'description' => array('type' => 'text', 'label' => 'Description', 'visible' => -1, 'enabled' => 1, 'position' => 60, 'notnull' => -1, 'cssview' => 'small', 'csslist' => 'small tdoverflowmax200'),
123 'host' => array('type' => 'varchar(255)', 'label' => 'EMailHost', 'visible' => 1, 'enabled' => 1, 'position' => 90, 'notnull' => 1, 'searchall' => 1, 'comment' => "IMAP server", 'help' => 'Example: imap.gmail.com', 'csslist' => 'tdoverflowmax125'),
124 'port' => array('type' => 'varchar(10)', 'label' => 'EMailHostPort', 'visible' => 1, 'enabled' => 1, 'position' => 91, 'notnull' => 1, 'searchall' => 0, 'comment' => "IMAP server port", 'help' => 'Example: 993', 'csslist' => 'tdoverflowmax50', 'default' => '993'),
125 'imap_encryption' => array('type' => 'varchar(16)', 'label' => 'ImapEncryption', 'visible' => -1, 'enabled' => 1, 'position' => 92, 'searchall' => 0, 'comment' => "IMAP encryption", 'help' => 'ImapEncryptionHelp', 'arrayofkeyval' => array('ssl' => 'SSL', 'tls' => 'TLS', 'notls' => 'NOTLS'), 'default' => 'ssl'),
126 'hostcharset' => array('type' => 'varchar(16)', 'label' => 'HostCharset', 'visible' => -1, 'enabled' => 1, 'position' => 93, 'notnull' => 0, 'searchall' => 0, 'comment' => "IMAP server charset", 'help' => 'Example: "UTF-8" (May be "US-ASCII" with some Office365)', 'default' => 'UTF-8'),
127 'norsh' => array('type' => 'integer', 'label' => 'NoRSH', 'visible' => -1, 'enabled' => "!getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')", 'position' => 94, 'searchall' => 0, 'help' => 'NoRSHHelp', 'arrayofkeyval' => array(0 => 'No', 1 => 'Yes'), 'default' => '0'),
128 'acces_type' => array('type' => 'integer', 'label' => 'AuthenticationMethod', 'visible' => -1, 'enabled' => "getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')", 'position' => 101, 'notnull' => 1, 'index' => 1, 'comment' => "IMAP login type", 'arrayofkeyval' => array(0 => 'loginPassword', 1 => 'oauthToken'), 'default' => '0', 'help' => ''),
129 'login' => array('type' => 'varchar(128)', 'label' => 'Login', 'visible' => -1, 'enabled' => 1, 'position' => 102, 'notnull' => -1, 'index' => 1, 'comment' => "IMAP login", 'help' => 'Example: myaccount@gmail.com'),
130 'password' => array('type' => 'password', 'label' => 'Password', 'visible' => -1, 'enabled' => "1", 'position' => 103, 'notnull' => -1, 'comment' => "IMAP password", 'help' => 'WithGMailYouCanCreateADedicatedPassword'),
131 'oauth_service' => array('type' => 'varchar(128)', 'label' => 'oauthService', 'visible' => -1, 'enabled' => "getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')", 'position' => 104, 'notnull' => 0, 'index' => 1, 'comment' => "IMAP login oauthService", 'arrayofkeyval' => array(), 'help' => 'TokenMustHaveBeenCreated'),
132 'source_directory' => array('type' => 'varchar(255)', 'label' => 'MailboxSourceDirectory', 'visible' => -1, 'enabled' => 1, 'position' => 109, 'notnull' => 1, 'default' => 'Inbox', 'csslist' => 'tdoverflowmax100', 'help' => 'Example: INBOX, [Gmail]/Spam, [Gmail]/Draft, [Gmail]/Brouillons, [Gmail]/Sent Mail, [Gmail]/Messages envoyés, ...'),
133 'target_directory' => array('type' => 'varchar(255)', 'label' => 'MailboxTargetDirectory', 'visible' => 1, 'enabled' => 1, 'position' => 110, 'notnull' => 0, 'csslist' => 'tdoverflowmax100', 'help' => "EmailCollectorTargetDir"),
134 'maxemailpercollect' => array('type' => 'integer', 'label' => 'MaxEmailCollectPerCollect', 'visible' => -1, 'enabled' => 1, 'position' => 111, 'default' => '50'),
135 'datelastresult' => array('type' => 'datetime', 'label' => 'DateLastCollectResult', 'visible' => 1, 'enabled' => '$action != "create" && $action != "edit"', 'position' => 121, 'notnull' => -1, 'csslist' => 'nowraponall'),
136 'codelastresult' => array('type' => 'varchar(16)', 'label' => 'CodeLastResult', 'visible' => 1, 'enabled' => '$action != "create" && $action != "edit"', 'position' => 122, 'notnull' => -1,),
137 'lastresult' => array('type' => 'varchar(255)', 'label' => 'LastResult', 'visible' => 1, 'enabled' => '$action != "create" && $action != "edit"', 'position' => 123, 'notnull' => -1, 'cssview' => 'small', 'csslist' => 'maxwidth250imp'),
138 'datelastok' => array('type' => 'datetime', 'label' => 'DateLastcollectResultOk', 'visible' => 1, 'enabled' => '$action != "create"', 'position' => 125, 'notnull' => -1, 'csslist' => 'nowraponall'),
139 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'visible' => 0, 'enabled' => 1, 'position' => 61, 'notnull' => -1,),
140 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'visible' => 0, 'enabled' => 1, 'position' => 62, 'notnull' => -1,),
141 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'visible' => -2, 'enabled' => 1, 'position' => 500, 'notnull' => 1,),
142 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'visible' => -2, 'enabled' => 1, 'position' => 501, 'notnull' => 1,),
143 //'date_validation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
144 'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'visible' => -2, 'enabled' => 1, 'position' => 510, 'notnull' => 1,),
145 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'visible' => -2, 'enabled' => 1, 'position' => 511, 'notnull' => -1,),
146 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
147 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'visible' => -2, 'enabled' => 1, 'position' => 1000, 'notnull' => -1,),
148 'status' => array('type' => 'integer', 'label' => 'Status', 'visible' => 1, 'enabled' => 1, 'position' => 1000, 'notnull' => 1, 'default' => '0', 'index' => 1, 'arrayofkeyval' => array(0 => 'Inactive', 1 => 'Active'))
149 );
150
151
155 public $rowid;
156
160 public $ref;
161
165 public $label;
166
170 public $description;
171
175 public $status;
176
180 public $fk_user_creat;
181
185 public $fk_user_modif;
186
190 public $import_key;
191
195 public $host;
199 public $port;
203 public $hostcharset;
207 public $login;
211 public $password;
215 public $acces_type;
219 public $oauth_service;
223 public $imap_encryption;
227 public $norsh;
231 public $source_directory;
235 public $target_directory;
239 public $maxemailpercollect;
240
244 public $datelastresult;
245
249 public $codelastresult;
253 public $lastresult;
257 public $datelastok;
258 // END MODULEBUILDER PROPERTIES
259
263 public $filters;
267 public $actions;
268
272 public $debuginfo;
273
274 const STATUS_DISABLED = 0;
275 const STATUS_ENABLED = 1;
276
277
283 public function __construct(DoliDB $db)
284 {
285 global $conf, $langs;
286
287 $this->db = $db;
288
289 $this->ismultientitymanaged = 1;
290 $this->isextrafieldmanaged = 0;
291
292 /*if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
293 $this->fields['rowid']['visible'] = 0;
294 }*/
295 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
296 $this->fields['entity']['enabled'] = 0;
297 }
298
299 // List of oauth services
300 $oauthservices = array();
301
302 foreach ($conf->global as $key => $val) {
303 if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) {
304 $key = preg_replace('/^OAUTH_/', '', $key);
305 $key = preg_replace('/_ID$/', '', $key);
306 if (preg_match('/^.*-/', $key)) {
307 $name = preg_replace('/^.*-/', '', $key);
308 } else {
309 $name = $langs->trans("NoName");
310 }
311 $provider = preg_replace('/-.*$/', '', $key);
312 $provider = ucfirst(strtolower($provider));
313
314 $oauthservices[$key] = $name." (".$provider.")";
315 }
316 }
317
318 $this->fields['oauth_service']['arrayofkeyval'] = $oauthservices;
319
320 // Unset fields that are disabled
321 foreach ($this->fields as $key => $val) {
322 if (isset($val['enabled']) && empty($val['enabled'])) {
323 unset($this->fields[$key]);
324 }
325 }
326
327 // Translate some data of arrayofkeyval
328 foreach ($this->fields as $key => $val) {
329 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
330 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
331 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
332 }
333 }
334 }
335 }
336
344 public function create(User $user, $notrigger = 0)
345 {
346 global $langs;
347
348 // Check parameters
349 if ($this->host && preg_match('/^http:/i', trim($this->host))) {
350 $langs->load("errors");
351 $this->error = $langs->trans("ErrorHostMustNotStartWithHttp", $this->host);
352 return -1;
353 }
354
355 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
356 $this->password = dolEncrypt($this->password);
357
358 $id = $this->createCommon($user, $notrigger);
359
360 $this->password = dolDecrypt($this->password);
361
362 if (is_array($this->filters) && count($this->filters)) {
363 $emailcollectorfilter = new EmailCollectorFilter($this->db);
364
365 foreach ($this->filters as $filter) {
366 $emailcollectorfilter->type = $filter['type'];
367 $emailcollectorfilter->rulevalue = $filter['rulevalue'];
368 $emailcollectorfilter->fk_emailcollector = $this->id;
369 $emailcollectorfilter->status = $filter['status'];
370
371 $emailcollectorfilter->create($user);
372 }
373 }
374
375 if (is_array($this->actions) && count($this->actions)) {
376 $emailcollectoroperation = new EmailCollectorAction($this->db);
377
378 foreach ($this->actions as $operation) {
379 $emailcollectoroperation->type = $operation['type'];
380 $emailcollectoroperation->actionparam = $operation['actionparam'];
381 $emailcollectoroperation->fk_emailcollector = $this->id;
382 $emailcollectoroperation->status = $operation['status'];
383 $emailcollectoroperation->position = $operation['position'];
384
385 $emailcollectoroperation->create($user);
386 }
387 }
388
389 return $id;
390 }
391
399 public function createFromClone(User $user, $fromid)
400 {
401 global $langs, $extrafields;
402 $error = 0;
403
404 dol_syslog(__METHOD__, LOG_DEBUG);
405
406 $object = new self($this->db);
407
408 $this->db->begin();
409
410 // Load source object
411 $object->fetchCommon($fromid);
412
413 $object->fetchFilters(); // Rules
414 $object->fetchActions(); // Operations
415
416 // Reset some properties
417 unset($object->id);
418 unset($object->fk_user_creat);
419 unset($object->import_key);
420 unset($object->password);
421 unset($object->lastresult);
422 unset($object->codelastresult);
423 unset($object->datelastresult);
424 unset($object->datelastok);
425 unset($object->debuginfo);
426
427 // Clear fields
428 $object->ref = "copy_of_".$object->ref;
429 $object->label = $langs->trans("CopyOf")." ".$object->label;
430 if (empty($object->host)) {
431 $object->host = 'imap.example.com';
432 }
433 // Clear extrafields that are unique
434 if (is_array($object->array_options) && count($object->array_options) > 0) {
435 $extrafields->fetch_name_optionals_label($this->table_element);
436 foreach ($object->array_options as $key => $option) {
437 $shortkey = preg_replace('/options_/', '', $key);
438 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
439 unset($object->array_options[$key]);
440 }
441 }
442 }
443
444 // Create clone
445 $object->context['createfromclone'] = 'createfromclone';
446 $result = $object->create($user);
447 if ($result < 0) {
448 $error++;
450 }
451
452 unset($object->context['createfromclone']);
453
454 // End
455 if (!$error) {
456 $this->db->commit();
457 return $object;
458 } else {
459 $this->db->rollback();
460 return -1;
461 }
462 }
463
471 public function fetch($id, $ref = null)
472 {
473 $result = $this->fetchCommon($id, $ref);
474
475 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
476 $this->password = dolDecrypt($this->password);
477
478 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
479 return $result;
480 }
481
487 /*
488 public function fetchLines()
489 {
490 $this->lines=array();
491
492 // Load lines with object EmailCollectorLine
493
494 return count($this->lines)?1:0;
495 }
496 */
497
509 public function fetchAll(User $user, $activeOnly = 0, $sortfield = 's.rowid', $sortorder = 'ASC', $limit = 100, $page = 0)
510 {
511 $obj_ret = array();
512
513 $sql = "SELECT s.rowid";
514 $sql .= " FROM ".MAIN_DB_PREFIX."emailcollector_emailcollector as s";
515 $sql .= ' WHERE s.entity IN ('.getEntity('emailcollector').')';
516 if ($activeOnly) {
517 $sql .= " AND s.status = 1";
518 }
519 $sql .= $this->db->order($sortfield, $sortorder);
520 if ($limit) {
521 if ($page < 0) {
522 $page = 0;
523 }
524 $offset = $limit * $page;
525
526 $sql .= $this->db->plimit($limit + 1, $offset);
527 }
528
529 $result = $this->db->query($sql);
530 if ($result) {
531 $num = $this->db->num_rows($result);
532 $i = 0;
533 while ($i < $num) {
534 $obj = $this->db->fetch_object($result);
535 $emailcollector_static = new EmailCollector($this->db);
536 if ($emailcollector_static->fetch($obj->rowid)) {
537 $obj_ret[] = $emailcollector_static;
538 }
539 $i++;
540 }
541 } else {
542 $this->errors[] = 'EmailCollector::fetchAll Error when retrieve emailcollector list';
543 dol_syslog('EmailCollector::fetchAll Error when retrieve emailcollector list', LOG_ERR);
544 }
545 if (!count($obj_ret)) {
546 dol_syslog('EmailCollector::fetchAll No emailcollector found', LOG_DEBUG);
547 }
548
549 return $obj_ret;
550 }
551
559 public function update(User $user, $notrigger = 0)
560 {
561 global $langs;
562
563 // Check parameters
564 if ($this->host && preg_match('/^http:/i', trim($this->host))) {
565 $langs->load("errors");
566 $this->error = $langs->trans("ErrorHostMustNotStartWithHttp", $this->host);
567 return -1;
568 }
569
570 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
571 $this->password = dolEncrypt($this->password);
572
573 $result = $this->updateCommon($user, $notrigger);
574
575 $this->password = dolDecrypt($this->password);
576
577 return $result;
578 }
579
587 public function delete(User $user, $notrigger = 0)
588 {
589 return $this->deleteCommon($user, $notrigger, 1);
590 }
591
602 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
603 {
604 global $conf, $langs, $action, $hookmanager;
605
606 if (!empty($conf->dol_no_mouse_hover)) {
607 $notooltip = 1; // Force disable tooltips
608 }
609
610 $result = '';
611
612 $label = '<u>'.$langs->trans("EmailCollector").'</u>';
613 $label .= '<br>';
614 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
615
616 $url = DOL_URL_ROOT.'/admin/emailcollector_card.php?id='.$this->id;
617
618 if ($option != 'nolink') {
619 // Add param to save lastsearch_values or not
620 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
621 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
622 $add_save_lastsearch_values = 1;
623 }
624 if ($add_save_lastsearch_values) {
625 $url .= '&save_lastsearch_values=1';
626 }
627 }
628
629 $linkclose = '';
630 if (empty($notooltip)) {
631 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
632 $label = $langs->trans("ShowEmailCollector");
633 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
634 }
635 $linkclose .= ' title="'.dolPrintHTMLForAttribute($label).'"';
636 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
637 } else {
638 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
639 }
640
641 $linkstart = '<a href="'.$url.'"';
642 $linkstart .= $linkclose.'>';
643 $linkend = '</a>';
644
645 $result .= $linkstart;
646 if ($withpicto) {
647 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
648 }
649 if ($withpicto != 2) {
650 $result .= $this->ref;
651 }
652 $result .= $linkend;
653 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
654
655 $hookmanager->initHooks(array('emailcollectordao'));
656 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
657 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
658 if ($reshook > 0) {
659 $result = $hookmanager->resPrint;
660 } else {
661 $result .= $hookmanager->resPrint;
662 }
663
664 return $result;
665 }
666
673 public function getLibStatut($mode = 0)
674 {
675 return $this->LibStatut($this->status, $mode);
676 }
677
678 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
686 public function LibStatut($status, $mode = 0)
687 {
688 // phpcs:enable
689 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
690 global $langs;
691 //$langs->load("mymodule");
692 $this->labelStatus[self::STATUS_ENABLED] = $langs->transnoentitiesnoconv('Enabled');
693 $this->labelStatus[self::STATUS_DISABLED] = $langs->transnoentitiesnoconv('Disabled');
694 $this->labelStatusShort[self::STATUS_ENABLED] = $langs->transnoentitiesnoconv('Enabled');
695 $this->labelStatusShort[self::STATUS_DISABLED] = $langs->transnoentitiesnoconv('Disabled');
696 }
697
698 $statusType = 'status5';
699 if ($status == self::STATUS_ENABLED) {
700 $statusType = 'status4';
701 }
702
703 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
704 }
705
712 public function info($id)
713 {
714 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
715 $sql .= ' fk_user_creat, fk_user_modif';
716 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
717 $sql .= ' WHERE t.rowid = '.((int) $id);
718 $result = $this->db->query($sql);
719 if ($result) {
720 if ($this->db->num_rows($result)) {
721 $obj = $this->db->fetch_object($result);
722
723 $this->id = $obj->rowid;
724
725 $this->user_creation_id = $obj->fk_user_creat;
726 $this->user_modification_id = $obj->fk_user_modif;
727 $this->date_creation = $this->db->jdate($obj->datec);
728 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
729 }
730
731 $this->db->free($result);
732 } else {
733 dol_print_error($this->db);
734 }
735 }
736
743 public function initAsSpecimen()
744 {
745 $this->host = 'localhost';
746 $this->login = 'alogin';
747
748 return $this->initAsSpecimenCommon();
749 }
750
757 public function fetchFilters()
758 {
759 $this->filters = array();
760
761 $sql = 'SELECT rowid, type, rulevalue, status';
762 $sql .= ' FROM '.MAIN_DB_PREFIX.'emailcollector_emailcollectorfilter';
763 $sql .= ' WHERE fk_emailcollector = '.((int) $this->id);
764 //$sql.= ' ORDER BY position';
765
766 $resql = $this->db->query($sql);
767 if ($resql) {
768 $num = $this->db->num_rows($resql);
769 $i = 0;
770 while ($i < $num) {
771 $obj = $this->db->fetch_object($resql);
772 $this->filters[$obj->rowid] = array('id' => $obj->rowid, 'type' => $obj->type, 'rulevalue' => $obj->rulevalue, 'status' => $obj->status);
773 $i++;
774 }
775 $this->db->free($resql);
776 } else {
777 dol_print_error($this->db);
778 }
779
780 return 1;
781 }
782
789 public function fetchActions()
790 {
791 $this->actions = array();
792
793 $sql = 'SELECT rowid, type, actionparam, status';
794 $sql .= ' FROM '.MAIN_DB_PREFIX.'emailcollector_emailcollectoraction';
795 $sql .= ' WHERE fk_emailcollector = '.((int) $this->id);
796 $sql .= ' ORDER BY position';
797
798 $resql = $this->db->query($sql);
799 if ($resql) {
800 $num = $this->db->num_rows($resql);
801 $i = 0;
802 while ($i < $num) {
803 $obj = $this->db->fetch_object($resql);
804 $this->actions[$obj->rowid] = array('id' => $obj->rowid, 'type' => $obj->type, 'actionparam' => $obj->actionparam, 'status' => $obj->status);
805 $i++;
806 }
807 $this->db->free($resql);
808
809 return 1;
810 } else {
811 dol_print_error($this->db);
812
813 return -1;
814 }
815 }
816
817
823 public function getConnectStringIMAP()
824 {
825 // Connect to IMAP
826 $flags = '/service=imap'; // IMAP
827 if (getDolGlobalString('IMAP_FORCE_TLS')) {
828 $flags .= '/tls';
829 } elseif (empty($this->imap_encryption) || ($this->imap_encryption == 'ssl' && getDolGlobalString('IMAP_FORCE_NOSSL'))) {
830 $flags .= '';
831 } else {
832 $flags .= '/' . $this->imap_encryption;
833 }
834
835 $flags .= '/novalidate-cert';
836 //$flags.='/readonly';
837 //$flags.='/debug';
838 if (!empty($this->norsh) || getDolGlobalString('IMAP_FORCE_NORSH')) {
839 $flags .= '/norsh';
840 }
841 //Used in shared mailbox from Office365
842 if (!empty($this->login) && strpos($this->login, '/') != false) {
843 $partofauth = explode('/', $this->login);
844 $flags .= '/authuser='.$partofauth[0].'/user='.$partofauth[1];
845 }
846
847 $connectstringserver = '{'.$this->host.':'.$this->port.$flags.'}';
848
849 return $connectstringserver;
850 }
851
858 public function getEncodedUtf7($str)
859 {
860 if (function_exists('mb_convert_encoding')) {
861 // change spaces by entropy because mb_convert fail with spaces
862 $str = preg_replace("/ /", "xxxSPACExxx", $str); // the replacement string must be valid in utf7 so _ can't be used
863 $str = preg_replace("/_/", "xxxUNDERSCORExxx", $str); // encode underscore to avoid encoding issues with mb_convert
864 $str = preg_replace("/\[Gmail\]/", "xxxGMAILxxx", $str); // the replacement string must be valid in utf7 so _ can't be used
865 // if mb_convert work
866 if ($str = mb_convert_encoding($str, "UTF-7")) {
867 // change characters
868 $str = preg_replace("/\+A/", "&A", $str);
869 $str = preg_replace("/xxxUNDERSCORExxx/", "_", $str);
870 // change to spaces again
871 $str = preg_replace("/xxxSPACExxx/", " ", $str);
872 // change to [Gmail] again
873 $str = preg_replace("/xxxGMAILxxx/", "[Gmail]", $str);
874 return $str;
875 } else {
876 // print error and return false
877 $this->error = "error: is not possible to encode this string '".$str."'";
878 return false;
879 }
880 } else {
881 return $str;
882 }
883 }
884
891 public function doCollect()
892 {
893 global $user;
894
895 $nbErrors = 0;
896
897 $arrayofcollectors = $this->fetchAll($user, 1);
898 // Loop on each collector
899 foreach ($arrayofcollectors as $emailcollector) {
900 $result = $emailcollector->doCollectOneCollector(0);
901
902 dol_syslog("doCollect result = ".$result." for emailcollector->id = ".$emailcollector->id);
903
904 $this->error .= 'EmailCollector ID '.$emailcollector->id.':'.$emailcollector->error.'<br>';
905 if (!empty($emailcollector->errors)) {
906 $this->error .= implode('<br>', $emailcollector->errors);
907 $nbErrors++;
908 }
909 $this->output .= 'EmailCollector ID '.$emailcollector->id.': '.$emailcollector->lastresult.'<br>';
910
911 if ($result < 0) {
912 $nbErrors++;
913 }
914 }
915
916 return $nbErrors;
917 }
918
930 private function overwritePropertiesOfObject(&$object, $actionparam, $messagetext, $subject, $header, &$operationslog)
931 {
932 global $conf, $langs;
933
934 $errorforthisaction = 0;
935
936 // set output lang
937 $outputlangs = $langs;
938 $newlang = '';
939 if (getDolGlobalInt('MAIN_MULTILANGS') /* && empty($newlang) */ && GETPOST('lang_id', 'aZ09')) {
940 $newlang = GETPOST('lang_id', 'aZ09');
941 }
942 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) {
943 $newlang = !empty($object->thirdparty->default_lang) ? $object->thirdparty->default_lang : $newlang;
944 }
945 if (!empty($newlang)) {
946 $outputlangs = new Translate('', $conf);
947 $outputlangs->setDefaultLang($newlang);
948 }
949
950 // Overwrite values with values extracted from source email
951 // $this->actionparam = 'opportunity_status=123;abc=EXTRACT:BODY:....'
952 $arrayvaluetouse = dolExplodeIntoArray($actionparam, '(\n\r|\r|\n|;)', '=');
953
954 $tmp = array();
955
956 // Loop on each property set into actionparam
957 foreach ($arrayvaluetouse as $propertytooverwrite => $valueforproperty) {
958 $tmpclass = '';
959 $tmpproperty = '';
960 $tmparray = explode('.', $propertytooverwrite);
961 if (count($tmparray) == 2) {
962 $tmpclass = $tmparray[0];
963 $tmpproperty = $tmparray[1];
964 } else {
965 $tmpproperty = $tmparray[0];
966 }
967 if ($tmpclass && ($tmpclass != $object->element)) {
968 continue; // Property is for another type of object
969 }
970
971 //if (property_exists($object, $tmpproperty) || preg_match('/^options_/', $tmpproperty))
972 if ($tmpproperty) {
973 $sourcestring = '';
974 $sourcefield = '';
975 $regexstring = '';
976 //$transformationstring='';
977 $regforregex = array();
978 if (preg_match('/^EXTRACT:([a-zA-Z0-9_]+):(.*):([^:])$/', $valueforproperty, $regforregex)) {
979 $sourcefield = $regforregex[1];
980 $regexstring = $regforregex[2];
981 //$transofrmationstring=$regforregex[3];
982 } elseif (preg_match('/^EXTRACT:([a-zA-Z0-9_]+):(.*)$/', $valueforproperty, $regforregex)) {
983 $sourcefield = $regforregex[1];
984 $regexstring = $regforregex[2];
985 }
986
987 if (!empty($sourcefield) && !empty($regexstring)) {
988 if (strtolower($sourcefield) == 'body') {
989 $sourcestring = $messagetext;
990 } elseif (strtolower($sourcefield) == 'subject') {
991 $sourcestring = $subject;
992 } elseif (strtolower($sourcefield) == 'header') {
993 $sourcestring = $header;
994 }
995
996 if ($sourcestring) {
997 $regforval = array();
998 $regexoptions = '';
999 if (strtolower($sourcefield) == 'body') {
1000 $regexoptions = 'ms'; // The m means ^ and $ char is valid at each new line. The s means the char '.' is valid for new lines char too
1001 }
1002 if (strtolower($sourcefield) == 'header') {
1003 $regexoptions = 'm'; // The m means ^ and $ char is valid at each new line.
1004 }
1005
1006 if (preg_match('/'.$regexstring.'/'.$regexoptions, $sourcestring, $regforval)) {
1007 // Overwrite param $tmpproperty
1008 $valueextracted = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : '';
1009 $valuetoset = (strtolower($sourcefield) == 'body') ? $valueextracted : $this->decodeSMTPSubject($valueextracted);
1010 if (preg_match('/^options_/', $tmpproperty)) {
1011 $object->array_options[preg_replace('/^options_/', '', $tmpproperty)] = $valuetoset;
1012 } else {
1013 if (property_exists($object, $tmpproperty)) {
1014 $object->$tmpproperty = $valuetoset;
1015 } else {
1016 $tmp[$tmpproperty] = $valuetoset;
1017 }
1018 }
1019
1020 if (preg_match('/^options_/', $tmpproperty)) {
1021 $operationslog .= '<br>Regex /'.dol_escape_htmltag($regexstring).'/'.dol_escape_htmltag($regexoptions).' into '.strtolower($sourcefield).' -> extract '.dol_escape_htmltag(dol_trunc($object->array_options[preg_replace('/^options_/', '', $tmpproperty)], 128)).' so object extrafield '.dol_escape_htmltag($tmpproperty).' is set.';
1022 } else {
1023 if (property_exists($object, $tmpproperty)) {
1024 $operationslog .= '<br>Regex /'.dol_escape_htmltag($regexstring).'/'.dol_escape_htmltag($regexoptions).' into '.strtolower($sourcefield).' -> extract '.dol_escape_htmltag(dol_trunc($object->$tmpproperty, 128)).' so object property '.dol_escape_htmltag($tmpproperty).' is set.';
1025 } else {
1026 $operationslog .= '<br>Regex /'.dol_escape_htmltag($regexstring).'/'.dol_escape_htmltag($regexoptions).' into '.strtolower($sourcefield).' -> extract '.dol_escape_htmltag(dol_trunc($tmp[$tmpproperty], 128)).' so var '.dol_escape_htmltag($tmpproperty).' is set.';
1027 }
1028 }
1029 } else {
1030 // Regex not found
1031 if (property_exists($object, $tmpproperty)) {
1032 $object->$tmpproperty = null;
1033 $operationslog .= '<br>Regex /'.dol_escape_htmltag($regexstring).'/'.dol_escape_htmltag($regexoptions).' into '.strtolower($sourcefield).' -> not found, so object property '.dol_escape_htmltag($tmpproperty).' is set to null.';
1034 } else {
1035 $tmp[$tmpproperty] = null;
1036 $operationslog .= '<br>Regex /'.dol_escape_htmltag($regexstring).'/'.dol_escape_htmltag($regexoptions).' into '.strtolower($sourcefield).' -> not found, so var '.dol_escape_htmltag($tmpproperty).' is set to null.';
1037 }
1038 }
1039 } else {
1040 // Nothing can be done for this param
1041 $errorforthisaction++;
1042 $this->error = 'The extract rule to use to overwrite properties has on an unknown source (must be HEADER, SUBJECT or BODY)';
1043 $this->errors[] = $this->error;
1044
1045 $operationslog .= '<br>'.$this->error;
1046 }
1047 } elseif (preg_match('/^(SET|SETIFEMPTY):(.*)$/', $valueforproperty, $regforregex)) {
1048 $valuecurrent = '';
1049 if (preg_match('/^options_/', $tmpproperty)) {
1050 $valuecurrent = $object->array_options[preg_replace('/^options_/', '', $tmpproperty)];
1051 } else {
1052 if (property_exists($object, $tmpproperty)) {
1053 $valuecurrent = $object->$tmpproperty;
1054 } else {
1055 // False positive @phan-suppress-next-line PhanTypeInvalidDimOffset
1056 $valuecurrent = $tmp[$tmpproperty];
1057 }
1058 }
1059
1060 if ($regforregex[1] == 'SET' || empty($valuecurrent)) {
1061 $valuetouse = $regforregex[2];
1062 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $object);
1063 complete_substitutions_array($substitutionarray, $outputlangs, $object);
1064 $matcharray = array();
1065 preg_match_all('/__([a-z0-9]+(?:_[a-z0-9]+)?)__/i', $valuetouse, $matcharray);
1066 if (is_array($matcharray[1])) { // $matcharray[1] is an array with the list of all substitution keys found with the __X__ syntax into the SET entry.
1067 foreach ($matcharray[1] as $keytoreplace) {
1068 if ($keytoreplace) {
1069 if (preg_match('/^options_/', $keytoreplace)) {
1070 $substitutionarray['__'.$keytoreplace.'__'] = $object->array_options[preg_replace('/^options_/', '', $keytoreplace)];
1071 } else {
1072 if (property_exists($object, $keytoreplace)) {
1073 $substitutionarray['__'.$keytoreplace.'__'] = $object->$keytoreplace;
1074 } else {
1075 // False positive @phan-suppress-next-line PhanTypeInvalidDimOffset
1076 $substitutionarray['__'.$keytoreplace.'__'] = $tmp[$keytoreplace];
1077 }
1078 }
1079 }
1080 }
1081 }
1082 // dol_syslog('substitutionarray='.var_export($substitutionarray, true));
1083
1084 $valuetouse = make_substitutions($valuetouse, $substitutionarray);
1085 if (preg_match('/^options_/', $tmpproperty)) {
1086 $object->array_options[preg_replace('/^options_/', '', $tmpproperty)] = $valuetouse;
1087
1088 $operationslog .= '<br>Set value '.dol_escape_htmltag($valuetouse).' into object->array_options['.dol_escape_htmltag(preg_replace('/^options_/', '', $tmpproperty)).']';
1089 } else {
1090 if (property_exists($object, $tmpproperty)) {
1091 $object->$tmpproperty = $valuetouse;
1092 } else {
1093 $tmp[$tmpproperty] = $valuetouse;
1094 }
1095
1096 $operationslog .= '<br>Set value '.dol_escape_htmltag($valuetouse).' into object->'.dol_escape_htmltag($tmpproperty);
1097 }
1098 }
1099 } else {
1100 $errorforthisaction++;
1101 $this->error = 'Bad syntax for description of action parameters: '.$actionparam;
1102 $this->errors[] = $this->error;
1103 }
1104 }
1105 }
1106
1107 return $errorforthisaction;
1108 }
1109
1116 public function doCollectOneCollector($mode = 0)
1117 {
1118 global $db, $conf, $langs, $user;
1119 global $hookmanager;
1120
1121 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1122
1123 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1124 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1125 require_once DOL_DOCUMENT_ROOT.'/includes/webklex/php-imap/vendor/autoload.php';
1126 }
1127
1128 dol_syslog("EmailCollector::doCollectOneCollector start for id=".$this->id." - ".$this->ref, LOG_INFO);
1129
1130 $langs->loadLangs(array("project", "companies", "mails", "errors", "ticket", "agenda", "commercial"));
1131
1132 $error = 0;
1133 $this->output = '';
1134 $this->error = '';
1135 $this->debuginfo = '';
1136
1137 $search = '';
1138 $searchhead = '';
1139 $searchfilterdoltrackid = 0;
1140 $searchfilternodoltrackid = 0;
1141 $searchfilterisanswer = 0;
1142 $searchfilterisnotanswer = 0;
1143 $searchfilterreplyto = 0;
1144 $searchfilterexcludebodyarray = array();
1145 $searchfilterexcludesubjectarray = array();
1146 $searchfilterexcludeemailarray = array();
1147 $searchfilterexcludedomainarray = array();
1148 $searchfilterexcludeemailmap = array();
1149 $operationslog = '';
1150 $rulesreplyto = array();
1151 $connectstringsource = '';
1152 $connectstringtarget = '';
1153 $connection = false;
1154 $arrayofemail = array();
1155
1156 $now = dol_now();
1157 $datelastok = $now;
1158
1159 if (empty($this->host)) {
1160 $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('EMailHost'));
1161 return -1;
1162 }
1163 if (empty($this->login)) {
1164 $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Login'));
1165 return -1;
1166 }
1167 if (empty($this->source_directory)) {
1168 $this->error = $langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('MailboxSourceDirectory'));
1169 return -1;
1170 }
1171
1172 $client = null;
1173
1174 $sourcedir = $this->source_directory;
1175 $targetdir = ($this->target_directory ? $this->target_directory : ''); // Can be '[Gmail]/Trash' or 'mytag'
1176
1177 $this->fetchFilters();
1178 $this->fetchActions();
1179
1180 $sourcedir = $this->source_directory;
1181 $targetdir = ($this->target_directory ? $this->target_directory : ''); // Can be '[Gmail]/Trash' or 'mytag'
1182
1183 // Avoid long blocks on IMAP operations (applies to native IMAP and Webklex/php-imap).
1184 $timeoutconnect = (int) getDolGlobalInt('MAIN_USE_CONNECT_TIMEOUT', 5);
1185 if ($timeoutconnect <= 0) {
1186 $timeoutconnect = 5;
1187 }
1188 $timeoutread = (int) getDolGlobalInt('MAIN_USE_RESPONSE_TIMEOUT', 20);
1189 if ($timeoutread <= 0) {
1190 $timeoutread = 20;
1191 }
1192 if (function_exists('imap_timeout')) {
1193 imap_timeout(IMAP_OPENTIMEOUT, $timeoutconnect); // timeout seems ignored with ssl connect
1194 imap_timeout(IMAP_READTIMEOUT, $timeoutread);
1195 imap_timeout(IMAP_WRITETIMEOUT, 5);
1196 imap_timeout(IMAP_CLOSETIMEOUT, 5);
1197 $this->debuginfo .= 'IMAP timeouts: connect='.$timeoutconnect.'s, read='.$timeoutread.'s<br>';
1198 }
1199
1200 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1201 if ($this->acces_type == 1) {
1202 // Mode OAUth2 (access_type == 1) with PHP-IMAP
1203 $this->debuginfo .= 'doCollectOneCollector is using method MAIN_IMAP_USE_PHPIMAP=1, access_type=1 (OAUTH2)<br>';
1204
1205 require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php';
1206
1207 $supportedoauth2array = getSupportedOauth2Array();
1208
1209 $keyforsupportedoauth2array = $this->oauth_service;
1210 if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1211 $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1212 } else {
1213 $keyforprovider = '';
1214 }
1215 $keyforsupportedoauth2array = preg_replace('/-.*$/', '', strtoupper($keyforsupportedoauth2array));
1216 $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
1217
1218 if (!empty($supportedoauth2array)) {
1219 $nameofservice = ucfirst(strtolower(empty($supportedoauth2array[$keyforsupportedoauth2array]['callbackfile']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['callbackfile']));
1220 $nameofservice .= ($keyforprovider ? '-'.$keyforprovider : '');
1221 $OAUTH_SERVICENAME = $nameofservice;
1222 } else {
1223 $OAUTH_SERVICENAME = 'Unknown';
1224 }
1225
1226 $keyforparamtenant = 'OAUTH_'.strtoupper(empty($supportedoauth2array[$keyforsupportedoauth2array]['callbackfile']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['callbackfile']).($keyforprovider ? '-'.$keyforprovider : '').'_TENANT';
1227
1228 require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
1229 //$debugtext = "Host: ".$this->host."<br>Port: ".$this->port."<br>Login: ".$this->login."<br>Password: ".$this->password."<br>access type: ".$this->acces_type."<br>oauth service: ".$this->oauth_service."<br>Max email per collect: ".$this->maxemailpercollect;
1230 //dol_syslog($debugtext);
1231
1232 $token = '';
1233
1234 $storage = new DoliStorage($db, $conf, $keyforprovider, getDolGlobalString($keyforparamtenant));
1235
1236 try {
1237 $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1238
1239 $expire = false;
1240 if (is_object($tokenobj) && method_exists($tokenobj, 'getEndOfLife')) {
1241 $endOfLife = $tokenobj->getEndOfLife();
1242 if ($endOfLife !== -9002 && $endOfLife !== -9001 && time() > ($endOfLife - 30)) {
1243 $expire = true;
1244 }
1245 }
1246
1247 // Token expired so we refresh it
1248 if (is_object($tokenobj) && $expire) {
1249 $this->debuginfo .= 'Refresh token '.$OAUTH_SERVICENAME.'<br>';
1250 $credentials = new Credentials(
1251 getDolGlobalString('OAUTH_'.$this->oauth_service.'_ID'),
1252 getDolGlobalString('OAUTH_'.$this->oauth_service.'_SECRET'),
1253 getDolGlobalString('OAUTH_'.$this->oauth_service.'_URLCALLBACK')
1254 );
1255 $serviceFactory = new \OAuth\ServiceFactory();
1256 $oauthname = explode('-', $OAUTH_SERVICENAME);
1257 // ex service is Google-Emails we need only the first part Google
1258
1259 $scopes = array();
1260 if (preg_match('/^Microsoft/', $OAUTH_SERVICENAME)) {
1261 //$extraparams = $tokenobj->getExtraParams();
1262 $tmp = explode('-', $OAUTH_SERVICENAME);
1263 $scopes = explode(',', getDolGlobalString('OAUTH_'.strtoupper($tmp[0]).(empty($tmp[1]) ? '' : '-'.$tmp[1]).'_SCOPE'));
1264 }
1265
1266 $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, $scopes);
1267
1268 '@phan-var-force OAuth\OAuth2\Service\AbstractService|OAuth\OAuth1\Service\AbstractService $apiService'; // createService is only ServiceInterface
1269
1270 $refreshtoken = $tokenobj->getRefreshToken();
1271 $tokenobj = $apiService->refreshAccessToken($tokenobj);
1272
1273 // We have to save the token because answer give it only once
1274 $tokenobj->setRefreshToken($refreshtoken);
1275 $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1276 }
1277 $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1278 if (is_object($tokenobj)) {
1279 $token = $tokenobj->getAccessToken();
1280 } else {
1281 $this->error = "Token not found";
1282 return -1;
1283 }
1284 } catch (Exception $e) {
1285 // Return an error if token not found
1286 $this->error = $e->getMessage();
1287 dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
1288 return -1;
1289 }
1290
1291 $cm = new ClientManager();
1292 $client = $cm->make([
1293 'host' => $this->host,
1294 'port' => $this->port,
1295 'encryption' => !empty($this->imap_encryption) ? $this->imap_encryption : false,
1296 'validate_cert' => true,
1297 'protocol' => 'imap',
1298 'username' => $this->login,
1299 'password' => $token,
1300 'authentication' => "oauth",
1301 ]);
1302 } else {
1303 // Mode LOGIN (login/pass) with PHP-IMAP
1304 $this->debuginfo .= 'doCollectOneCollector is using method MAIN_IMAP_USE_PHPIMAP=1, access_type=0 (LOGIN)<br>';
1305
1306 $cm = new ClientManager();
1307 $client = $cm->make([
1308 'host' => $this->host,
1309 'port' => $this->port,
1310 'encryption' => !empty($this->imap_encryption) ? $this->imap_encryption : false,
1311 'validate_cert' => true,
1312 'protocol' => 'imap',
1313 'username' => $this->login,
1314 'password' => $this->password,
1315 'authentication' => "login",
1316 ]);
1317 }
1318
1319 try {
1320 $client->connect();
1321
1322 $connection = true;
1323 } catch (ConnectionFailedException $e) {
1324 $this->error = $e->getMessage();
1325 $this->errors[] = $this->error;
1326 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1327 return -1;
1328 }
1329
1330 $host = dol_getprefix('email');
1331 } else {
1332 // Use native IMAP functions
1333 $this->debuginfo .= 'doCollectOneCollector is using method MAIN_IMAP_USE_PHPIMAP=0 (native PHP imap, LOGIN)<br>';
1334
1335 if (!function_exists('imap_open')) {
1336 $this->error = 'IMAP function not enabled on your PHP';
1337 return -2;
1338 }
1339
1340 $connectstringserver = $this->getConnectStringIMAP();
1341 if (!getDolGlobalString('MAIL_DISABLE_UTF7_ENCODE_OF_DIR')) {
1342 $connectstringsource = $connectstringserver.$this->getEncodedUtf7($sourcedir);
1343 $connectstringtarget = $connectstringserver.$this->getEncodedUtf7($targetdir);
1344 } else {
1345 $connectstringsource = $connectstringserver.$sourcedir;
1346 $connectstringtarget = $connectstringserver.$targetdir;
1347 }
1348
1349 $this->debuginfo .= 'connectstringsource = '.$connectstringsource.', $connectstringtarget='.$connectstringtarget.'<br>';
1350
1351 $connection = imap_open($connectstringsource, $this->login, $this->password);
1352 if ($connection === false) {
1353 $this->error = 'Failed to open IMAP connection '.$connectstringsource.' '.imap_last_error();
1354 return -3;
1355 }
1356 '@phan-var-force resource|IMAP\Connection $connection';
1357 imap_errors(); // Clear stack of errors.
1358
1359 $host = dol_getprefix('email');
1360 //$host = '123456';
1361
1362 // Define the IMAP search string
1363 // See https://tools.ietf.org/html/rfc3501#section-6.4.4 for IMAPv4 (PHP not yet compatible)
1364 // See https://tools.ietf.org/html/rfc1064 page 13 for IMAPv2
1365 //$search='ALL';
1366 }
1367
1368 $criteria = array();
1369 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1370 // Use PHPIMAP external library
1371 $criteria = array(array('UNDELETED')); // Seems not supported by some servers
1372 foreach ($this->filters as $rule) {
1373 if (empty($rule['status'])) {
1374 continue;
1375 }
1376
1377 $not = '';
1378 if (strpos($rule['rulevalue'], '!') === 0) {
1379 // The value start with !, so we exclude the criteria
1380 $not = 'NOT ';
1381 // Then remove the ! from the string for next filters
1382 $rule['rulevalue'] = substr($rule['rulevalue'], 1);
1383 }
1384
1385 if ($rule['type'] == 'excludeemail') {
1386 $tmpvalues = preg_split('/[\\r\\n,;]+/', (string) $rule['rulevalue']);
1387 if (is_array($tmpvalues)) {
1388 foreach ($tmpvalues as $tmpvalue) {
1389 $tmpvalue = trim((string) $tmpvalue);
1390 if ($tmpvalue === '') {
1391 continue;
1392 }
1393
1394 if (preg_match_all('/[a-z0-9._%+\\-]+@[a-z0-9.\\-]+\\.[a-z]{2,}/i', $tmpvalue, $tmpmatches)) {
1395 foreach ($tmpmatches[0] as $tmpemail) {
1396 $tmpemail = strtolower(trim($tmpemail));
1397 if ($tmpemail !== '') {
1398 $searchfilterexcludeemailarray[] = $tmpemail;
1399 }
1400 }
1401 }
1402 }
1403 }
1404 continue;
1405 }
1406 if ($rule['type'] == 'excludedomain') {
1407 $tmpvalues = preg_split('/[\\r\\n,;]+/', (string) $rule['rulevalue']);
1408 if (is_array($tmpvalues)) {
1409 foreach ($tmpvalues as $tmpvalue) {
1410 $tmpvalue = trim((string) $tmpvalue);
1411 if ($tmpvalue === '') {
1412 continue;
1413 }
1414
1415 if (preg_match_all('/[a-z0-9._%+\\-]+@([a-z0-9.\\-]+\\.[a-z]{2,})/i', $tmpvalue, $tmpmatches)) {
1416 foreach ($tmpmatches[1] as $tmpdomain) {
1417 $tmpdomain = strtolower(trim($tmpdomain));
1418 $tmpdomain = trim($tmpdomain, ". \t\n\r\0\x0B");
1419 if ($tmpdomain !== '') {
1420 $searchfilterexcludedomainarray[] = $tmpdomain;
1421 }
1422 }
1423 continue;
1424 }
1425
1426 $tmpdomain = strtolower($tmpvalue);
1427 $tmpdomain = preg_replace('/^mailto:/i', '', $tmpdomain);
1428 $tmpdomain = trim($tmpdomain);
1429 $tmpdomain = trim($tmpdomain, "<> \t\n\r\0\x0B.,;");
1430 if (strpos($tmpdomain, '@') === 0) {
1431 $tmpdomain = substr($tmpdomain, 1);
1432 }
1433 $posat = strrpos($tmpdomain, '@');
1434 if ($posat !== false) {
1435 $tmpdomain = substr($tmpdomain, $posat + 1);
1436 }
1437 $tmpdomain = trim($tmpdomain, ". \t\n\r\0\x0B");
1438 if ($tmpdomain !== '') {
1439 $searchfilterexcludedomainarray[] = $tmpdomain;
1440 }
1441 }
1442 }
1443 continue;
1444 }
1445
1446 if ($rule['type'] == 'from') {
1447 $tmprulevaluearray = explode('*', $rule['rulevalue']);
1448 if (count($tmprulevaluearray) >= 2) {
1449 foreach ($tmprulevaluearray as $tmprulevalue) {
1450 array_push($criteria, array($not."FROM" => $tmprulevalue));
1451 }
1452 } else {
1453 array_push($criteria, array($not."FROM" => $rule['rulevalue']));
1454 }
1455 }
1456 if ($rule['type'] == 'to') {
1457 $tmprulevaluearray = explode('*', $rule['rulevalue']);
1458 if (count($tmprulevaluearray) >= 2) {
1459 foreach ($tmprulevaluearray as $tmprulevalue) {
1460 array_push($criteria, array($not."TO" => $tmprulevalue));
1461 }
1462 } else {
1463 array_push($criteria, array($not."TO" => $rule['rulevalue']));
1464 }
1465 }
1466 if ($rule['type'] == 'bcc') {
1467 array_push($criteria, array($not."BCC" => $rule['rulevalue']));
1468 }
1469 if ($rule['type'] == 'cc') {
1470 array_push($criteria, array($not."CC" => $rule['rulevalue']));
1471 }
1472 if ($rule['type'] == 'subject') {
1473 if ($not) {
1474 //array_push($criteria, array("NOT SUBJECT" => $rule['rulevalue']));
1475 $searchfilterexcludesubjectarray[] = $rule['rulevalue'];
1476 } else {
1477 array_push($criteria, array("SUBJECT" => $rule['rulevalue']));
1478 }
1479 }
1480 if ($rule['type'] == 'body') {
1481 if ($not) {
1482 //array_push($criteria, array("NOT BODY" => $rule['rulevalue']));
1483 $searchfilterexcludebodyarray[] = $rule['rulevalue'];
1484 } else {
1485 array_push($criteria, array("BODY" => $rule['rulevalue']));
1486 }
1487 }
1488 if ($rule['type'] == 'header') {
1489 array_push($criteria, array($not."HEADER" => $rule['rulevalue']));
1490 }
1491
1492 /* seems not used */
1493 /*
1494 if ($rule['type'] == 'notinsubject') {
1495 array_push($criteria, array($not."SUBJECT NOT" => $rule['rulevalue']));
1496 }
1497 if ($rule['type'] == 'notinbody') {
1498 array_push($criteria, array($not."BODY NOT" => $rule['rulevalue']));
1499 }*/
1500
1501 if ($rule['type'] == 'seen') {
1502 array_push($criteria, array($not."SEEN"));
1503 }
1504 if ($rule['type'] == 'unseen') {
1505 array_push($criteria, array($not."UNSEEN"));
1506 }
1507 if ($rule['type'] == 'unanswered') {
1508 array_push($criteria, array($not."UNANSWERED"));
1509 }
1510 if ($rule['type'] == 'answered') {
1511 array_push($criteria, array($not."ANSWERED"));
1512 }
1513 if ($rule['type'] == 'smaller') {
1514 array_push($criteria, array($not."SMALLER"));
1515 }
1516 if ($rule['type'] == 'larger') {
1517 array_push($criteria, array($not."LARGER"));
1518 }
1519
1520 // Rules to filter after the search imap
1521 if ($rule['type'] == 'withtrackingidinmsgid') {
1522 $searchfilterdoltrackid++;
1523 $searchhead .= '/Message-ID.*@'.preg_quote($host, '/').'/';
1524 }
1525 if ($rule['type'] == 'withouttrackingidinmsgid') {
1526 $searchfilterdoltrackid++;
1527 $searchhead .= '/Message-ID.*@'.preg_quote($host, '/').'/';
1528 }
1529 if ($rule['type'] == 'withtrackingid') {
1530 $searchfilterdoltrackid++;
1531 $searchhead .= '/References.*@'.preg_quote($host, '/').'/';
1532 }
1533 if ($rule['type'] == 'withouttrackingid') {
1534 $searchfilternodoltrackid++;
1535 $searchhead .= '! /References.*@'.preg_quote($host, '/').'/';
1536 }
1537
1538 if ($rule['type'] == 'isanswer') {
1539 $searchfilterisanswer++;
1540 $searchhead .= '/References.*@.*/';
1541 }
1542 if ($rule['type'] == 'isnotanswer') {
1543 $searchfilterisnotanswer++;
1544 $searchhead .= '! /References.*@.*/';
1545 }
1546
1547 if ($rule['type'] == 'replyto') {
1548 $searchfilterreplyto++;
1549 $rulesreplyto[] = $rule['rulevalue'];
1550 $searchhead .= '/Reply-To.*'.preg_quote($rule['rulevalue'], '/').'/';
1551 }
1552 }
1553
1554 if (empty($targetdir) || !getDolGlobalString('EMAILCOLLECTOR_NO_FILTER_ON_DATE_IF_THERE_IS_A_TARGETDIR')) { // Use the last date of successful check as a filter if there is no targetdir defined.
1555 $fromdate = 0;
1556 if ($this->datelastok) {
1557 $fromdate = $this->datelastok;
1558 }
1559 if ($fromdate > 0) {
1560 // IMAP SINCE works by day; keep a 1-day overlap so we don't miss emails left unprocessed (e.g. discarded by filters).
1561 array_push($criteria, array("SINCE" => date('j-M-Y', $fromdate - 86400)));
1562 }
1563 //$search.=($search?' ':'').'SINCE 8-Apr-2022';
1564 }
1565
1566 dol_syslog("IMAP search string = ".formatLogObject($criteria));
1567 $search = var_export($criteria, true);
1568 } else {
1569 // Use native IMAP functions
1570 $search = 'UNDELETED'; // Seems not supported by some servers
1571 foreach ($this->filters as $rule) {
1572 if (empty($rule['status'])) {
1573 continue;
1574 }
1575
1576 // Forge the IMAP search string.
1577 // See https://www.rfc-editor.org/rfc/rfc3501
1578
1579 $not = '';
1580 if (!empty($rule['rulevalue']) && strpos($rule['rulevalue'], '!') === 0) {
1581 // The value start with !, so we exclude the criteria
1582 $not = 'NOT ';
1583 // Then remove the ! from the string for next filters
1584 $rule['rulevalue'] = substr($rule['rulevalue'], 1);
1585 }
1586
1587 if ($rule['type'] == 'excludeemail') {
1588 $tmpvalues = preg_split('/[\\r\\n,;]+/', (string) $rule['rulevalue']);
1589 if (is_array($tmpvalues)) {
1590 foreach ($tmpvalues as $tmpvalue) {
1591 $tmpvalue = trim((string) $tmpvalue);
1592 if ($tmpvalue === '') {
1593 continue;
1594 }
1595
1596 if (preg_match_all('/[a-z0-9._%+\\-]+@[a-z0-9.\\-]+\\.[a-z]{2,}/i', $tmpvalue, $tmpmatches)) {
1597 foreach ($tmpmatches[0] as $tmpemail) {
1598 $tmpemail = strtolower(trim($tmpemail));
1599 if ($tmpemail !== '') {
1600 $searchfilterexcludeemailarray[] = $tmpemail;
1601 }
1602 }
1603 }
1604 }
1605 }
1606 continue;
1607 }
1608 if ($rule['type'] == 'excludedomain') {
1609 $tmpvalues = preg_split('/[\\r\\n,;]+/', (string) $rule['rulevalue']);
1610 if (is_array($tmpvalues)) {
1611 foreach ($tmpvalues as $tmpvalue) {
1612 $tmpvalue = trim((string) $tmpvalue);
1613 if ($tmpvalue === '') {
1614 continue;
1615 }
1616
1617 if (preg_match_all('/[a-z0-9._%+\\-]+@([a-z0-9.\\-]+\\.[a-z]{2,})/i', $tmpvalue, $tmpmatches)) {
1618 foreach ($tmpmatches[1] as $tmpdomain) {
1619 $tmpdomain = strtolower(trim($tmpdomain));
1620 $tmpdomain = trim($tmpdomain, ". \t\n\r\0\x0B");
1621 if ($tmpdomain !== '') {
1622 $searchfilterexcludedomainarray[] = $tmpdomain;
1623 }
1624 }
1625 continue;
1626 }
1627
1628 $tmpdomain = strtolower($tmpvalue);
1629 $tmpdomain = preg_replace('/^mailto:/i', '', $tmpdomain);
1630 $tmpdomain = trim($tmpdomain);
1631 $tmpdomain = trim($tmpdomain, "<> \t\n\r\0\x0B.,;");
1632 if (strpos($tmpdomain, '@') === 0) {
1633 $tmpdomain = substr($tmpdomain, 1);
1634 }
1635 $posat = strrpos($tmpdomain, '@');
1636 if ($posat !== false) {
1637 $tmpdomain = substr($tmpdomain, $posat + 1);
1638 }
1639 $tmpdomain = trim($tmpdomain, ". \t\n\r\0\x0B");
1640 if ($tmpdomain !== '') {
1641 $searchfilterexcludedomainarray[] = $tmpdomain;
1642 }
1643 }
1644 }
1645 continue;
1646 }
1647
1648 if ($rule['type'] == 'from') {
1649 $tmprulevaluearray = explode('*', $rule['rulevalue']); // Search on abc*def means searching on 'abc' and on 'def'
1650 if (count($tmprulevaluearray) >= 2) {
1651 foreach ($tmprulevaluearray as $tmprulevalue) {
1652 $search .= ($search ? ' ' : '').$not.'FROM "'.str_replace('"', '', $tmprulevalue).'"';
1653 }
1654 } else {
1655 $search .= ($search ? ' ' : '').$not.'FROM "'.str_replace('"', '', $rule['rulevalue']).'"';
1656 }
1657 }
1658 if ($rule['type'] == 'to') {
1659 $tmprulevaluearray = explode('*', $rule['rulevalue']); // Search on abc*def means searching on 'abc' and on 'def'
1660 if (count($tmprulevaluearray) >= 2) {
1661 foreach ($tmprulevaluearray as $tmprulevalue) {
1662 $search .= ($search ? ' ' : '').$not.'TO "'.str_replace('"', '', $tmprulevalue).'"';
1663 }
1664 } else {
1665 $search .= ($search ? ' ' : '').$not.'TO "'.str_replace('"', '', $rule['rulevalue']).'"';
1666 }
1667 }
1668 if ($rule['type'] == 'bcc') {
1669 $search .= ($search ? ' ' : '').$not.'BCC';
1670 }
1671 if ($rule['type'] == 'cc') {
1672 $search .= ($search ? ' ' : '').$not.'CC';
1673 }
1674 if ($rule['type'] == 'subject') {
1675 if ($not) {
1676 //$search .= ($search ? ' ' : '').'NOT BODY "'.str_replace('"', '', $rule['rulevalue']).'"';
1677 $searchfilterexcludesubjectarray[] = $rule['rulevalue'];
1678 } else {
1679 $search .= ($search ? ' ' : '').'SUBJECT "'.str_replace('"', '', $rule['rulevalue']).'"';
1680 }
1681 }
1682 if ($rule['type'] == 'body') {
1683 if ($not) {
1684 //$search .= ($search ? ' ' : '').'NOT BODY "'.str_replace('"', '', $rule['rulevalue']).'"';
1685 $searchfilterexcludebodyarray[] = $rule['rulevalue'];
1686 } else {
1687 // Warning: Google doesn't implement IMAP properly, and only matches whole words,
1688 $search .= ($search ? ' ' : '').'BODY "'.str_replace('"', '', $rule['rulevalue']).'"';
1689 }
1690 }
1691 if ($rule['type'] == 'header') {
1692 $search .= ($search ? ' ' : '').$not.'HEADER '.$rule['rulevalue'];
1693 }
1694
1695 /* seems not used */
1696 /*
1697 if ($rule['type'] == 'notinsubject') {
1698 $search .= ($search ? ' ' : '').'NOT SUBJECT "'.str_replace('"', '', $rule['rulevalue']).'"';
1699 }
1700 if ($rule['type'] == 'notinbody') {
1701 $search .= ($search ? ' ' : '').'NOT BODY "'.str_replace('"', '', $rule['rulevalue']).'"';
1702 }*/
1703
1704 if ($rule['type'] == 'seen') {
1705 $search .= ($search ? ' ' : '').$not.'SEEN';
1706 }
1707 if ($rule['type'] == 'unseen') {
1708 $search .= ($search ? ' ' : '').$not.'UNSEEN';
1709 }
1710 if ($rule['type'] == 'unanswered') {
1711 $search .= ($search ? ' ' : '').$not.'UNANSWERED';
1712 }
1713 if ($rule['type'] == 'answered') {
1714 $search .= ($search ? ' ' : '').$not.'ANSWERED';
1715 }
1716 if ($rule['type'] == 'smaller') {
1717 $search .= ($search ? ' ' : '').$not.'SMALLER "'.str_replace('"', '', $rule['rulevalue']).'"';
1718 }
1719 if ($rule['type'] == 'larger') {
1720 $search .= ($search ? ' ' : '').$not.'LARGER "'.str_replace('"', '', $rule['rulevalue']).'"';
1721 }
1722
1723 // Rules to filter after the search imap
1724 if ($rule['type'] == 'withtrackingidinmsgid') {
1725 $searchfilterdoltrackid++;
1726 $searchhead .= '/Message-ID.*@'.preg_quote($host, '/').'/';
1727 }
1728 if ($rule['type'] == 'withouttrackingidinmsgid') {
1729 $searchfilterdoltrackid++;
1730 $searchhead .= '/Message-ID.*@'.preg_quote($host, '/').'/';
1731 }
1732 if ($rule['type'] == 'withtrackingid') {
1733 $searchfilterdoltrackid++;
1734 $searchhead .= '/References.*@'.preg_quote($host, '/').'/';
1735 }
1736 if ($rule['type'] == 'withouttrackingid') {
1737 $searchfilternodoltrackid++;
1738 $searchhead .= '! /References.*@'.preg_quote($host, '/').'/';
1739 }
1740
1741 if ($rule['type'] == 'isanswer') {
1742 $searchfilterisanswer++;
1743 $searchhead .= '/References.*@.*/';
1744 }
1745 if ($rule['type'] == 'isnotanswer') {
1746 $searchfilterisnotanswer++;
1747 $searchhead .= '! /References.*@.*/';
1748 }
1749
1750 if ($rule['type'] == 'replyto') {
1751 $searchfilterreplyto++;
1752 $rulesreplyto[] = $rule['rulevalue'];
1753 $searchhead .= '/Reply-To.*'.preg_quote($rule['rulevalue'], '/').'/';
1754 }
1755 }
1756
1757 if (empty($targetdir)) { // Use last date as filter if there is no targetdir defined.
1758 $fromdate = 0;
1759 if ($this->datelastok) {
1760 $fromdate = $this->datelastok;
1761 }
1762 if ($fromdate > 0) {
1763 // IMAP SINCE works by day; keep a 1-day overlap so we don't miss emails left unprocessed (e.g. discarded by filters).
1764 $search .= ($search ? ' ' : '').'SINCE '.date('j-M-Y', $fromdate - 86400); // SENTSINCE not supported. Date must be X-Abc-9999 (X on 1 digit if < 10)
1765 }
1766 //$search.=($search?' ':'').'SINCE 8-Apr-2018';
1767 }
1768
1769 dol_syslog("IMAP search string = ".$search);
1770 }
1771
1772 if (!empty($searchfilterexcludeemailarray)) {
1773 $searchfilterexcludeemailarray = array_values(array_filter(array_unique($searchfilterexcludeemailarray)));
1774 $searchfilterexcludeemailmap = array_fill_keys($searchfilterexcludeemailarray, true);
1775 }
1776 if (!empty($searchfilterexcludedomainarray)) {
1777 $searchfilterexcludedomainarray = array_values(array_filter(array_unique($searchfilterexcludedomainarray)));
1778 }
1779
1780 $nbemailprocessed = 0;
1781 $nbemailok = 0;
1782 $nbactiondone = 0;
1783 $charset = ($this->hostcharset ? $this->hostcharset : "UTF-8");
1784 $arrayofemail = array();
1785
1786 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP') && is_object($client)) {
1787 $Query = null;
1788
1789 try {
1790 // Uncomment this to output debug info
1791 //$client->getConnection()->enableDebug();
1792
1793 $tmpsourcedir = $sourcedir;
1794 if (!getDolGlobalString('MAIL_DISABLE_UTF7_ENCODE_OF_DIR')) {
1795 $tmpsourcedir = $this->getEncodedUtf7($sourcedir);
1796 }
1797
1798 $f = $client->getFolders(false, $tmpsourcedir); // Note the search of directory do a search on sourcedir*
1799 if ($f) {
1800 // getFolders does a sourcedir* match so INBOX also returns INBOX.Traites
1801 // etc. Pick the folder whose full_name matches the requested source
1802 // before falling back to the first result, otherwise polling can land
1803 // on an empty subfolder instead of INBOX (#37357).
1804 $folder = $f[0];
1805 foreach ($f as $candidate) {
1806 if ($candidate instanceof Webklex\PHPIMAP\Folder && $candidate->full_name === $tmpsourcedir) {
1807 $folder = $candidate;
1808 break;
1809 }
1810 }
1811 if ($folder instanceof Webklex\PHPIMAP\Folder) {
1812 $Query = $folder->messages()->where($criteria); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
1813 }
1814 } else {
1815 $error++;
1816 $this->error = "Failed to execute getfolders";
1817 $this->errors[] = $this->error;
1818 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1819 return -1;
1820 }
1821 } catch (InvalidWhereQueryCriteriaException $e) {
1822 $this->error = $e->getMessage();
1823 $this->errors[] = $this->error;
1824 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1825 return -1;
1826 } catch (Exception $e) {
1827 $this->error = $e->getMessage();
1828 $this->errors[] = $this->error;
1829 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1830 return -1;
1831 }
1832
1833 // @phpstan-ignore-line
1834 if (empty($Query)) {
1835 $error++;
1836 $this->error = "Source directory ".$sourcedir." not found";
1837 $this->errors[] = $this->error;
1838 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_WARNING);
1839 return -1;
1840 }
1841
1842 '@phan-var-force Webklex\PHPIMAP\Query\Query $Query';
1843 try {
1844 if ($mode > 0) {
1845 $Query->leaveUnread();
1846 }
1847 $arrayofemail = $Query->limit($this->maxemailpercollect)->setFetchOrder("asc")->get();
1848 dol_syslog("EmailCollector::doCollectOneCollector nb arrayofemail ".(is_array($arrayofemail) ? count($arrayofemail) : 'Not array')); // @phpstan-ignore-line
1849 } catch (Exception $e) {
1850 $this->error = $e->getMessage();
1851 $this->errors[] = $this->error;
1852 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1853 return -1;
1854 }
1855 } elseif ($connection !== false) {
1856 // Scan IMAP dir (for native IMAP, the source dir is inside the $connection variable)
1857 $arrayofemail = imap_search($connection, $search, SE_UID, $charset);
1858
1859 if ($arrayofemail === false) {
1860 // Nothing found or search string not understood
1861 $mapoferrrors = imap_errors();
1862 if ($mapoferrrors !== false) {
1863 $error++;
1864 $this->error = "Search string not understood - ".implode(',', $mapoferrrors);
1865 $this->errors[] = $this->error;
1866 }
1867 }
1868 }
1869
1870 $arrayofemailtodelete = array(); // Track email to delete to make the deletion at end.
1871
1872 // Loop on each email found
1873 if (!$error && !empty($arrayofemail) && count($arrayofemail) > 0 && $connection !== false) {
1874 // Loop to get part html and plain
1875 /*
1876 0 multipart/mixed
1877 1 multipart/alternative
1878 1.1 text/plain
1879 1.2 text/html
1880 2 message/rfc822
1881 2 multipart/mixed
1882 2.1 multipart/alternative
1883 2.1.1 text/plain
1884 2.1.2 text/html
1885 2.2 message/rfc822
1886 2.2 multipart/alternative
1887 2.2.1 text/plain
1888 2.2.2 text/html
1889 */
1890 dol_syslog("Start of loop on email", LOG_INFO, 1);
1891
1892 $richarrayofemail = array();
1893
1894 foreach ($arrayofemail as $imapemail) {
1895 if ($nbemailprocessed > 1000) {
1896 break; // Do not process more than 1000 email per launch (this is a different protection than maxnbcollectedpercollect)
1897 }
1898
1899 // GET header and overview datas
1900 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1901 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
1902 $header = $imapemail->getHeader()->raw; // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
1903 $overview = $imapemail->getAttributes();
1904 } else {
1905 $header = imap_fetchheader($connection, $imapemail, FT_UID);
1906 $overview = imap_fetch_overview($connection, $imapemail, FT_UID);
1907 }
1908
1909 $header = preg_replace('/\r\n\s+/m', ' ', $header); // When a header line is on several lines, merge lines
1910
1911 $matches = array();
1912 preg_match_all('/([^: ]+): (.+?(?:\r\n\s(?:.+?))*)(\r\n|\s$)/m', $header, $matches);
1913 $headers = array_combine($matches[1], $matches[2]);
1914
1915
1916 $richarrayofemail[] = array('imapemail' => $imapemail, 'header' => $header, 'headers' => $headers, 'overview' => $overview, 'date' => empty($headers['Date']) ? false : strtotime($headers['Date']));
1917 }
1918
1919
1920 // Sort email found by ascending date
1921 $richarrayofemail = dol_sort_array($richarrayofemail, 'date', 'asc');
1922
1923
1924 $iforemailloop = 0;
1925 foreach ($richarrayofemail as $tmpval) {
1926 $iforemailloop++;
1927
1928 $imapemail = $tmpval['imapemail'];
1929 $header = $tmpval['header'];
1930 $overview = $tmpval['overview'];
1931 $headers = $tmpval['headers'];
1932
1933 if (!empty($headers['in-reply-to']) && empty($headers['In-Reply-To'])) {
1934 $headers['In-Reply-To'] = $headers['in-reply-to'];
1935 }
1936 if (!empty($headers['references']) && empty($headers['References'])) {
1937 $headers['References'] = $headers['references'];
1938 }
1939 if (!empty($headers['message-id']) && empty($headers['Message-ID'])) {
1940 $headers['Message-ID'] = $headers['message-id'];
1941 }
1942 if (!empty($headers['subject']) && empty($headers['Subject'])) {
1943 $headers['Subject'] = $headers['subject'];
1944 }
1945
1946 $headers['Subject'] = $this->decodeSMTPSubject($headers['Subject']);
1947
1948 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
1949 $emailto = (string) $overview['to'];
1950 } else {
1951 $emailto = $this->decodeSMTPSubject($overview[0]->to);
1952 }
1953
1954 $operationslog .= '<br>** Process email #'.dol_escape_htmltag((string) $iforemailloop);
1955
1956 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
1958 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
1959 // $operationslog .= " - ".dol_escape_htmltag((string) $imapemail);
1960 $msgid = str_replace(array('<', '>'), '', $overview['message_id']);
1961 } else {
1962 $operationslog .= " - ".dol_escape_htmltag((string) $imapemail);
1963 $msgid = str_replace(array('<', '>'), '', $overview[0]->message_id);
1964 }
1965 $operationslog .= " - MsgId: ".$msgid;
1966 $operationslog .= " - Date: ".($headers['Date'] ?? $langs->transnoentitiesnoconv("NotFound"));
1967 $operationslog .= " - References: ".dol_escape_htmltag($headers['References'] ?? $langs->transnoentitiesnoconv("NotFound"))." - Subject: ".dol_escape_htmltag($headers['Subject']);
1968
1969 dol_syslog("-- Process email #".$iforemailloop.", MsgId: ".$msgid.", Date: ".($headers['Date'] ?? '').", References: ".($headers['References'] ?? '').", Subject: ".$headers['Subject']);
1970
1971
1972 $trackidfoundintorecipienttype = '';
1973 $trackidfoundintorecipientid = 0;
1974 $reg = array();
1975 // See also later list of all supported tags...
1976 // Note: "th[i]" to avoid matching a codespell suggestion to convert to "this".
1977 // TODO Add host after the @'.preg_quote($host, '/')
1978 if (preg_match('/\+(th[i]|ctc|use|mem|sub|proj|tas|con|tic|pro|ord|inv|spro|sor|sin|leav|stockinv|job|surv|salary)([0-9]+)@/', $emailto, $reg)) {
1979 $trackidfoundintorecipienttype = $reg[1];
1980 $trackidfoundintorecipientid = $reg[2];
1981 } elseif (preg_match('/\+emailing-(\w+)@/', $emailto, $reg)) { // Can be 'emailing-test' or 'emailing-IdMailing-IdRecipient'
1982 $trackidfoundintorecipienttype = 'emailing';
1983 $trackidfoundintorecipientid = $reg[1];
1984 }
1985
1986 $trackidfoundintomsgidtype = '';
1987 $trackidfoundintomsgidid = 0;
1988 $reg = array();
1989 // See also later list of all supported tags...
1990 // Note: "th[i]" to avoid matching a codespell suggestion to convert to "this".
1991 // TODO Add host after the @
1992 if (preg_match('/(?:[\+\-])(th[i]|ctc|use|mem|sub|proj|tas|con|tic|pro|ord|inv|spro|sor|sin|leav|stockinv|job|surv|salary)([0-9]+)@/', $msgid, $reg)) {
1993 $trackidfoundintomsgidtype = $reg[1];
1994 $trackidfoundintomsgidid = $reg[2];
1995 } elseif (preg_match('/(?:[\+\-])emailing-(\w+)@/', $msgid, $reg)) { // Can be 'emailing-test' or 'emailing-IdMailing-IdRecipient'
1996 $trackidfoundintomsgidtype = 'emailing';
1997 $trackidfoundintomsgidid = $reg[1];
1998 }
1999
2000 // Now apply some filters.
2001
2002 // If there is an emailcollector filter on trackid
2003 if ($searchfilterdoltrackid > 0) {
2004 $referencesForFilter = $headers['References'] ?? '';
2005 if (!empty($headers['References']) && !empty($headers['In-Reply-To'])) {
2006 $referencesForFilter .= ' ';
2007 }
2008 $referencesForFilter .= ($headers['In-Reply-To'] ?? '');
2009
2010 if (empty($trackidfoundintorecipienttype) && empty($trackidfoundintomsgidtype)) {
2011 if (empty($referencesForFilter) || !preg_match('/@'.preg_quote($host, '/').'/', $referencesForFilter)) {
2012 $nbemailprocessed++;
2013 dol_syslog(" Discarded - No suffix in email recipient, and no Header 'References/In-Reply-To' found matching the signature of the application, so with a trackid coming from the application");
2014 continue; // Exclude email
2015 }
2016 }
2017 }
2018 // If we DON'T want email if there is a trackid
2019 if ($searchfilternodoltrackid > 0) {
2020 $referencesForFilter = $headers['References'] ?? '';
2021 if (!empty($headers['References']) && !empty($headers['In-Reply-To'])) {
2022 $referencesForFilter .= ' ';
2023 }
2024 $referencesForFilter .= ($headers['In-Reply-To'] ?? '');
2025
2026 if (!empty($trackidfoundintorecipienttype) || !empty($trackidfoundintomsgidtype) || (!empty($referencesForFilter) && preg_match('/@'.preg_quote($host, '/').'/', $referencesForFilter))) {
2027 $nbemailprocessed++;
2028 dol_syslog(" Discarded - Suffix found into email recipient, or Header 'References/In-Reply-To' found and matching signature of application so with a trackid");
2029 continue; // Exclude email
2030 }
2031 }
2032
2033 if ($searchfilterisanswer > 0) {
2034 $referencesforanswer = '';
2035 if (!empty($headers['References'])) {
2036 $referencesforanswer .= $headers['References'].' ';
2037 }
2038 if (!empty($headers['In-Reply-To'])) {
2039 $referencesforanswer .= $headers['In-Reply-To'];
2040 }
2041
2042 $hasdolibarrreference = 0;
2043 if (!empty($referencesforanswer)) {
2044 if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote($host, '/').'/', $referencesforanswer)) {
2045 $hasdolibarrreference = 1;
2046 } elseif (getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE')) {
2047 if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote(getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE'), '/').'/', $referencesforanswer)) {
2048 $hasdolibarrreference = 1;
2049 }
2050 }
2051 }
2052
2053 $isanswer = 0;
2054 if (preg_match('/^(回复|回覆|SV|Antw|VS|RE|Re|AW|Aw|ΑΠ|השב| תשובה | הועבר|Vá|R|RIF|BLS|Atb|RES|Odp|பதில்|YNT|ATB)\s*:\s+/i', $headers['Subject'])) {
2055 $isanswer = 1;
2056 }
2057 if (getDolGlobalString('EMAILCOLLECTOR_USE_IN_REPLY_TO_TO_DETECT_ANSWERS')) {
2058 // Note: "In-Reply-To" to detect if mail is an answer of another mail is not reliable because we can have:
2059 // Message-ID=A, In-Reply-To=B, References=B and message can BE an answer but may be NOT (for example a transfer of an email rewritten)
2060 if (!empty($headers['In-Reply-To'])) {
2061 $isanswer = 1;
2062 }
2063 }
2064 if ($hasdolibarrreference) {
2065 $isanswer = 1;
2066 }
2067 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && empty($headers['References'])) $isanswer = 1; // If in-reply-to differs of message-id, this is a reply
2068 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && !empty($headers['References']) && strpos($headers['References'], $headers['Message-ID']) !== false) $isanswer = 1;
2069
2070 if (!$isanswer) {
2071 $nbemailprocessed++;
2072 dol_syslog(" Discarded - Email is not an answer (no reply marker detected, and test on In-Reply-To not requested because not reliable, or test on In-Reply-To requested but not found)");
2073 continue; // Exclude email
2074 }
2075 }
2076 if ($searchfilterisnotanswer > 0) {
2077 // Note: "In-Reply-To" to detect if mail is an answer is not reliable because we can have:
2078 // Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (for example a transfer rewritten)
2079 $isanswer = 0;
2080 if (preg_match('/^(回复|回覆|SV|Antw|VS|RE|Re|AW|Aw|ΑΠ|השב| תשובה | הועבר|Vá|R|RIF|BLS|Atb|RES|Odp|பதில்|YNT|ATB)\s*:\s+/i', $headers['Subject'])) {
2081 $isanswer = 1;
2082 }
2083 // By default, ignore generic non-empty References to avoid false positives from
2084 // automated platform notifications. This option restores legacy behavior.
2085 if (!$isanswer && getDolGlobalInt('EMAILCOLLECTOR_ISNOTANSWER_USE_REFERENCES') && !empty($headers['References'])) {
2086 $isanswer = 1;
2087 }
2088 if (!$isanswer && getDolGlobalString('EMAILCOLLECTOR_USE_IN_REPLY_TO_TO_DETECT_ANSWERS')) {
2089 if (!empty($headers['In-Reply-To'])) {
2090 $isanswer = 1;
2091 }
2092 }
2093 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && empty($headers['References'])) $isanswer = 1; // If in-reply-to differs of message-id, this is a reply
2094 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && !empty($headers['References']) && strpos($headers['References'], $headers['Message-ID']) !== false) $isanswer = 1;
2095 if ($isanswer) {
2096 $nbemailprocessed++;
2097 dol_syslog(" Discarded - Email is an answer");
2098 continue; // Exclude email
2099 }
2100 }
2101 if ($searchfilterreplyto > 0) {
2102 if (!empty($headers['Reply-To'])) {
2103 $isreplytook = 0;
2104 foreach ($rulesreplyto as $key => $rulereplyto) {
2105 if (preg_match('/'.preg_quote($rulereplyto, '/').'/', $headers['Reply-To'])) {
2106 $isreplytook++;
2107 }
2108 }
2109
2110 if (!$isreplytook || $isreplytook != count($rulesreplyto)) {
2111 $nbemailprocessed++;
2112 dol_syslog(" Discarded - Reply-to does not match");
2113 continue; // Exclude email
2114 }
2115 }
2116 }
2117
2118 //print "Process mail ".$iforemailloop." Subject: ".dol_escape_htmltag($headers['Subject'])." selected<br>\n";
2119
2120 $thirdpartystatic = new Societe($this->db);
2121 $contactstatic = new Contact($this->db);
2122 $projectstatic = new Project($this->db);
2123
2124 $nbactiondoneforemail = 0;
2125 $errorforemail = 0;
2126 $errorforactions = 0;
2127 $thirdpartyfoundby = '';
2128 $contactfoundby = '';
2129 $projectfoundby = '';
2130 $ticketfoundby = '';
2131 $candidaturefoundby = '';
2132
2133
2134 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
2135 $dateformated = dol_print_date($overview['date'], 'dayrfc', 'gmt'); // May generate a warning "dol_print_date($overview['date'], 'dayrfc', 'gmt')" in log
2136 dol_syslog("msgid=".$overview['message_id']." date=".$dateformated." from=".$overview['from']." to=".$overview['to']." subject=".$overview['subject']);
2137
2138 // Removed emojis
2139 $overview['subject'] = removeEmoji($overview['subject'], getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
2140 } else {
2141 dol_syslog("msgid=".$overview[0]->message_id." date=".dol_print_date($overview[0]->udate, 'dayrfc', 'gmt')." from=".$overview[0]->from." to=".$overview[0]->to." subject=".$overview[0]->subject);
2142
2143 $overview[0]->subject = $this->decodeSMTPSubject($overview[0]->subject);
2144
2145 $overview[0]->from = $this->decodeSMTPSubject($overview[0]->from);
2146
2147 // Removed emojis
2148 $overview[0]->subject = removeEmoji($overview[0]->subject, getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
2149 }
2150 // GET IMAP email structure/content
2151 global $htmlmsg, $plainmsg, $charset, $attachments;
2152
2153 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
2155 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
2156 // Reset message body globals to prevent carryover from previous email in loop
2157 // Note: $charset is NOT reset as PHPIMAP handles charset internally
2158 $htmlmsg = $plainmsg = '';
2159 $attachments = array();
2160
2161 if ($imapemail->hasHTMLBody()) {
2162 $htmlmsg = $imapemail->getHTMLBody();
2163 }
2164 if ($imapemail->hasTextBody() && $imapemail->getTextBody() != "\n") {
2165 $plainmsg = $imapemail->getTextBody();
2166 }
2167 if ($imapemail->hasAttachments()) {
2168 $attachments = $imapemail->getAttachments()->all();
2169 } else {
2170 $attachments = array();
2171 }
2172 } else {
2173 $getMsg = $this->getmsg($connection, $imapemail); // This set global var $charset, $htmlmsg, $plainmsg, $attachments
2174 if ($getMsg < 0) {
2175 $this->errors = array_merge($this->errors, [$this->error]);
2176 return $getMsg;
2177 }
2178 }
2179 '@phan-var-force Webklex\PHPIMAP\Attachment[] $attachments';
2182 //$htmlmsg,$plainmsg,$charset,$attachments
2183 $messagetext = $plainmsg ? $plainmsg : dol_string_nohtmltag((string) $htmlmsg, 0);
2184 // Removed emojis
2185
2186 if (utf8_valid($messagetext)) {
2187 $messagetext = removeEmoji($messagetext, getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
2188 } else {
2189 $operationslog .= '<br>Discarded - Email body is not valid utf8';
2190 dol_syslog(" Discarded - Email body is not valid utf8");
2191 continue; // Exclude email
2192 }
2193
2194 if (!empty($searchfilterexcludebodyarray)) {
2195 foreach ($searchfilterexcludebodyarray as $searchfilterexcludebody) {
2196 if (preg_match('/'.preg_quote($searchfilterexcludebody, '/').'/ms', $messagetext)) {
2197 $nbemailprocessed++;
2198 $operationslog .= '<br>Discarded - Email body contains string '.$searchfilterexcludebody;
2199 dol_syslog(" Discarded - Email body contains string ".$searchfilterexcludebody);
2200 continue 2; // Exclude email
2201 }
2202 }
2203 }
2204
2205 // Parse IMAP email structure
2206 /*
2207 $structure = imap_fetchstructure($connection, $imapemail, FT_UID);
2208
2209 $partplain = $parthtml = -1;
2210 $encodingplain = $encodinghtml = '';
2211
2212 $result = createPartArray($structure, '');
2213
2214 foreach($result as $part)
2215 {
2216 // $part['part_object']->type seems 0 for content
2217 // $part['part_object']->type seems 5 for attachment
2218 if (empty($part['part_object'])) continue;
2219 if ($part['part_object']->subtype == 'HTML')
2220 {
2221 $parthtml=$part['part_number'];
2222 if ($part['part_object']->encoding == 4)
2223 {
2224 $encodinghtml = 'aaa';
2225 }
2226 }
2227 if ($part['part_object']->subtype == 'PLAIN')
2228 {
2229 $partplain=$part['part_number'];
2230 if ($part['part_object']->encoding == 4)
2231 {
2232 $encodingplain = 'rr';
2233 }
2234 }
2235 }
2236
2237 $messagetext = imap_fetchbody($connection, $imapemail, ($parthtml != '-1' ? $parthtml : ($partplain != '-1' ? $partplain : 1)), FT_PEEK|FTP_UID);
2238 */
2239
2240 $fromstring = '';
2241 $replytostring = '';
2242
2243 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
2244 $fromstring = $this->decodeSMTPSubject((string) ($overview['from'] ?? ''));
2245 $replytostring = !empty($headers['Reply-To']) ? $this->decodeSMTPSubject($headers['Reply-To']) : '';
2246
2247 $sender = $this->decodeSMTPSubject((string) ($overview['sender'] ?? ''));
2248 $to = $this->decodeSMTPSubject((string) ($overview['to'] ?? ''));
2249 $sendtocc = $this->decodeSMTPSubject((string) ($overview['cc'] ?? ''));
2250 $sendtobcc = $this->decodeSMTPSubject((string) ($overview['bcc'] ?? ''));
2251
2252 $tmpdate = $overview['date']->toDate(); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
2253 $tmptimezone = $tmpdate->getTimezone()->getName(); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
2254
2255 $dateemail = dol_stringtotime((string) $overview['date'], 'gmt'); // if $overview['timezone'] is "+00:00"
2256 if (preg_match('/^([+\-])(\d\d):(\d\d)/', $tmptimezone, $reg)) {
2257 if ($reg[1] == '+' && ($reg[2] != '00' || $reg[3] != '00')) {
2258 $dateemail -= (3600 * (int) $reg[2]);
2259 $dateemail -= (60 * (int) $reg[3]);
2260 }
2261 if ($reg[1] == '-' && ($reg[2] != '00' || $reg[3] != '00')) {
2262 $dateemail += (3600 * (int) $reg[2]);
2263 $dateemail += (60 * (int) $reg[3]);
2264 }
2265 }
2266 $subject = $this->decodeSMTPSubject((string) ($overview['subject'] ?? ''));
2267 } else {
2268 $fromstring = (string) $overview[0]->from;
2269 $replytostring = (!empty($overview['in_reply-to']) ? $overview['in_reply-to'] : (!empty($headers['Reply-To']) ? $headers['Reply-To'] : "")) ;
2270
2271 $sender = !empty($overview[0]->sender) ? $overview[0]->sender : '';
2272 $to = $overview[0]->to;
2273 $sendtocc = !empty($overview[0]->cc) ? $overview[0]->cc : '';
2274 $sendtobcc = !empty($overview[0]->bcc) ? $overview[0]->bcc : '';
2275 $dateemail = dol_stringtotime((string) $overview[0]->udate, 'gmt');
2276 $subject = (string) $overview[0]->subject;
2277 }
2278
2279 if (!empty($searchfilterexcludesubjectarray)) {
2280 foreach ($searchfilterexcludesubjectarray as $searchfilterexcludesubject) {
2281 if (preg_match('/'.preg_quote($searchfilterexcludesubject, '/').'/ms', $subject)) {
2282 $nbemailprocessed++;
2283 $operationslog .= '<br>Discarded - Email subject contains string '.$searchfilterexcludesubject;
2284 dol_syslog(" Discarded - Email subject contains string ".$searchfilterexcludesubject);
2285 continue 2; // Exclude email
2286 }
2287 }
2288 }
2289
2290 $reg = array();
2291 if (preg_match('/^(.*)<(.*)>$/', $fromstring, $reg)) {
2292 $email_from = $reg[2];
2293 $fromtext = $reg[1];
2294 } else {
2295 $email_from = $fromstring;
2296 $fromtext = '';
2297 }
2298 if (preg_match('/^(.*)<(.*)>$/', $replytostring, $reg)) {
2299 $replyto = $reg[2];
2300 $replytotext = $reg[1];
2301 } else {
2302 $replyto = $replytostring;
2303 $replytotext = '';
2304 }
2305
2306 if (!empty($searchfilterexcludeemailmap) || !empty($searchfilterexcludedomainarray)) {
2307 $emailsToCheck = array();
2308 $tmpaddressblob = trim($fromstring.' '.$replytostring);
2309 if ($tmpaddressblob !== '' && preg_match_all('/[a-z0-9._%+\\-]+@[a-z0-9.\\-]+\\.[a-z]{2,}/i', $tmpaddressblob, $tmpmatches)) {
2310 foreach ($tmpmatches[0] as $tmpEmail) {
2311 $tmpEmail = strtolower(trim($tmpEmail));
2312 if ($tmpEmail !== '') {
2313 $emailsToCheck[$tmpEmail] = true;
2314 }
2315 }
2316 }
2317
2318 if (empty($emailsToCheck)) {
2319 foreach (array($email_from, $replyto) as $tmpEmail) {
2320 $tmpEmail = strtolower(trim((string) $tmpEmail));
2321 if ($tmpEmail !== '' && strpos($tmpEmail, '@') !== false) {
2322 $emailsToCheck[$tmpEmail] = true;
2323 }
2324 }
2325 }
2326
2327 $discardEmail = '';
2328 $discardDomain = '';
2329 $matchedDomainRule = '';
2330
2331 foreach (array_keys($emailsToCheck) as $tmpEmail) {
2332 if (!empty($searchfilterexcludeemailmap[$tmpEmail])) {
2333 $discardEmail = $tmpEmail;
2334 break;
2335 }
2336
2337 if (!empty($searchfilterexcludedomainarray)) {
2338 $posat = strrpos($tmpEmail, '@');
2339 if ($posat === false) {
2340 continue;
2341 }
2342 $tmpDomain = strtolower(substr($tmpEmail, $posat + 1));
2343 foreach ($searchfilterexcludedomainarray as $excludedDomain) {
2344 if ($excludedDomain === '') {
2345 continue;
2346 }
2347 if ($tmpDomain === $excludedDomain || substr($tmpDomain, -strlen('.'.$excludedDomain)) === '.'.$excludedDomain) {
2348 $discardDomain = $tmpDomain;
2349 $matchedDomainRule = $excludedDomain;
2350 break 2;
2351 }
2352 }
2353 }
2354 }
2355
2356 if ($discardEmail !== '' || $discardDomain !== '') {
2357 $nbemailprocessed++;
2358 if ($discardEmail !== '') {
2359 $operationslog .= '<br>Discarded - Sender email excluded: '.$discardEmail;
2360 dol_syslog(" Discarded - Sender email excluded: ".$discardEmail);
2361 } else {
2362 $operationslog .= '<br>Discarded - Sender domain excluded: '.$discardDomain.($matchedDomainRule !== '' && $matchedDomainRule !== $discardDomain ? ' (matched '.$matchedDomainRule.')' : '');
2363 dol_syslog(" Discarded - Sender domain excluded: ".$discardDomain.($matchedDomainRule !== '' && $matchedDomainRule !== $discardDomain ? " (matched ".$matchedDomainRule.")" : ""));
2364 }
2365 continue;
2366 }
2367 }
2368
2369 $fk_element_id = 0;
2370 $fk_element_type = '';
2371
2372
2373 $this->db->begin();
2374
2375 $contactid = 0;
2376 $thirdpartyid = 0;
2377 $projectid = 0;
2378 $ticketid = 0;
2379
2380 // Analyze TrackId in field References (already analyzed previously into the "To:" and "Message-Id").
2381 // For example:
2382 // References: <1542377954.SMTPs-dolibarr-thi649@8f6014fde11ec6cdec9a822234fc557e>
2383 // References: <1542377954.SMTPs-dolibarr-tic649@8f6014fde11ec6cdec9a822234fc557e>
2384 // References: <1542377954.SMTPs-dolibarr-abc649@8f6014fde11ec6cdec9a822234fc557e>
2385 $trackid = '';
2386 $objectid = 0;
2387 $objectemail = null;
2388
2389 $reg = array();
2390 $arrayofreferences = array();
2391 if (!empty($headers['References'])) {
2392 $arrayofreferences = preg_split('/(,|\s+)/', $headers['References']);
2393 }
2394 if (!in_array('<'.$msgid.'>', $arrayofreferences)) {
2395 $arrayofreferences = array_merge($arrayofreferences, array('<'.$msgid.'>'));
2396 }
2397
2398 // We loop on References, but as soon as we found one that allow us to find an existing object,
2399 // we do a break (See line with comment "Exit loop of references").
2400 foreach ($arrayofreferences as $reference) {
2401 //print "Process mail ".$iforemailloop." email_msgid ".$msgid.", date ".dol_print_date($dateemail, 'dayhour', 'gmt').", subject ".$subject.", reference ".dol_escape_htmltag($reference)."<br>\n";
2402 if (!empty($trackidfoundintorecipienttype)) {
2403 $resultsearchtrackid = -1; // trackid found
2404 $reg[1] = $trackidfoundintorecipienttype;
2405 $reg[2] = $trackidfoundintorecipientid;
2406 } elseif (!empty($trackidfoundintomsgidtype)) {
2407 $resultsearchtrackid = -1; // trackid found
2408 $reg[1] = $trackidfoundintomsgidtype;
2409 $reg[2] = $trackidfoundintomsgidid;
2410 } else {
2411 $resultsearchtrackid = preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote($host, '/').'/', $reference, $reg); // trackid found or not
2412 if (empty($resultsearchtrackid) && getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE')) {
2413 $resultsearchtrackid = preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote(getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE'), '/').'/', $reference, $reg); // trackid found
2414 }
2415 }
2416
2417 if (!empty($resultsearchtrackid)) {
2418 // We found a tracker (in recipient email or msgid or into a Reference matching the Dolibarr server)
2419 $trackid = $reg[1].$reg[2];
2420
2421 $objectid = $reg[2];
2422 // See also list into interface_50_modAgenda_ActionsAuto
2423 if ($reg[1] == 'thi') { // Third-party
2424 $objectemail = new Societe($this->db);
2425 }
2426 if ($reg[1] == 'ctc') { // Contact
2427 $objectemail = new Contact($this->db);
2428 }
2429 if ($reg[1] == 'inv') { // Customer Invoice
2430 $objectemail = new Facture($this->db);
2431 }
2432 if ($reg[1] == 'sinv') { // Supplier Invoice
2433 $objectemail = new FactureFournisseur($this->db);
2434 }
2435 if ($reg[1] == 'pro') { // Customer Proposal
2436 $objectemail = new Propal($this->db);
2437 }
2438 if ($reg[1] == 'ord') { // Sale Order
2439 $objectemail = new Commande($this->db);
2440 }
2441 if ($reg[1] == 'shi') { // Shipment
2442 $objectemail = new Expedition($this->db);
2443 }
2444 if ($reg[1] == 'spro') { // Supplier Proposal
2445 $objectemail = new SupplierProposal($this->db);
2446 }
2447 if ($reg[1] == 'sord') { // Supplier Order
2448 $objectemail = new CommandeFournisseur($this->db);
2449 }
2450 if ($reg[1] == 'rec') { // Reception
2451 $objectemail = new Reception($this->db);
2452 }
2453 if ($reg[1] == 'proj') { // Project
2454 $objectemail = new Project($this->db);
2455 $projectfoundby = 'TrackID dolibarr-'.$trackid.'@...';
2456 }
2457 if ($reg[1] == 'tas') { // Task
2458 $objectemail = new Task($this->db);
2459 }
2460 if ($reg[1] == 'con') { // Contact
2461 $objectemail = new Contact($this->db);
2462 }
2463 if ($reg[1] == 'use') { // User
2464 $objectemail = new User($this->db);
2465 }
2466 if ($reg[1] == 'tic') { // Ticket
2467 $objectemail = new Ticket($this->db);
2468 $ticketfoundby = 'TrackID dolibarr-'.$trackid.'@...';
2469 }
2470 if ($reg[1] == 'recruitmentcandidature') { // Recruiting Candidate
2471 $objectemail = new RecruitmentCandidature($this->db);
2472 $candidaturefoundby = 'TrackID dolibarr-'.$trackid.'@...';
2473 }
2474 if ($reg[1] == 'mem') { // Member
2475 $objectemail = new Adherent($this->db);
2476 }
2477 /*if ($reg[1] == 'leav') { // Leave / Holiday
2478 $objectemail = new Holiday($db);
2479 }
2480 if ($reg[1] == 'exp') { // ExpenseReport
2481 $objectemail = new ExpenseReport($db);
2482 }*/
2483 } elseif (preg_match('/<(.*@.*)>/', $reference, $reg)) {
2484 // This is an external reference, we check if we have it in our database
2485 if (is_null($objectemail) && isModEnabled('ticket')) {
2486 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."ticket";
2487 $sql .= " WHERE email_msgid = '".$this->db->escape($reg[1])."' OR origin_references LIKE '%".$this->db->escape($this->db->escapeforlike($reg[1]))."%'";
2488 $resql = $this->db->query($sql);
2489 if ($resql) {
2490 $obj = $this->db->fetch_object($resql);
2491 if ($obj) {
2492 $objectid = $obj->rowid;
2493 $objectemail = new Ticket($this->db);
2494 $ticketfoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2495 }
2496 } else {
2497 $errorforemail++;
2498 }
2499 }
2500
2501 if (!is_object($objectemail) && isModEnabled('project')) {
2502 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."projet where email_msgid = '".$this->db->escape($reg[1])."'";
2503 $resql = $this->db->query($sql);
2504 if ($resql) {
2505 $obj = $this->db->fetch_object($resql);
2506 if ($obj) {
2507 $objectid = $obj->rowid;
2508 $objectemail = new Project($this->db);
2509 $projectfoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2510 }
2511 } else {
2512 $errorforemail++;
2513 }
2514 }
2515
2516 if (!is_object($objectemail) && isModEnabled('recruitment')) {
2517 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."recruitment_recruitmentcandidature where email_msgid = '".$this->db->escape($reg[1])."'";
2518 $resql = $this->db->query($sql);
2519 if ($resql) {
2520 $obj = $this->db->fetch_object($resql);
2521 if ($obj) {
2522 $objectid = $obj->rowid;
2523 $objectemail = new RecruitmentCandidature($this->db);
2524 $candidaturefoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2525 }
2526 } else {
2527 $errorforemail++;
2528 }
2529 }
2530 }
2531
2532 // Load object linked to email
2533 if (is_object($objectemail)) {
2534 $result = $objectemail->fetch($objectid);
2535 if ($result > 0) {
2536 $fk_element_id = $objectemail->id;
2537 $fk_element_type = $objectemail->element;
2538 // Fix fk_element_type
2539 if ($fk_element_type == 'facture') {
2540 $fk_element_type = 'invoice';
2541 }
2542
2543 if (get_class($objectemail) != 'Societe') {
2544 $thirdpartyid = $objectemail->fk_soc ?? $objectemail->socid;
2545 } else {
2546 $thirdpartyid = $objectemail->id;
2547 }
2548
2549 if (get_class($objectemail) != 'Contact') {
2550 $contactid = $objectemail->fk_socpeople;
2551 } else {
2552 $contactid = $objectemail->id;
2553 }
2554
2555 if (get_class($objectemail) != 'Project') {
2556 $projectid = isset($objectemail->fk_project) ? $objectemail->fk_project : $objectemail->fk_projet;
2557 } else {
2558 $projectid = $objectemail->id;
2559 }
2560
2561 if ($objectemail instanceof Ticket) { // For tickets, we have a column to store all met references, so we complete it if we need to.
2562 $ticketid = $objectemail->id;
2563
2564 $changeonticket_references = false;
2565 if (empty($trackid)) {
2566 $trackid = $objectemail->track_id;
2567 }
2568 if (empty($objectemail->origin_references)) {
2569 $objectemail->origin_references = !empty($headers['References']) ? $headers['References'] : null;
2570 $changeonticket_references = true;
2571 } else {
2572 foreach ($arrayofreferences as $key => $referencetmp) {
2573 if (!str_contains($objectemail->origin_references, $referencetmp)) {
2574 $objectemail->origin_references .= " ".$referencetmp;
2575 $changeonticket_references = true;
2576 }
2577 }
2578 }
2579 if ($changeonticket_references) {
2580 $operationslog .= '<br>We complete ticket ID='.$ticketid.' with property origin_references='.$objectemail->origin_references;
2581 $objectemail->update($user, 1); // We complete the references field with all references mentioned into this email. This field is for technical tracking purpose, not a user field, so no need to execute triggers
2582 }
2583 }
2584 }
2585 }
2586
2587 // Project
2588 if ($projectid > 0) {
2589 $result = $projectstatic->fetch($projectid);
2590 if ($result <= 0) {
2591 $projectstatic->id = 0;
2592 } else {
2593 $projectid = $projectstatic->id;
2594 if ($trackid) {
2595 $projectfoundby = 'trackid ('.$trackid.')';
2596 }
2597 if (empty($contactid)) {
2598 $contactid = $projectstatic->fk_contact;
2599 }
2600 if (empty($thirdpartyid)) {
2601 $thirdpartyid = $projectstatic->fk_soc;
2602 }
2603 }
2604 }
2605 // Contact
2606 if ($contactid > 0) {
2607 $result = $contactstatic->fetch($contactid);
2608 if ($result <= 0) {
2609 $contactstatic->id = 0;
2610 } else {
2611 $contactid = $contactstatic->id;
2612 if ($trackid) {
2613 $contactfoundby = 'trackid ('.$trackid.')';
2614 }
2615 if (empty($thirdpartyid)) {
2616 $thirdpartyid = $contactstatic->fk_soc;
2617 }
2618 }
2619 }
2620 // Thirdparty
2621 if ($thirdpartyid > 0) {
2622 $result = $thirdpartystatic->fetch($thirdpartyid);
2623 if ($result <= 0) {
2624 $thirdpartystatic->id = 0;
2625 } else {
2626 $thirdpartyid = $thirdpartystatic->id;
2627 if ($trackid) {
2628 $thirdpartyfoundby = 'trackid ('.$trackid.')';
2629 }
2630 }
2631 }
2632
2633 if (is_object($objectemail)) {
2634 break; // Exit loop of references. We already found an accurate reference
2635 }
2636 }
2637
2638 if (empty($contactid)) { // Try to find contact using email
2639 $result = $contactstatic->fetch(0, null, '', $email_from);
2640
2641 if ($result > 0) {
2642 dol_syslog("We found a contact with the email ".$email_from);
2643 $contactid = $contactstatic->id;
2644 $contactfoundby = 'email of contact ('.$email_from.')';
2645 if (empty($thirdpartyid) && $contactstatic->socid > 0) {
2646 $result = $thirdpartystatic->fetch($contactstatic->socid);
2647 if ($result > 0) {
2648 $thirdpartyid = $thirdpartystatic->id;
2649 $thirdpartyfoundby = 'email of contact ('.$email_from.')';
2650 }
2651 }
2652 }
2653 }
2654
2655 if (empty($thirdpartyid)) { // Try to find thirdparty using email
2656 $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $email_from);
2657 if ($result > 0) {
2658 dol_syslog("We found a thirdparty with the email ".$email_from);
2659 $thirdpartyid = $thirdpartystatic->id;
2660 $thirdpartyfoundby = 'email ('.$email_from.')';
2661 }
2662 }
2663
2664 /*
2665 if ($replyto) {
2666 if (empty($contactid)) { // Try to find contact using email
2667 $result = $contactstatic->fetch(0, null, '', $replyto);
2668
2669 if ($result > 0) {
2670 dol_syslog("We found a contact with the email ".$replyto);
2671 $contactid = $contactstatic->id;
2672 $contactfoundby = 'email of contact ('.$replyto.')';
2673 if (empty($thirdpartyid) && $contactstatic->socid > 0) {
2674 $result = $thirdpartystatic->fetch($contactstatic->socid);
2675 if ($result > 0) {
2676 $thirdpartyid = $thirdpartystatic->id;
2677 $thirdpartyfoundby = 'email of contact ('.$replyto.')';
2678 }
2679 }
2680 }
2681 }
2682
2683 if (empty($thirdpartyid)) { // Try to find thirdparty using email
2684 $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $replyto);
2685 if ($result > 0) {
2686 dol_syslog("We found a thirdparty with the email ".$replyto);
2687 $thirdpartyid = $thirdpartystatic->id;
2688 $thirdpartyfoundby = 'email ('.$replyto.')';
2689 }
2690 }
2691 }
2692 */
2693
2694 // Persist attachments for some linked objects (so hooks can rely on files on disk).
2695 $savedattachments = array();
2696 $savedattachmentsdir = '';
2697 $savedattachmentsnote = '';
2698 if (empty($mode) && !empty($attachments) && $fk_element_id > 0 && $fk_element_type === 'order_supplier' && $objectemail instanceof CommandeFournisseur) {
2699 $orderref = isset($objectemail->ref) ? (string) $objectemail->ref : '';
2700 if ($orderref !== '' && !empty($conf->fournisseur->commande)) {
2701 $entityforobject = isset($objectemail->entity) ? (int) $objectemail->entity : (int) $conf->entity;
2702 $basedir = (!empty($conf->fournisseur->commande->multidir_output[$entityforobject]) ? $conf->fournisseur->commande->multidir_output[$entityforobject] : $conf->fournisseur->commande->dir_output);
2703 $savedattachmentsdir = rtrim($basedir, '/').'/'.dol_sanitizeFileName($orderref);
2704 $savedattachments = $this->saveEmailCollectorAttachmentsToDir($savedattachmentsdir, $attachments);
2705 if (!empty($savedattachments)) {
2706 $operationslog .= '<br>Saved '.count($savedattachments).' attachment(s) into '.dol_escape_htmltag($savedattachmentsdir);
2707 }
2708 }
2709 }
2710 if (!empty($savedattachments)) {
2711 $names = array();
2712 foreach ($savedattachments as $attmeta) {
2713 if (!is_array($attmeta) || empty($attmeta['name'])) {
2714 continue;
2715 }
2716 $names[] = (string) $attmeta['name'];
2717 }
2718 if (!empty($names)) {
2719 $savedattachmentsnote = $langs->trans("NbOfAttachedFiles").' : '.count($names);
2720 $savedattachmentsnote .= ' ('.implode(', ', array_slice($names, 0, 10)).(count($names) > 10 ? '...' : '').')';
2721 }
2722 }
2723
2724
2725 // Now do all operations for the email (extract variables and creating data)
2726 if ($mode < 2) { // 0=Mode production, 1=Mode test (read IMAP and try SQL update then rollback), 2=Mode test with no SQL updates
2727 foreach ($this->actions as $operation) {
2728 $errorforthisaction = 0;
2729 $ticketalreadyexists = 0;
2730 if ($errorforactions) {
2731 break;
2732 }
2733 if (empty($operation['status'])) {
2734 continue;
2735 }
2736
2737 $operationslog .= '<br>* Process operation '.$operation['type'];
2738
2739 // Make Operation
2740 dol_syslog("Execute action ".$operation['type']." actionparam=".$operation['actionparam'].' thirdpartystatic->id='.$thirdpartystatic->id.' contactstatic->id='.$contactstatic->id.' projectstatic->id='.$projectstatic->id);
2741 dol_syslog("Execute action fk_element_id=".$fk_element_id." fk_element_type=".$fk_element_type); // If a Dolibarr tracker id is found, we should now the id of object
2742
2743 // Try to guess if this is an email in or out.
2744 $actioncode = 'EMAIL_IN';
2745 // If we scan the Sent box, we use the code for out email
2746 if (preg_match('/Sent$/', $sourcedir) || preg_match('/envoyés$/i', $sourcedir)) {
2747 $actioncode = 'EMAIL';
2748 }
2749 // If sender is in the list MAIL_FROM_EMAILS_TO_CONSIDER_SENDING
2750 $arrayofemailtoconsidersender = array_filter(array_map('trim', explode(',', getDolGlobalString('MAIL_FROM_EMAILS_TO_CONSIDER_SENDING'))));
2751 foreach ($arrayofemailtoconsidersender as $emailtoconsidersender) {
2752 if ($emailtoconsidersender === '') {
2753 continue;
2754 }
2755 if (preg_match('/'.preg_quote($emailtoconsidersender, '/').'/', $fromstring)) {
2756 $actioncode = 'EMAIL';
2757 }
2758 }
2759 $operationslog .= '<br>Email will have actioncode='.$actioncode;
2760
2761 $description = $descriptiontitle = $descriptionmeta = $descriptionfull = '';
2762
2763 $descriptiontitle = $langs->transnoentitiesnoconv("RecordCreatedByEmailCollector", $this->ref);
2764
2765 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("EmailMsgID").' : '.dol_escape_htmltag($msgid));
2766 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailTopic").' : '.dol_escape_htmltag($subject));
2767 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailDate").($langs->trans("MailDate") != 'Date' ? ' (Date)' : '').' : '.dol_escape_htmltag(dol_print_date($dateemail, "dayhourtext", "gmt")));
2768 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailFrom").($langs->trans("MailFrom") != 'From' ? ' (From)' : '').' : '.dol_escape_htmltag($fromstring));
2769 if ($sender) {
2770 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("Sender").($langs->trans("Sender") != 'Sender' ? ' (Sender)' : '').' : '.dol_escape_htmltag($sender));
2771 }
2772 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailTo").($langs->trans("MailTo") != 'To' ? ' (To)' : '').' : '.dol_escape_htmltag($to));
2773 if ($replyto) {
2774 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailReply").($langs->trans("MailReply") != 'Reply to' ? ' (Reply to)' : '').' : '.dol_escape_htmltag($replyto));
2775 }
2776 if ($sendtocc) {
2777 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailCC").($langs->trans("MailCC") != 'CC' ? ' (CC)' : '').' : '.dol_escape_htmltag($sendtocc));
2778 }
2779 if ($savedattachmentsnote) {
2780 $descriptionmeta = dol_concatdesc($descriptionmeta, $savedattachmentsnote);
2781 }
2782
2783 if ($operation['type'] == 'ticket') {
2784 // Verify if ticket already exists to fall back on the right operation
2785 $tickettocreate = new Ticket($this->db);
2786 $errorfetchticket = 0;
2787 $alreadycreated = 0;
2788 if (!empty($objectid) && $objectemail instanceof Ticket) {
2789 $alreadycreated = $tickettocreate->fetch((int) $objectid);
2790 }
2791 if ($alreadycreated == 0 && $ticketid > 0) { // objectemail was on a ticket it ticketid set
2792 $alreadycreated = $tickettocreate->fetch($ticketid);
2793 }
2794 if ($alreadycreated == 0 && !empty($trackid)) { // objectemail was on a ticket if trackid is set
2795 $alreadycreated = $tickettocreate->fetch(0, '', $trackid);
2796 }
2797 if ($alreadycreated == 0 && !empty($msgid)) { // objectemail was on a ticket if msgid is set
2798 $alreadycreated = $tickettocreate->fetch(0, '', '', $msgid);
2799 }
2800 if ($alreadycreated < 0) {
2801 $errorfetchticket++;
2802 }
2803 if (empty($errorfetchticket)) {
2804 if ($alreadycreated == 0) {
2805 $operationslog .= '<br>Ticket not found using trackid='.$trackid.' or msgid='.$msgid;
2806 $ticketalreadyexists = 0;
2807 } else {
2808 $operationslog .= '<br>Ticket already found using trackid='.$trackid.' or msgid='.$msgid.", we replace operation 'ticket' with 'recordevent' to add a new message"; // We change the operation type to do
2809 $ticketalreadyexists = 1;
2810 $operation['type'] = 'recordevent';
2811 }
2812 } else {
2813 $ticketalreadyexists = -1;
2814 }
2815 }
2816
2817 // Process now the operation according to its type
2818
2819 // Search and create thirdparty
2820 if ($operation['type'] == 'loadthirdparty' || $operation['type'] == 'loadandcreatethirdparty') {
2821 if ($thirdpartyid > 0) {
2822 // We already have found the thirdparty id to load, so we bypass the action loadthirdparty
2823 $idtouseforthirdparty = $thirdpartyid;
2824 $operationslog .= '<br>We already have found a related thirdparty id, so we bypass the action loadthirdparty and use idtouseforthirdparty='.$idtouseforthirdparty;
2825 } elseif (empty($operation['actionparam'])) {
2826 $errorforactions++;
2827 $this->error = "Action loadthirdparty or loadandcreatethirdparty has empty parameter. Must be a rule like 'name=HEADER:^From:(.*);' or 'name=SET:xxx' or 'name=EXTRACT:(body|subject):regex where 'name' can be replaced with 'id' or 'email' to define how to set or extract data. More properties can also be set, for example client=SET:2;";
2828 $this->errors[] = $this->error;
2829 } else {
2830 $actionparam = $operation['actionparam'];
2831 $idtouseforthirdparty = '';
2832 $nametouseforthirdparty = '';
2833 $emailtouseforthirdparty = '';
2834 $namealiastouseforthirdparty = '';
2835
2836 $operationslog .= '<br>Loop on each property to set into actionparam';
2837
2838 // $actionparam = 'param=SET:aaa' or 'param=EXTRACT:BODY:....'
2839 $arrayvaluetouse = dolExplodeIntoArray($actionparam, '(\n\r|\r|\n|;)', '=');
2840 foreach ($arrayvaluetouse as $propertytooverwrite => $valueforproperty) {
2841 $sourcestring = '';
2842 $sourcefield = '';
2843 $regexstring = '';
2844 $regforregex = array();
2845
2846 if (preg_match('/^EXTRACT:([a-zA-Z0-9_]+):(.*)$/', $valueforproperty, $regforregex)) {
2847 $sourcefield = $regforregex[1];
2848 $regexstring = $regforregex[2];
2849 }
2850
2851 if (!empty($sourcefield) && !empty($regexstring)) {
2852 if (strtolower($sourcefield) == 'body') {
2853 $sourcestring = $messagetext;
2854 } elseif (strtolower($sourcefield) == 'subject') {
2855 $sourcestring = $subject;
2856 } elseif (strtolower($sourcefield) == 'header') {
2857 $sourcestring = $header;
2858 }
2859
2860 if ($sourcestring) {
2861 $regforval = array();
2862 if (preg_match('/'.$regexstring.'/ms', $sourcestring, $regforval)) {
2863 // Overwrite param $tmpproperty
2864 if ($propertytooverwrite == 'id') {
2865 $idtouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2866
2867 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found idtouseforthirdparty='.dol_escape_htmltag($idtouseforthirdparty);
2868 } elseif ($propertytooverwrite == 'email') {
2869 $emailtouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2870
2871 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found emailtouseforthirdparty='.dol_escape_htmltag($emailtouseforthirdparty);
2872 } elseif ($propertytooverwrite == 'name') {
2873 $nametouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2874
2875 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found nametouseforthirdparty='.dol_escape_htmltag($nametouseforthirdparty);
2876 } elseif ($propertytooverwrite == 'name_alias') {
2877 $namealiastouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2878
2879 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found namealiastouseforthirdparty='.dol_escape_htmltag($namealiastouseforthirdparty);
2880 } else {
2881 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> We discard this, not a field used to search an existing thirdparty';
2882 }
2883 } else {
2884 // Regex not found
2885 if (in_array($propertytooverwrite, array('id', 'email', 'name', 'name_alias'))) {
2886 $idtouseforthirdparty = null;
2887 $nametouseforthirdparty = null;
2888 $emailtouseforthirdparty = null;
2889 $namealiastouseforthirdparty = null;
2890
2891 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Not found. Property searched is critical so we cancel the search.';
2892 } else {
2893 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Not found';
2894 }
2895 }
2896 } else {
2897 // Nothing can be done for this param
2898 $errorforactions++;
2899 $this->error = 'The extract rule to use to load thirdparty for email '.$msgid.' has an unknown source (must be HEADER, SUBJECT or BODY)';
2900 $this->errors[] = $this->error;
2901
2902 $operationslog .= '<br>'.$this->error;
2903 }
2904 } elseif (preg_match('/^(SET|SETIFEMPTY):(.*)$/', $valueforproperty, $reg)) {
2905 //if (preg_match('/^options_/', $tmpproperty)) $object->array_options[preg_replace('/^options_/', '', $tmpproperty)] = $reg[1];
2906 //else $object->$tmpproperty = $reg[1];
2907 // Example: id=SETIFEMPTY:123
2908 if ($propertytooverwrite == 'id') {
2909 $idtouseforthirdparty = $reg[2];
2910
2911 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property idtouseforthrdparty='.dol_escape_htmltag($idtouseforthirdparty);
2912 } elseif ($propertytooverwrite == 'email') {
2913 $emailtouseforthirdparty = $reg[2];
2914
2915 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property emailtouseforthrdparty='.dol_escape_htmltag($emailtouseforthirdparty);
2916 } elseif ($propertytooverwrite == 'name') {
2917 $nametouseforthirdparty = $reg[2];
2918
2919 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property nametouseforthirdparty='.dol_escape_htmltag($nametouseforthirdparty);
2920 } elseif ($propertytooverwrite == 'name_alias') {
2921 $namealiastouseforthirdparty = $reg[2];
2922
2923 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property namealiastouseforthirdparty='.dol_escape_htmltag($namealiastouseforthirdparty);
2924 }
2925 } else {
2926 $errorforactions++;
2927 $this->error = 'Bad syntax for description of action parameters: '.$actionparam;
2928 $this->errors[] = $this->error;
2929 break;
2930 }
2931 }
2932
2933 if (!$errorforactions && ($idtouseforthirdparty || $emailtouseforthirdparty || $nametouseforthirdparty || $namealiastouseforthirdparty)) {
2934 // We make another search on thirdparty
2935 $operationslog .= '<br>We have this initial main data to search thirdparty: id='.$idtouseforthirdparty.', email='.$emailtouseforthirdparty.', name='.$nametouseforthirdparty.', name_alias='.$namealiastouseforthirdparty.'.';
2936
2937 $tmpobject = new stdClass();
2938 $tmpobject->element = 'generic';
2939 $tmpobject->id = $idtouseforthirdparty;
2940 $tmpobject->name = $nametouseforthirdparty;
2941 $tmpobject->name_alias = $namealiastouseforthirdparty;
2942 $tmpobject->email = $emailtouseforthirdparty;
2943
2944 $this->overwritePropertiesOfObject($tmpobject, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2945
2946 $idtouseforthirdparty = $tmpobject->id;
2947 $nametouseforthirdparty = $tmpobject->name;
2948 $namealiastouseforthirdparty = $tmpobject->name_alias;
2949 $emailtouseforthirdparty = $tmpobject->email;
2950
2951 $operationslog .= '<br>We try to search existing thirdparty with idtouseforthirdparty='.$idtouseforthirdparty.' emailtouseforthirdparty='.$emailtouseforthirdparty.' nametouseforthirdparty='.$nametouseforthirdparty.' namealiastouseforthirdparty='.$namealiastouseforthirdparty;
2952
2953 // Try to find the thirdparty that match the most the information we have
2954 $result = $thirdpartystatic->findNearest((int) $idtouseforthirdparty, (string) $nametouseforthirdparty, '', '', '', '', '', '', '', '', (string) $emailtouseforthirdparty, (string) $namealiastouseforthirdparty);
2955
2956 if ($result < 0) {
2957 if (getDolGlobalInt('EMAILCOLLECTOR_USE_THIS_THIRDPARTY_ID_IF_DUPLICATE') && $result == -2) { // If 2 thirdparty found, we will use a generic one defined by EMAILCOLLECTOR_USE_THIS_THIRDPARTYID_IF_DUPLICATE
2958 $idtouseforthirdparty = getDolGlobalInt('EMAILCOLLECTOR_USE_THIS_THIRDPARTY_ID_IF_DUPLICATE');
2959
2960 $thirdpartystatic->fetch($idtouseforthirdparty);
2961
2962 dol_syslog('Thirdparty found twice (or more) so, according to option EMAILCOLLECTOR_USE_THIS_THIRDPARTY_ID_IF_DUPLICATE, we will use the generic one with id = '.dol_escape_htmltag((string) $thirdpartystatic->id)." and name ".dol_escape_htmltag($thirdpartystatic->name));
2963
2964 $operationslog .= '<br>Thirdparty found twice (or more) so, according to option EMAILCOLLECTOR_USE_THIS_THIRDPARTY_ID_IF_DUPLICATE, we will use the generic one with id = '.dol_escape_htmltag((string) $thirdpartystatic->id)." and name ".dol_escape_htmltag($thirdpartystatic->name);
2965 } else {
2966 $errorforactions++;
2967 $this->error = 'Error when getting thirdparty with name '.((string) $nametouseforthirdparty).', alternative name '.((string) $namealiastouseforthirdparty).' and email '.$emailtouseforthirdparty.' (may be 2 record exists with same name ?)';
2968 $this->errors[] = $this->error;
2969 break;
2970 }
2971 }
2972 if ($result == 0) { // No thirdparty found
2973 if ($operation['type'] == 'loadthirdparty') {
2974 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was not found");
2975
2976 // Search into contacts of thirdparties to try to guess the thirdparty to use
2977 $resultContact = $contactstatic->findNearest(0, '', '', '', (string) $emailtouseforthirdparty, '', 0);
2978 if ($resultContact > 0) {
2979 $contactstatic->fetch($resultContact);
2980 $idtouseforthirdparty = $contactstatic->socid;
2981 $result = $thirdpartystatic->fetch($idtouseforthirdparty);
2982 if ($result > 0) {
2983 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was found thanks to linked contact search");
2984 } else {
2985 $errorforactions++;
2986 $langs->load("errors");
2987 $this->error = $langs->trans('ErrorFailedToLoadThirdParty', (string) $idtouseforthirdparty, (string) $emailtouseforthirdparty, (string) $nametouseforthirdparty, (string) $namealiastouseforthirdparty);
2988 $this->errors[] = $this->error;
2989 }
2990 } else {
2991 $errorforactions++;
2992 $langs->load("errors");
2993 $this->error = $langs->trans('ErrorFailedToLoadThirdParty', (string) $idtouseforthirdparty, (string) $emailtouseforthirdparty, (string) $nametouseforthirdparty, (string) $namealiastouseforthirdparty);
2994 $this->errors[] = $this->error;
2995 }
2996 } elseif ($operation['type'] == 'loadandcreatethirdparty') {
2997 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was not found. We try to create it.");
2998
2999 // Create thirdparty
3000 $thirdpartystatic = new Societe($db);
3001 $thirdpartystatic->name = (string) $nametouseforthirdparty;
3002 if (!empty($namealiastouseforthirdparty)) {
3003 if ($namealiastouseforthirdparty != $nametouseforthirdparty) {
3004 $thirdpartystatic->name_alias = $namealiastouseforthirdparty;
3005 }
3006 } else {
3007 $thirdpartystatic->name_alias = (empty($replytostring) ? (empty($fromtext) ? '' : $fromtext) : $replytostring);
3008 }
3009 $thirdpartystatic->email = (empty($emailtouseforthirdparty) ? (empty($replyto) ? (empty($email_from) ? '' : $email_from) : $replyto) : $emailtouseforthirdparty);
3010
3011 // Overwrite values with values extracted from source email
3012 $errorforthisaction = $this->overwritePropertiesOfObject($thirdpartystatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3013
3014 if ($thirdpartystatic->client && empty($thirdpartystatic->code_client)) {
3015 $thirdpartystatic->code_client = 'auto';
3016 }
3017 if ($thirdpartystatic->fournisseur && empty($thirdpartystatic->code_fournisseur)) {
3018 $thirdpartystatic->code_fournisseur = 'auto';
3019 }
3020
3021 if ($errorforthisaction) {
3022 $errorforactions++;
3023 } else {
3024 $result = $thirdpartystatic->create($user);
3025 if ($result <= 0) {
3026 $errorforactions++;
3027 $this->error = $thirdpartystatic->error;
3028 $this->errors = $thirdpartystatic->errors;
3029 } else {
3030 $operationslog .= '<br>Thirdparty created -> id = '.dol_escape_htmltag((string) $thirdpartystatic->id);
3031 }
3032 }
3033 }
3034 } elseif ($result > 0) { // if $result > 0, it is ID of thirdparty
3035 dol_syslog("One and only one existing third party has been found");
3036
3037 $thirdpartystatic->fetch($result);
3038
3039 $operationslog .= '<br>Thirdparty already exists with id = '.dol_escape_htmltag((string) $thirdpartystatic->id)." and name ".dol_escape_htmltag($thirdpartystatic->name);
3040 }
3041 }
3042 }
3043 } elseif ($operation['type'] == 'loadandcreatecontact') { // Search and create contact
3044 if (empty($operation['actionparam'])) {
3045 $errorforactions++;
3046 $this->error = "Action loadandcreatecontact has empty parameter. Must be 'SET:xxx' or 'EXTRACT:(body|subject):regex' to define how to extract data";
3047 $this->errors[] = $this->error;
3048 } else {
3049 $contact_static = new Contact($this->db);
3050 // Overwrite values with values extracted from source email
3051 $errorforthisaction = $this->overwritePropertiesOfObject($contact_static, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3052 if ($errorforthisaction) {
3053 $errorforactions++;
3054 } else {
3055 if (!empty($contact_static->email) && $contact_static->email != $email_from) {
3056 $email_from = $contact_static->email;
3057 }
3058 $email_from = (string) $email_from;
3059
3060 $email_from = (string) $email_from;
3061
3062 $result = $contactstatic->fetch(0, null, '', $email_from);
3063 if ($result < 0) {
3064 $errorforactions++;
3065 $this->error = 'Error when getting contact with email ' . $email_from;
3066 $this->errors[] = $this->error;
3067 break;
3068 } elseif ($result == 0) {
3069 dol_syslog("Contact with email " . $email_from . " was not found. We try to create it.");
3070 $contactstatic = new Contact($this->db);
3071
3072 // Create contact
3073 $contactstatic->email = $email_from;
3074 $operationslog .= '<br>We set property email='.dol_escape_htmltag($email_from);
3075
3076 // Overwrite values with values extracted from source email
3077 $errorforthisaction = $this->overwritePropertiesOfObject($contactstatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3078
3079 if ($errorforthisaction) {
3080 $errorforactions++;
3081 } else {
3082 // Search country by name or code
3083 if (!empty($contactstatic->country)) {
3084 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
3085 $result = getCountry('', '3', $this->db, null, 1, $contactstatic->country);
3086 if ($result == 'NotDefined') {
3087 $errorforactions++;
3088 $this->error = "Error country not found by this name '" . $contactstatic->country . "'";
3089 } elseif (!($result > 0)) {
3090 $errorforactions++;
3091 $this->error = "Error when search country by this name '" . $contactstatic->country . "'";
3092 $this->errors[] = $this->db->lasterror();
3093 } else {
3094 $contactstatic->country_id = $result;
3095 $operationslog .= '<br>We set property country_id='.dol_escape_htmltag($result);
3096 }
3097 } elseif (!empty($contactstatic->country_code)) {
3098 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
3099 $result = getCountry($contactstatic->country_code, '3', $this->db);
3100 if ($result == 'NotDefined') {
3101 $errorforactions++;
3102 $this->error = "Error country not found by this code '" . $contactstatic->country_code . "'";
3103 } elseif (!($result > 0)) {
3104 $errorforactions++;
3105 $this->error = "Error when search country by this code '" . $contactstatic->country_code . "'";
3106 $this->errors[] = $this->db->lasterror();
3107 } else {
3108 $contactstatic->country_id = $result;
3109 $operationslog .= '<br>We set property country_id='.dol_escape_htmltag($result);
3110 }
3111 }
3112
3113 if (!$errorforactions) {
3114 // Search state by name or code (for country if defined)
3115 if (!empty($contactstatic->state)) {
3116 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
3117 $result = dol_getIdFromCode($this->db, $contactstatic->state, 'c_departements', 'nom', 'rowid');
3118 if (empty($result)) {
3119 $errorforactions++;
3120 $this->error = "Error state not found by this name '" . $contactstatic->state . "'";
3121 } elseif (!($result > 0)) {
3122 $errorforactions++;
3123 $this->error = "Error when search state by this name '" . $contactstatic->state . "'";
3124 $this->errors[] = $this->db->lasterror();
3125 } else {
3126 $contactstatic->state_id = $result;
3127 $operationslog .= '<br>We set property state_id='.dol_escape_htmltag($result);
3128 }
3129 } elseif (!empty($contactstatic->state_code)) {
3130 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
3131 $result = dol_getIdFromCode($this->db, $contactstatic->state_code, 'c_departements', 'code_departement', 'rowid');
3132 if (empty($result)) {
3133 $errorforactions++;
3134 $this->error = "Error state not found by this code '" . $contactstatic->state_code . "'";
3135 } elseif (!($result > 0)) {
3136 $errorforactions++;
3137 $this->error = "Error when search state by this code '" . $contactstatic->state_code . "'";
3138 $this->errors[] = $this->db->lasterror();
3139 } else {
3140 $contactstatic->state_id = $result;
3141 $operationslog .= '<br>We set property state_id='.dol_escape_htmltag($result);
3142 }
3143 }
3144 }
3145
3146 if (!$errorforactions) {
3147 $result = $contactstatic->create($user);
3148 if ($result <= 0) {
3149 $errorforactions++;
3150 $this->error = $contactstatic->error;
3151 $this->errors = $contactstatic->errors;
3152 } else {
3153 $operationslog .= '<br>Contact created -> id = '.dol_escape_htmltag((string) $contactstatic->id);
3154 }
3155 }
3156 }
3157 }
3158 }
3159 }
3160 } elseif ($operation['type'] == 'recordevent') {
3161 // Create event
3162 $actioncomm = new ActionComm($this->db);
3163
3164 $alreadycreated = $actioncomm->fetch(0, '', '', $msgid);
3165 if ($alreadycreated == 0) {
3166 $operationslog .= '<br>We did not find existing actioncomm with msgid='.$msgid;
3167
3168 if ($projectstatic->id > 0) {
3169 if ($projectfoundby) {
3170 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Project found from '.$projectfoundby);
3171 }
3172 }
3173 if ($thirdpartystatic->id > 0) {
3174 if ($thirdpartyfoundby) {
3175 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
3176 }
3177 }
3178 if ($contactstatic->id > 0) {
3179 if ($contactfoundby) {
3180 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
3181 }
3182 }
3183
3184 $description = $descriptiontitle;
3185
3186 $description = dol_concatdesc($description, $descriptionmeta);
3187 $description = dol_concatdesc($description, "-----");
3188 $description = dol_concatdesc($description, $messagetext);
3189
3190 $descriptionfull = $description;
3191 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3192 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3193 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3194 }
3195
3196 // Insert record of emails sent
3197 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
3198 $actioncomm->code = 'AC_'.$actioncode;
3199 $actioncomm->label = $langs->trans("ActionAC_".$actioncode).' - '.$langs->trans("MailFrom").' '.$email_from;
3200 $actioncomm->note_private = $descriptionfull;
3201 $actioncomm->fk_project = $projectstatic->id;
3202 $actioncomm->datep = $dateemail; // date of email
3203 $actioncomm->datef = $dateemail; // date of email
3204 $actioncomm->percentage = -1; // Not applicable
3205 $actioncomm->socid = $thirdpartystatic->id;
3206 $actioncomm->contact_id = $contactstatic->id;
3207 $actioncomm->socpeopleassigned = (!empty($contactstatic->id) ? array($contactstatic->id) : array());
3208 $actioncomm->authorid = $user->id; // User saving action
3209 $actioncomm->userownerid = $user->id; // Owner of action
3210 // Fields when action is an email (content should be added into note)
3211 $actioncomm->email_msgid = $msgid;
3212 $actioncomm->email_from = $fromstring;
3213 $actioncomm->email_sender = $sender;
3214 $actioncomm->email_to = $to;
3215 $actioncomm->email_tocc = $sendtocc;
3216 $actioncomm->email_tobcc = $sendtobcc;
3217 $actioncomm->email_subject = $subject;
3218 $actioncomm->errors_to = '';
3219
3220 if (!in_array($fk_element_type, array('societe', 'contact', 'project', 'user'))) {
3221 $actioncomm->fk_element = $fk_element_id;
3222 $actioncomm->elementid = $fk_element_id;
3223 $actioncomm->elementtype = $fk_element_type;
3224 if (is_object($objectemail) && $objectemail->module) {
3225 $actioncomm->elementtype .= '@'.$objectemail->module;
3226 }
3227 }
3228
3229 //$actioncomm->extraparams = $extraparams;
3230
3231 // Overwrite values with values extracted from source email
3232 $errorforthisaction = $this->overwritePropertiesOfObject($actioncomm, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3233
3234 if ($errorforthisaction) {
3235 $errorforactions++;
3236 } else {
3237 $result = $actioncomm->create($user);
3238 if ($result <= 0) {
3239 $errorforactions++;
3240 $this->errors = $actioncomm->errors;
3241 } else {
3242 if ($fk_element_type == "ticket" && is_object($objectemail)) {
3243 if ($objectemail->status == Ticket::STATUS_CLOSED || $objectemail->status == Ticket::STATUS_CANCELED || $objectemail->status == Ticket::STATUS_NEED_MORE_INFO || $objectemail->status == Ticket::STATUS_WAITING) {
3244 if ($objectemail->fk_user_assign != null) {
3245 $res = $objectemail->setStatut(Ticket::STATUS_ASSIGNED);
3246 } else {
3247 $res = $objectemail->setStatut(Ticket::STATUS_NOT_READ);
3248 }
3249
3250 if ($res) {
3251 $operationslog .= '<br>Ticket Re-Opened successfully -> ref='.$objectemail->ref;
3252 } else {
3253 $errorforactions++;
3254 $this->error = 'Error while changing the ticket status -> ref='.$objectemail->ref;
3255 $this->errors[] = $this->error;
3256 }
3257 }
3258 if (!empty($attachments)) {
3259 // There is an attachment for the ticket -> store attachment
3260 $ticket = new Ticket($this->db);
3261 $ticket->fetch($fk_element_id);
3262 $destdir = $conf->ticket->dir_output.'/'.$ticket->ref;
3263 if (!dol_is_dir($destdir)) {
3264 dol_mkdir($destdir);
3265 }
3266 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3267 foreach ($attachments as $attachment) {
3268 $attachment->save($destdir.'/');
3269 }
3270 } else {
3271 $this->getmsg($connection, $imapemail, $destdir);
3272 }
3273 }
3274 }
3275
3276 $operationslog .= '<br>Event created -> id='.dol_escape_htmltag((string) $actioncomm->id);
3277 }
3278 }
3279 } else {
3280 $operationslog .= '<br>An event in actioncomm table already exists for the msgid = '.$msgid.' so we bypass this action.';
3281 }
3282 } elseif ($operation['type'] == 'recordjoinpiece') {
3283 $data = [];
3284 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3285 foreach ($attachments as $attachment) {
3286 if ($attachment->getName() === 'undefined') {
3287 continue;
3288 }
3289 $data[$attachment->getName()] = $attachment->getContent();
3290 }
3291 } else {
3292 $pj = getAttachments($imapemail, $connection);
3293 foreach ($pj as $key => $val) {
3294 $data[$val['filename']] = getFileData($imapemail, (string) $val['pos'], $val['type'], $connection);
3295 }
3296 }
3297 if (count($data) > 0) {
3298 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."user WHERE email LIKE '%".$this->db->escape($email_from)."%'";
3299 $resql = $this->db->query($sql);
3300 if ($this->db->num_rows($resql) == 0) {
3301 $this->errors[] = "User Not allowed to add documents ({$email_from})";
3302 }
3303 $arrayobject = array(
3304 'propale' => array('table' => 'propal',
3305 'fields' => array('ref'),
3306 'class' => 'comm/propal/class/propal.class.php',
3307 'object' => 'Propal'),
3308 'holiday' => array('table' => 'holiday',
3309 'fields' => array('ref'),
3310 'class' => 'holiday/class/holiday.class.php',
3311 'object' => 'Holiday'),
3312 'expensereport' => array('table' => 'expensereport',
3313 'fields' => array('ref'),
3314 'class' => 'expensereport/class/expensereport.class.php',
3315 'object' => 'ExpenseReport'),
3316 'recruitment/recruitmentjobposition' => array('table' => 'recruitment_recruitmentjobposition',
3317 'fields' => array('ref'),
3318 'class' => 'recruitment/class/recruitmentjobposition.class.php',
3319 'object' => 'RecruitmentJobPosition'),
3320 'recruitment/recruitmentcandidature' => array('table' => 'recruitment_recruitmentcandidature',
3321 'fields' => array('ref'),
3322 'class' => 'recruitment/class/recruitmentcandidature.class.php',
3323 'object' => 'RecruitmentCandidature'),
3324 'societe' => array('table' => 'societe',
3325 'fields' => array('code_client', 'code_fournisseur'),
3326 'class' => 'societe/class/societe.class.php',
3327 'object' => 'Societe'),
3328 'commande' => array('table' => 'commande',
3329 'fields' => array('ref'),
3330 'class' => 'commande/class/commande.class.php',
3331 'object' => 'Commande'),
3332 'expedition' => array('table' => 'expedition',
3333 'fields' => array('ref'),
3334 'class' => 'expedition/class/expedition.class.php',
3335 'object' => 'Expedition'),
3336 'contract' => array('table' => 'contrat',
3337 'fields' => array('ref'),
3338 'class' => 'contrat/class/contrat.class.php',
3339 'object' => 'Contrat'),
3340 'fichinter' => array('table' => 'fichinter',
3341 'fields' => array('ref'),
3342 'class' => 'fichinter/class/fichinter.class.php',
3343 'object' => 'Fichinter'),
3344 'ticket' => array('table' => 'ticket',
3345 'fields' => array('ref'),
3346 'class' => 'ticket/class/ticket.class.php',
3347 'object' => 'Ticket'),
3348 'knowledgemanagement' => array('table' => 'knowledgemanagement_knowledgerecord',
3349 'fields' => array('ref'),
3350 'class' => 'knowledgemanagement/class/knowledgemanagement.class.php',
3351 'object' => 'KnowledgeRecord'),
3352 'supplier_proposal' => array('table' => 'supplier_proposal',
3353 'fields' => array('ref'),
3354 'class' => 'supplier_proposal/class/supplier_proposal.class.php',
3355 'object' => 'SupplierProposal'),
3356 'fournisseur/commande' => array('table' => 'commande_fournisseur',
3357 'fields' => array('ref', 'ref_supplier'),
3358 'class' => 'fourn/class/fournisseur.commande.class.php',
3359 'object' => 'SupplierProposal'),
3360 'facture' => array('table' => 'facture',
3361 'fields' => array('ref'),
3362 'class' => 'compta/facture/class/facture.class.php',
3363 'object' => 'Facture'),
3364 'fournisseur/facture' => array('table' => 'facture_fourn',
3365 'fields' => array('ref', 'ref_client'),
3366 'class' => 'fourn/class/fournisseur.facture.class.php',
3367 'object' => 'FactureFournisseur'),
3368 'produit' => array('table' => 'product',
3369 'fields' => array('ref'),
3370 'class' => 'product/class/product.class.php',
3371 'object' => 'Product'),
3372 'productlot' => array('table' => 'product_lot',
3373 'fields' => array('batch'),
3374 'class' => 'product/stock/class/productlot.class.php',
3375 'object' => 'Productlot'),
3376 'projet' => array('table' => 'projet',
3377 'fields' => array('ref'),
3378 'class' => 'projet/class/projet.class.php',
3379 'object' => 'Project'),
3380 'projet_task' => array('table' => 'projet_task',
3381 'fields' => array('ref'),
3382 'class' => 'projet/class/task.class.php',
3383 'object' => 'Task'),
3384 'ressource' => array('table' => 'resource',
3385 'fields' => array('ref'),
3386 'class' => 'ressource/class/dolressource.class.php',
3387 'object' => 'Dolresource'),
3388 'bom' => array('table' => 'bom_bom',
3389 'fields' => array('ref'),
3390 'class' => 'bom/class/bom.class.php',
3391 'object' => 'BOM'),
3392 'mrp' => array('table' => 'mrp_mo',
3393 'fields' => array('ref'),
3394 'class' => 'mrp/class/mo.class.php',
3395 'object' => 'Mo'),
3396 );
3397
3398 if (!is_object($hookmanager)) {
3399 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
3400 $hookmanager = new HookManager($this->db);
3401 }
3402 $hookmanager->initHooks(array('emailcolector'));
3403 $parameters = array('arrayobject' => $arrayobject);
3404 $reshook = $hookmanager->executeHooks('addmoduletoeamailcollectorjoinpiece', $parameters); // Note that $action and $object may have been modified by some hooks
3405 if ($reshook > 0) {
3406 $arrayobject = $hookmanager->resArray;
3407 }
3408
3409 $resultobj = array();
3410
3411 foreach ($arrayobject as $key => $objectdesc) {
3412 $sql = 'SELECT DISTINCT t.rowid ';
3413 $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->db->sanitize($objectdesc['table']) . ' AS t';
3414 $sql .= ' WHERE ';
3415 foreach ($objectdesc['fields'] as $field) {
3416 $sql .= "('" .$this->db->escape($subject) . "' LIKE CONCAT('%', t." . $this->db->sanitize($field) . ", '%') AND t." . $this->db->sanitize($field) . " <> '') OR ";
3417 }
3418 $sql = substr($sql, 0, -4);
3419
3420 $ressqlobj = $this->db->query($sql);
3421 if ($ressqlobj) {
3422 while ($obj = $this->db->fetch_object($ressqlobj)) {
3423 $resultobj[$key][] = $obj->rowid;
3424 }
3425 }
3426 }
3427 $dirs = array();
3428 foreach ($resultobj as $mod => $ids) {
3429 $moddesc = $arrayobject[$mod];
3430 $elementpath = $mod;
3431 dol_include_once($moddesc['class']);
3432 $objectmanaged = new $moddesc['object']($this->db);
3433 '@phan-var-force CommonObject $objectmanaged';
3434 foreach ($ids as $val) {
3435 $res = $objectmanaged->fetch($val);
3436 if ($res) {
3437 $path = ($objectmanaged->entity > 1 ? "/" . $objectmanaged->entity : '');
3438 $dirs[] = DOL_DATA_ROOT . $path . "/" . $elementpath . '/' . dol_sanitizeFileName($objectmanaged->ref) . '/';
3439 } else {
3440 $this->errors[] = 'object not found';
3441 }
3442 }
3443 }
3444 foreach ($dirs as $target) {
3445 $prefix = $this->actions[$this->id]['actionparam'];
3446 foreach ($data as $filename => $content) {
3447 $resr = $this->saveAttachment($target, $prefix . '_' . $filename, $content);
3448 if ($resr == -1) {
3449 $this->errors[] = 'Doc not saved';
3450 }
3451 }
3452 }
3453
3454 $operationslog .= '<br>Save attachment files on disk';
3455 } else {
3456 $this->errors[] = 'no joined piece';
3457
3458 $operationslog .= '<br>No joinded files';
3459 }
3460 } elseif ($operation['type'] == 'project') {
3461 // Create project / lead
3462 $projecttocreate = new Project($this->db);
3463 $alreadycreated = $projecttocreate->fetch(0, '', '', $msgid);
3464 if ($alreadycreated == 0) {
3465 if ($thirdpartystatic->id > 0) {
3466 $projecttocreate->socid = $thirdpartystatic->id;
3467 if ($thirdpartyfoundby) {
3468 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
3469 }
3470 }
3471 if ($contactstatic->id > 0) {
3472 $projecttocreate->contact_id = $contactstatic->id;
3473 if ($contactfoundby) {
3474 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
3475 }
3476 }
3477
3478 $description = $descriptiontitle;
3479
3480 $description = dol_concatdesc($description, $descriptionmeta);
3481 $description = dol_concatdesc($description, "-----");
3482 $description = dol_concatdesc($description, $messagetext);
3483
3484 $descriptionfull = $description;
3485 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3486 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3487 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3488 }
3489
3490 $id_opp_status = dol_getIdFromCode($this->db, 'PROSP', 'c_lead_status', 'code', 'rowid');
3491 $percent_opp_status = dol_getIdFromCode($this->db, 'PROSP', 'c_lead_status', 'code', 'percent');
3492
3493 $projecttocreate->title = $subject;
3494 $projecttocreate->date_start = $dateemail; // date of email
3495 $projecttocreate->date_end = 0;
3496 $projecttocreate->opp_status = $id_opp_status;
3497 $projecttocreate->opp_percent = $percent_opp_status;
3498 $projecttocreate->description = dol_concatdesc(dolGetFirstLineOfText(dol_string_nohtmltag($description, 2), 10), '...'.$langs->transnoentities("SeePrivateNote").'...');
3499 $projecttocreate->note_private = $descriptionfull;
3500 $projecttocreate->entity = $conf->entity;
3501 // Fields when action is an email (content should be added into agenda event)
3502 $projecttocreate->email_date = $dateemail;
3503 $projecttocreate->email_msgid = $msgid;
3504 $projecttocreate->email_from = $fromstring;
3505 $projecttocreate->email_sender = $sender;
3506 $projecttocreate->email_to = $to;
3507 $projecttocreate->email_tocc = $sendtocc;
3508 $projecttocreate->email_tobcc = $sendtobcc;
3509 $projecttocreate->email_subject = $subject;
3510 $projecttocreate->errors_to = '';
3511
3512 $savesocid = $projecttocreate->socid;
3513
3514 // Overwrite values with values extracted from source email.
3515 // This may overwrite any $projecttocreate->xxx properties.
3516 $errorforthisaction = $this->overwritePropertiesOfObject($projecttocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3517 $modele = null;
3518
3519 // Set project ref if not yet defined
3520 if (empty($projecttocreate->ref)) {
3521 // Get next Ref
3522 $defaultref = '';
3523 $modele = getDolGlobalString('PROJECT_ADDON', 'mod_project_simple');
3524
3525 // Search template files
3526 $file = '';
3527 $classname = '';
3528 $reldir = '';
3529 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3530 foreach ($dirmodels as $reldir) {
3531 $file = dol_buildpath($reldir."core/modules/project/".$modele.'.php', 0);
3532 if (file_exists($file)) {
3533 $classname = $modele;
3534 break;
3535 }
3536 }
3537
3538 if ($classname !== '') {
3539 if ($savesocid > 0) {
3540 if ($savesocid != $projecttocreate->socid) {
3541 $errorforactions++;
3542 setEventMessages('You loaded a thirdparty (id='.$savesocid.') and you force another thirdparty id (id='.$projecttocreate->socid.') by setting socid in operation with a different value', null, 'errors');
3543 }
3544 } else {
3545 if ($projecttocreate->socid > 0) {
3546 $thirdpartystatic->fetch($projecttocreate->socid);
3547 }
3548 }
3549
3550 $result = dol_include_once($reldir."core/modules/project/".$modele.'.php');
3551 $modModuleToUseForNextValue = new $classname();
3552 '@phan-var-force ModeleNumRefProjects $modModuleToUseForNextValue';
3553 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $projecttocreate);
3554 }
3555 $projecttocreate->ref = $defaultref;
3556 }
3557
3558
3559 if ($errorforthisaction) {
3560 $errorforactions++;
3561 } else {
3562 if (empty($projecttocreate->ref) || (is_numeric($projecttocreate->ref) && $projecttocreate->ref <= 0)) {
3563 $errorforactions++;
3564 $this->error = 'Failed to create project: Can\'t get a valid value for the field ref with numbering template = '.$modele.', thirdparty id = '.$thirdpartystatic->id;
3565
3566 $operationslog .= '<br>'.$this->error;
3567 } else {
3568 // Create project
3569 $result = $projecttocreate->create($user);
3570 if ($result <= 0) {
3571 $errorforactions++;
3572 $this->error = 'Failed to create project: '.$langs->trans($projecttocreate->error);
3573 $this->errors = $projecttocreate->errors;
3574
3575 $operationslog .= '<br>'.$this->error;
3576 } else {
3577 if ($attachments) {
3578 $destdir = $conf->project->dir_output.'/'.$projecttocreate->ref;
3579 if (!dol_is_dir($destdir)) {
3580 dol_mkdir($destdir);
3581 }
3582 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3583 foreach ($attachments as $attachment) {
3584 // $attachment->save($destdir.'/');
3585 $typeattachment = (string) $attachment->getDisposition();
3586 $filename = $attachment->getFilename();
3587 $content = $attachment->getContent();
3588 $this->saveAttachment($destdir, $filename, $content);
3589 }
3590 } else {
3591 $getMsg = $this->getmsg($connection, $imapemail, $destdir);
3592 if ($getMsg < 0) {
3593 $this->errors = array_merge($this->errors, [$this->error]);
3594 return $getMsg;
3595 }
3596 }
3597
3598 $operationslog .= '<br>Project created with attachments -> id='.dol_escape_htmltag((string) $projecttocreate->id);
3599 } else {
3600 $operationslog .= '<br>Project created without attachments -> id='.dol_escape_htmltag((string) $projecttocreate->id);
3601 }
3602 }
3603 }
3604 }
3605 } else {
3606 dol_syslog("Project already exists for msgid = ".dol_escape_htmltag($msgid).", so we do not recreate it.");
3607
3608 $operationslog .= '<br>Project already exists for msgid ='.dol_escape_htmltag($msgid);
3609 }
3610 } elseif ($operation['type'] == 'ticket') {
3611 // Create ticket
3612 $tickettocreate = new Ticket($this->db);
3613 if ($ticketalreadyexists == 0) {
3614 if ($thirdpartystatic->id > 0) {
3615 $tickettocreate->socid = $thirdpartystatic->id;
3616 $tickettocreate->fk_soc = $thirdpartystatic->id;
3617 if ($thirdpartyfoundby) {
3618 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
3619 }
3620 }
3621 if ($contactstatic->id > 0) {
3622 $tickettocreate->contact_id = $contactstatic->id;
3623 if ($contactfoundby) {
3624 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
3625 }
3626 }
3627
3628 $description = $descriptiontitle;
3629
3630 $description = dol_concatdesc($description, $descriptionmeta);
3631 $description = dol_concatdesc($description, "-----");
3632 $description = dol_concatdesc($description, $messagetext);
3633
3634 $descriptionfull = $description;
3635 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3636 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3637 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3638 }
3639
3640 $tickettocreate->subject = $subject;
3641 $tickettocreate->message = $description;
3642 $tickettocreate->type_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_TYPE_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_type', 'use_default', 'code', 1)));
3643 $tickettocreate->category_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_CATEGORY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_category', 'use_default', 'code', 1)));
3644 $tickettocreate->severity_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_SEVERITY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_severity', 'use_default', 'code', 1)));
3645 $tickettocreate->origin_email = $email_from;
3646 $tickettocreate->origin_replyto = (!empty($replyto) ? $replyto : null);
3647 $tickettocreate->origin_references = (!empty($headers['References']) ? $headers['References'] : null);
3648 $tickettocreate->fk_user_create = $user->id;
3649 $tickettocreate->datec = dol_now();
3650 $tickettocreate->fk_project = $projectstatic->id;
3651 $tickettocreate->notify_tiers_at_create = getDolGlobalInt('TICKET_CHECK_NOTIFY_THIRDPARTY_AT_CREATION');
3652 $tickettocreate->note_private = $descriptionfull;
3653 $tickettocreate->entity = $conf->entity;
3654 // Fields when action is an email (content should be added into agenda event)
3655 $tickettocreate->email_date = $dateemail;
3656 $tickettocreate->email_msgid = $msgid;
3657 $tickettocreate->email_from = $fromstring;
3658 $tickettocreate->email_sender = $sender;
3659 $tickettocreate->email_to = $to;
3660 $tickettocreate->email_tocc = $sendtocc;
3661 $tickettocreate->email_tobcc = $sendtobcc;
3662 $tickettocreate->email_subject = $subject;
3663 $tickettocreate->errors_to = '';
3664
3665 //$tickettocreate->fk_contact = $contactstatic->id;
3666
3667 $savesocid = $tickettocreate->socid;
3668
3669 // Overwrite values with values extracted from source email.
3670 // This may overwrite any $projecttocreate->xxx properties.
3671 $errorforthisaction = $this->overwritePropertiesOfObject($tickettocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3672
3673 $modele = 'UNDEFINED';
3674 // Set ticket ref if not yet defined
3675 if (empty($tickettocreate->ref)) {
3676 // Get next Ref
3677 $defaultref = '';
3678 $modele = getDolGlobalString('TICKET_ADDON', 'mod_ticket_simple');
3679
3680 // Search template files
3681 $file = '';
3682 $classname = '';
3683 $reldir = '';
3684 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3685 foreach ($dirmodels as $reldir) {
3686 $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
3687 if (file_exists($file)) {
3688 $classname = $modele;
3689 break;
3690 }
3691 }
3692
3693 if ($classname !== '') {
3694 if ($savesocid > 0) {
3695 if ($savesocid != $tickettocreate->socid) {
3696 $errorforactions++;
3697 setEventMessages('You loaded a thirdparty (id='.$savesocid.') and you force another thirdparty id (id='.$tickettocreate->socid.') by setting socid in operation with a different value', null, 'errors');
3698 }
3699 } else {
3700 if ($tickettocreate->socid > 0) {
3701 $thirdpartystatic->fetch($tickettocreate->socid);
3702 }
3703 }
3704
3705 $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
3706 $modModuleToUseForNextValue = new $classname();
3707 '@phan-var-force ModeleNumRefTicket $modModuleToUseForNextValue';
3708 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $tickettocreate);
3709 }
3710 $tickettocreate->ref = $defaultref;
3711 }
3712
3713 if ($errorforthisaction) {
3714 $errorforactions++;
3715 } else {
3716 if (is_numeric($tickettocreate->ref) && $tickettocreate->ref <= 0) {
3717 $errorforactions++;
3718 $this->error = 'Failed to create ticket: Can\'t get a valid value for the field ref with numbering template = '.$modele.', thirdparty id = '.$thirdpartystatic->id;
3719 } else {
3720 // Create ticket
3721 $tickettocreate->context['actionmsg2'] = $langs->trans("ActionAC_EMAIL_IN").' - '.$langs->trans("TICKET_CREATEInDolibarr");
3722 $tickettocreate->context['actionmsg'] = $langs->trans("ActionAC_EMAIL_IN").' - '.$langs->trans("TICKET_CREATEInDolibarr");
3723 //$tickettocreate->email_fields_no_propagate_in_actioncomm = 0;
3724
3725 // Add sender to context array to make sure that confirmation e-mail can be sent by trigger script
3726 $sender_contact = new Contact($this->db);
3727 $sender_contact->fetch(0, null, '', $email_from);
3728 if (!empty($sender_contact->id)) {
3729 $tickettocreate->context['contact_id'] = $sender_contact->id;
3730 }
3731
3732 $result = $tickettocreate->create($user);
3733 if ($result <= 0) {
3734 $errorforactions++;
3735 $this->error = 'Failed to create ticket: '.$langs->trans($tickettocreate->error);
3736 $this->errors = $tickettocreate->errors;
3737 } else {
3738 if ($attachments) {
3739 $destdir = $conf->ticket->dir_output.'/'.$tickettocreate->ref;
3740 if (!dol_is_dir($destdir)) {
3741 dol_mkdir($destdir);
3742 }
3743 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3744 foreach ($attachments as $attachment) {
3745 // $attachment->save($destdir.'/');
3746 $typeattachment = (string) $attachment->getDisposition();
3747 $filename = $attachment->getName();
3748 $content = $attachment->getContent();
3749 $this->saveAttachment($destdir, $filename, $content);
3750 }
3751 } else {
3752 $getMsg = $this->getmsg($connection, $imapemail, $destdir);
3753 if ($getMsg < 0) {
3754 $this->errors = array_merge($this->errors, [$this->error]);
3755 return $getMsg;
3756 }
3757 }
3758
3759 $operationslog .= '<br>Ticket created with attachments -> id='.dol_escape_htmltag((string) $tickettocreate->id);
3760 } else {
3761 $operationslog .= '<br>Ticket created without attachments -> id='.dol_escape_htmltag((string) $tickettocreate->id);
3762 }
3763 }
3764 }
3765 }
3766 } else {
3767 // Do nothing in action ticket if ticket already exists.
3768 // Note that if we want to add a new event to a ticket, we should have a collector with action "Record event in agenda" or hope the type of action 'ticket' was
3769 // already replaced automatically by operation "recordevent".
3770 $operationslog .= '<br>Ticket already exists, so we bypass action "ticket"';
3771 }
3772 } elseif ($operation['type'] == 'candidature') {
3773 // Create candidature
3774 $candidaturetocreate = new RecruitmentCandidature($this->db);
3775
3776 $alreadycreated = $candidaturetocreate->fetch(0, '', $msgid);
3777 if ($alreadycreated == 0) {
3778 $description = $descriptiontitle;
3779 $description = dol_concatdesc($description, "-----");
3780 $description = dol_concatdesc($description, $descriptionmeta);
3781 $description = dol_concatdesc($description, "-----");
3782 $description = dol_concatdesc($description, $messagetext);
3783
3784 $descriptionfull = $description;
3785 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3786 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3787 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3788 }
3789
3790 $candidaturetocreate->subject = $subject;
3791 $candidaturetocreate->message = $description;
3792 $candidaturetocreate->type_code = 0;
3793 $candidaturetocreate->category_code = null;
3794 $candidaturetocreate->severity_code = null;
3795 $candidaturetocreate->email = $email_from;
3796 //$candidaturetocreate->lastname = $langs->trans("Anonymous").' - '.$email_from;
3797 $candidaturetocreate->fk_user_creat = $user->id;
3798 $candidaturetocreate->date_creation = dol_now();
3799 $candidaturetocreate->fk_project = $projectstatic->id;
3800 $candidaturetocreate->description = $description;
3801 $candidaturetocreate->note_private = $descriptionfull;
3802 $candidaturetocreate->entity = $conf->entity;
3803 $candidaturetocreate->email_msgid = $msgid;
3804 $candidaturetocreate->email_date = $dateemail; // date of email
3805 $candidaturetocreate->status = $candidaturetocreate::STATUS_DRAFT;
3806 //$candidaturetocreate->fk_contact = $contactstatic->id;
3807
3808 // Overwrite values with values extracted from source email.
3809 // This may overwrite any $projecttocreate->xxx properties.
3810 $errorforthisaction = $this->overwritePropertiesOfObject($candidaturetocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3811
3812 // Set candidature ref if not yet defined
3813 /*if (empty($candidaturetocreate->ref)) We do not need this because we create object in draft status
3814 {
3815 // Get next Ref
3816 $defaultref = '';
3817 $modele = empty($conf->global->CANDIDATURE_ADDON) ? 'mod_candidature_simple' : $conf->global->CANDIDATURE_ADDON;
3818
3819 // Search template files
3820 $file = ''; $classname = ''; $filefound = 0; $reldir = '';
3821 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3822 foreach ($dirmodels as $reldir)
3823 {
3824 $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
3825 if (file_exists($file)) {
3826 $filefound = 1;
3827 $classname = $modele;
3828 break;
3829 }
3830 }
3831
3832 if ($filefound) {
3833 if ($savesocid > 0) {
3834 if ($savesocid != $candidaturetocreate->socid) {
3835 $errorforactions++;
3836 setEventMessages('You loaded a thirdparty (id='.$savesocid.') and you force another thirdparty id (id='.$candidaturetocreate->socid.') by setting socid in operation with a different value', null, 'errors');
3837 }
3838 } else {
3839 if ($candidaturetocreate->socid > 0)
3840 {
3841 $thirdpartystatic->fetch($candidaturetocreate->socid);
3842 }
3843 }
3844
3845 $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
3846 $modModuleToUseForNextValue = new $classname;
3847 '@phan-var-force ModeleNumRefTicket $modModuleToUseForNextValue';
3848 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $tickettocreate);
3849 }
3850 $candidaturetocreate->ref = $defaultref;
3851 }*/
3852
3853 if ($errorforthisaction) {
3854 $errorforactions++;
3855 } else {
3856 // Create project
3857 $result = $candidaturetocreate->create($user);
3858 if ($result <= 0) {
3859 $errorforactions++;
3860 $this->error = 'Failed to create candidature: '.implode(', ', $candidaturetocreate->errors);
3861 $this->errors = $candidaturetocreate->errors;
3862 }
3863
3864 $operationslog .= '<br>Candidature created without attachments -> id='.dol_escape_htmltag((string) $candidaturetocreate->id);
3865 }
3866 }
3867 } elseif (substr($operation['type'], 0, 4) == 'hook') {
3868 // Create event specific on hook
3869 // this code action is hook..... for support this call
3870 if (!is_object($hookmanager)) {
3871 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
3872 $hookmanager = new HookManager($this->db);
3873 }
3874 $hookmanager->initHooks(['emailcolector']);
3875
3876 $parameters = array(
3877 'connection' => $connection,
3878 'imapemail' => $imapemail,
3879 'overview' => $overview,
3880
3881 'from' => $email_from,
3882 'fromtext' => $fromtext,
3883
3884 'actionparam' => $operation['actionparam'],
3885
3886 'thirdpartyid' => ($thirdpartyid ? $thirdpartyid : $thirdpartystatic->id),
3887 'objectid' => $objectid,
3888 'objectemail' => $objectemail,
3889
3890 'messagetext' => $messagetext,
3891 'subject' => $subject,
3892 'header' => $header,
3893 'attachments' => $attachments,
3894 'savedattachments' => $savedattachments,
3895 'savedattachmentsdir' => $savedattachmentsdir,
3896 );
3897 $reshook = $hookmanager->executeHooks('doCollectImapOneCollector', $parameters, $this, $operation['type']);
3898
3899 if ($reshook < 0) {
3900 $errorforthisaction++;
3901 $this->error = $hookmanager->resPrint;
3902 if (!empty($hookmanager->resPrint)) {
3903 $operationslog .= '<br>Hook error: '.dol_escape_htmltag($hookmanager->resPrint);
3904 }
3905 }
3906 if ($errorforthisaction) {
3907 $errorforactions++;
3908 $operationslog .= '<br>Hook doCollectImapOneCollector executed with error';
3909 } else {
3910 $operationslog .= '<br>Hook doCollectImapOneCollector executed without error';
3911 }
3912 }
3913
3914 if (!$errorforactions) {
3915 $nbactiondoneforemail++;
3916 }
3917 }
3918 }
3919
3920 // Error for email or not ?
3921 if (!$errorforactions) {
3922 if (!empty($targetdir)) {
3923 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3924 // Move mail using PHP-IMAP
3925 dol_syslog("EmailCollector::doCollectOneCollector move message ".($imapemail->getHeader()->get('subject'))." to ".$targetdir, LOG_DEBUG);
3926 $operationslog .= '<br>Move mail '.($this->uidAsString($imapemail)).' - '.$msgid.' - '.$imapemail->getHeader()->get('subject').' to '.$targetdir;
3927
3928 $arrayofemailtodelete[$this->uidAsString($imapemail)] = $imapemail;
3929 // Note: Real move is done later using $arrayofemailtodelete
3930 } else {
3931 dol_syslog("EmailCollector::doCollectOneCollector move message ".($this->uidAsString($imapemail))." to ".$connectstringtarget, LOG_DEBUG);
3932 $operationslog .= '<br>Move mail '.($this->uidAsString($imapemail)).' - '.$msgid;
3933
3934 $arrayofemailtodelete[$imapemail] = $msgid;
3935 // Note: Real move is done later using $arrayofemailtodelete
3936 }
3937 } else {
3938 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3939 dol_syslog("EmailCollector::doCollectOneCollector message ".($this->uidAsString($imapemail))." '".($imapemail->getHeader()->get('subject'))."' using this->host=".$this->host.", this->access_type=".$this->acces_type." was set to read", LOG_DEBUG);
3940 } else {
3941 dol_syslog("EmailCollector::doCollectOneCollector message ".($this->uidAsString($imapemail))." to ".$connectstringtarget." was set to read", LOG_DEBUG);
3942 }
3943 }
3944 } else {
3945 $errorforemail++;
3946 }
3947
3948
3949 unset($objectemail);
3950 unset($projectstatic);
3951 unset($thirdpartystatic);
3952 unset($contactstatic);
3953
3954 $nbemailprocessed++;
3955
3956 if (!$errorforemail) {
3957 $nbactiondone += $nbactiondoneforemail;
3958 $nbemailok++;
3959
3960 if (empty($mode)) {
3961 $this->db->commit();
3962 } else {
3963 $this->db->rollback();
3964 }
3965
3966 // Stop the loop to process email if we reach maximum collected per collect
3967 if ($this->maxemailpercollect > 0 && $nbemailok >= $this->maxemailpercollect) {
3968 dol_syslog("EmailCollect::doCollectOneCollector We reach maximum of ".$nbemailok." collected with success, so we stop this collector now.");
3969 $datelastok = strtotime($headers['Date']); // Set datetime
3970 break;
3971 }
3972 } else {
3973 $error++;
3974
3975 $this->db->rollback();
3976 }
3977 }
3978
3979 $output = $langs->trans('XEmailsDoneYActionsDone', $nbemailprocessed, $nbemailok, $nbactiondone);
3980
3981 dol_syslog("End of loop on emails", LOG_INFO, -1);
3982 } else {
3983 $langs->load("admin");
3984 $output = $langs->trans('NoNewEmailToProcess');
3985 $output .= ' (defaultlang='.$langs->defaultlang.')';
3986 }
3987
3988 // Disconnect
3989 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3990 // We sort to move/delete array with the more recent first (with higher number) so renumbering does not affect number of others to delete
3991 krsort($arrayofemailtodelete, SORT_NUMERIC);
3992
3993 foreach ($arrayofemailtodelete as $imapemailnum => $imapemail) {
3994 dol_syslog("EmailCollect::doCollectOneCollector delete email ".$imapemailnum);
3995
3996 $operationslog .= "<br> move email ".$imapemailnum.($mode > 0 ? ' (test)' : '');
3997
3998 if (empty($mode) && empty($error)) {
3999 $tmptargetdir = $targetdir;
4000 if (!getDolGlobalString('MAIL_DISABLE_UTF7_ENCODE_OF_DIR')) {
4001 $tmptargetdir = $this->getEncodedUtf7($targetdir);
4002 }
4003
4004 $result = 0;
4005 try {
4006 $result = $imapemail->move($tmptargetdir, false);
4007 } catch (Exception $e) {
4008 // Nothing to do. $result will remain 0
4009 $operationslog .= '<br>Exception !!!! '.$e->getMessage();
4010 }
4011 if (empty($result)) {
4012 dol_syslog("Failed to move email into target directory ".$targetdir);
4013 $operationslog .= '<br>Failed to move email into target directory '.$targetdir;
4014 $error++;
4015 }
4016 }
4017 }
4018
4019 if (empty($mode) && empty($error)) {
4020 dol_syslog("Expunge", LOG_DEBUG);
4021 $operationslog .= "<br>Expunge";
4022
4023 $client->expunge(); // To validate all moves
4024 }
4025
4026 $client->disconnect();
4027 } else {
4028 foreach ($arrayofemailtodelete as $imapemail => $msgid) {
4029 dol_syslog("EmailCollect::doCollectOneCollector delete email ".$imapemail." ".$msgid);
4030
4031 $operationslog .= "<br> delete email ".$imapemail." ".$msgid.($mode > 0 ? ' (test)' : '');
4032
4033 if (empty($mode) && empty($error)) {
4034 $res = imap_mail_move($connection, $imapemail, $targetdir, CP_UID);
4035 if (!$res) {
4036 // $errorforemail++; // Not in loop, not needed, not initialised
4037 $this->error = imap_last_error();
4038 $this->errors[] = $this->error;
4039
4040 $operationslog .= '<br>Error in move '.$this->error;
4041
4042 dol_syslog(imap_last_error());
4043 }
4044 }
4045 }
4046
4047 if (empty($mode) && empty($error)) {
4048 dol_syslog("Expunge", LOG_DEBUG);
4049 $operationslog .= "<br>Expunge";
4050
4051 imap_expunge($connection); // To validate all moves
4052 }
4053 imap_close($connection);
4054 }
4055
4056 $this->datelastresult = $now;
4057 $this->lastresult = $output;
4058 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
4059 $this->debuginfo .= 'IMAP search array used : '.$search;
4060 } else {
4061 $this->debuginfo .= 'IMAP search string used : '.$search;
4062 }
4063 if ($searchhead) {
4064 $this->debuginfo .= '<br>Then search string into email header : '.dol_escape_htmltag($searchhead);
4065 }
4066 if ($operationslog) {
4067 $this->debuginfo .= $operationslog;
4068 }
4069
4070 if (empty($error) && empty($mode)) {
4071 $this->datelastok = $datelastok;
4072 }
4073
4074 if (!empty($this->errors)) {
4075 $this->lastresult .= "<br>".implode("<br>", $this->errors);
4076 }
4077 $this->codelastresult = ($error ? 'KO' : 'OK');
4078
4079 if (empty($mode)) {
4080 $this->update($user);
4081 }
4082
4083 dol_syslog("EmailCollector::doCollectOneCollector end error=".$error, LOG_INFO);
4084
4085 return $error ? -1 : 1;
4086 }
4087
4088
4089
4090 // Loop to get part html and plain. Code found on PHP imap_fetchstructure documentation
4091
4100 private function getmsg($mbox, $mid, $destdir = ''): int
4101 {
4102 // input $mbox = IMAP stream, $mid = message id
4103 // output all the following:
4104 global $charset, $htmlmsg, $plainmsg, $attachments;
4105 $htmlmsg = $plainmsg = $charset = '';
4106 $attachments = array();
4107
4108 // HEADER
4109 //$h = imap_header($mbox,$mid);
4110 // add code here to get date, from, to, cc, subject...
4111
4112 // BODY
4113 $s = imap_fetchstructure($mbox, $mid, FT_UID);
4114 if ($s === false) {
4115 $this->errors = array_merge($this->errors, [imap_last_error()]);
4116 return -1;
4117 }
4118
4119 if (empty($s->parts)) {
4120 // simple
4121 $this->getpart($mbox, $mid, $s, '0'); // pass '0' as part-number
4122 } else {
4123 // multipart: cycle through each part
4124 foreach ($s->parts as $partno0 => $p) {
4125 $this->getpart($mbox, $mid, $p, (string) ($partno0 + 1), $destdir);
4126 }
4127 }
4128
4129 return 1;
4130 }
4131
4132 /* partno string
4133 0 multipart/mixed
4134 1 multipart/alternative
4135 1.1 text/plain
4136 1.2 text/html
4137 2 message/rfc822
4138 2 multipart/mixed
4139 2.1 multipart/alternative
4140 2.1.1 text/plain
4141 2.1.2 text/html
4142 2.2 message/rfc822
4143 2.2 multipart/alternative
4144 2.2.1 text/plain
4145 2.2.2 text/html
4146 */
4147
4158 private function getpart($mbox, $mid, $p, $partno, $destdir = '')
4159 {
4160 // $partno = '1', '2', '2.1', '2.1.3', etc for multipart, 0 if simple
4161 global $htmlmsg, $plainmsg, $charset, $attachments;
4162
4163 // DECODE DATA
4164 $data = ($partno) ?
4165 imap_fetchbody($mbox, $mid, $partno, FT_UID) : // multipart
4166 imap_body($mbox, $mid, FT_UID); // simple
4167 // Any part may be encoded, even plain text messages, so check everything.
4168 if ($p->encoding == 4) {
4169 $data = quoted_printable_decode($data);
4170 } elseif ($p->encoding == 3) {
4171 $data = base64_decode($data);
4172 }
4173
4174 // PARAMETERS
4175 // get all parameters, like charset, filenames of attachments, etc.
4176 $params = array();
4177 if ($p->parameters) {
4178 foreach ($p->parameters as $x) {
4179 $params[strtolower($x->attribute)] = $x->value;
4180 }
4181 }
4182 if (!empty($p->dparameters)) {
4183 foreach ($p->dparameters as $x) {
4184 $params[strtolower($x->attribute)] = $x->value;
4185 }
4186 }
4187 '@phan-var-force array{filename?:string,name?:string,charset?:string} $params';
4188
4189 // ATTACHMENT
4190 // Any part with a filename is an attachment,
4191 // so an attached text file (type 0) is not mistaken as the message.
4192 if (!empty($params['filename']) || !empty($params['name'])) {
4193 // filename may be given as 'Filename' or 'Name' or both
4194 $filename = $params['filename'] ?? $params['name'];
4195 // filename may be encoded, so see imap_mime_header_decode()
4196 $attachments[$filename] = $data; // this is a problem if two files have same name
4197
4198 if (strlen($destdir)) {
4199 if (substr($destdir, -1) != '/') {
4200 $destdir .= '/';
4201 }
4202
4203 // Get file name (with extension)
4204 $file_name_complete = $filename;
4205 $destination = $destdir.$file_name_complete;
4206
4207 // Extract file extension
4208 $extension = pathinfo($file_name_complete, PATHINFO_EXTENSION);
4209
4210 // Extract file name without extension
4211 $file_name = pathinfo($file_name_complete, PATHINFO_FILENAME);
4212
4213 // Save an original file name variable to track while renaming if file already exists
4214 $file_name_original = $file_name;
4215
4216 // Increment file name by 1
4217 $num = 1;
4218
4223 while (file_exists($destdir.$file_name.".".$extension)) {
4224 $file_name = $file_name_original . ' (' . $num . ')';
4225 $file_name_complete = $file_name . "." . $extension;
4226 $destination = $destdir.$file_name_complete;
4227 $num++;
4228 }
4229
4230 $destination = dol_sanitizePathName($destination);
4231
4232 file_put_contents($destination, $data);
4233 dolChmod($destination);
4234 }
4235 }
4236
4237 // TEXT
4238 if ($p->type == 0 && $data) {
4239 if (!empty($params['charset'])) {
4240 $data = $this->convertStringEncoding($data, $params['charset']);
4241 }
4242 // Messages may be split in different parts because of inline attachments,
4243 // so append parts together with blank row.
4244 if (strtolower($p->subtype) == 'plain') {
4245 $plainmsg .= trim($data)."\n\n";
4246 } else {
4247 $htmlmsg .= $data."<br><br>";
4248 }
4249 $charset = $params['charset']; // assume all parts are same charset
4250 } elseif ($p->type == 2 && $data) {
4251 // EMBEDDED MESSAGE
4252 // Many bounce notifications embed the original message as type 2,
4253 // but AOL uses type 1 (multipart), which is not handled here.
4254 // There are no PHP functions to parse embedded messages,
4255 // so this just appends the raw source to the main message.
4256 if (!empty($params['charset'])) {
4257 $data = $this->convertStringEncoding($data, $params['charset']);
4258 }
4259 $plainmsg .= $data."\n\n";
4260 }
4261
4262 // SUBPART RECURSION
4263 if (!empty($p->parts)) {
4264 foreach ($p->parts as $partno0 => $p2) {
4265 $this->getpart($mbox, $mid, $p2, $partno.'.'.($partno0 + 1), $destdir); // 1.2, 1.2.1, etc.
4266 }
4267 }
4268 }
4269
4279 protected function convertStringEncoding($string, $fromEncoding, $toEncoding = 'UTF-8')
4280 {
4281 if (!$string || $fromEncoding == $toEncoding) {
4282 return $string;
4283 }
4284 $convertedString = function_exists('iconv') ? @iconv($fromEncoding, $toEncoding.'//IGNORE', $string) : null;
4285 if (!$convertedString && extension_loaded('mbstring')) {
4286 $convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding);
4287 }
4288 if (!$convertedString) {
4289 throw new Exception('Mime string encoding conversion failed');
4290 }
4291 return $convertedString;
4292 }
4293
4304 protected function decodeSMTPSubject($subject)
4305 {
4306 // Decode $overview[0]->subject according to RFC2047
4307 // Can use also imap_mime_header_decode($str)
4308 // Can use also mb_decode_mimeheader($str)
4309 // Can use also iconv_mime_decode($str, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8')
4310 if (function_exists('imap_mime_header_decode') && function_exists('iconv_mime_decode')) {
4311 $elements = imap_mime_header_decode($subject);
4312 $newstring = '';
4313 if (!empty($elements)) {
4314 $num = count($elements);
4315 for ($i = 0; $i < $num; $i++) {
4316 $stringinutf8 = (in_array(strtoupper($elements[$i]->charset), array('DEFAULT', 'UTF-8')) ? $elements[$i]->text : iconv_mime_decode($elements[$i]->text, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, $elements[$i]->charset));
4317 $newstring .= $stringinutf8;
4318 }
4319 $subject = $newstring;
4320 }
4321 } elseif (!function_exists('mb_decode_mimeheader')) {
4322 $subject = mb_decode_mimeheader($subject);
4323 } elseif (function_exists('iconv_mime_decode')) {
4324 $subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
4325 }
4326
4327 return (string) $subject;
4328 }
4329
4337 private function saveEmailCollectorAttachmentsToDir($destdir, $attachments)
4338 {
4339 $destdir = rtrim((string) $destdir, '/');
4340 if ($destdir === '' || empty($attachments) || !is_array($attachments)) {
4341 return array();
4342 }
4343
4344 if (!dol_is_dir($destdir)) {
4345 if (dol_mkdir($destdir) < 0) {
4346 return array();
4347 }
4348 }
4349
4350 $stored = array();
4351 $seenHashes = array();
4352
4353 // Build a hash map of existing files to avoid storing duplicates.
4354 $filelist = dol_dir_list($destdir, 'files', 0, '', '(\\.meta|_preview.*.*\\.png)$', 'date', SORT_DESC, 0, 1);
4355 foreach ($filelist as $fileinfo) {
4356 if (empty($fileinfo['fullname']) || !is_file($fileinfo['fullname']) || !is_readable($fileinfo['fullname'])) {
4357 continue;
4358 }
4359 $hash = hash_file('sha256', $fileinfo['fullname']);
4360 if ($hash !== false && empty($seenHashes[$hash])) {
4361 $seenHashes[$hash] = $fileinfo['name'];
4362 }
4363 }
4364
4365 // Compute path relative to DOL_DATA_ROOT (used by hooks to reopen attachments later).
4366 $relativeDir = '';
4367 if (defined('DOL_DATA_ROOT')) {
4368 $root = rtrim((string) DOL_DATA_ROOT, '/').'/';
4369 $destdirwithslash = $destdir.'/';
4370 if (strpos($destdirwithslash, $root) === 0) {
4371 $relativeDir = rtrim(substr($destdirwithslash, strlen($root)), '/');
4372 }
4373 }
4374
4375 $index = 0;
4376 foreach ($attachments as $key => $attachment) {
4377 $index++;
4378 $origName = '';
4379 $content = '';
4380 $mime = null;
4381
4382 // PHP-IMAP (Webklex): array of Attachment objects.
4383 if (is_object($attachment)) {
4384 '@phan-var-force Webklex\PHPIMAP\Attachment $attachment';
4386 try {
4387 $origName = (string) $attachment->getName(); // Webklex Attachment uses magic __call
4388 } catch (Throwable $e) {
4389 $origName = '';
4390 }
4391 try {
4392 $content = (string) $attachment->getContent(); // Webklex Attachment uses magic __call
4393 } catch (Throwable $e) {
4394 $content = '';
4395 }
4396 try {
4397 $tmpmime = (string) $attachment->getContentType(); // Webklex Attachment uses magic __call
4398 $mime = ($tmpmime !== '' ? $tmpmime : null);
4399 } catch (Throwable $e) {
4400 $mime = null;
4401 }
4402 } else {
4403 // Native IMAP: array(filename => raw_data).
4404 $origName = (string) $key;
4405 $content = (string) $attachment;
4406 }
4407
4408 if ($origName === '' || $origName === 'undefined') {
4409 $origName = 'attachment-'.$index;
4410 }
4411 if ($content === '') {
4412 continue;
4413 }
4414
4415 $safeName = dol_sanitizeFileName($origName, '_', 1, 0);
4416 if ($safeName === '') {
4417 $safeName = 'attachment-'.$index;
4418 }
4419
4420 $contentHash = hash('sha256', $content);
4421 $finalName = $safeName;
4422 $useExisting = false;
4423 if (!empty($seenHashes[$contentHash])) {
4424 $finalName = $seenHashes[$contentHash];
4425 if (file_exists($destdir.'/'.$finalName)) {
4426 $useExisting = true;
4427 }
4428 }
4429
4430 if (!$useExisting) {
4431 $n = 1;
4432 while (file_exists($destdir.'/'.$finalName)) {
4433 $finalName = preg_replace('/(\\.[A-Za-z0-9]{1,10})$/', '', $safeName).'-'.$n;
4434 if (preg_match('/\\.[A-Za-z0-9]{1,10}$/', $safeName, $m)) {
4435 $finalName .= $m[0];
4436 }
4437 $n++;
4438 }
4439
4440 $this->saveAttachment($destdir, $finalName, $content);
4441 $seenHashes[$contentHash] = $finalName;
4442 }
4443
4444 $fullPath = $destdir.'/'.$finalName;
4445 $size = (file_exists($fullPath) ? filesize($fullPath) : null);
4446 $sizevalue = ($size === false ? null : $size);
4447 $sha256 = $contentHash;
4448
4449 $stored[] = array(
4450 'name' => $finalName,
4451 'original_name' => $origName,
4452 'relative_path' => ($relativeDir !== '' ? $relativeDir.'/'.$finalName : ''),
4453 'content_type' => $mime,
4454 'size' => ($sizevalue !== null ? (int) $sizevalue : null),
4455 'sha256' => $sha256,
4456 );
4457 }
4458
4459 return $stored;
4460 }
4461
4470 private function saveAttachment($destdir, $filename, $content)
4471 {
4472 require_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4473
4474 $destdir = dol_sanitizePathName($destdir);
4475 $filename = dol_sanitizeFileName($filename);
4476
4477 if (!file_exists($destdir)) {
4478 if (dol_mkdir($destdir) < 0) {
4479 return -1;
4480 }
4481 }
4482
4483 $tmp = explode('.', $filename);
4484 $ext = array_pop($tmp);
4485 $filename = implode('.', $tmp);
4486
4487 $i = 1;
4488 $filepath = $destdir . (preg_match('/\/$/', $destdir) ? '' : '/') . $filename . '.' . $ext;
4489
4490 while (file_exists($filepath)) {
4491 $filepath = $destdir . (preg_match('/\/$/', $destdir) ? '' : '/') . $filename . '(' . $i . ').' . $ext;
4492 $i++;
4493 }
4494
4495 file_put_contents($filepath, $content);
4496 dolChmod($filepath);
4497
4498 if (image_format_supported($filename) == 1) {
4499 $tmparraysize = getDefaultImageSizes();
4500 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
4501 $maxheightsmall = $tmparraysize['maxheightsmall'];
4502 $maxwidthmini = $tmparraysize['maxwidthmini'];
4503 $maxheightmini = $tmparraysize['maxheightmini'];
4504 $quality = $tmparraysize['quality'];
4505
4506 // Create thumbs
4507 vignette($filepath, $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs");
4508 // Create mini thumbs for image (Ratio is near 16/9)
4509 vignette($filepath, $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs");
4510 }
4511
4512 addFileIntoDatabaseIndex($destdir, $filename);
4513
4514 return $filepath;
4515 }
4516
4523 protected function uidAsString($imapemail)
4524 {
4525 if (is_object($imapemail)) {
4526 return $imapemail->getAttributes()["uid"];
4527 } else {
4528 return (string) $imapemail;
4529 }
4530 }
4531}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$object ref
Definition info.php:90
Class to manage agenda events (actions)
Class to manage members of a foundation.
Class to manage predefined suppliers products.
Class to manage customers orders.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
setErrorsFromObject($object)
setErrorsFromObject
createCommon(User $user, $notrigger=0)
Create object in the database.
updateCommon(User $user, $notrigger=0)
Update object into database.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
fetchCommon($id, $ref=null, $morewhere='', $noextrafields=0)
Load object in memory from the database.
deleteCommon(User $user, $notrigger=0, $forcechilddeletion=0)
Delete object in database.
Class to manage contact/addresses.
Class to manage Dolibarr database access.
Class for EmailCollectorAction.
Class for EmailCollectorFilter.
Class for EmailCollector.
fetch($id, $ref=null)
Load object in memory from the database.
createFromClone(User $user, $fromid)
Clone and object into another one.
convertStringEncoding($string, $fromEncoding, $toEncoding='UTF-8')
Converts a string from one encoding to another.
overwritePropertiesOfObject(&$object, $actionparam, $messagetext, $subject, $header, &$operationslog)
overwitePropertiesOfObject
__construct(DoliDB $db)
Constructor.
getpart($mbox, $mid, $p, $partno, $destdir='')
Sub function for getpart().
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
getEncodedUtf7($str)
Convert str to UTF-7 imap.
getConnectStringIMAP()
Return the connectstring to use with IMAP connection function.
decodeSMTPSubject($subject)
Decode a subject string according to RFC2047 Example: '=?Windows-1252?Q?RE=A0:_ABC?...
fetchFilters()
Fetch filters.
uidAsString($imapemail)
Get UID of message as a string.
LibStatut($status, $mode=0)
Return the status.
fetchActions()
Fetch actions.
update(User $user, $notrigger=0)
Update object into database.
saveAttachment($destdir, $filename, $content)
saveAttachment
fetchAll(User $user, $activeOnly=0, $sortfield='s.rowid', $sortorder='ASC', $limit=100, $page=0)
Load object lines in memory from the database.
info($id)
Charge les information d'ordre info dans l'objet commande.
getmsg($mbox, $mid, $destdir='')
getmsg
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionally the picto)
create(User $user, $notrigger=0)
Create object into database.
doCollect()
Action executed by scheduler CAN BE A CRON TASK.
getLibStatut($mode=0)
Return label of the status.
Class to manage suppliers invoices.
Class to manage invoices.
Class to manage hooks.
Class to manage projects.
Class to manage proposals.
Class to manage receptions.
Class for RecruitmentCandidature.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage price ask supplier.
Class to manage tasks.
Class to manage translations.
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
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
dol_stringtotime($string, $gm=1)
Convert a string date into a GM Timestamps date Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not s...
Definition date.lib.php:436
getFileData($jk, $fpos, $type, $mbox)
Get content of a joined file from its position into a given email.
getAttachments($jk, $mbox)
Get attachments of a given mail.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
addFileIntoDatabaseIndex($dir, $file, $fullpathorig='', $mode='uploaded', $setsharekey=0, $object=null, $forceFullTextIndexation='')
Add a file into database index.
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:64
dol_is_dir($folder)
Test if filename is a directory.
removeEmoji($text, $allowedemoji=1)
Remove EMoji from email content.
dol_now($mode='gmt')
Return date for now.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
dolExplodeIntoArray($string, $delimiter=';', $kv='=')
Split a string with 2 keys into key array.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
utf8_valid($str)
Check if a string is in UTF8.
dolChmod($filepath, $newmask='')
Change mod of a file.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
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...
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
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_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
if(!defined( 'IMAGETYPE_WEBP')) getDefaultImageSizes()
Return default values for image sizes.
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
Class to generate the form for creating a new ticket.
getSupportedOauth2Array()
Return array of tabs to use on pages to setup cron module.
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:487
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.
dolEncrypt($chain, $key='', $ciphering='', $forceseed='', $obfuscationmode='dolcrypt')
Encode a string with a symmetric encryption.