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