EmailCollector::convertStringEncoding()   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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