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