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 $folder = $f[0];
1658 if ($folder instanceof Webklex\PHPIMAP\Folder) {
1659 $Query = $folder->messages()->where($criteria); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
1660 } else {
1661 $error++;
1662 $this->error = "Source directory ".$sourcedir." not found";
1663 $this->errors[] = $this->error;
1664 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_WARNING);
1665 return -1;
1666 }
1667 } else {
1668 $error++;
1669 $this->error = "Failed to execute getfolders";
1670 $this->errors[] = $this->error;
1671 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1672 return -1;
1673 }
1674 } catch (InvalidWhereQueryCriteriaException $e) {
1675 $this->error = $e->getMessage();
1676 $this->errors[] = $this->error;
1677 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1678 return -1;
1679 } catch (Exception $e) {
1680 $this->error = $e->getMessage();
1681 $this->errors[] = $this->error;
1682 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1683 return -1;
1684 }
1685
1686 '@phan-var-force Webklex\PHPIMAP\Query\Query $Query';
1687 try {
1688 if ($mode > 0) {
1689 $Query->leaveUnread();
1690 }
1691 $arrayofemail = $Query->limit($this->maxemailpercollect)->setFetchOrder("asc")->get();
1692 dol_syslog("EmailCollector::doCollectOneCollector nb arrayofemail ".(is_array($arrayofemail) ? count($arrayofemail) : 'Not array')); // @phpstan-ignore-line
1693 } catch (Exception $e) {
1694 $this->error = $e->getMessage();
1695 $this->errors[] = $this->error;
1696 dol_syslog("EmailCollector::doCollectOneCollector ".$this->error, LOG_ERR);
1697 return -1;
1698 }
1699 } elseif ($connection !== false) {
1700 // Scan IMAP dir (for native IMAP, the source dir is inside the $connection variable)
1701 $arrayofemail = imap_search($connection, $search, SE_UID, $charset);
1702
1703 if ($arrayofemail === false) {
1704 // Nothing found or search string not understood
1705 $mapoferrrors = imap_errors();
1706 if ($mapoferrrors !== false) {
1707 $error++;
1708 $this->error = "Search string not understood - ".implode(',', $mapoferrrors);
1709 $this->errors[] = $this->error;
1710 }
1711 }
1712 }
1713
1714 $arrayofemailtodelete = array(); // Track email to delete to make the deletion at end.
1715
1716 // Loop on each email found
1717 if (!$error && !empty($arrayofemail) && count($arrayofemail) > 0 && $connection !== false) {
1718 // Loop to get part html and plain
1719 /*
1720 0 multipart/mixed
1721 1 multipart/alternative
1722 1.1 text/plain
1723 1.2 text/html
1724 2 message/rfc822
1725 2 multipart/mixed
1726 2.1 multipart/alternative
1727 2.1.1 text/plain
1728 2.1.2 text/html
1729 2.2 message/rfc822
1730 2.2 multipart/alternative
1731 2.2.1 text/plain
1732 2.2.2 text/html
1733 */
1734 dol_syslog("Start of loop on email", LOG_INFO, 1);
1735
1736 $richarrayofemail = array();
1737
1738 foreach ($arrayofemail as $imapemail) {
1739 if ($nbemailprocessed > 1000) {
1740 break; // Do not process more than 1000 email per launch (this is a different protection than maxnbcollectedpercollect)
1741 }
1742
1743 // GET header and overview datas
1744 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1745 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
1746 $header = $imapemail->getHeader()->raw; // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
1747 $overview = $imapemail->getAttributes();
1748 } else {
1749 $header = imap_fetchheader($connection, $imapemail, FT_UID);
1750 $overview = imap_fetch_overview($connection, $imapemail, FT_UID);
1751 }
1752
1753 $header = preg_replace('/\r\n\s+/m', ' ', $header); // When a header line is on several lines, merge lines
1754
1755 $matches = array();
1756 preg_match_all('/([^: ]+): (.+?(?:\r\n\s(?:.+?))*)(\r\n|\s$)/m', $header, $matches);
1757 $headers = array_combine($matches[1], $matches[2]);
1758
1759
1760 $richarrayofemail[] = array('imapemail' => $imapemail, 'header' => $header, 'headers' => $headers, 'overview' => $overview, 'date' => strtotime($headers['Date']));
1761 }
1762
1763
1764 // Sort email found by ascending date
1765 $richarrayofemail = dol_sort_array($richarrayofemail, 'date', 'asc');
1766
1767
1768 $iforemailloop = 0;
1769 foreach ($richarrayofemail as $tmpval) {
1770 $iforemailloop++;
1771
1772 try {
1773 $imapemail = $tmpval['imapemail'];
1774 $header = $tmpval['header'];
1775 $overview = $tmpval['overview'];
1776 $headers = $tmpval['headers'];
1777
1778 if (!empty($headers['in-reply-to']) && empty($headers['In-Reply-To'])) {
1779 $headers['In-Reply-To'] = $headers['in-reply-to'];
1780 }
1781 if (!empty($headers['references']) && empty($headers['References'])) {
1782 $headers['References'] = $headers['references'];
1783 }
1784 if (!empty($headers['message-id']) && empty($headers['Message-ID'])) {
1785 $headers['Message-ID'] = $headers['message-id'];
1786 }
1787 if (!empty($headers['subject']) && empty($headers['Subject'])) {
1788 $headers['Subject'] = $headers['subject'];
1789 }
1790
1791 $headers['Subject'] = $this->decodeSMTPSubject($headers['Subject']);
1792
1793 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
1794 $emailto = (string) $overview['to'];
1795 } else {
1796 $emailto = $this->decodeSMTPSubject($overview[0]->to);
1797 }
1798
1799 $operationslog .= '<br>** Process email #'.dol_escape_htmltag((string) $iforemailloop);
1800
1801 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
1803 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
1804 // $operationslog .= " - ".dol_escape_htmltag((string) $imapemail);
1805 $msgid = str_replace(array('<', '>'), '', $overview['message_id']);
1806 } else {
1807 $operationslog .= " - ".dol_escape_htmltag((string) $imapemail);
1808 $msgid = str_replace(array('<', '>'), '', $overview[0]->message_id);
1809 }
1810 $operationslog .= " - MsgId: ".$msgid;
1811 $operationslog .= " - Date: ".($headers['Date'] ?? $langs->transnoentitiesnoconv("NotFound"));
1812 $operationslog .= " - References: ".dol_escape_htmltag($headers['References'] ?? $langs->transnoentitiesnoconv("NotFound"))." - Subject: ".dol_escape_htmltag($headers['Subject']);
1813
1814 dol_syslog("-- Process email #".$iforemailloop.", MsgId: ".$msgid.", Date: ".($headers['Date'] ?? '').", References: ".($headers['References'] ?? '').", Subject: ".$headers['Subject']);
1815
1816
1817 $trackidfoundintorecipienttype = '';
1818 $trackidfoundintorecipientid = 0;
1819 $reg = array();
1820 // See also later list of all supported tags...
1821 // Note: "th[i]" to avoid matching a codespell suggestion to convert to "this".
1822 // TODO Add host after the @'.preg_quote($host, '/')
1823 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)) {
1824 $trackidfoundintorecipienttype = $reg[1];
1825 $trackidfoundintorecipientid = $reg[2];
1826 } elseif (preg_match('/\+emailing-(\w+)@/', $emailto, $reg)) { // Can be 'emailing-test' or 'emailing-IdMailing-IdRecipient'
1827 $trackidfoundintorecipienttype = 'emailing';
1828 $trackidfoundintorecipientid = $reg[1];
1829 }
1830
1831 $trackidfoundintomsgidtype = '';
1832 $trackidfoundintomsgidid = 0;
1833 $reg = array();
1834 // See also later list of all supported tags...
1835 // Note: "th[i]" to avoid matching a codespell suggestion to convert to "this".
1836 // TODO Add host after the @
1837 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)) {
1838 $trackidfoundintomsgidtype = $reg[1];
1839 $trackidfoundintomsgidid = $reg[2];
1840 } elseif (preg_match('/(?:[\+\-])emailing-(\w+)@/', $msgid, $reg)) { // Can be 'emailing-test' or 'emailing-IdMailing-IdRecipient'
1841 $trackidfoundintomsgidtype = 'emailing';
1842 $trackidfoundintomsgidid = $reg[1];
1843 }
1844
1845 // Now apply some filters.
1846
1847 // If there is an emailcollector filter on trackid
1848 if ($searchfilterdoltrackid > 0) {
1849 $referencesForFilter = $headers['References'] ?? '';
1850 if (!empty($headers['References']) && !empty($headers['In-Reply-To'])) {
1851 $referencesForFilter .= ' ';
1852 }
1853 $referencesForFilter .= ($headers['In-Reply-To'] ?? '');
1854
1855 if (empty($trackidfoundintorecipienttype) && empty($trackidfoundintomsgidtype)) {
1856 if (empty($referencesForFilter) || !preg_match('/@'.preg_quote($host, '/').'/', $referencesForFilter)) {
1857 $nbemailprocessed++;
1858 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");
1859 continue; // Exclude email
1860 }
1861 }
1862 }
1863 // If we DON'T want email if there is a trackid
1864 if ($searchfilternodoltrackid > 0) {
1865 $referencesForFilter = $headers['References'] ?? '';
1866 if (!empty($headers['References']) && !empty($headers['In-Reply-To'])) {
1867 $referencesForFilter .= ' ';
1868 }
1869 $referencesForFilter .= ($headers['In-Reply-To'] ?? '');
1870
1871 if (!empty($trackidfoundintorecipienttype) || !empty($trackidfoundintomsgidtype) || (!empty($referencesForFilter) && preg_match('/@'.preg_quote($host, '/').'/', $referencesForFilter))) {
1872 $nbemailprocessed++;
1873 dol_syslog(" Discarded - Suffix found into email recipient, or Header 'References/In-Reply-To' found and matching signature of application so with a trackid");
1874 continue; // Exclude email
1875 }
1876 }
1877
1878 if ($searchfilterisanswer > 0) {
1879 $referencesforanswer = '';
1880 if (!empty($headers['References'])) {
1881 $referencesforanswer .= $headers['References'].' ';
1882 }
1883 if (!empty($headers['In-Reply-To'])) {
1884 $referencesforanswer .= $headers['In-Reply-To'];
1885 }
1886
1887 $hasdolibarrreference = 0;
1888 if (!empty($referencesforanswer)) {
1889 if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote($host, '/').'/', $referencesforanswer)) {
1890 $hasdolibarrreference = 1;
1891 } elseif (getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE')) {
1892 if (preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote(getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE'), '/').'/', $referencesforanswer)) {
1893 $hasdolibarrreference = 1;
1894 }
1895 }
1896 }
1897
1898 $isanswer = 0;
1899 if (preg_match('/^(回复|回覆|SV|Antw|VS|RE|Re|AW|Aw|ΑΠ|השב| תשובה | הועבר|Vá|R|RIF|BLS|Atb|RES|Odp|பதில்|YNT|ATB)\s*:\s+/i', $headers['Subject'])) {
1900 $isanswer = 1;
1901 }
1902 if (getDolGlobalString('EMAILCOLLECTOR_USE_IN_REPLY_TO_TO_DETECT_ANSWERS')) {
1903 // Note: "In-Reply-To" to detect if mail is an answer of another mail is not reliable because we can have:
1904 // 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)
1905 if (!empty($headers['In-Reply-To'])) {
1906 $isanswer = 1;
1907 }
1908 }
1909 if ($hasdolibarrreference) {
1910 $isanswer = 1;
1911 }
1912 //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
1913 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && !empty($headers['References']) && strpos($headers['References'], $headers['Message-ID']) !== false) $isanswer = 1;
1914
1915 if (!$isanswer) {
1916 $nbemailprocessed++;
1917 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)");
1918 continue; // Exclude email
1919 }
1920 }
1921 if ($searchfilterisnotanswer > 0) {
1922 if (!empty($headers['In-Reply-To'])) {
1923 // Note: we can have
1924 // Message-ID=A, In-Reply-To=B, References=B and message can BE an answer or NOT (a transfer rewritten)
1925 $isanswer = 0;
1926 if (preg_match('/^(回复|回覆|SV|Antw|VS|RE|Re|AW|Aw|ΑΠ|השב| תשובה | הועבר|Vá|R|RIF|BLS|Atb|RES|Odp|பதில்|YNT|ATB)\s*:\s+/i', $headers['Subject'])) {
1927 $isanswer = 1;
1928 }
1929 //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
1930 //if ($headers['In-Reply-To'] != $headers['Message-ID'] && !empty($headers['References']) && strpos($headers['References'], $headers['Message-ID']) !== false) $isanswer = 1;
1931 if ($isanswer) {
1932 $nbemailprocessed++;
1933 dol_syslog(" Discarded - Email is an answer");
1934 continue; // Exclude email
1935 }
1936 }
1937 }
1938 if ($searchfilterreplyto > 0) {
1939 if (!empty($headers['Reply-To'])) {
1940 $isreplytook = 0;
1941 foreach ($rulesreplyto as $key => $rulereplyto) {
1942 if (preg_match('/'.preg_quote($rulereplyto, '/').'/', $headers['Reply-To'])) {
1943 $isreplytook++;
1944 }
1945 }
1946
1947 if (!$isreplytook || $isreplytook != count($rulesreplyto)) {
1948 $nbemailprocessed++;
1949 dol_syslog(" Discarded - Reply-to does not match");
1950 continue; // Exclude email
1951 }
1952 }
1953 }
1954
1955 //print "Process mail ".$iforemailloop." Subject: ".dol_escape_htmltag($headers['Subject'])." selected<br>\n";
1956
1957 $thirdpartystatic = new Societe($this->db);
1958 $contactstatic = new Contact($this->db);
1959 $projectstatic = new Project($this->db);
1960
1961 $nbactiondoneforemail = 0;
1962 $errorforemail = 0;
1963 $errorforactions = 0;
1964 $thirdpartyfoundby = '';
1965 $contactfoundby = '';
1966 $projectfoundby = '';
1967 $ticketfoundby = '';
1968 $candidaturefoundby = '';
1969
1970
1971 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
1972 $dateformated = dol_print_date($overview['date'], 'dayrfc', 'gmt'); // May generate a warning "dol_print_date($overview['date'], 'dayrfc', 'gmt')" in log
1973 dol_syslog("msgid=".$overview['message_id']." date=".$dateformated." from=".$overview['from']." to=".$overview['to']." subject=".$overview['subject']);
1974
1975 // Removed emojis
1976 $overview['subject'] = removeEmoji($overview['subject'], getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
1977 } else {
1978 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);
1979
1980 $overview[0]->subject = $this->decodeSMTPSubject($overview[0]->subject);
1981
1982 $overview[0]->from = $this->decodeSMTPSubject($overview[0]->from);
1983
1984 // Removed emojis
1985 $overview[0]->subject = removeEmoji($overview[0]->subject, getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
1986 }
1987 // GET IMAP email structure/content
1988 global $htmlmsg, $plainmsg, $charset, $attachments;
1989
1990 if (getDolGlobalInt('MAIN_IMAP_USE_PHPIMAP')) {
1992 '@phan-var-force Webklex\PHPIMAP\Message $imapemail';
1993 // Reset message body globals to prevent carryover from previous email in loop
1994 // Note: $charset is NOT reset as PHPIMAP handles charset internally
1995 $htmlmsg = $plainmsg = '';
1996 $attachments = array();
1997
1998 if ($imapemail->hasHTMLBody()) {
1999 $htmlmsg = $imapemail->getHTMLBody();
2000 }
2001 if ($imapemail->hasTextBody() && $imapemail->getTextBody() != "\n") {
2002 $plainmsg = $imapemail->getTextBody();
2003 }
2004 if ($imapemail->hasAttachments()) {
2005 $attachments = $imapemail->getAttachments()->all();
2006 } else {
2007 $attachments = array();
2008 }
2009 } else {
2010 $getMsg = $this->getmsg($connection, $imapemail); // This set global var $charset, $htmlmsg, $plainmsg, $attachments
2011 if ($getMsg < 0) {
2012 $this->errors = array_merge($this->errors, [$this->error]);
2013 return $getMsg;
2014 }
2015 }
2016 '@phan-var-force Webklex\PHPIMAP\Attachment[] $attachments';
2019 //$htmlmsg,$plainmsg,$charset,$attachments
2020 $messagetext = $plainmsg ? $plainmsg : dol_string_nohtmltag((string) $htmlmsg, 0);
2021 // Removed emojis
2022
2023 if (utf8_valid($messagetext)) {
2024 $messagetext = removeEmoji($messagetext, getDolGlobalInt('MAIN_EMAIL_COLLECTOR_ACCEPT_EMOJIS', 1));
2025 } else {
2026 $operationslog .= '<br>Discarded - Email body is not valid utf8';
2027 dol_syslog(" Discarded - Email body is not valid utf8");
2028 continue; // Exclude email
2029 }
2030
2031 if (!empty($searchfilterexcludebodyarray)) {
2032 foreach ($searchfilterexcludebodyarray as $searchfilterexcludebody) {
2033 if (preg_match('/'.preg_quote($searchfilterexcludebody, '/').'/ms', $messagetext)) {
2034 $nbemailprocessed++;
2035 $operationslog .= '<br>Discarded - Email body contains string '.$searchfilterexcludebody;
2036 dol_syslog(" Discarded - Email body contains string ".$searchfilterexcludebody);
2037 continue 2; // Exclude email
2038 }
2039 }
2040 }
2041
2042 // Parse IMAP email structure
2043 /*
2044 $structure = imap_fetchstructure($connection, $imapemail, FT_UID);
2045
2046 $partplain = $parthtml = -1;
2047 $encodingplain = $encodinghtml = '';
2048
2049 $result = createPartArray($structure, '');
2050
2051 foreach($result as $part)
2052 {
2053 // $part['part_object']->type seems 0 for content
2054 // $part['part_object']->type seems 5 for attachment
2055 if (empty($part['part_object'])) continue;
2056 if ($part['part_object']->subtype == 'HTML')
2057 {
2058 $parthtml=$part['part_number'];
2059 if ($part['part_object']->encoding == 4)
2060 {
2061 $encodinghtml = 'aaa';
2062 }
2063 }
2064 if ($part['part_object']->subtype == 'PLAIN')
2065 {
2066 $partplain=$part['part_number'];
2067 if ($part['part_object']->encoding == 4)
2068 {
2069 $encodingplain = 'rr';
2070 }
2071 }
2072 }
2073
2074 $messagetext = imap_fetchbody($connection, $imapemail, ($parthtml != '-1' ? $parthtml : ($partplain != '-1' ? $partplain : 1)), FT_PEEK|FTP_UID);
2075 */
2076
2077 $fromstring = '';
2078 $replytostring = '';
2079
2080 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
2081 $fromstring = $this->decodeSMTPSubject((string) ($overview['from'] ?? ''));
2082 $replytostring = !empty($headers['Reply-To']) ? $this->decodeSMTPSubject($headers['Reply-To']) : '';
2083
2084 $sender = $this->decodeSMTPSubject((string) ($overview['sender'] ?? ''));
2085 $to = $this->decodeSMTPSubject((string) ($overview['to'] ?? ''));
2086 $sendtocc = $this->decodeSMTPSubject((string) ($overview['cc'] ?? ''));
2087 $sendtobcc = $this->decodeSMTPSubject((string) ($overview['bcc'] ?? ''));
2088
2089 $tmpdate = $overview['date']->toDate(); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
2090 $tmptimezone = $tmpdate->getTimezone()->getName(); // @phan-suppress-current-line PhanPluginUnknownObjectMethodCall
2091
2092 $dateemail = dol_stringtotime((string) $overview['date'], 'gmt'); // if $overview['timezone'] is "+00:00"
2093 if (preg_match('/^([+\-])(\d\d):(\d\d)/', $tmptimezone, $reg)) {
2094 if ($reg[1] == '+' && ($reg[2] != '00' || $reg[3] != '00')) {
2095 $dateemail -= (3600 * (int) $reg[2]);
2096 $dateemail -= (60 * (int) $reg[3]);
2097 }
2098 if ($reg[1] == '-' && ($reg[2] != '00' || $reg[3] != '00')) {
2099 $dateemail += (3600 * (int) $reg[2]);
2100 $dateemail += (60 * (int) $reg[3]);
2101 }
2102 }
2103 $subject = $this->decodeSMTPSubject((string) ($overview['subject'] ?? ''));
2104 } else {
2105 $fromstring = $overview[0]->from;
2106 $replytostring = (!empty($overview['in_reply-to']) ? $overview['in_reply-to'] : (!empty($headers['Reply-To']) ? $headers['Reply-To'] : "")) ;
2107
2108 $sender = !empty($overview[0]->sender) ? $overview[0]->sender : '';
2109 $to = $overview[0]->to;
2110 $sendtocc = !empty($overview[0]->cc) ? $overview[0]->cc : '';
2111 $sendtobcc = !empty($overview[0]->bcc) ? $overview[0]->bcc : '';
2112 $dateemail = dol_stringtotime((string) $overview[0]->udate, 'gmt');
2113 $subject = $overview[0]->subject;
2114 }
2115
2116 if (!empty($searchfilterexcludesubjectarray)) {
2117 foreach ($searchfilterexcludesubjectarray as $searchfilterexcludesubject) {
2118 if (preg_match('/'.preg_quote($searchfilterexcludesubject, '/').'/ms', $subject)) {
2119 $nbemailprocessed++;
2120 $operationslog .= '<br>Discarded - Email subject contains string '.$searchfilterexcludesubject;
2121 dol_syslog(" Discarded - Email subject contains string ".$searchfilterexcludesubject);
2122 continue 2; // Exclude email
2123 }
2124 }
2125 }
2126
2127 $reg = array();
2128 if (preg_match('/^(.*)<(.*)>$/', $fromstring, $reg)) {
2129 $from = $reg[2];
2130 $fromtext = $reg[1];
2131 } else {
2132 $from = $fromstring;
2133 $fromtext = '';
2134 }
2135 if (preg_match('/^(.*)<(.*)>$/', $replytostring, $reg)) {
2136 $replyto = $reg[2];
2137 $replytotext = $reg[1];
2138 } else {
2139 $replyto = $replytostring;
2140 $replytotext = '';
2141 }
2142 $fk_element_id = 0;
2143 $fk_element_type = '';
2144
2145
2146 $this->db->begin();
2147
2148 $contactid = 0;
2149 $thirdpartyid = 0;
2150 $projectid = 0;
2151 $ticketid = 0;
2152
2153 // Analyze TrackId in field References (already analyzed previously into the "To:" and "Message-Id").
2154 // For example:
2155 // References: <1542377954.SMTPs-dolibarr-thi649@8f6014fde11ec6cdec9a822234fc557e>
2156 // References: <1542377954.SMTPs-dolibarr-tic649@8f6014fde11ec6cdec9a822234fc557e>
2157 // References: <1542377954.SMTPs-dolibarr-abc649@8f6014fde11ec6cdec9a822234fc557e>
2158 $trackid = '';
2159 $objectid = 0;
2160 $objectemail = null;
2161
2162 $reg = array();
2163 $arrayofreferences = array();
2164 if (!empty($headers['References'])) {
2165 $arrayofreferences = preg_split('/(,|\s+)/', $headers['References']);
2166 }
2167 if (!in_array('<'.$msgid.'>', $arrayofreferences)) {
2168 $arrayofreferences = array_merge($arrayofreferences, array('<'.$msgid.'>'));
2169 }
2170
2171 // We loop on References, but as soon as we found one that allow us to find an existing object,
2172 // we do a break (See line with comment "Exit loop of references").
2173 foreach ($arrayofreferences as $reference) {
2174 //print "Process mail ".$iforemailloop." email_msgid ".$msgid.", date ".dol_print_date($dateemail, 'dayhour', 'gmt').", subject ".$subject.", reference ".dol_escape_htmltag($reference)."<br>\n";
2175 if (!empty($trackidfoundintorecipienttype)) {
2176 $resultsearchtrackid = -1; // trackid found
2177 $reg[1] = $trackidfoundintorecipienttype;
2178 $reg[2] = $trackidfoundintorecipientid;
2179 } elseif (!empty($trackidfoundintomsgidtype)) {
2180 $resultsearchtrackid = -1; // trackid found
2181 $reg[1] = $trackidfoundintomsgidtype;
2182 $reg[2] = $trackidfoundintomsgidid;
2183 } else {
2184 $resultsearchtrackid = preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote($host, '/').'/', $reference, $reg); // trackid found or not
2185 if (empty($resultsearchtrackid) && getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE')) {
2186 $resultsearchtrackid = preg_match('/dolibarr-([a-z]+)([0-9]+)@'.preg_quote(getDolGlobalString('EMAIL_ALTERNATIVE_HOST_SIGNATURE'), '/').'/', $reference, $reg); // trackid found
2187 }
2188 }
2189
2190 if (!empty($resultsearchtrackid)) {
2191 // We found a tracker (in recipient email or msgid or into a Reference matching the Dolibarr server)
2192 $trackid = $reg[1].$reg[2];
2193
2194 $objectid = $reg[2];
2195 // See also list into interface_50_modAgenda_ActionsAuto
2196 if ($reg[1] == 'thi') { // Third-party
2197 $objectemail = new Societe($this->db);
2198 }
2199 if ($reg[1] == 'ctc') { // Contact
2200 $objectemail = new Contact($this->db);
2201 }
2202 if ($reg[1] == 'inv') { // Customer Invoice
2203 $objectemail = new Facture($this->db);
2204 }
2205 if ($reg[1] == 'sinv') { // Supplier Invoice
2206 $objectemail = new FactureFournisseur($this->db);
2207 }
2208 if ($reg[1] == 'pro') { // Customer Proposal
2209 $objectemail = new Propal($this->db);
2210 }
2211 if ($reg[1] == 'ord') { // Sale Order
2212 $objectemail = new Commande($this->db);
2213 }
2214 if ($reg[1] == 'shi') { // Shipment
2215 $objectemail = new Expedition($this->db);
2216 }
2217 if ($reg[1] == 'spro') { // Supplier Proposal
2218 $objectemail = new SupplierProposal($this->db);
2219 }
2220 if ($reg[1] == 'sord') { // Supplier Order
2221 $objectemail = new CommandeFournisseur($this->db);
2222 }
2223 if ($reg[1] == 'rec') { // Reception
2224 $objectemail = new Reception($this->db);
2225 }
2226 if ($reg[1] == 'proj') { // Project
2227 $objectemail = new Project($this->db);
2228 $projectfoundby = 'TrackID dolibarr-'.$trackid.'@...';
2229 }
2230 if ($reg[1] == 'tas') { // Task
2231 $objectemail = new Task($this->db);
2232 }
2233 if ($reg[1] == 'con') { // Contact
2234 $objectemail = new Contact($this->db);
2235 }
2236 if ($reg[1] == 'use') { // User
2237 $objectemail = new User($this->db);
2238 }
2239 if ($reg[1] == 'tic') { // Ticket
2240 $objectemail = new Ticket($this->db);
2241 $ticketfoundby = 'TrackID dolibarr-'.$trackid.'@...';
2242 }
2243 if ($reg[1] == 'recruitmentcandidature') { // Recruiting Candidate
2244 $objectemail = new RecruitmentCandidature($this->db);
2245 $candidaturefoundby = 'TrackID dolibarr-'.$trackid.'@...';
2246 }
2247 if ($reg[1] == 'mem') { // Member
2248 $objectemail = new Adherent($this->db);
2249 }
2250 /*if ($reg[1] == 'leav') { // Leave / Holiday
2251 $objectemail = new Holiday($db);
2252 }
2253 if ($reg[1] == 'exp') { // ExpenseReport
2254 $objectemail = new ExpenseReport($db);
2255 }*/
2256 } elseif (preg_match('/<(.*@.*)>/', $reference, $reg)) {
2257 // This is an external reference, we check if we have it in our database
2258 if (is_null($objectemail) && isModEnabled('ticket')) {
2259 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."ticket";
2260 $sql .= " WHERE email_msgid = '".$this->db->escape($reg[1])."' OR origin_references LIKE '%".$this->db->escape($this->db->escapeforlike($reg[1]))."%'";
2261 $resql = $this->db->query($sql);
2262 if ($resql) {
2263 $obj = $this->db->fetch_object($resql);
2264 if ($obj) {
2265 $objectid = $obj->rowid;
2266 $objectemail = new Ticket($this->db);
2267 $ticketfoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2268 }
2269 } else {
2270 $errorforemail++;
2271 }
2272 }
2273
2274 if (!is_object($objectemail) && isModEnabled('project')) {
2275 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."projet where email_msgid = '".$this->db->escape($reg[1])."'";
2276 $resql = $this->db->query($sql);
2277 if ($resql) {
2278 $obj = $this->db->fetch_object($resql);
2279 if ($obj) {
2280 $objectid = $obj->rowid;
2281 $objectemail = new Project($this->db);
2282 $projectfoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2283 }
2284 } else {
2285 $errorforemail++;
2286 }
2287 }
2288
2289 if (!is_object($objectemail) && isModEnabled('recruitment')) {
2290 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."recruitment_recruitmentcandidature where email_msgid = '".$this->db->escape($reg[1])."'";
2291 $resql = $this->db->query($sql);
2292 if ($resql) {
2293 $obj = $this->db->fetch_object($resql);
2294 if ($obj) {
2295 $objectid = $obj->rowid;
2296 $objectemail = new RecruitmentCandidature($this->db);
2297 $candidaturefoundby = $langs->transnoentitiesnoconv("EmailMsgID").' ('.$reg[1].')';
2298 }
2299 } else {
2300 $errorforemail++;
2301 }
2302 }
2303 }
2304
2305 // Load object linked to email
2306 if (is_object($objectemail)) {
2307 $result = $objectemail->fetch($objectid);
2308 if ($result > 0) {
2309 $fk_element_id = $objectemail->id;
2310 $fk_element_type = $objectemail->element;
2311 // Fix fk_element_type
2312 if ($fk_element_type == 'facture') {
2313 $fk_element_type = 'invoice';
2314 }
2315
2316 if (get_class($objectemail) != 'Societe') {
2317 $thirdpartyid = $objectemail->fk_soc ?? $objectemail->socid;
2318 } else {
2319 $thirdpartyid = $objectemail->id;
2320 }
2321
2322 if (get_class($objectemail) != 'Contact') {
2323 $contactid = $objectemail->fk_socpeople;
2324 } else {
2325 $contactid = $objectemail->id;
2326 }
2327
2328 if (get_class($objectemail) != 'Project') {
2329 $projectid = isset($objectemail->fk_project) ? $objectemail->fk_project : $objectemail->fk_projet;
2330 } else {
2331 $projectid = $objectemail->id;
2332 }
2333
2334 if ($objectemail instanceof Ticket) { // For tickets, we have a column to store all met references, so we complete it if we need to.
2335 $ticketid = $objectemail->id;
2336
2337 $changeonticket_references = false;
2338 if (empty($trackid)) {
2339 $trackid = $objectemail->track_id;
2340 }
2341 if (empty($objectemail->origin_references)) {
2342 $objectemail->origin_references = !empty($headers['References']) ? $headers['References'] : null;
2343 $changeonticket_references = true;
2344 } else {
2345 foreach ($arrayofreferences as $key => $referencetmp) {
2346 if (!str_contains($objectemail->origin_references, $referencetmp)) {
2347 $objectemail->origin_references .= " ".$referencetmp;
2348 $changeonticket_references = true;
2349 }
2350 }
2351 }
2352 if ($changeonticket_references) {
2353 $operationslog .= '<br>We complete ticket ID='.$ticketid.' with property origin_references='.$objectemail->origin_references;
2354 $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
2355 }
2356 }
2357 }
2358 }
2359
2360 // Project
2361 if ($projectid > 0) {
2362 $result = $projectstatic->fetch($projectid);
2363 if ($result <= 0) {
2364 $projectstatic->id = 0;
2365 } else {
2366 $projectid = $projectstatic->id;
2367 if ($trackid) {
2368 $projectfoundby = 'trackid ('.$trackid.')';
2369 }
2370 if (empty($contactid)) {
2371 $contactid = $projectstatic->fk_contact;
2372 }
2373 if (empty($thirdpartyid)) {
2374 $thirdpartyid = $projectstatic->fk_soc;
2375 }
2376 }
2377 }
2378 // Contact
2379 if ($contactid > 0) {
2380 $result = $contactstatic->fetch($contactid);
2381 if ($result <= 0) {
2382 $contactstatic->id = 0;
2383 } else {
2384 $contactid = $contactstatic->id;
2385 if ($trackid) {
2386 $contactfoundby = 'trackid ('.$trackid.')';
2387 }
2388 if (empty($thirdpartyid)) {
2389 $thirdpartyid = $contactstatic->fk_soc;
2390 }
2391 }
2392 }
2393 // Thirdparty
2394 if ($thirdpartyid > 0) {
2395 $result = $thirdpartystatic->fetch($thirdpartyid);
2396 if ($result <= 0) {
2397 $thirdpartystatic->id = 0;
2398 } else {
2399 $thirdpartyid = $thirdpartystatic->id;
2400 if ($trackid) {
2401 $thirdpartyfoundby = 'trackid ('.$trackid.')';
2402 }
2403 }
2404 }
2405
2406 if (is_object($objectemail)) {
2407 break; // Exit loop of references. We already found an accurate reference
2408 }
2409 }
2410
2411 if (empty($contactid)) { // Try to find contact using email
2412 $result = $contactstatic->fetch(0, null, '', $from);
2413
2414 if ($result > 0) {
2415 dol_syslog("We found a contact with the email ".$from);
2416 $contactid = $contactstatic->id;
2417 $contactfoundby = 'email of contact ('.$from.')';
2418 if (empty($thirdpartyid) && $contactstatic->socid > 0) {
2419 $result = $thirdpartystatic->fetch($contactstatic->socid);
2420 if ($result > 0) {
2421 $thirdpartyid = $thirdpartystatic->id;
2422 $thirdpartyfoundby = 'email of contact ('.$from.')';
2423 }
2424 }
2425 }
2426 }
2427
2428 if (empty($thirdpartyid)) { // Try to find thirdparty using email
2429 $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $from);
2430 if ($result > 0) {
2431 dol_syslog("We found a thirdparty with the email ".$from);
2432 $thirdpartyid = $thirdpartystatic->id;
2433 $thirdpartyfoundby = 'email ('.$from.')';
2434 }
2435 }
2436
2437 /*
2438 if ($replyto) {
2439 if (empty($contactid)) { // Try to find contact using email
2440 $result = $contactstatic->fetch(0, null, '', $replyto);
2441
2442 if ($result > 0) {
2443 dol_syslog("We found a contact with the email ".$replyto);
2444 $contactid = $contactstatic->id;
2445 $contactfoundby = 'email of contact ('.$replyto.')';
2446 if (empty($thirdpartyid) && $contactstatic->socid > 0) {
2447 $result = $thirdpartystatic->fetch($contactstatic->socid);
2448 if ($result > 0) {
2449 $thirdpartyid = $thirdpartystatic->id;
2450 $thirdpartyfoundby = 'email of contact ('.$replyto.')';
2451 }
2452 }
2453 }
2454 }
2455
2456 if (empty($thirdpartyid)) { // Try to find thirdparty using email
2457 $result = $thirdpartystatic->fetch(0, '', '', '', '', '', '', '', '', '', $replyto);
2458 if ($result > 0) {
2459 dol_syslog("We found a thirdparty with the email ".$replyto);
2460 $thirdpartyid = $thirdpartystatic->id;
2461 $thirdpartyfoundby = 'email ('.$replyto.')';
2462 }
2463 }
2464 }
2465 */
2466
2467
2468 // Now do all operations for the email (extract variables and creating data)
2469 if ($mode < 2) { // 0=Mode production, 1=Mode test (read IMAP and try SQL update then rollback), 2=Mode test with no SQL updates
2470 foreach ($this->actions as $operation) {
2471 $errorforthisaction = 0;
2472 $ticketalreadyexists = 0;
2473 if ($errorforactions) {
2474 break;
2475 }
2476 if (empty($operation['status'])) {
2477 continue;
2478 }
2479
2480 $operationslog .= '<br>* Process operation '.$operation['type'];
2481
2482 // Make Operation
2483 dol_syslog("Execute action ".$operation['type']." actionparam=".$operation['actionparam'].' thirdpartystatic->id='.$thirdpartystatic->id.' contactstatic->id='.$contactstatic->id.' projectstatic->id='.$projectstatic->id);
2484 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
2485
2486 // Try to guess if this is an email in or out.
2487 $actioncode = 'EMAIL_IN';
2488 // If we scan the Sent box, we use the code for out email
2489 if (preg_match('/Sent$/', $sourcedir) || preg_match('/envoyés$/i', $sourcedir)) {
2490 $actioncode = 'EMAIL';
2491 }
2492 // If sender is in the list MAIL_FROM_EMAILS_TO_CONSIDER_SENDING
2493 $arrayofemailtoconsidersender = array_filter(array_map('trim', explode(',', getDolGlobalString('MAIL_FROM_EMAILS_TO_CONSIDER_SENDING'))));
2494 foreach ($arrayofemailtoconsidersender as $emailtoconsidersender) {
2495 if ($emailtoconsidersender === '') {
2496 continue;
2497 }
2498 if (preg_match('/'.preg_quote($emailtoconsidersender, '/').'/', $fromstring)) {
2499 $actioncode = 'EMAIL';
2500 }
2501 }
2502 $operationslog .= '<br>Email will have actioncode='.$actioncode;
2503
2504 $description = $descriptiontitle = $descriptionmeta = $descriptionfull = '';
2505
2506 $descriptiontitle = $langs->transnoentitiesnoconv("RecordCreatedByEmailCollector", $this->ref);
2507
2508 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("EmailMsgID").' : '.dol_escape_htmltag($msgid));
2509 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailTopic").' : '.dol_escape_htmltag($subject));
2510 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailDate").($langs->trans("MailDate") != 'Date' ? ' (Date)' : '').' : '.dol_escape_htmltag(dol_print_date($dateemail, "dayhourtext", "gmt")));
2511 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailFrom").($langs->trans("MailFrom") != 'From' ? ' (From)' : '').' : '.dol_escape_htmltag($fromstring));
2512 if ($sender) {
2513 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("Sender").($langs->trans("Sender") != 'Sender' ? ' (Sender)' : '').' : '.dol_escape_htmltag($sender));
2514 }
2515 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailTo").($langs->trans("MailTo") != 'To' ? ' (To)' : '').' : '.dol_escape_htmltag($to));
2516 if ($replyto) {
2517 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailReply").($langs->trans("MailReply") != 'Reply to' ? ' (Reply to)' : '').' : '.dol_escape_htmltag($replyto));
2518 }
2519 if ($sendtocc) {
2520 $descriptionmeta = dol_concatdesc($descriptionmeta, $langs->trans("MailCC").($langs->trans("MailCC") != 'CC' ? ' (CC)' : '').' : '.dol_escape_htmltag($sendtocc));
2521 }
2522
2523 if ($operation['type'] == 'ticket') {
2524 // Verify if ticket already exists to fall back on the right operation
2525 $tickettocreate = new Ticket($this->db);
2526 $errorfetchticket = 0;
2527 $alreadycreated = 0;
2528 if (!empty($objectid) && $objectemail instanceof Ticket) {
2529 $alreadycreated = $tickettocreate->fetch((int) $objectid);
2530 }
2531 if ($alreadycreated == 0 && $ticketid > 0) { // objectemail was on a ticket it ticketid set
2532 $alreadycreated = $tickettocreate->fetch($ticketid);
2533 }
2534 if ($alreadycreated == 0 && !empty($trackid)) { // objectemail was on a ticket if trackid is set
2535 $alreadycreated = $tickettocreate->fetch(0, '', $trackid);
2536 }
2537 if ($alreadycreated == 0 && !empty($msgid)) { // objectemail was on a ticket if msgid is set
2538 $alreadycreated = $tickettocreate->fetch(0, '', '', $msgid);
2539 }
2540 if ($alreadycreated < 0) {
2541 $errorfetchticket++;
2542 }
2543 if (empty($errorfetchticket)) {
2544 if ($alreadycreated == 0) {
2545 $operationslog .= '<br>Ticket not found using trackid='.$trackid.' or msgid='.$msgid;
2546 $ticketalreadyexists = 0;
2547 } else {
2548 $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
2549 $ticketalreadyexists = 1;
2550 $operation['type'] = 'recordevent';
2551 }
2552 } else {
2553 $ticketalreadyexists = -1;
2554 }
2555 }
2556
2557 // Process now the operation according to its type
2558
2559 // Search and create thirdparty
2560 if ($operation['type'] == 'loadthirdparty' || $operation['type'] == 'loadandcreatethirdparty') {
2561 if ($thirdpartyid > 0) {
2562 // We already have found the thirdparty id to load, so we bypass the action loadthirdparty
2563 $idtouseforthirdparty = $thirdpartyid;
2564 $operationslog .= '<br>We already have found a related thirdparty id, so we bypass the action loadthirdparty and use idtouseforthirdparty='.$idtouseforthirdparty;
2565 } elseif (empty($operation['actionparam'])) {
2566 $errorforactions++;
2567 $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;";
2568 $this->errors[] = $this->error;
2569 } else {
2570 $actionparam = $operation['actionparam'];
2571 $idtouseforthirdparty = '';
2572 $nametouseforthirdparty = '';
2573 $emailtouseforthirdparty = '';
2574 $namealiastouseforthirdparty = '';
2575
2576 $operationslog .= '<br>Loop on each property to set into actionparam';
2577
2578 // $actionparam = 'param=SET:aaa' or 'param=EXTRACT:BODY:....'
2579 $arrayvaluetouse = dolExplodeIntoArray($actionparam, '(\n\r|\r|\n|;)', '=');
2580 foreach ($arrayvaluetouse as $propertytooverwrite => $valueforproperty) {
2581 $sourcestring = '';
2582 $sourcefield = '';
2583 $regexstring = '';
2584 $regforregex = array();
2585
2586 if (preg_match('/^EXTRACT:([a-zA-Z0-9_]+):(.*)$/', $valueforproperty, $regforregex)) {
2587 $sourcefield = $regforregex[1];
2588 $regexstring = $regforregex[2];
2589 }
2590
2591 if (!empty($sourcefield) && !empty($regexstring)) {
2592 if (strtolower($sourcefield) == 'body') {
2593 $sourcestring = $messagetext;
2594 } elseif (strtolower($sourcefield) == 'subject') {
2595 $sourcestring = $subject;
2596 } elseif (strtolower($sourcefield) == 'header') {
2597 $sourcestring = $header;
2598 }
2599
2600 if ($sourcestring) {
2601 $regforval = array();
2602 if (preg_match('/'.$regexstring.'/ms', $sourcestring, $regforval)) {
2603 // Overwrite param $tmpproperty
2604 if ($propertytooverwrite == 'id') {
2605 $idtouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2606
2607 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found idtouseforthirdparty='.dol_escape_htmltag($idtouseforthirdparty);
2608 } elseif ($propertytooverwrite == 'email') {
2609 $emailtouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2610
2611 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found emailtouseforthirdparty='.dol_escape_htmltag($emailtouseforthirdparty);
2612 } elseif ($propertytooverwrite == 'name') {
2613 $nametouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2614
2615 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found nametouseforthirdparty='.dol_escape_htmltag($nametouseforthirdparty);
2616 } elseif ($propertytooverwrite == 'name_alias') {
2617 $namealiastouseforthirdparty = isset($regforval[count($regforval) - 1]) ? trim($regforval[count($regforval) - 1]) : null;
2618
2619 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Found namealiastouseforthirdparty='.dol_escape_htmltag($namealiastouseforthirdparty);
2620 } else {
2621 $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';
2622 }
2623 } else {
2624 // Regex not found
2625 if (in_array($propertytooverwrite, array('id', 'email', 'name', 'name_alias'))) {
2626 $idtouseforthirdparty = null;
2627 $nametouseforthirdparty = null;
2628 $emailtouseforthirdparty = null;
2629 $namealiastouseforthirdparty = null;
2630
2631 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Not found. Property searched is critical so we cancel the search.';
2632 } else {
2633 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' Regex /'.dol_escape_htmltag($regexstring).'/ms into '.strtoupper($sourcefield).' -> Not found';
2634 }
2635 }
2636 } else {
2637 // Nothing can be done for this param
2638 $errorforactions++;
2639 $this->error = 'The extract rule to use to load thirdparty for email '.$msgid.' has an unknown source (must be HEADER, SUBJECT or BODY)';
2640 $this->errors[] = $this->error;
2641
2642 $operationslog .= '<br>'.$this->error;
2643 }
2644 } elseif (preg_match('/^(SET|SETIFEMPTY):(.*)$/', $valueforproperty, $reg)) {
2645 //if (preg_match('/^options_/', $tmpproperty)) $object->array_options[preg_replace('/^options_/', '', $tmpproperty)] = $reg[1];
2646 //else $object->$tmpproperty = $reg[1];
2647 // Example: id=SETIFEMPTY:123
2648 if ($propertytooverwrite == 'id') {
2649 $idtouseforthirdparty = $reg[2];
2650
2651 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property idtouseforthrdparty='.dol_escape_htmltag($idtouseforthirdparty);
2652 } elseif ($propertytooverwrite == 'email') {
2653 $emailtouseforthirdparty = $reg[2];
2654
2655 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property emailtouseforthrdparty='.dol_escape_htmltag($emailtouseforthirdparty);
2656 } elseif ($propertytooverwrite == 'name') {
2657 $nametouseforthirdparty = $reg[2];
2658
2659 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property nametouseforthirdparty='.dol_escape_htmltag($nametouseforthirdparty);
2660 } elseif ($propertytooverwrite == 'name_alias') {
2661 $namealiastouseforthirdparty = $reg[2];
2662
2663 $operationslog .= '<br>propertytooverwrite='.$propertytooverwrite.' We set property namealiastouseforthirdparty='.dol_escape_htmltag($namealiastouseforthirdparty);
2664 }
2665 } else {
2666 $errorforactions++;
2667 $this->error = 'Bad syntax for description of action parameters: '.$actionparam;
2668 $this->errors[] = $this->error;
2669 break;
2670 }
2671 }
2672
2673 if (!$errorforactions && ($idtouseforthirdparty || $emailtouseforthirdparty || $nametouseforthirdparty || $namealiastouseforthirdparty)) {
2674 // We make another search on thirdparty
2675 $operationslog .= '<br>We have this initial main data to search thirdparty: id='.$idtouseforthirdparty.', email='.$emailtouseforthirdparty.', name='.$nametouseforthirdparty.', name_alias='.$namealiastouseforthirdparty.'.';
2676
2677 $tmpobject = new stdClass();
2678 $tmpobject->element = 'generic';
2679 $tmpobject->id = $idtouseforthirdparty;
2680 $tmpobject->name = $nametouseforthirdparty;
2681 $tmpobject->name_alias = $namealiastouseforthirdparty;
2682 $tmpobject->email = $emailtouseforthirdparty;
2683
2684 $this->overwritePropertiesOfObject($tmpobject, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2685
2686 $idtouseforthirdparty = $tmpobject->id;
2687 $nametouseforthirdparty = $tmpobject->name;
2688 $namealiastouseforthirdparty = $tmpobject->name_alias;
2689 $emailtouseforthirdparty = $tmpobject->email;
2690
2691 $operationslog .= '<br>We try to search existing thirdparty with idtouseforthirdparty='.$idtouseforthirdparty.' emailtouseforthirdparty='.$emailtouseforthirdparty.' nametouseforthirdparty='.$nametouseforthirdparty.' namealiastouseforthirdparty='.$namealiastouseforthirdparty;
2692
2693 // Try to find the thirdparty that match the most the information we have
2694 $result = $thirdpartystatic->findNearest((int) $idtouseforthirdparty, (string) $nametouseforthirdparty, '', '', '', '', '', '', '', '', (string) $emailtouseforthirdparty, (string) $namealiastouseforthirdparty);
2695
2696 if ($result < 0) {
2697 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
2698 $idtouseforthirdparty = getDolGlobalInt('EMAILCOLLECTOR_USE_THIS_THIRDPARTY_ID_IF_DUPLICATE');
2699
2700 $thirdpartystatic->fetch($idtouseforthirdparty);
2701
2702 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));
2703
2704 $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);
2705 } else {
2706 $errorforactions++;
2707 $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 ?)';
2708 $this->errors[] = $this->error;
2709 break;
2710 }
2711 }
2712 if ($result == 0) { // No thirdparty found
2713 if ($operation['type'] == 'loadthirdparty') {
2714 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was not found");
2715
2716 // Search into contacts of thirdparties to try to guess the thirdparty to use
2717 $resultContact = $contactstatic->findNearest(0, '', '', '', (string) $emailtouseforthirdparty, '', 0);
2718 if ($resultContact > 0) {
2719 $contactstatic->fetch($resultContact);
2720 $idtouseforthirdparty = $contactstatic->socid;
2721 $result = $thirdpartystatic->fetch($idtouseforthirdparty);
2722 if ($result > 0) {
2723 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was found thanks to linked contact search");
2724 } else {
2725 $errorforactions++;
2726 $langs->load("errors");
2727 $this->error = $langs->trans('ErrorFailedToLoadThirdParty', (string) $idtouseforthirdparty, (string) $emailtouseforthirdparty, (string) $nametouseforthirdparty, (string) $namealiastouseforthirdparty);
2728 $this->errors[] = $this->error;
2729 }
2730 } else {
2731 $errorforactions++;
2732 $langs->load("errors");
2733 $this->error = $langs->trans('ErrorFailedToLoadThirdParty', (string) $idtouseforthirdparty, (string) $emailtouseforthirdparty, (string) $nametouseforthirdparty, (string) $namealiastouseforthirdparty);
2734 $this->errors[] = $this->error;
2735 }
2736 } elseif ($operation['type'] == 'loadandcreatethirdparty') {
2737 dol_syslog("Third party with id=".$idtouseforthirdparty." email=".$emailtouseforthirdparty." name=".$nametouseforthirdparty." name_alias=".$namealiastouseforthirdparty." was not found. We try to create it.");
2738
2739 // Create thirdparty
2740 $thirdpartystatic = new Societe($db);
2741 $thirdpartystatic->name = (string) $nametouseforthirdparty;
2742 if (!empty($namealiastouseforthirdparty)) {
2743 if ($namealiastouseforthirdparty != $nametouseforthirdparty) {
2744 $thirdpartystatic->name_alias = $namealiastouseforthirdparty;
2745 }
2746 } else {
2747 $thirdpartystatic->name_alias = (empty($replytostring) ? (empty($fromtext) ? '' : $fromtext) : $replytostring);
2748 }
2749 $thirdpartystatic->email = (empty($emailtouseforthirdparty) ? (empty($replyto) ? (empty($from) ? '' : $from) : $replyto) : $emailtouseforthirdparty);
2750
2751 // Overwrite values with values extracted from source email
2752 $errorforthisaction = $this->overwritePropertiesOfObject($thirdpartystatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2753
2754 if ($thirdpartystatic->client && empty($thirdpartystatic->code_client)) {
2755 $thirdpartystatic->code_client = 'auto';
2756 }
2757 if ($thirdpartystatic->fournisseur && empty($thirdpartystatic->code_fournisseur)) {
2758 $thirdpartystatic->code_fournisseur = 'auto';
2759 }
2760
2761 if ($errorforthisaction) {
2762 $errorforactions++;
2763 } else {
2764 $result = $thirdpartystatic->create($user);
2765 if ($result <= 0) {
2766 $errorforactions++;
2767 $this->error = $thirdpartystatic->error;
2768 $this->errors = $thirdpartystatic->errors;
2769 } else {
2770 $operationslog .= '<br>Thirdparty created -> id = '.dol_escape_htmltag((string) $thirdpartystatic->id);
2771 }
2772 }
2773 }
2774 } elseif ($result > 0) { // if $result > 0, it is ID of thirdparty
2775 dol_syslog("One and only one existing third party has been found");
2776
2777 $thirdpartystatic->fetch($result);
2778
2779 $operationslog .= '<br>Thirdparty already exists with id = '.dol_escape_htmltag((string) $thirdpartystatic->id)." and name ".dol_escape_htmltag($thirdpartystatic->name);
2780 }
2781 }
2782 }
2783 } elseif ($operation['type'] == 'loadandcreatecontact') { // Search and create contact
2784 if (empty($operation['actionparam'])) {
2785 $errorforactions++;
2786 $this->error = "Action loadandcreatecontact has empty parameter. Must be 'SET:xxx' or 'EXTRACT:(body|subject):regex' to define how to extract data";
2787 $this->errors[] = $this->error;
2788 } else {
2789 $contact_static = new Contact($this->db);
2790 // Overwrite values with values extracted from source email
2791 $errorforthisaction = $this->overwritePropertiesOfObject($contact_static, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2792 if ($errorforthisaction) {
2793 $errorforactions++;
2794 } else {
2795 if (!empty($contact_static->email) && $contact_static->email != $from) {
2796 $from = $contact_static->email;
2797 }
2798
2799 $from = (string) $from;
2800
2801 $result = $contactstatic->fetch(0, null, '', $from);
2802 if ($result < 0) {
2803 $errorforactions++;
2804 $this->error = 'Error when getting contact with email ' . $from;
2805 $this->errors[] = $this->error;
2806 break;
2807 } elseif ($result == 0) {
2808 dol_syslog("Contact with email " . $from . " was not found. We try to create it.");
2809 $contactstatic = new Contact($this->db);
2810
2811 // Create contact
2812 $contactstatic->email = $from;
2813 $operationslog .= '<br>We set property email='.dol_escape_htmltag($from);
2814
2815 // Overwrite values with values extracted from source email
2816 $errorforthisaction = $this->overwritePropertiesOfObject($contactstatic, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2817
2818 if ($errorforthisaction) {
2819 $errorforactions++;
2820 } else {
2821 // Search country by name or code
2822 if (!empty($contactstatic->country)) {
2823 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
2824 $result = getCountry('', '3', $this->db, null, 1, $contactstatic->country);
2825 if ($result == 'NotDefined') {
2826 $errorforactions++;
2827 $this->error = "Error country not found by this name '" . $contactstatic->country . "'";
2828 } elseif (!($result > 0)) {
2829 $errorforactions++;
2830 $this->error = "Error when search country by this name '" . $contactstatic->country . "'";
2831 $this->errors[] = $this->db->lasterror();
2832 } else {
2833 $contactstatic->country_id = $result;
2834 $operationslog .= '<br>We set property country_id='.dol_escape_htmltag($result);
2835 }
2836 } elseif (!empty($contactstatic->country_code)) {
2837 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
2838 $result = getCountry($contactstatic->country_code, '3', $this->db);
2839 if ($result == 'NotDefined') {
2840 $errorforactions++;
2841 $this->error = "Error country not found by this code '" . $contactstatic->country_code . "'";
2842 } elseif (!($result > 0)) {
2843 $errorforactions++;
2844 $this->error = "Error when search country by this code '" . $contactstatic->country_code . "'";
2845 $this->errors[] = $this->db->lasterror();
2846 } else {
2847 $contactstatic->country_id = $result;
2848 $operationslog .= '<br>We set property country_id='.dol_escape_htmltag($result);
2849 }
2850 }
2851
2852 if (!$errorforactions) {
2853 // Search state by name or code (for country if defined)
2854 if (!empty($contactstatic->state)) {
2855 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
2856 $result = dol_getIdFromCode($this->db, $contactstatic->state, 'c_departements', 'nom', 'rowid');
2857 if (empty($result)) {
2858 $errorforactions++;
2859 $this->error = "Error state not found by this name '" . $contactstatic->state . "'";
2860 } elseif (!($result > 0)) {
2861 $errorforactions++;
2862 $this->error = "Error when search state by this name '" . $contactstatic->state . "'";
2863 $this->errors[] = $this->db->lasterror();
2864 } else {
2865 $contactstatic->state_id = $result;
2866 $operationslog .= '<br>We set property state_id='.dol_escape_htmltag($result);
2867 }
2868 } elseif (!empty($contactstatic->state_code)) {
2869 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
2870 $result = dol_getIdFromCode($this->db, $contactstatic->state_code, 'c_departements', 'code_departement', 'rowid');
2871 if (empty($result)) {
2872 $errorforactions++;
2873 $this->error = "Error state not found by this code '" . $contactstatic->state_code . "'";
2874 } elseif (!($result > 0)) {
2875 $errorforactions++;
2876 $this->error = "Error when search state by this code '" . $contactstatic->state_code . "'";
2877 $this->errors[] = $this->db->lasterror();
2878 } else {
2879 $contactstatic->state_id = $result;
2880 $operationslog .= '<br>We set property state_id='.dol_escape_htmltag($result);
2881 }
2882 }
2883 }
2884
2885 if (!$errorforactions) {
2886 $result = $contactstatic->create($user);
2887 if ($result <= 0) {
2888 $errorforactions++;
2889 $this->error = $contactstatic->error;
2890 $this->errors = $contactstatic->errors;
2891 } else {
2892 $operationslog .= '<br>Contact created -> id = '.dol_escape_htmltag((string) $contactstatic->id);
2893 }
2894 }
2895 }
2896 }
2897 }
2898 }
2899 } elseif ($operation['type'] == 'recordevent') {
2900 // Create event
2901 $actioncomm = new ActionComm($this->db);
2902
2903 $alreadycreated = $actioncomm->fetch(0, '', '', $msgid);
2904 if ($alreadycreated == 0) {
2905 $operationslog .= '<br>We did not find existing actioncomm with msgid='.$msgid;
2906
2907 if ($projectstatic->id > 0) {
2908 if ($projectfoundby) {
2909 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Project found from '.$projectfoundby);
2910 }
2911 }
2912 if ($thirdpartystatic->id > 0) {
2913 if ($thirdpartyfoundby) {
2914 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
2915 }
2916 }
2917 if ($contactstatic->id > 0) {
2918 if ($contactfoundby) {
2919 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
2920 }
2921 }
2922
2923 $description = $descriptiontitle;
2924
2925 $description = dol_concatdesc($description, $descriptionmeta);
2926 $description = dol_concatdesc($description, "-----");
2927 $description = dol_concatdesc($description, $messagetext);
2928
2929 $descriptionfull = $description;
2930 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
2931 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
2932 $descriptionfull = dol_concatdesc($descriptionfull, $header);
2933 }
2934
2935 // Insert record of emails sent
2936 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2937 $actioncomm->code = 'AC_'.$actioncode;
2938 $actioncomm->label = $langs->trans("ActionAC_".$actioncode).' - '.$langs->trans("MailFrom").' '.$from;
2939 $actioncomm->note_private = $descriptionfull;
2940 $actioncomm->fk_project = $projectstatic->id;
2941 $actioncomm->datep = $dateemail; // date of email
2942 $actioncomm->datef = $dateemail; // date of email
2943 $actioncomm->percentage = -1; // Not applicable
2944 $actioncomm->socid = $thirdpartystatic->id;
2945 $actioncomm->contact_id = $contactstatic->id;
2946 $actioncomm->socpeopleassigned = (!empty($contactstatic->id) ? array($contactstatic->id) : array());
2947 $actioncomm->authorid = $user->id; // User saving action
2948 $actioncomm->userownerid = $user->id; // Owner of action
2949 // Fields when action is an email (content should be added into note)
2950 $actioncomm->email_msgid = $msgid;
2951 $actioncomm->email_from = $fromstring;
2952 $actioncomm->email_sender = $sender;
2953 $actioncomm->email_to = $to;
2954 $actioncomm->email_tocc = $sendtocc;
2955 $actioncomm->email_tobcc = $sendtobcc;
2956 $actioncomm->email_subject = $subject;
2957 $actioncomm->errors_to = '';
2958
2959 if (!in_array($fk_element_type, array('societe', 'contact', 'project', 'user'))) {
2960 $actioncomm->fk_element = $fk_element_id;
2961 $actioncomm->elementid = $fk_element_id;
2962 $actioncomm->elementtype = $fk_element_type;
2963 if (is_object($objectemail) && $objectemail->module) {
2964 $actioncomm->elementtype .= '@'.$objectemail->module;
2965 }
2966 }
2967
2968 //$actioncomm->extraparams = $extraparams;
2969
2970 // Overwrite values with values extracted from source email
2971 $errorforthisaction = $this->overwritePropertiesOfObject($actioncomm, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
2972
2973 if ($errorforthisaction) {
2974 $errorforactions++;
2975 } else {
2976 $result = $actioncomm->create($user);
2977 if ($result <= 0) {
2978 $errorforactions++;
2979 $this->errors = $actioncomm->errors;
2980 } else {
2981 if ($fk_element_type == "ticket" && is_object($objectemail)) {
2982 if ($objectemail->status == Ticket::STATUS_CLOSED || $objectemail->status == Ticket::STATUS_CANCELED || $objectemail->status == Ticket::STATUS_NEED_MORE_INFO || $objectemail->status == Ticket::STATUS_WAITING) {
2983 if ($objectemail->fk_user_assign != null) {
2984 $res = $objectemail->setStatut(Ticket::STATUS_ASSIGNED);
2985 } else {
2986 $res = $objectemail->setStatut(Ticket::STATUS_NOT_READ);
2987 }
2988
2989 if ($res) {
2990 $operationslog .= '<br>Ticket Re-Opened successfully -> ref='.$objectemail->ref;
2991 } else {
2992 $errorforactions++;
2993 $this->error = 'Error while changing the ticket status -> ref='.$objectemail->ref;
2994 $this->errors[] = $this->error;
2995 }
2996 }
2997 if (!empty($attachments)) {
2998 // There is an attachment for the ticket -> store attachment
2999 $ticket = new Ticket($this->db);
3000 $ticket->fetch($fk_element_id);
3001 $destdir = $conf->ticket->dir_output.'/'.$ticket->ref;
3002 if (!dol_is_dir($destdir)) {
3003 dol_mkdir($destdir);
3004 }
3005 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3006 foreach ($attachments as $attachment) {
3007 $attachment->save($destdir.'/');
3008 }
3009 } else {
3010 $this->getmsg($connection, $imapemail, $destdir);
3011 }
3012 }
3013 }
3014
3015 $operationslog .= '<br>Event created -> id='.dol_escape_htmltag((string) $actioncomm->id);
3016 }
3017 }
3018 } else {
3019 $operationslog .= '<br>An event in actioncomm table already exists for the msgid = '.$msgid.' so we bypass this action.';
3020 }
3021 } elseif ($operation['type'] == 'recordjoinpiece') {
3022 $data = [];
3023 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3024 foreach ($attachments as $attachment) {
3025 if ($attachment->getName() === 'undefined') {
3026 continue;
3027 }
3028 $data[$attachment->getName()] = $attachment->getContent();
3029 }
3030 } else {
3031 $pj = getAttachments($imapemail, $connection);
3032 foreach ($pj as $key => $val) {
3033 $data[$val['filename']] = getFileData($imapemail, (string) $val['pos'], $val['type'], $connection);
3034 }
3035 }
3036 if (count($data) > 0) {
3037 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."user WHERE email LIKE '%".$this->db->escape($from)."%'";
3038 $resql = $this->db->query($sql);
3039 if ($this->db->num_rows($resql) == 0) {
3040 $this->errors[] = "User Not allowed to add documents ({$from})";
3041 }
3042 $arrayobject = array(
3043 'propale' => array('table' => 'propal',
3044 'fields' => array('ref'),
3045 'class' => 'comm/propal/class/propal.class.php',
3046 'object' => 'Propal'),
3047 'holiday' => array('table' => 'holiday',
3048 'fields' => array('ref'),
3049 'class' => 'holiday/class/holiday.class.php',
3050 'object' => 'Holiday'),
3051 'expensereport' => array('table' => 'expensereport',
3052 'fields' => array('ref'),
3053 'class' => 'expensereport/class/expensereport.class.php',
3054 'object' => 'ExpenseReport'),
3055 'recruitment/recruitmentjobposition' => array('table' => 'recruitment_recruitmentjobposition',
3056 'fields' => array('ref'),
3057 'class' => 'recruitment/class/recruitmentjobposition.class.php',
3058 'object' => 'RecruitmentJobPosition'),
3059 'recruitment/recruitmentcandidature' => array('table' => 'recruitment_recruitmentcandidature',
3060 'fields' => array('ref'),
3061 'class' => 'recruitment/class/recruitmentcandidature.class.php',
3062 'object' => 'RecruitmentCandidature'),
3063 'societe' => array('table' => 'societe',
3064 'fields' => array('code_client', 'code_fournisseur'),
3065 'class' => 'societe/class/societe.class.php',
3066 'object' => 'Societe'),
3067 'commande' => array('table' => 'commande',
3068 'fields' => array('ref'),
3069 'class' => 'commande/class/commande.class.php',
3070 'object' => 'Commande'),
3071 'expedition' => array('table' => 'expedition',
3072 'fields' => array('ref'),
3073 'class' => 'expedition/class/expedition.class.php',
3074 'object' => 'Expedition'),
3075 'contract' => array('table' => 'contrat',
3076 'fields' => array('ref'),
3077 'class' => 'contrat/class/contrat.class.php',
3078 'object' => 'Contrat'),
3079 'fichinter' => array('table' => 'fichinter',
3080 'fields' => array('ref'),
3081 'class' => 'fichinter/class/fichinter.class.php',
3082 'object' => 'Fichinter'),
3083 'ticket' => array('table' => 'ticket',
3084 'fields' => array('ref'),
3085 'class' => 'ticket/class/ticket.class.php',
3086 'object' => 'Ticket'),
3087 'knowledgemanagement' => array('table' => 'knowledgemanagement_knowledgerecord',
3088 'fields' => array('ref'),
3089 'class' => 'knowledgemanagement/class/knowledgemanagement.class.php',
3090 'object' => 'KnowledgeRecord'),
3091 'supplier_proposal' => array('table' => 'supplier_proposal',
3092 'fields' => array('ref'),
3093 'class' => 'supplier_proposal/class/supplier_proposal.class.php',
3094 'object' => 'SupplierProposal'),
3095 'fournisseur/commande' => array('table' => 'commande_fournisseur',
3096 'fields' => array('ref', 'ref_supplier'),
3097 'class' => 'fourn/class/fournisseur.commande.class.php',
3098 'object' => 'SupplierProposal'),
3099 'facture' => array('table' => 'facture',
3100 'fields' => array('ref'),
3101 'class' => 'compta/facture/class/facture.class.php',
3102 'object' => 'Facture'),
3103 'fournisseur/facture' => array('table' => 'facture_fourn',
3104 'fields' => array('ref', 'ref_client'),
3105 'class' => 'fourn/class/fournisseur.facture.class.php',
3106 'object' => 'FactureFournisseur'),
3107 'produit' => array('table' => 'product',
3108 'fields' => array('ref'),
3109 'class' => 'product/class/product.class.php',
3110 'object' => 'Product'),
3111 'productlot' => array('table' => 'product_lot',
3112 'fields' => array('batch'),
3113 'class' => 'product/stock/class/productlot.class.php',
3114 'object' => 'Productlot'),
3115 'projet' => array('table' => 'projet',
3116 'fields' => array('ref'),
3117 'class' => 'projet/class/projet.class.php',
3118 'object' => 'Project'),
3119 'projet_task' => array('table' => 'projet_task',
3120 'fields' => array('ref'),
3121 'class' => 'projet/class/task.class.php',
3122 'object' => 'Task'),
3123 'ressource' => array('table' => 'resource',
3124 'fields' => array('ref'),
3125 'class' => 'ressource/class/dolressource.class.php',
3126 'object' => 'Dolresource'),
3127 'bom' => array('table' => 'bom_bom',
3128 'fields' => array('ref'),
3129 'class' => 'bom/class/bom.class.php',
3130 'object' => 'BOM'),
3131 'mrp' => array('table' => 'mrp_mo',
3132 'fields' => array('ref'),
3133 'class' => 'mrp/class/mo.class.php',
3134 'object' => 'Mo'),
3135 );
3136
3137 if (!is_object($hookmanager)) {
3138 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
3139 $hookmanager = new HookManager($this->db);
3140 }
3141 $hookmanager->initHooks(array('emailcolector'));
3142 $parameters = array('arrayobject' => $arrayobject);
3143 $reshook = $hookmanager->executeHooks('addmoduletoeamailcollectorjoinpiece', $parameters); // Note that $action and $object may have been modified by some hooks
3144 if ($reshook > 0) {
3145 $arrayobject = $hookmanager->resArray;
3146 }
3147
3148 $resultobj = array();
3149
3150 foreach ($arrayobject as $key => $objectdesc) {
3151 $sql = 'SELECT DISTINCT t.rowid ';
3152 $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->db->sanitize($objectdesc['table']) . ' AS t';
3153 $sql .= ' WHERE ';
3154 foreach ($objectdesc['fields'] as $field) {
3155 $sql .= "('" .$this->db->escape($subject) . "' LIKE CONCAT('%', t." . $this->db->sanitize($field) . ", '%') AND t." . $this->db->sanitize($field) . " <> '') OR ";
3156 }
3157 $sql = substr($sql, 0, -4);
3158
3159 $ressqlobj = $this->db->query($sql);
3160 if ($ressqlobj) {
3161 while ($obj = $this->db->fetch_object($ressqlobj)) {
3162 $resultobj[$key][] = $obj->rowid;
3163 }
3164 }
3165 }
3166 $dirs = array();
3167 foreach ($resultobj as $mod => $ids) {
3168 $moddesc = $arrayobject[$mod];
3169 $elementpath = $mod;
3170 dol_include_once($moddesc['class']);
3171 $objectmanaged = new $moddesc['object']($this->db);
3172 '@phan-var-force CommonObject $objectmanaged';
3173 foreach ($ids as $val) {
3174 $res = $objectmanaged->fetch($val);
3175 if ($res) {
3176 $path = ($objectmanaged->entity > 1 ? "/" . $objectmanaged->entity : '');
3177 $dirs[] = DOL_DATA_ROOT . $path . "/" . $elementpath . '/' . dol_sanitizeFileName($objectmanaged->ref) . '/';
3178 } else {
3179 $this->errors[] = 'object not found';
3180 }
3181 }
3182 }
3183 foreach ($dirs as $target) {
3184 $prefix = $this->actions[$this->id]['actionparam'];
3185 foreach ($data as $filename => $content) {
3186 $resr = saveAttachment($target, $prefix . '_' . $filename, $content);
3187 if ($resr == -1) {
3188 $this->errors[] = 'Doc not saved';
3189 }
3190 }
3191 }
3192
3193 $operationslog .= '<br>Save attachment files on disk';
3194 } else {
3195 $this->errors[] = 'no joined piece';
3196
3197 $operationslog .= '<br>No joinded files';
3198 }
3199 } elseif ($operation['type'] == 'project') {
3200 // Create project / lead
3201 $projecttocreate = new Project($this->db);
3202 $alreadycreated = $projecttocreate->fetch(0, '', '', $msgid);
3203 if ($alreadycreated == 0) {
3204 if ($thirdpartystatic->id > 0) {
3205 $projecttocreate->socid = $thirdpartystatic->id;
3206 if ($thirdpartyfoundby) {
3207 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
3208 }
3209 }
3210 if ($contactstatic->id > 0) {
3211 $projecttocreate->contact_id = $contactstatic->id;
3212 if ($contactfoundby) {
3213 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
3214 }
3215 }
3216
3217 $description = $descriptiontitle;
3218
3219 $description = dol_concatdesc($description, $descriptionmeta);
3220 $description = dol_concatdesc($description, "-----");
3221 $description = dol_concatdesc($description, $messagetext);
3222
3223 $descriptionfull = $description;
3224 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3225 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3226 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3227 }
3228
3229 $id_opp_status = dol_getIdFromCode($this->db, 'PROSP', 'c_lead_status', 'code', 'rowid');
3230 $percent_opp_status = dol_getIdFromCode($this->db, 'PROSP', 'c_lead_status', 'code', 'percent');
3231
3232 $projecttocreate->title = $subject;
3233 $projecttocreate->date_start = $dateemail; // date of email
3234 $projecttocreate->date_end = 0;
3235 $projecttocreate->opp_status = $id_opp_status;
3236 $projecttocreate->opp_percent = $percent_opp_status;
3237 $projecttocreate->description = dol_concatdesc(dolGetFirstLineOfText(dol_string_nohtmltag($description, 2), 10), '...'.$langs->transnoentities("SeePrivateNote").'...');
3238 $projecttocreate->note_private = $descriptionfull;
3239 $projecttocreate->entity = $conf->entity;
3240 // Fields when action is an email (content should be added into agenda event)
3241 $projecttocreate->email_date = $dateemail;
3242 $projecttocreate->email_msgid = $msgid;
3243 $projecttocreate->email_from = $fromstring;
3244 $projecttocreate->email_sender = $sender;
3245 $projecttocreate->email_to = $to;
3246 $projecttocreate->email_tocc = $sendtocc;
3247 $projecttocreate->email_tobcc = $sendtobcc;
3248 $projecttocreate->email_subject = $subject;
3249 $projecttocreate->errors_to = '';
3250
3251 $savesocid = $projecttocreate->socid;
3252
3253 // Overwrite values with values extracted from source email.
3254 // This may overwrite any $projecttocreate->xxx properties.
3255 $errorforthisaction = $this->overwritePropertiesOfObject($projecttocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3256 $modele = null;
3257
3258 // Set project ref if not yet defined
3259 if (empty($projecttocreate->ref)) {
3260 // Get next Ref
3261 $defaultref = '';
3262 $modele = getDolGlobalString('PROJECT_ADDON', 'mod_project_simple');
3263
3264 // Search template files
3265 $file = '';
3266 $classname = '';
3267 $reldir = '';
3268 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3269 foreach ($dirmodels as $reldir) {
3270 $file = dol_buildpath($reldir."core/modules/project/".$modele.'.php', 0);
3271 if (file_exists($file)) {
3272 $classname = $modele;
3273 break;
3274 }
3275 }
3276
3277 if ($classname !== '') {
3278 if ($savesocid > 0) {
3279 if ($savesocid != $projecttocreate->socid) {
3280 $errorforactions++;
3281 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');
3282 }
3283 } else {
3284 if ($projecttocreate->socid > 0) {
3285 $thirdpartystatic->fetch($projecttocreate->socid);
3286 }
3287 }
3288
3289 $result = dol_include_once($reldir."core/modules/project/".$modele.'.php');
3290 $modModuleToUseForNextValue = new $classname();
3291 '@phan-var-force ModeleNumRefProjects $modModuleToUseForNextValue';
3292 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $projecttocreate);
3293 }
3294 $projecttocreate->ref = $defaultref;
3295 }
3296
3297
3298 if ($errorforthisaction) {
3299 $errorforactions++;
3300 } else {
3301 if (empty($projecttocreate->ref) || (is_numeric($projecttocreate->ref) && $projecttocreate->ref <= 0)) {
3302 $errorforactions++;
3303 $this->error = 'Failed to create project: Can\'t get a valid value for the field ref with numbering template = '.$modele.', thirdparty id = '.$thirdpartystatic->id;
3304
3305 $operationslog .= '<br>'.$this->error;
3306 } else {
3307 // Create project
3308 $result = $projecttocreate->create($user);
3309 if ($result <= 0) {
3310 $errorforactions++;
3311 $this->error = 'Failed to create project: '.$langs->trans($projecttocreate->error);
3312 $this->errors = $projecttocreate->errors;
3313
3314 $operationslog .= '<br>'.$this->error;
3315 } else {
3316 if ($attachments) {
3317 $destdir = $conf->project->dir_output.'/'.$projecttocreate->ref;
3318 if (!dol_is_dir($destdir)) {
3319 dol_mkdir($destdir);
3320 }
3321 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3322 foreach ($attachments as $attachment) {
3323 // $attachment->save($destdir.'/');
3324 $typeattachment = (string) $attachment->getDisposition();
3325 $filename = $attachment->getFilename();
3326 $content = $attachment->getContent();
3327 $this->saveAttachment($destdir, $filename, $content);
3328 }
3329 } else {
3330 $getMsg = $this->getmsg($connection, $imapemail, $destdir);
3331 if ($getMsg < 0) {
3332 $this->errors = array_merge($this->errors, [$this->error]);
3333 return $getMsg;
3334 }
3335 }
3336
3337 $operationslog .= '<br>Project created with attachments -> id='.dol_escape_htmltag((string) $projecttocreate->id);
3338 } else {
3339 $operationslog .= '<br>Project created without attachments -> id='.dol_escape_htmltag((string) $projecttocreate->id);
3340 }
3341 }
3342 }
3343 }
3344 } else {
3345 dol_syslog("Project already exists for msgid = ".dol_escape_htmltag($msgid).", so we do not recreate it.");
3346
3347 $operationslog .= '<br>Project already exists for msgid ='.dol_escape_htmltag($msgid);
3348 }
3349 } elseif ($operation['type'] == 'ticket') {
3350 // Create ticket
3351 $tickettocreate = new Ticket($this->db);
3352 if ($ticketalreadyexists == 0) {
3353 if ($thirdpartystatic->id > 0) {
3354 $tickettocreate->socid = $thirdpartystatic->id;
3355 $tickettocreate->fk_soc = $thirdpartystatic->id;
3356 if ($thirdpartyfoundby) {
3357 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Third party found from '.$thirdpartyfoundby);
3358 }
3359 }
3360 if ($contactstatic->id > 0) {
3361 $tickettocreate->contact_id = $contactstatic->id;
3362 if ($contactfoundby) {
3363 $descriptionmeta = dol_concatdesc($descriptionmeta, 'Contact/address found from '.$contactfoundby);
3364 }
3365 }
3366
3367 $description = $descriptiontitle;
3368
3369 $description = dol_concatdesc($description, $descriptionmeta);
3370 $description = dol_concatdesc($description, "-----");
3371 $description = dol_concatdesc($description, $messagetext);
3372
3373 $descriptionfull = $description;
3374 if (!getDolGlobalString('MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER')) {
3375 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3376 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3377 }
3378
3379 $tickettocreate->subject = $subject;
3380 $tickettocreate->message = $description;
3381 $tickettocreate->type_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_TYPE_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_type', 'use_default', 'code', 1)));
3382 $tickettocreate->category_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_CATEGORY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_category', 'use_default', 'code', 1)));
3383 $tickettocreate->severity_code = (getDolGlobalString('MAIN_EMAILCOLLECTOR_TICKET_SEVERITY_CODE', dol_getIdFromCode($this->db, 1, 'c_ticket_severity', 'use_default', 'code', 1)));
3384 $tickettocreate->origin_email = $from;
3385 $tickettocreate->origin_replyto = (!empty($replyto) ? $replyto : null);
3386 $tickettocreate->origin_references = (!empty($headers['References']) ? $headers['References'] : null);
3387 $tickettocreate->fk_user_create = $user->id;
3388 $tickettocreate->datec = dol_now();
3389 $tickettocreate->fk_project = $projectstatic->id;
3390 $tickettocreate->notify_tiers_at_create = getDolGlobalInt('TICKET_CHECK_NOTIFY_THIRDPARTY_AT_CREATION');
3391 $tickettocreate->note_private = $descriptionfull;
3392 $tickettocreate->entity = $conf->entity;
3393 // Fields when action is an email (content should be added into agenda event)
3394 $tickettocreate->email_date = $dateemail;
3395 $tickettocreate->email_msgid = $msgid;
3396 $tickettocreate->email_from = $fromstring;
3397 $tickettocreate->email_sender = $sender;
3398 $tickettocreate->email_to = $to;
3399 $tickettocreate->email_tocc = $sendtocc;
3400 $tickettocreate->email_tobcc = $sendtobcc;
3401 $tickettocreate->email_subject = $subject;
3402 $tickettocreate->errors_to = '';
3403
3404 //$tickettocreate->fk_contact = $contactstatic->id;
3405
3406 $savesocid = $tickettocreate->socid;
3407
3408 // Overwrite values with values extracted from source email.
3409 // This may overwrite any $projecttocreate->xxx properties.
3410 $errorforthisaction = $this->overwritePropertiesOfObject($tickettocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3411
3412 $modele = 'UNDEFINED';
3413 // Set ticket ref if not yet defined
3414 if (empty($tickettocreate->ref)) {
3415 // Get next Ref
3416 $defaultref = '';
3417 $modele = getDolGlobalString('TICKET_ADDON', 'mod_ticket_simple');
3418
3419 // Search template files
3420 $file = '';
3421 $classname = '';
3422 $reldir = '';
3423 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3424 foreach ($dirmodels as $reldir) {
3425 $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
3426 if (file_exists($file)) {
3427 $classname = $modele;
3428 break;
3429 }
3430 }
3431
3432 if ($classname !== '') {
3433 if ($savesocid > 0) {
3434 if ($savesocid != $tickettocreate->socid) {
3435 $errorforactions++;
3436 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');
3437 }
3438 } else {
3439 if ($tickettocreate->socid > 0) {
3440 $thirdpartystatic->fetch($tickettocreate->socid);
3441 }
3442 }
3443
3444 $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
3445 $modModuleToUseForNextValue = new $classname();
3446 '@phan-var-force ModeleNumRefTicket $modModuleToUseForNextValue';
3447 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $tickettocreate);
3448 }
3449 $tickettocreate->ref = $defaultref;
3450 }
3451
3452 if ($errorforthisaction) {
3453 $errorforactions++;
3454 } else {
3455 if (is_numeric($tickettocreate->ref) && $tickettocreate->ref <= 0) {
3456 $errorforactions++;
3457 $this->error = 'Failed to create ticket: Can\'t get a valid value for the field ref with numbering template = '.$modele.', thirdparty id = '.$thirdpartystatic->id;
3458 } else {
3459 // Create ticket
3460 $tickettocreate->context['actionmsg2'] = $langs->trans("ActionAC_EMAIL_IN").' - '.$langs->trans("TICKET_CREATEInDolibarr");
3461 $tickettocreate->context['actionmsg'] = $langs->trans("ActionAC_EMAIL_IN").' - '.$langs->trans("TICKET_CREATEInDolibarr");
3462 //$tickettocreate->email_fields_no_propagate_in_actioncomm = 0;
3463
3464 // Add sender to context array to make sure that confirmation e-mail can be sent by trigger script
3465 $sender_contact = new Contact($this->db);
3466 $sender_contact->fetch(0, null, '', $from);
3467 if (!empty($sender_contact->id)) {
3468 $tickettocreate->context['contact_id'] = $sender_contact->id;
3469 }
3470
3471 $result = $tickettocreate->create($user);
3472 if ($result <= 0) {
3473 $errorforactions++;
3474 $this->error = 'Failed to create ticket: '.$langs->trans($tickettocreate->error);
3475 $this->errors = $tickettocreate->errors;
3476 } else {
3477 if ($attachments) {
3478 $destdir = $conf->ticket->dir_output.'/'.$tickettocreate->ref;
3479 if (!dol_is_dir($destdir)) {
3480 dol_mkdir($destdir);
3481 }
3482 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3483 foreach ($attachments as $attachment) {
3484 // $attachment->save($destdir.'/');
3485 $typeattachment = (string) $attachment->getDisposition();
3486 $filename = $attachment->getName();
3487 $content = $attachment->getContent();
3488 $this->saveAttachment($destdir, $filename, $content);
3489 }
3490 } else {
3491 $getMsg = $this->getmsg($connection, $imapemail, $destdir);
3492 if ($getMsg < 0) {
3493 $this->errors = array_merge($this->errors, [$this->error]);
3494 return $getMsg;
3495 }
3496 }
3497
3498 $operationslog .= '<br>Ticket created with attachments -> id='.dol_escape_htmltag((string) $tickettocreate->id);
3499 } else {
3500 $operationslog .= '<br>Ticket created without attachments -> id='.dol_escape_htmltag((string) $tickettocreate->id);
3501 }
3502 }
3503 }
3504 }
3505 } else {
3506 // Do nothing in action ticket if ticket already exists.
3507 // 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
3508 // already replaced automatically by operation "recordevent".
3509 $operationslog .= '<br>Ticket already exists, so we bypass action "ticket"';
3510 }
3511 } elseif ($operation['type'] == 'candidature') {
3512 // Create candidature
3513 $candidaturetocreate = new RecruitmentCandidature($this->db);
3514
3515 $alreadycreated = $candidaturetocreate->fetch(0, '', $msgid);
3516 if ($alreadycreated == 0) {
3517 $description = $descriptiontitle;
3518 $description = dol_concatdesc($description, "-----");
3519 $description = dol_concatdesc($description, $descriptionmeta);
3520 $description = dol_concatdesc($description, "-----");
3521 $description = dol_concatdesc($description, $messagetext);
3522
3523 $descriptionfull = $description;
3524 $descriptionfull = dol_concatdesc($descriptionfull, "----- Header");
3525 $descriptionfull = dol_concatdesc($descriptionfull, $header);
3526
3527 $candidaturetocreate->subject = $subject;
3528 $candidaturetocreate->message = $description;
3529 $candidaturetocreate->type_code = 0;
3530 $candidaturetocreate->category_code = null;
3531 $candidaturetocreate->severity_code = null;
3532 $candidaturetocreate->email = $from;
3533 //$candidaturetocreate->lastname = $langs->trans("Anonymous").' - '.$from;
3534 $candidaturetocreate->fk_user_creat = $user->id;
3535 $candidaturetocreate->date_creation = dol_now();
3536 $candidaturetocreate->fk_project = $projectstatic->id;
3537 $candidaturetocreate->description = $description;
3538 $candidaturetocreate->note_private = $descriptionfull;
3539 $candidaturetocreate->entity = $conf->entity;
3540 $candidaturetocreate->email_msgid = $msgid;
3541 $candidaturetocreate->email_date = $dateemail; // date of email
3542 $candidaturetocreate->status = $candidaturetocreate::STATUS_DRAFT;
3543 //$candidaturetocreate->fk_contact = $contactstatic->id;
3544
3545 // Overwrite values with values extracted from source email.
3546 // This may overwrite any $projecttocreate->xxx properties.
3547 $errorforthisaction = $this->overwritePropertiesOfObject($candidaturetocreate, $operation['actionparam'], $messagetext, $subject, $header, $operationslog);
3548
3549 // Set candidature ref if not yet defined
3550 /*if (empty($candidaturetocreate->ref)) We do not need this because we create object in draft status
3551 {
3552 // Get next Ref
3553 $defaultref = '';
3554 $modele = empty($conf->global->CANDIDATURE_ADDON) ? 'mod_candidature_simple' : $conf->global->CANDIDATURE_ADDON;
3555
3556 // Search template files
3557 $file = ''; $classname = ''; $filefound = 0; $reldir = '';
3558 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3559 foreach ($dirmodels as $reldir)
3560 {
3561 $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
3562 if (file_exists($file)) {
3563 $filefound = 1;
3564 $classname = $modele;
3565 break;
3566 }
3567 }
3568
3569 if ($filefound) {
3570 if ($savesocid > 0) {
3571 if ($savesocid != $candidaturetocreate->socid) {
3572 $errorforactions++;
3573 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');
3574 }
3575 } else {
3576 if ($candidaturetocreate->socid > 0)
3577 {
3578 $thirdpartystatic->fetch($candidaturetocreate->socid);
3579 }
3580 }
3581
3582 $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
3583 $modModuleToUseForNextValue = new $classname;
3584 '@phan-var-force ModeleNumRefTicket $modModuleToUseForNextValue';
3585 $defaultref = $modModuleToUseForNextValue->getNextValue(($thirdpartystatic->id > 0 ? $thirdpartystatic : null), $tickettocreate);
3586 }
3587 $candidaturetocreate->ref = $defaultref;
3588 }*/
3589
3590 if ($errorforthisaction) {
3591 $errorforactions++;
3592 } else {
3593 // Create project
3594 $result = $candidaturetocreate->create($user);
3595 if ($result <= 0) {
3596 $errorforactions++;
3597 $this->error = 'Failed to create candidature: '.implode(', ', $candidaturetocreate->errors);
3598 $this->errors = $candidaturetocreate->errors;
3599 }
3600
3601 $operationslog .= '<br>Candidature created without attachments -> id='.dol_escape_htmltag((string) $candidaturetocreate->id);
3602 }
3603 }
3604 } elseif (substr($operation['type'], 0, 4) == 'hook') {
3605 // Create event specific on hook
3606 // this code action is hook..... for support this call
3607 if (!is_object($hookmanager)) {
3608 include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
3609 $hookmanager = new HookManager($this->db);
3610 }
3611 $hookmanager->initHooks(['emailcolector']);
3612
3613 $parameters = array(
3614 'connection' => $connection,
3615 'imapemail' => $imapemail,
3616 'overview' => $overview,
3617
3618 'from' => $from,
3619 'fromtext' => $fromtext,
3620
3621 'actionparam' => $operation['actionparam'],
3622
3623 'thirdpartyid' => $thirdpartyid,
3624 'objectid' => $objectid,
3625 'objectemail' => $objectemail,
3626
3627 'messagetext' => $messagetext,
3628 'subject' => $subject,
3629 'header' => $header,
3630 'attachments' => $attachments,
3631 );
3632 $reshook = $hookmanager->executeHooks('doCollectImapOneCollector', $parameters, $this, $operation['type']);
3633
3634 if ($reshook < 0) {
3635 $errorforthisaction++;
3636 $this->error = $hookmanager->resPrint;
3637 }
3638 if ($errorforthisaction) {
3639 $errorforactions++;
3640 $operationslog .= '<br>Hook doCollectImapOneCollector executed with error';
3641 } else {
3642 $operationslog .= '<br>Hook doCollectImapOneCollector executed without error';
3643 }
3644 }
3645
3646 if (!$errorforactions) {
3647 $nbactiondoneforemail++;
3648 }
3649 }
3650 }
3651
3652 // Error for email or not ?
3653 if (!$errorforactions) {
3654 if (!empty($targetdir)) {
3655 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3656 // Move mail using PHP-IMAP
3657 dol_syslog("EmailCollector::doCollectOneCollector move message ".($imapemail->getHeader()->get('subject'))." to ".$targetdir, LOG_DEBUG);
3658 $operationslog .= '<br>Move mail '.($this->uidAsString($imapemail)).' - '.$msgid.' - '.$imapemail->getHeader()->get('subject').' to '.$targetdir;
3659
3660 $arrayofemailtodelete[$this->uidAsString($imapemail)] = $imapemail;
3661 // Note: Real move is done later using $arrayofemailtodelete
3662 } else {
3663 dol_syslog("EmailCollector::doCollectOneCollector move message ".($this->uidAsString($imapemail))." to ".$connectstringtarget, LOG_DEBUG);
3664 $operationslog .= '<br>Move mail '.($this->uidAsString($imapemail)).' - '.$msgid;
3665
3666 $arrayofemailtodelete[$imapemail] = $msgid;
3667 // Note: Real move is done later using $arrayofemailtodelete
3668 }
3669 } else {
3670 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3671 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);
3672 } else {
3673 dol_syslog("EmailCollector::doCollectOneCollector message ".($this->uidAsString($imapemail))." to ".$connectstringtarget." was set to read", LOG_DEBUG);
3674 }
3675 }
3676 } else {
3677 $errorforemail++;
3678 }
3679
3680
3681 unset($objectemail);
3682 unset($projectstatic);
3683 unset($thirdpartystatic);
3684 unset($contactstatic);
3685
3686 $nbemailprocessed++;
3687
3688 if (!$errorforemail) {
3689 $nbactiondone += $nbactiondoneforemail;
3690 $nbemailok++;
3691
3692 if (empty($mode)) {
3693 $this->db->commit();
3694 } else {
3695 $this->db->rollback();
3696 }
3697
3698 // Stop the loop to process email if we reach maximum collected per collect
3699 if ($this->maxemailpercollect > 0 && $nbemailok >= $this->maxemailpercollect) {
3700 dol_syslog("EmailCollect::doCollectOneCollector We reach maximum of ".$nbemailok." collected with success, so we stop this collector now.");
3701 $datelastok = strtotime($headers['Date']); // Set datetime
3702 break;
3703 }
3704 } else {
3705 $error++;
3706
3707 $this->db->rollback();
3708 }
3709 } catch (Throwable $e) {
3710 // Skip this 'poison' email and keep processing the rest. Without this, an exception aborts the
3711 // whole foreach mid-run, the processing lock stays set and the collector is stuck until cleared.
3712 try {
3713 $this->db->rollback();
3714 } catch (Throwable $eInner) {
3715 dol_syslog("EmailCollector loop catch rollback failed: ".$eInner->getMessage(), LOG_ERR);
3716 }
3717 dol_syslog("EmailCollector loop caught exception on one email, skipping: ".$e->getMessage(), LOG_WARNING);
3718 $operationslog .= '<br>** Exception while processing email #'.dol_escape_htmltag((string) $iforemailloop).' - '.dol_escape_htmltag($e->getMessage()).' (skipped)';
3719 }
3720 }
3721
3722 $output = $langs->trans('XEmailsDoneYActionsDone', $nbemailprocessed, $nbemailok, $nbactiondone);
3723
3724 dol_syslog("End of loop on emails", LOG_INFO, -1);
3725 } else {
3726 $langs->load("admin");
3727 $output = $langs->trans('NoNewEmailToProcess');
3728 $output .= ' (defaultlang='.$langs->defaultlang.')';
3729 }
3730
3731 // Disconnect
3732 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3733 // We sort to move/delete array with the more recent first (with higher number) so renumbering does not affect number of others to delete
3734 krsort($arrayofemailtodelete, SORT_NUMERIC);
3735
3736 foreach ($arrayofemailtodelete as $imapemailnum => $imapemail) {
3737 dol_syslog("EmailCollect::doCollectOneCollector delete email ".$imapemailnum);
3738
3739 $operationslog .= "<br> move email ".$imapemailnum.($mode > 0 ? ' (test)' : '');
3740
3741 if (empty($mode) && empty($error)) {
3742 $tmptargetdir = $targetdir;
3743 if (!getDolGlobalString('MAIL_DISABLE_UTF7_ENCODE_OF_DIR')) {
3744 $tmptargetdir = $this->getEncodedUtf7($targetdir);
3745 }
3746
3747 $result = 0;
3748 try {
3749 $result = $imapemail->move($tmptargetdir, false);
3750 } catch (Exception $e) {
3751 // Nothing to do. $result will remain 0
3752 $operationslog .= '<br>Exception !!!! '.$e->getMessage();
3753 }
3754 if (empty($result)) {
3755 dol_syslog("Failed to move email into target directory ".$targetdir);
3756 $operationslog .= '<br>Failed to move email into target directory '.$targetdir;
3757 $error++;
3758 }
3759 }
3760 }
3761
3762 if (empty($mode) && empty($error)) {
3763 dol_syslog("Expunge", LOG_DEBUG);
3764 $operationslog .= "<br>Expunge";
3765
3766 $client->expunge(); // To validate all moves
3767 }
3768
3769 $client->disconnect();
3770 } else {
3771 foreach ($arrayofemailtodelete as $imapemail => $msgid) {
3772 dol_syslog("EmailCollect::doCollectOneCollector delete email ".$imapemail." ".$msgid);
3773
3774 $operationslog .= "<br> delete email ".$imapemail." ".$msgid.($mode > 0 ? ' (test)' : '');
3775
3776 if (empty($mode) && empty($error)) {
3777 $res = imap_mail_move($connection, $imapemail, $targetdir, CP_UID);
3778 if (!$res) {
3779 // $errorforemail++; // Not in loop, not needed, not initialised
3780 $this->error = imap_last_error();
3781 $this->errors[] = $this->error;
3782
3783 $operationslog .= '<br>Error in move '.$this->error;
3784
3785 dol_syslog(imap_last_error());
3786 }
3787 }
3788 }
3789
3790 if (empty($mode) && empty($error)) {
3791 dol_syslog("Expunge", LOG_DEBUG);
3792 $operationslog .= "<br>Expunge";
3793
3794 imap_expunge($connection); // To validate all moves
3795 }
3796 imap_close($connection);
3797 }
3798
3799 $this->datelastresult = $now;
3800 $this->lastresult = $output;
3801 if (getDolGlobalString('MAIN_IMAP_USE_PHPIMAP')) {
3802 $this->debuginfo .= 'IMAP search array used : '.$search;
3803 } else {
3804 $this->debuginfo .= 'IMAP search string used : '.$search;
3805 }
3806 if ($searchhead) {
3807 $this->debuginfo .= '<br>Then search string into email header : '.dol_escape_htmltag($searchhead);
3808 }
3809 if ($operationslog) {
3810 $this->debuginfo .= $operationslog;
3811 }
3812
3813 if (empty($error) && empty($mode)) {
3814 $this->datelastok = $datelastok;
3815 }
3816
3817 if (!empty($this->errors)) {
3818 $this->lastresult .= "<br>".implode("<br>", $this->errors);
3819 }
3820 $this->codelastresult = ($error ? 'KO' : 'OK');
3821
3822 if (empty($mode)) {
3823 $this->update($user);
3824 }
3825
3826 dol_syslog("EmailCollector::doCollectOneCollector end error=".$error, LOG_INFO);
3827
3828 return $error ? -1 : 1;
3829 }
3830
3831
3832
3833 // Loop to get part html and plain. Code found on PHP imap_fetchstructure documentation
3834
3843 private function getmsg($mbox, $mid, $destdir = ''): int
3844 {
3845 // input $mbox = IMAP stream, $mid = message id
3846 // output all the following:
3847 global $charset, $htmlmsg, $plainmsg, $attachments;
3848 $htmlmsg = $plainmsg = $charset = '';
3849 $attachments = array();
3850
3851 // HEADER
3852 //$h = imap_header($mbox,$mid);
3853 // add code here to get date, from, to, cc, subject...
3854
3855 // BODY
3856 $s = imap_fetchstructure($mbox, $mid, FT_UID);
3857 if ($s === false) {
3858 $this->errors = array_merge($this->errors, [imap_last_error()]);
3859 return -1;
3860 }
3861
3862 if (empty($s->parts)) {
3863 // simple
3864 $this->getpart($mbox, $mid, $s, '0'); // pass '0' as part-number
3865 } else {
3866 // multipart: cycle through each part
3867 foreach ($s->parts as $partno0 => $p) {
3868 $this->getpart($mbox, $mid, $p, (string) ($partno0 + 1), $destdir);
3869 }
3870 }
3871
3872 return 1;
3873 }
3874
3875 /* partno string
3876 0 multipart/mixed
3877 1 multipart/alternative
3878 1.1 text/plain
3879 1.2 text/html
3880 2 message/rfc822
3881 2 multipart/mixed
3882 2.1 multipart/alternative
3883 2.1.1 text/plain
3884 2.1.2 text/html
3885 2.2 message/rfc822
3886 2.2 multipart/alternative
3887 2.2.1 text/plain
3888 2.2.2 text/html
3889 */
3890
3901 private function getpart($mbox, $mid, $p, $partno, $destdir = '')
3902 {
3903 // $partno = '1', '2', '2.1', '2.1.3', etc for multipart, 0 if simple
3904 global $htmlmsg, $plainmsg, $charset, $attachments;
3905
3906 // DECODE DATA
3907 $data = ($partno) ?
3908 imap_fetchbody($mbox, $mid, $partno, FT_UID) : // multipart
3909 imap_body($mbox, $mid, FT_UID); // simple
3910 // Any part may be encoded, even plain text messages, so check everything.
3911 if ($p->encoding == 4) {
3912 $data = quoted_printable_decode($data);
3913 } elseif ($p->encoding == 3) {
3914 $data = base64_decode($data);
3915 }
3916
3917 // PARAMETERS
3918 // get all parameters, like charset, filenames of attachments, etc.
3919 $params = array();
3920 if ($p->parameters) {
3921 foreach ($p->parameters as $x) {
3922 $params[strtolower($x->attribute)] = $x->value;
3923 }
3924 }
3925 if (!empty($p->dparameters)) {
3926 foreach ($p->dparameters as $x) {
3927 $params[strtolower($x->attribute)] = $x->value;
3928 }
3929 }
3930 '@phan-var-force array{filename?:string,name?:string,charset?:string} $params';
3931
3932 // ATTACHMENT
3933 // Any part with a filename is an attachment,
3934 // so an attached text file (type 0) is not mistaken as the message.
3935 if (!empty($params['filename']) || !empty($params['name'])) {
3936 // filename may be given as 'Filename' or 'Name' or both
3937 $filename = $params['filename'] ?? $params['name'];
3938 // filename may be encoded, so see imap_mime_header_decode()
3939 $attachments[$filename] = $data; // this is a problem if two files have same name
3940
3941 if (strlen($destdir)) {
3942 if (substr($destdir, -1) != '/') {
3943 $destdir .= '/';
3944 }
3945
3946 // Get file name (with extension)
3947 $file_name_complete = $filename;
3948 $destination = $destdir.$file_name_complete;
3949
3950 // Extract file extension
3951 $extension = pathinfo($file_name_complete, PATHINFO_EXTENSION);
3952
3953 // Extract file name without extension
3954 $file_name = pathinfo($file_name_complete, PATHINFO_FILENAME);
3955
3956 // Save an original file name variable to track while renaming if file already exists
3957 $file_name_original = $file_name;
3958
3959 // Increment file name by 1
3960 $num = 1;
3961
3966 while (file_exists($destdir.$file_name.".".$extension)) {
3967 $file_name = $file_name_original . ' (' . $num . ')';
3968 $file_name_complete = $file_name . "." . $extension;
3969 $destination = $destdir.$file_name_complete;
3970 $num++;
3971 }
3972
3973 $destination = dol_sanitizePathName($destination);
3974
3975 file_put_contents($destination, $data);
3976 dolChmod($destination);
3977 }
3978 }
3979
3980 // TEXT
3981 if ($p->type == 0 && $data) {
3982 if (!empty($params['charset'])) {
3983 $data = $this->convertStringEncoding($data, $params['charset']);
3984 }
3985 // Messages may be split in different parts because of inline attachments,
3986 // so append parts together with blank row.
3987 if (strtolower($p->subtype) == 'plain') {
3988 $plainmsg .= trim($data)."\n\n";
3989 } else {
3990 $htmlmsg .= $data."<br><br>";
3991 }
3992 $charset = $params['charset']; // assume all parts are same charset
3993 } elseif ($p->type == 2 && $data) {
3994 // EMBEDDED MESSAGE
3995 // Many bounce notifications embed the original message as type 2,
3996 // but AOL uses type 1 (multipart), which is not handled here.
3997 // There are no PHP functions to parse embedded messages,
3998 // so this just appends the raw source to the main message.
3999 if (!empty($params['charset'])) {
4000 $data = $this->convertStringEncoding($data, $params['charset']);
4001 }
4002 $plainmsg .= $data."\n\n";
4003 }
4004
4005 // SUBPART RECURSION
4006 if (!empty($p->parts)) {
4007 foreach ($p->parts as $partno0 => $p2) {
4008 $this->getpart($mbox, $mid, $p2, $partno.'.'.($partno0 + 1), $destdir); // 1.2, 1.2.1, etc.
4009 }
4010 }
4011 }
4012
4022 protected function convertStringEncoding($string, $fromEncoding, $toEncoding = 'UTF-8')
4023 {
4024 if (!$string || $fromEncoding == $toEncoding) {
4025 return $string;
4026 }
4027 $convertedString = function_exists('iconv') ? @iconv($fromEncoding, $toEncoding.'//IGNORE', $string) : null;
4028 if (!$convertedString && extension_loaded('mbstring')) {
4029 $convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding);
4030 }
4031 if (!$convertedString) {
4032 throw new Exception('Mime string encoding conversion failed');
4033 }
4034 return $convertedString;
4035 }
4036
4047 protected function decodeSMTPSubject($subject)
4048 {
4049 // Decode $overview[0]->subject according to RFC2047
4050 // Can use also imap_mime_header_decode($str)
4051 // Can use also mb_decode_mimeheader($str)
4052 // Can use also iconv_mime_decode($str, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8')
4053 if (function_exists('imap_mime_header_decode') && function_exists('iconv_mime_decode')) {
4054 $elements = imap_mime_header_decode($subject);
4055 $newstring = '';
4056 if (!empty($elements)) {
4057 $num = count($elements);
4058 for ($i = 0; $i < $num; $i++) {
4059 $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));
4060 $newstring .= $stringinutf8;
4061 }
4062 $subject = $newstring;
4063 }
4064 } elseif (!function_exists('mb_decode_mimeheader')) {
4065 $subject = mb_decode_mimeheader($subject);
4066 } elseif (function_exists('iconv_mime_decode')) {
4067 $subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
4068 }
4069
4070 return $subject;
4071 }
4072
4081 private function saveAttachment($destdir, $filename, $content)
4082 {
4083 require_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4084
4085 $tmparraysize = getDefaultImageSizes();
4086 $maxwidthsmall = $tmparraysize['maxwidthsmall'];
4087 $maxheightsmall = $tmparraysize['maxheightsmall'];
4088 $maxwidthmini = $tmparraysize['maxwidthmini'];
4089 $maxheightmini = $tmparraysize['maxheightmini'];
4090 $quality = $tmparraysize['quality'];
4091
4092 file_put_contents($destdir.'/'.$filename, $content);
4093 dolChmod($destdir.'/'.$filename);
4094
4095 if (image_format_supported($filename) == 1) {
4096 // Create thumbs
4097 vignette($destdir.'/'.$filename, $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs");
4098 // Create mini thumbs for image (Ratio is near 16/9)
4099 vignette($destdir.'/'.$filename, $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs");
4100 }
4101
4102 addFileIntoDatabaseIndex($destdir, $filename);
4103 }
4104
4111 protected function uidAsString($imapemail)
4112 {
4113 if (is_object($imapemail)) {
4114 return $imapemail->getAttributes()["uid"];
4115 } else {
4116 return (string) $imapemail;
4117 }
4118 }
4119}
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.