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