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