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