Mail::_getStatus()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 3
nop 2
dl 0
loc 17
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware - Mail - worker class
4
 *
5
 * @link http://www.egroupware.org
6
 * @package api
7
 * @subpackage amil
8
 * @author Stylite AG [[email protected]]
9
 * @copyright (c) 2013-2016 by Stylite AG <info-AT-stylite.de>
10
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
11
 * @version $Id$
12
 */
13
14
namespace EGroupware\Api;
15
16
use Horde_Imap_Client;
17
use Horde_Imap_Client_Ids;
18
use Horde_Imap_Client_Fetch_Query;
19
use Horde_Imap_Client_Data_Fetch;
20
use Horde_Mime_Part;
21
use Horde_Imap_Client_Search_Query;
22
use Horde_Idna;
23
use Horde_Imap_Client_DateTime;
24
use Horde_Mime_Headers;
25
use Horde_Compress;
26
use Horde_Mime_Magic;
27
use Horde_Mail_Rfc822;
28
use Horde_Mail_Rfc822_List;
29
use Horde_Mime_Mdn;
30
use Horde_Translation;
31
use Horde_Translation_Handler_Gettext;
32
use EGroupware\Api;
33
34
use tidy;
35
36
/**
37
 * Mail worker class
38
 *  -provides backend functionality for all classes in Mail
39
 *  -provides classes that may be used by other apps too
40
 *
41
 * @link https://github.com/horde/horde/blob/master/imp/lib/Contents.php
42
 */
43
class Mail
44
{
45
	/**
46
	 * the current selected user profile
47
	 * @var int
48
	 */
49
	var $profileID = 0;
50
51
	/**
52
	 * delimiter - used to separate acc_id from mailbox / folder-tree-structure
53
	 *
54
	 * @var string
55
	 */
56
	const DELIMITER = '::';
57
58
	/**
59
	 * the current display char set
60
	 * @var string
61
	 */
62
	static $displayCharset;
63
	static $activeFolderCache;
64
	static $folderStatusCache;
65
	static $supportsORinQuery;
66
67
	/**
68
	 * Active preferences
69
	 *
70
	 * @var array
71
	 */
72
	var $mailPreferences;
73
74
	/**
75
	 * active html Options
76
	 *
77
	 * @var array
78
	 */
79
	var $htmlOptions;
80
81
	/**
82
	 * Active mimeType
83
	 *
84
	 * @var string
85
	 */
86
	var $activeMimeType;
87
88
	/**
89
	 * Active incomming (IMAP) Server Object
90
	 *
91
	 * @var Api\Mail\Imap
92
	 */
93
	var $icServer;
94
95
	/**
96
	 * Active outgoing (smtp) Server Object
97
	 *
98
	 * @var Api\Mail\Smtp
99
	 */
100
	var $ogServer;
101
102
	/**
103
	 * errorMessage
104
	 *
105
	 * @var string $errorMessage
106
	 */
107
	var $errorMessage;
108
109
	/**
110
	 * switch to enable debug; sometimes debuging is quite handy, to see things. check with the error log to see results
111
	 * @var boolean
112
	 */
113
	static $debug = false; //true;
114
	static $debugTimes = false; //true;
115
116
	/**
117
	 * static used to hold the mail Config values
118
	 * @array
119
	 */
120
	static $mailConfig;
121
122
	/**
123
	 * static used to configure tidy - if tidy is loadable, this config is used with tidy to straighten out html, instead of using purifiers tidy mode
124
	 *
125
	 * @array
126
	 */
127
	static $tidy_config = array('clean'=>false,'output-html'=>true,'join-classes'=>true,'join-styles'=>true,'show-body-only'=>"auto",'word-2000'=>true,'wrap'=>0);
128
129
	/**
130
	 * static used to configure htmLawed, for use with emails
131
	 *
132
	 * @array
133
	 */
134
	static $htmLawed_config = array('comment'=>1, //remove comments
135
		'make_tag_strict' => 3, // 3 is a new own config value, to indicate that transformation is to be performed, but don't transform font as size transformation of numeric sizes to keywords alters the intended result too much
136
		'keep_bad'=>2, //remove tags but keep element content (4 and 6 keep element content only if text (pcdata) is valid in parent element as per specs, this may lead to textloss if balance is switched on)
137
		// we switch the balance off because of some broken html mails contents get removed like (td in table), and let browser deal with it
138
		'balance'=>0,//turn off tag-balancing (config['balance']=>0). That will not introduce any security risk; only standards-compliant tag nesting check/filtering will be turned off (basic tag-balance will remain; i.e., there won't be any unclosed tag, etc., after filtering)
139
		'direct_list_nest' => 1,
140
		'allow_for_inline' => array('table','div','li','p'),//block elements allowed for nesting when only inline is allowed; Example span does not allow block elements as table; table is the only element tested so far
141
		// tidy eats away even some wanted whitespace, so we switch it off;
142
		// we used it for its compacting and beautifying capabilities, which resulted in better html for further processing
143
		'tidy'=>0,
144
		'elements' => "* -script -meta -object",
145
		'deny_attribute' => 'on*',
146
		'schemes'=>'href: file, ftp, http, https, mailto, phone, tel; src: cid, data, file, ftp, http, https; *:file, http, https, cid, src',
147
		'hook_tag' =>"hl_email_tag_transform",
148
	);
149
150
	/**
151
	 * static used define abbrevations for common access rights
152
	 *
153
	 * @array
154
	 */
155
	static $aclShortCuts = array('' => array('label'=>'none','title'=>'The user has no rights whatsoever.'),
156
		'lrs'		=> array('label'=>'readable','title'=>'Allows a user to read the contents of the mailbox.'),
157
		'lprs'		=> array('label'=>'post','title'=>'Allows a user to read the mailbox and post to it through the delivery system by sending mail to the submission address of the mailbox.'),
158
		'ilprs'		=> array('label'=>'append','title'=>'Allows a user to read the mailbox and append messages to it, either via IMAP or through the delivery system.'),
159
		'cdilprsw'	=> array('label'=>'write','title'=>'Allows a user to read the maibox, post to it, append messages to it, and delete messages or the mailbox itself. The only right not given is the right to change the ACL of the mailbox.'),
160
		'acdilprsw'	=> array('label'=>'all','title'=>'The user has all possible rights on the mailbox. This is usually granted to users only on the mailboxes they own.'),
161
		'custom'	=> array('label'=>'custom','title'=>'User defined combination of rights for the ACL'),
162
	);
163
164
	/**
165
	 * Folders that get automatic created AND get translated to the users language
166
	 * their creation is also controlled by users mailpreferences. if set to none / dont use folder
167
	 * the folder will not be automatically created. This is controlled in Mail->getFolderObjects
168
	 * so changing names here, must include a change of keywords there as well. Since these
169
	 * foldernames are subject to translation, keep that in mind too, if you change names here.
170
	 * lang('Drafts'), lang('Templates'), lang('Sent'), lang('Trash'), lang('Junk'), lang('Outbox')
171
	 * ActiveSync:
172
	 *  Outbox is needed by Nokia Clients to be able to send Mails
173
	 * @var array
174
	 */
175
	static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox');
176
177
	/**
178
	 * Array to cache the specialUseFolders, if existing
179
	 * @var array
180
	 */
181
	static $specialUseFolders;
182
183
	/**
184
	 * Hold instances by profileID for getInstance() singleton
185
	 *
186
	 * @var array
187
	 */
188
	private static $instances = array();
189
	private static $profileDefunct = array();
190
191
	/**
192
	 * Singleton for Mail
193
	 *
194
	 * @param boolean $_restoreSession = true
195
	 * @param int $_profileID = 0
196
	 * @param boolean $_validate = true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile
197
	 *                                  not matching the input profileID, if we can not find a profile matching the given ID
198
	 * @param mixed boolean/object $_icServerObject - if object, return instance with object set as icServer
0 ignored issues
show
Documentation Bug introduced by
The doc comment boolean/object at position 0 could not be parsed: Unknown type name 'boolean/object' at position 0 in boolean/object.
Loading history...
199
	 *												  immediately, if boolean === true use oldImapServer in constructor
200
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
201
	 * @return Mail
202
	 */
203
	public static function getInstance($_restoreSession=true, &$_profileID=0, $_validate=true, $_oldImapServerObject=false, $_reuseCache=null)
204
	{
205
		//$_restoreSession=false;
206
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
207
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.'/'.Mail\Account::get_default_acc_id().' for user:'.$GLOBALS['egw_info']['user']['account_lid'].' called from:'.function_backtrace());
208
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_oldImapServerObject));
209
		self::$profileDefunct = Cache::getCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),5*1);
210
		if (isset(self::$profileDefunct[$_profileID]) && strlen(self::$profileDefunct[$_profileID]))
211
		{
212
			throw new Exception(__METHOD__." failed to instanciate Mail for Profile #$_profileID Reason:".self::$profileDefunct[$_profileID]);
213
		}
214
		if ($_oldImapServerObject instanceof Mail\Imap)
215
		{
216
			if (!is_object(self::$instances[$_profileID]))
217
			{
218
				self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
219
			}
220
			self::$instances[$_profileID]->icServer = $_oldImapServerObject;
221
			self::$instances[$_profileID]->accountid= $_oldImapServerObject->ImapServerId;
222
			self::$instances[$_profileID]->profileID= $_oldImapServerObject->ImapServerId;
223
			self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
224
			self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
225
			return self::$instances[$_profileID];
226
		}
227
		if ($_profileID == 0)
228
		{
229
			if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
230
			{
231
				$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
232
			}
233
			else
234
			{
235
				$profileID = Mail\Account::get_default_acc_id();
236
			}
237
			if ($profileID!=$_profileID) $_restoreSession==false;
238
			$_profileID=$profileID;
239
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace());
240
		}
241
		// no validation or restoreSession for old ImapServer Object, just fetch it and return it
242
		if ($_oldImapServerObject===true)
243
		{
244
			return new Mail('utf-8',false,$_profileID,true,$_reuseCache);
245
		}
246
		if ($_profileID != 0 && $_validate)
247
		{
248
			$profileID = self::validateProfileID($_profileID);
249
			if ($profileID != $_profileID)
250
			{
251
				if (self::$debug)
252
				{
253
					error_log(__METHOD__.' ('.__LINE__.') '.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.');
254
					error_log(__METHOD__.' ('.__LINE__.') '.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);
255
				}
256
				$_profileID = $profileID;
257
				//$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
258
				// save prefs
259
				//$GLOBALS['egw']->preferences->save_repository(true);
260
			}
261
			//Cache::setSession('mail','activeProfileID',$_profileID);
262
		}
263
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
264
		if ($_profileID && (!isset(self::$instances[$_profileID]) || $_restoreSession===false))
265
		{
266
			self::$instances[$_profileID] = new Mail('utf-8',$_restoreSession,$_profileID,false,$_reuseCache);
267
		}
268
		else
269
		{
270
			//refresh objects
271
			try
272
			{
273
				self::$instances[$_profileID]->icServer = Mail\Account::read($_profileID)->imapServer();
274
				self::$instances[$_profileID]->ogServer = Mail\Account::read($_profileID)->smtpServer();
275
				// TODO: merge mailprefs into userprefs, for easy treatment
276
				self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
277
				self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
278
			} catch (\Exception $e)
279
			{
280
				$newprofileID = Mail\Account::get_default_acc_id();
281
				// try loading the default profile for the user
282
				error_log(__METHOD__.' ('.__LINE__.') '." Loading the Profile for ProfileID ".$_profileID.' failed for icServer; '.$e->getMessage().' Trigger new instance for Default-Profile '.$newprofileID.'. called from:'.function_backtrace());
283
				if ($newprofileID)
284
				{
285
					self::$instances[$newprofileID] = new Mail('utf-8',false,$newprofileID,false,$_reuseCache);
286
					$_profileID = $newprofileID;
287
				}
288
				else
289
				{
290
					throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_profileID with error:".$e->getMessage().($e->details?', '.$e->details:''));
291
				}
292
			}
293
			self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );
294
		}
295
		self::$instances[$_profileID]->profileID = $_profileID;
296
		if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new Horde_Idna;
297
		//if ($_profileID==0); error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID);
298
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
299
		return self::$instances[$_profileID];
300
	}
301
302
	/**
303
	 * This method tries to fix alias address lacking domain part
304
	 * by trying to add domain part extracted from given reference address
305
	 *
306
	 * @param string $refrence email address to be used for domain extraction
307
	 * @param string $address alias address
308
	 *
309
	 * @return string returns alias address with appended default domain
310
	 */
311
	public static function fixInvalidAliasAddress($refrence, $address)
312
	{
313
		$parts = explode('@', $refrence);
314
		if (!strpos($address,'@') && !empty($parts[1])) $address .= '@'.$parts[1];
315
		return $address;
316
	}
317
318
	/**
319
	 * store given ProfileID to Session and pref
320
	 *
321
	 * @param int $_profileID = 0
322
	 * @param boolean $_testConnection = 0
323
	 * @return mixed $_profileID or false on failed ConnectionTest
324
	 */
325
	public static function storeActiveProfileIDToPref($_icServerObject, $_profileID=0, $_testConnection=true)
326
	{
327
		if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
328
		{
329
			$oldProfileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
330
		}
331
		if ($_testConnection)
332
		{
333
			try
334
			{
335
				$_icServerObject->getCurrentMailbox();
336
			}
337
			catch (\Exception $e)
338
			{
339
				if ($_profileID != Mail\Account::get_default_acc_id()) $_profileID = Mail\Account::get_default_acc_id();
0 ignored issues
show
Unused Code introduced by
The assignment to $_profileID is dead and can be removed.
Loading history...
340
				error_log(__METHOD__.__LINE__.' '.$e->getMessage());
341
				return false;
342
			}
343
		}
344
		if ($oldProfileID != $_profileID)
345
		{
346
			if ($oldProfileID && $_profileID==0) $_profileID = $oldProfileID;
347
			$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
348
			// save prefs
349
			$GLOBALS['egw']->preferences->save_repository(true);
350
			$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $_profileID;
351
			Cache::setSession('mail','activeProfileID',$_profileID);
352
		}
353
		return $_profileID;
354
	}
355
356
	/**
357
	 * Validate given account acc_id to make sure account is valid for current user
358
	 *
359
	 * Validation checks:
360
	 * - non-empty imap-host
361
	 * - non-empty imap-username
362
	 *
363
	 * @param int $_acc_id = 0
364
	 * @return int validated acc_id -> either acc_id given, or first valid one
365
	 */
366
	public static function validateProfileID($_acc_id=0)
367
	{
368
		if ($_acc_id)
369
		{
370
			try {
371
				$account = Mail\Account::read($_acc_id);
372
				if ($account->is_imap())
373
				{
374
					return $_acc_id;
375
				}
376
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT valid, no imap-host!");
377
			}
378
			catch (\Exception $e) {
379
				unset($e);
380
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT found!");
381
			}
382
		}
383
		// no account specified or specified account not found or not valid
384
		// --> search existing account for first valid one and return that
385
		foreach(Mail\Account::search($only_current_user=true, 'acc_imap_host') as $acc_id => $imap_host)
386
		{
387
			if (!empty($imap_host) && ($account = Mail\Account::read($acc_id)) && $account->is_imap())
388
			{
389
				if (self::$debug && $_acc_id) error_log(__METHOD__."($_acc_id) using $acc_id instead");
390
				return $acc_id;
391
			}
392
		}
393
		if (self::$debug) error_log(__METHOD__."($_acc_id) NO valid account found!");
394
		return 0;
395
	}
396
397
398
	/**
399
	 * Private constructor, use Mail::getInstance() instead
400
	 *
401
	 * @param string $_displayCharset = 'utf-8'
402
	 * @param boolean $_restoreSession = true
403
	 * @param int $_profileID = 0 if not nummeric, we assume we only want an empty class object
404
	 * @param boolean $_oldImapServerObject = false
405
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
406
	 */
407
	private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0, $_oldImapServerObject=false, $_reuseCache=null)
408
	{
409
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
410
		if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset;
411
		// not nummeric, we assume we only want an empty class object
412
		if (!is_numeric($_profileID)) return true;
0 ignored issues
show
introduced by
The condition is_numeric($_profileID) is always true.
Loading history...
413
		if ($_restoreSession)
414
		{
415
			//error_log(__METHOD__." Session restore ".function_backtrace());
416
			$this->restoreSessionData();
417
			$lv_mailbox = $this->sessionData['mailbox'];
0 ignored issues
show
Unused Code introduced by
The assignment to $lv_mailbox is dead and can be removed.
Loading history...
418
			$firstMessage = $this->sessionData['previewMessage'];
0 ignored issues
show
Unused Code introduced by
The assignment to $firstMessage is dead and can be removed.
Loading history...
419
		}
420
		else
421
		{
422
			$this->restoreSessionData();
423
			$lv_mailbox = $this->sessionData['mailbox'];
424
			$firstMessage = $this->sessionData['previewMessage'];
425
			$this->sessionData = array();
0 ignored issues
show
Bug Best Practice introduced by
The property sessionData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
426
		}
427
		if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
428
		try
429
		{
430
			$this->profileID = self::validateProfileID($_profileID);
431
			$this->accountid	= $GLOBALS['egw_info']['user']['account_id'];
0 ignored issues
show
Bug Best Practice introduced by
The property accountid does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
432
433
			//error_log(__METHOD__.' ('.__LINE__.') '." ProfileID ".$this->profileID.' called from:'.function_backtrace());
434
			$acc = Mail\Account::read($this->profileID);
435
		}
436
		catch (\Exception $e)
437
		{
438
			throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage());
439
		}
440
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($acc->imapServer()));
441
		$this->icServer = ($_oldImapServerObject?$acc->oldImapServer():$acc->imapServer());
442
		$this->ogServer = $acc->smtpServer();
443
		// TODO: merge mailprefs into userprefs, for easy treatment
444
		$this->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
445
		$this->htmlOptions  = $this->mailPreferences['htmlOptions'];
446
		if (isset($this->icServer->ImapServerId) && !empty($this->icServer->ImapServerId))
447
		{
448
			$_profileID = $this->profileID = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->icServer->ImapServerId;
0 ignored issues
show
Unused Code introduced by
The assignment to $_profileID is dead and can be removed.
Loading history...
449
		}
450
451
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
452
	}
453
454
	/**
455
	 * forceEAProfileLoad
456
	 * used to force the load of a specific emailadmin profile; we assume administrative use only (as of now)
457
	 * @param int $_profile_id
458
	 * @return object instance of Mail (by reference)
459
	 */
460
	public static function &forceEAProfileLoad($_profile_id)
461
	{
462
		self::unsetCachedObjects($_profile_id);
463
		$mail = self::getInstance(false, $_profile_id,false);
464
		//_debug_array( $_profile_id);
465
		$mail->icServer = Mail\Account::read($_profile_id)->imapServer();
466
		$mail->ogServer = Mail\Account::read($_profile_id)->smtpServer();
467
		return $mail;
468
	}
469
470
	/**
471
	 * trigger the force of the reload of the SessionData by resetting the session to an empty array
472
	 * @param int $_profile_id
473
	 * @param boolean $_resetFolderObjects
474
	 */
475
	public static function forcePrefReload($_profile_id=null,$_resetFolderObjects=true)
476
	{
477
		// unset the mail_preferences session object, to force the reload/rebuild
478
		Cache::setSession('mail','mail_preferences',serialize(array()));
479
		Cache::setSession('emailadmin','session_data',serialize(array()));
480
		if ($_resetFolderObjects) self::resetFolderObjectCache($_profile_id);
481
	}
482
483
	/**
484
	 * restore the SessionData
485
	 */
486
	function restoreSessionData()
487
	{
488
		$this->sessionData = array();
0 ignored issues
show
Bug Best Practice introduced by
The property sessionData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
489
		self::$activeFolderCache = Cache::getCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
490
		if (is_array(self::$activeFolderCache[$this->profileID]))
491
		{
492
			foreach (self::$activeFolderCache[$this->profileID] as $key => $value)
493
			{
494
				$this->sessionData[$key] = $value;
495
			}
496
		}
497
	}
498
499
	/**
500
	 * saveSessionData saves session data
501
	 */
502
	function saveSessionData()
503
	{
504
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($this->sessionData)));
505
		foreach ($this->sessionData as $key => $value)
506
		{
507
			if (!is_array(self::$activeFolderCache) && empty(self::$activeFolderCache[$this->profileID]))
508
			{
509
				self::$activeFolderCache = array($this->profileID => array($key => $value));
510
			}
511
			else if(empty(self::$activeFolderCache[$this->profileID]))
512
			{
513
				self::$activeFolderCache += array($this->profileID => array($key => $value));
514
			}
515
			else
516
			{
517
				self::$activeFolderCache[$this->profileID] =  array_merge(self::$activeFolderCache[$this->profileID], array($key => $value));
518
			}
519
		}
520
521
		if (isset(self::$activeFolderCache) && is_array(self::$activeFolderCache))
522
		{
523
			Cache::setCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),self::$activeFolderCache, 60*60*10);
524
		}
525
		// no need to block session any longer
526
		$GLOBALS['egw']->session->commit_session();
527
	}
528
529
	/**
530
	 * unset certain CachedObjects for the given profile id, unsets the profile for default ID=0 as well
531
	 *
532
	 * 1) icServerIMAP_connectionError
533
	 * 2) icServerSIEVE_connectionError
534
	 * 3) INSTANCE OF MAIL_BO
535
	 * 4) HierarchyDelimiter
536
	 * 5) VacationNotice
537
	 *
538
	 * @param int $_profileID = null default profile of user as returned by getUserDefaultProfileID
539
	 * @return void
540
	 */
541
	static function unsetCachedObjects($_profileID=null)
542
	{
543
		if (is_null($_profileID)) $_profileID = Mail\Account::get_default_acc_id();
544
		if (is_array($_profileID) && $_profileID['account_id']) $account_id = $_profileID['account_id'];
0 ignored issues
show
introduced by
The condition is_array($_profileID) is always false.
Loading history...
545
		//error_log(__METHOD__.__LINE__.' called with ProfileID:'.array2string($_profileID).' from '.function_backtrace());
546
		if (!is_array($_profileID) && (is_numeric($_profileID) || !(stripos($_profileID,'tracker_')===false)))
0 ignored issues
show
introduced by
The condition is_numeric($_profileID) is always true.
Loading history...
introduced by
The condition is_array($_profileID) is always false.
Loading history...
547
		{
548
			self::resetConnectionErrorCache($_profileID);
549
			$rawHeadersCache = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
550
			if (isset($rawHeadersCache[$_profileID]))
551
			{
552
				unset($rawHeadersCache[$_profileID]);
553
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$rawHeadersCache, $expiration=60*60*1);
554
			}
555
			$HierarchyDelimiterCache = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*5);
556
			if (isset($HierarchyDelimiterCache[$_profileID]))
557
			{
558
				unset($HierarchyDelimiterCache[$_profileID]);
559
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$HierarchyDelimiterCache, $expiration=60*60*24*5);
560
			}
561
			//reset folderObject cache, to trigger reload
562
			self::resetFolderObjectCache($_profileID);
563
			//reset counter of deleted messages per folder
564
			$eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
565
			if (isset($eMailListContainsDeletedMessages[$_profileID]))
566
			{
567
				unset($eMailListContainsDeletedMessages[$_profileID]);
568
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$eMailListContainsDeletedMessages, $expiration=60*60*1);
569
			}
570
			$vacationCached = Cache::getCache(Cache::INSTANCE, 'email', 'vacationNotice'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*1);
571
			if (isset($vacationCached[$_profileID]))
572
			{
573
				unset($vacationCached[$_profileID]);
574
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),$vacationCached, $expiration=60*60*24*1);
575
			}
576
577
			if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
578
		}
579
		if (is_array($_profileID) && $_profileID['location'] == 'clear_cache')
580
		{
581
			// called via hook
582
			foreach($GLOBALS['egw']->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
583
			{
584
				//error_log(__METHOD__.__LINE__.array2string($account));
585
				$account_id = $account['account_id'];
586
				$_profileID = null;
587
				self::resetConnectionErrorCache($_profileID,$account_id);
588
				self::resetFolderObjectCache($_profileID,$account_id);
589
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),array(), 60*60*1);
590
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),array(), 60*60*24*5);
591
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),array(), 60*60*1);
592
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),array(), 60*60*24*1);
593
			}
594
		}
595
	}
596
597
	/**
598
	 * resets the various cache objects where connection error Objects may be cached
599
	 *
600
	 * @param int $_ImapServerId the profileID to look for
601
	 * @param int $account_id the egw account to look for
602
	 */
603
	static function resetConnectionErrorCache($_ImapServerId=null,$account_id=null)
604
	{
605
		//error_log(__METHOD__.' ('.__LINE__.') '.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($account_id));
606
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
607
		if (is_array($_ImapServerId))
0 ignored issues
show
introduced by
The condition is_array($_ImapServerId) is always false.
Loading history...
608
		{
609
			// called via hook
610
			$account_id = $_ImapServerId['account_id'];
611
			unset($_ImapServerId);
612
			$_ImapServerId = null;
613
		}
614
		if (is_null($_ImapServerId))
615
		{
616
			$isConError = array();
617
			$waitOnFailure = array();
618
		}
619
		else
620
		{
621
			$isConError = Cache::getCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id));
622
			if (isset($isConError[$_ImapServerId]))
623
			{
624
				unset($isConError[$_ImapServerId]);
625
			}
626
			$waitOnFailure = Cache::getCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),null,array(),60*60*2);
627
			if (isset($waitOnFailure[$_ImapServerId]))
628
			{
629
				unset($waitOnFailure[$_ImapServerId]);
630
			}
631
		}
632
		Cache::setCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,60*15);
633
		Cache::setCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),$waitOnFailure,60*60*2);
634
	}
635
636
	/**
637
	 * resets the various cache objects where Folder Objects may be cached
638
	 *
639
	 * @param int $_ImapServerId the profileID to look for
640
	 * @param int $account_id the egw account to look for
641
	 */
642
	static function resetFolderObjectCache($_ImapServerId=null,$account_id=null)
643
	{
644
		//error_log(__METHOD__.' ('.__LINE__.') '.' called for Profile:'.array2string($_ImapServerId).'->'.function_backtrace());
645
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
646
		// on [location] => verify_settings we coud either use [prefs] => Array([ActiveProfileID] => 9, .. as $_ImapServerId
647
		// or treat it as not given. we try that path
648
		if (is_null($_ImapServerId)||is_array($_ImapServerId))
649
		{
650
			$folders2return = array();
651
			$folderInfo = array();
652
			$folderBasicInfo = array();
653
			$_specialUseFolders = array();
654
		}
655
		else
656
		{
657
			$folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),null,array(),60*60*1);
658
			if (!empty($folders2return) && isset($folders2return[$_ImapServerId]))
659
			{
660
				unset($folders2return[$_ImapServerId]);
661
			}
662
			$folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),null,array(),60*60*5);
663
			if (!empty($folderInfo) && isset($folderInfo[$_ImapServerId]))
664
			{
665
				unset($folderInfo[$_ImapServerId]);
666
			}
667
			/*
668
			$lastFolderUsedForMove = Cache::getCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),null,array(),$expiration=60*60*1);
669
			if (isset($lastFolderUsedForMove[$_ImapServerId]))
670
			{
671
				unset($lastFolderUsedForMove[$_ImapServerId]);
672
			}
673
			*/
674
			$folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),null,array(),60*60*1);
675
			if (!empty($folderBasicInfo) && isset($folderBasicInfo[$_ImapServerId]))
676
			{
677
				unset($folderBasicInfo[$_ImapServerId]);
678
			}
679
			$_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),null,array(),60*60*12);
680
			if (!empty($_specialUseFolders) && isset($_specialUseFolders[$_ImapServerId]))
681
			{
682
				unset($_specialUseFolders[$_ImapServerId]);
683
				self::$specialUseFolders=null;
684
			}
685
		}
686
		Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),$folders2return, 60*60*1);
687
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),$folderInfo,60*60*5);
688
		//Cache::setCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),$lastFolderUsedForMove,$expiration=60*60*1);
689
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),$folderBasicInfo,60*60*1);
690
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),$_specialUseFolders,60*60*12);
691
	}
692
693
	/**
694
	 * checks if the imap server supports a given capability
695
	 *
696
	 * @param string $_capability the name of the capability to check for
697
	 * @return bool
698
	 */
699
	function hasCapability($_capability)
700
	{
701
		$rv = $this->icServer->hasCapability(strtoupper($_capability));
702
		//error_log(__METHOD__.' ('.__LINE__.') '." $_capability:".array2string($rv));
703
		return $rv;
704
	}
705
706
	/**
707
	 * getUserEMailAddresses - function to gather the emailadresses connected to the current mail-account
708
	 * @param string $_profileID the ID of the mailaccount to check for identities, if null current mail-account is used
709
	 * @return array - array(email=>realname)
710
	 */
711
	function getUserEMailAddresses($_profileID=null)
712
	{
713
		$acc = Mail\Account::read((!empty($_profileID)?$_profileID:$this->profileID));
714
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($acc));
715
		$identities = Mail\Account::identities($acc);
716
717
		$userEMailAdresses = array($acc['ident_email']=>$acc['ident_realname']);
718
719
		foreach($identities as $ik => $ident) {
720
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
721
			$identity = Mail\Account::read_identity($ik);
722
			if (!empty($identity['ident_email']) && !isset($userEMailAdresses[$identity['ident_email']])) $userEMailAdresses[$identity['ident_email']] = $identity['ident_realname'];
723
		}
724
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
725
		return $userEMailAdresses;
726
	}
727
728
	/**
729
	 * getAllIdentities - function to gather the identities connected to the current user
730
	 * @param string/int $_accountToSearch = null if set search accounts for user specified
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
731
	 * @param boolean $resolve_placeholders wether or not resolve possible placeholders in identities
732
	 * @return array - array(email=>realname)
733
	 */
734
	static function getAllIdentities($_accountToSearch=null,$resolve_placeholders=false)
735
	{
736
		$userEMailAdresses = array();
737
		foreach(Mail\Account::search($only_current_user=($_accountToSearch?$_accountToSearch:true), $just_name=true) as $acc_id => $identity_name)
738
		{
739
			$acc = Mail\Account::read($acc_id,($_accountToSearch?$_accountToSearch:null));
740
			if (!$resolve_placeholders) $userEMailAdresses[$acc['ident_id']] = array('acc_id'=>$acc_id,'ident_id'=>$acc['ident_id'],'ident_email'=>$acc['ident_email'],'ident_org'=>$acc['ident_org'],'ident_realname'=>$acc['ident_realname'],'ident_signature'=>$acc['ident_signature'],'ident_name'=>$acc['ident_name']);
741
742
			foreach(Mail\Account::identities($acc) as $ik => $ident) {
743
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
744
				$identity = Mail\Account::read_identity($ik,$resolve_placeholders);
745
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
746
				if (!isset($userEMailAdresses[$identity['ident_id']])) $userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$acc_id,'ident_id'=>$identity['ident_id'],'ident_email'=>$identity['ident_email'],'ident_org'=>$identity['ident_org'],'ident_realname'=>$identity['ident_realname'],'ident_signature'=>$identity['ident_signature'],'ident_name'=>$identity['ident_name']);
747
			}
748
		}
749
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
750
		return $userEMailAdresses;
751
	}
752
753
	/**
754
	 * Get all identities of given mailaccount
755
	 *
756
	 * @param int|Mail\Account $account account-object or acc_id
757
	 * @return array - array(email=>realname)
758
	 */
759
	function getAccountIdentities($account)
760
	{
761
		if (!$account instanceof Mail\Account)
762
		{
763
			$account = Mail\Account::read($account);
764
		}
765
		$userEMailAdresses = array();
766
		foreach(Mail\Account::identities($account, true, 'params') as $ik => $ident) {
767
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
768
			$identity = Mail\Account::read_identity($ik,true,null,$account);
769
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
770
			// standardIdentity has ident_id==acc_id (as it is done within account->identities)
771
			if (empty($identity['ident_id'])) $identity['ident_id'] = $identity['acc_id'];
772
			if (!isset($userEMailAdresses[$identity['ident_id']]))
773
			{
774
				$userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$identity['acc_id'],
775
																'ident_id'=>$identity['ident_id'],
776
																'ident_email'=>$identity['ident_email'],
777
																'ident_org'=>$identity['ident_org'],
778
																'ident_realname'=>$identity['ident_realname'],
779
																'ident_signature'=>$identity['ident_signature'],
780
																'ident_name'=>$identity['ident_name']);
781
			}
782
		}
783
784
		return $userEMailAdresses;
785
	}
786
787
	/**
788
	 * Function to gather the default identitiy connected to the current mailaccount
789
	 *
790
	 * @return int - id of the identity
791
	 */
792
	function getDefaultIdentity()
793
	{
794
		// retrieve the signature accociated with the identity
795
		$id = $this->getIdentitiesWithAccounts($_accountData=array());
0 ignored issues
show
Bug introduced by
$_accountData = array() cannot be passed to EGroupware\Api\Mail::getIdentitiesWithAccounts() as the parameter $identities expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

795
		$id = $this->getIdentitiesWithAccounts(/** @scrutinizer ignore-type */ $_accountData=array());
Loading history...
796
		foreach(Mail\Account::identities($_accountData[$this->profileID] ?
797
			$this->profileID : $_accountData[$id],false,'ident_id') as $accountData)
798
		{
799
			return $accountData;
800
		}
801
	}
802
803
	/**
804
	 * getIdentitiesWithAccounts
805
	 *
806
	 * @param array reference to pass all identities back
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\reference was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
807
	 * @return the default Identity (active) or 0
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
808
	 */
809
	function getIdentitiesWithAccounts(&$identities)
810
	{
811
		// account select box
812
		$selectedID = $this->profileID;
813
		$allAccountData = Mail\Account::search($only_current_user=true, false, null);
814
		if ($allAccountData) {
0 ignored issues
show
introduced by
$allAccountData is of type EGroupware\Api\Mail\Iterator, thus it always evaluated to true.
Loading history...
815
			$rememberFirst=$selectedFound=null;
816
			foreach ($allAccountData as $tmpkey => $icServers)
817
			{
818
				if (is_null($rememberFirst)) $rememberFirst = $tmpkey;
819
				if ($tmpkey == $selectedID) $selectedFound=true;
820
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($icServers->acc_imap_host));
821
				$host = $icServers->acc_imap_host;
822
				if (empty($host)) continue;
823
				$identities[$icServers->acc_id] = $icServers['ident_realname'].' '.$icServers['ident_org'].' <'.$icServers['ident_email'].'>';
824
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($identities[$icServers->acc_id]));
825
			}
826
		}
827
		return ($selectedFound?$selectedID:$rememberFirst);
828
	}
829
830
	/**
831
	 * construct the string representing an Identity passed by $identity
832
	 *
833
	 * @var array/object $identity, identity object that holds realname, organization, emailaddress and signatureid
834
	 * @var boolean $fullString full or false=NamePart only is returned
835
	 * @return string - constructed of identity object data as defined in mailConfig
836
	 */
837
	static function generateIdentityString($identity, $fullString=true)
838
	{
839
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($identity));
840
		//if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
841
		// not set? -> use default, means full display of all available data
842
		//if (!isset(self::$mailConfig['how2displayIdentities'])) self::$mailConfig['how2displayIdentities']='';
843
		$how2displayIdentities = '';
844
		switch ($how2displayIdentities)
845
		{
846
			case 'email';
847
				//$retData = str_replace('@',' ',$identity->emailAddress).($fullString===true?' <'.$identity->emailAddress.'>':'');
848
				$retData = $identity['ident_email'].($fullString===true?' <'.$identity['ident_email'].'>':'');
849
				break;
850
			case 'nameNemail';
851
				$retData = (!empty($identity['ident_realname'])?$identity['ident_realname']:substr_replace($identity['ident_email'],'',strpos($identity['ident_email'],'@'))).($fullString===true?' <'.$identity['ident_email'].'>':'');
852
				break;
853
			case 'orgNemail';
854
				$retData = (!empty($identity['ident_org'])?$identity['ident_org']:substr_replace($identity['ident_email'],'',0,strpos($identity['ident_email'],'@')+1)).($fullString===true?' <'.$identity['ident_email'].'>':'');
855
				break;
856
			default:
857
				$retData = $identity['ident_realname'].(!empty($identity['ident_org'])?' '.$identity['ident_org']:'').($fullString===true?' <'.$identity['ident_email'].'>':'');
858
		}
859
		return $retData;
860
	}
861
862
	/**
863
	 * closes a connection on the active Server ($this->icServer)
864
	 *
865
	 * @return void
866
	 */
867
	function closeConnection()
868
	{
869
		//if ($icServer->_connected) error_log(__METHOD__.' ('.__LINE__.') '.' disconnect from Server');
870
		//error_log(__METHOD__."() ".function_backtrace());
871
		$this->icServer->disconnect();
872
	}
873
874
	/**
875
	 * reopens a connection for the active Server ($this->icServer), and selects the folder given
876
	 *
877
	 * @param string $_foldername folder to open/select
878
	 * @return void
879
	 */
880
	function reopen($_foldername)
881
	{
882
		if (self::$debugTimes) $starttime = microtime (true);
883
884
		//error_log(__METHOD__.' ('.__LINE__.') '."('$_foldername') ".function_backtrace());
885
		// TODO: trying to reduce traffic to the IMAP Server here, introduces problems with fetching the bodies of
886
		// eMails when not in "current-Folder" (folder that is selected by UI)
887
		static $folderOpened;
888
		//if (empty($folderOpened) || $folderOpened!=$_foldername)
889
		//{
890
			//error_log( __METHOD__.' ('.__LINE__.') '." $_foldername ".function_backtrace());
891
			//error_log(__METHOD__.' ('.__LINE__.') '.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true));
892
			if ($this->folderIsSelectable($_foldername)) {
893
				$this->icServer->openMailbox($_foldername);
894
			}
895
			$folderOpened = $_foldername;
896
		//}
897
		if (self::$debugTimes) self::logRunTimes($starttime,null,'Folder:'.$_foldername,__METHOD__.' ('.__LINE__.') ');
898
	}
899
900
901
	/**
902
	 * openConnection
903
	 *
904
	 * @param int $_icServerID = 0
905
	 * @throws Horde_Imap_Client_Exception on connection error or authentication failure
906
	 * @throws InvalidArgumentException on missing credentials
907
	 */
908
	function openConnection($_icServerID=0)
909
	{
910
		//error_log( "-------------------------->open connection ".function_backtrace());
911
		//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
912
		if (self::$debugTimes) $starttime = microtime (true);
913
		$mailbox=null;
914
		try
915
		{
916
			if(isset($this->sessionData['mailbox'])&&$this->folderExists($this->sessionData['mailbox'])) $mailbox = $this->sessionData['mailbox'];
917
			if (empty($mailbox)) $mailbox = $this->icServer->getCurrentMailbox();
918
/*
919
			if (isset(Mail\Imap::$supports_keywords[$_icServerID]))
920
			{
921
				$this->icServer->openMailbox($mailbox);
922
			}
923
			else
924
			{
925
				$this->icServer->examineMailbox($mailbox);
926
			}
927
*/
928
			// the above should detect if there is a known information about supporting KEYWORDS
929
			// but does not work as expected :-(
930
			$this->icServer->examineMailbox($mailbox);
931
			//error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true));
932
			//error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID.function_backtrace());
933
934
			//make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache
935
			$this->getHierarchyDelimiter(false);
936
			self::$specialUseFolders = $this->getSpecialUseFolders();
937
		}
938
		catch (\Exception $e)
939
		{
940
			error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID." trying to examine ($mailbox) failed!".$e->getMessage());
941
			throw new Exception(__METHOD__." failed to ".__METHOD__." on Profile to $_icServerID while trying to examine $mailbox:".$e->getMessage());
942
		}
943
		if (self::$debugTimes) self::logRunTimes($starttime,null,'ProfileID:'.$_icServerID,__METHOD__.' ('.__LINE__.') ');
944
	}
945
946
	/**
947
	 * getQuotaRoot
948
	 * return the qouta of the users INBOX
949
	 *
950
	 * @return mixed array/boolean
951
	 */
952
	function getQuotaRoot()
953
	{
954
		static $quota;
955
		if (isset($quota)) return $quota;
956
		if (isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID]))
957
		{
958
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
959
			return false;
960
		}
961
		try
962
		{
963
			$this->icServer->getCurrentMailbox();
964
			if(!$this->icServer->hasCapability('QUOTA')) {
965
				$quota = false;
966
				return false;
967
			}
968
			$quota = $this->icServer->getStorageQuotaRoot('INBOX');
969
		}
970
		catch (Exception $e)
971
		{
972
			//error_log(__METHOD__.array2string($e));
973
			//error_log(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
974
			if ($e->getCode()==102)
975
			{
976
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
0 ignored issues
show
Bug introduced by
The property details does not seem to exist on EGroupware\Api\Exception.
Loading history...
977
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
978
				throw new Exception(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
979
			}
980
		}
981
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($quota));
982
		if(is_array($quota)) {
983
			$quota = array(
984
				'usage'	=> $quota['USED'],
985
				'limit'	=> $quota['QMAX'],
986
			);
987
		} else {
988
			$quota = false;
989
		}
990
		return $quota;
991
	}
992
993
	/**
994
	 * getTimeOut
995
	 *
996
	 * @param string _use decide if the use is for IMAP or SIEVE, by now only the default differs
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_use was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
997
	 *
998
	 * @return int - timeout (either set or default 20/10)
999
	 */
1000
	static function getTimeOut($_use='IMAP')
1001
	{
1002
		$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
1003
		if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
1004
		return $timeout;
1005
	}
1006
1007
	/**
1008
	 * Fetch the namespace from icServer
1009
	 *
1010
	 * An IMAPServer may present several namespaces under each key:
1011
	 * so we return an array of namespacearrays for our needs
1012
	 *
1013
	 * @return array array(prefix_present=>mixed (bool/string) ,prefix=>string,delimiter=>string,type=>string (personal|others|shared))
1014
	 */
1015
	function _getNameSpaces()
1016
	{
1017
		static $nameSpace = null;
1018
		$foldersNameSpace = array();
1019
		$delimiter = $this->getHierarchyDelimiter();
1020
		// TODO: cache by $this->icServer->ImapServerId
1021
		if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaceArray();
1022
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($nameSpace));
1023
		if (is_array($nameSpace)) {
1024
			foreach($nameSpace as $type => $singleNameSpaceArray)
1025
			{
1026
				foreach ($singleNameSpaceArray as $singleNameSpace)
1027
				{
1028
					$_foldersNameSpace = array();
1029
					if($type == 'personal' && $singleNameSpace['name'] == '#mh/' && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
1030
					{
1031
						$_foldersNameSpace['prefix_present'] = 'forced';
1032
						// uw-imap server with mailbox prefix or dovecot maybe
1033
						$_foldersNameSpace['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace['name'])?$singleNameSpace['name']:''));
1034
					}
1035
					elseif($type == 'personal' && ($singleNameSpace['name'] == '#mh/') && $this->folderExists('mail'))
1036
					{
1037
						$_foldersNameSpace['prefix_present'] = 'forced';
1038
						// uw-imap server with mailbox prefix or dovecot maybe
1039
						$_foldersNameSpace['prefix'] = 'mail';
1040
					} else {
1041
						$_foldersNameSpace['prefix_present'] = !empty($singleNameSpace['name']);
1042
						$_foldersNameSpace['prefix'] = $singleNameSpace['name'];
1043
					}
1044
					$_foldersNameSpace['delimiter'] = ($singleNameSpace['delimiter']?$singleNameSpace['delimiter']:$delimiter);
1045
					$_foldersNameSpace['type'] = $type;
1046
					$foldersNameSpace[] =$_foldersNameSpace;
1047
				}
1048
				//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################<br>";
1049
			}
1050
		}
1051
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($foldersNameSpace));
1052
		return $foldersNameSpace;
1053
	}
1054
1055
	/**
1056
	 * Wrapper to extract the folder prefix from folder compared to given namespace array
1057
	 *
1058
	 * @param array $nameSpace
1059
	 * @paam string $_folderName
1060
	 * @return string the prefix (may be an empty string)
1061
	 */
1062
	function getFolderPrefixFromNamespace($nameSpace, $folderName)
1063
	{
1064
		foreach($nameSpace as &$singleNameSpace)
1065
		{
1066
			//if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix'];
1067
			if (substr($folderName,0,strlen($singleNameSpace['prefix']))==$singleNameSpace['prefix']) return $singleNameSpace['prefix'];
1068
		}
1069
		return "";
1070
	}
1071
1072
	/**
1073
	 * getHierarchyDelimiter
1074
	 *
1075
	 * @var boolean $_useCache
1076
	 * @return string the hierarchyDelimiter
1077
	 */
1078
	function getHierarchyDelimiter($_useCache=true)
1079
	{
1080
		static $HierarchyDelimiter = null;
1081
		if (is_null($HierarchyDelimiter)) $HierarchyDelimiter = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
1082
		if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]);
1083
		if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId]))
1084
		{
1085
			return $HierarchyDelimiter[$this->icServer->ImapServerId];
1086
		}
1087
		$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1088
		try
1089
		{
1090
			$this->icServer->getCurrentMailbox();
1091
			$HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getDelimiter();
1092
		}
1093
		catch(\Exception $e)
1094
		{
1095
			if ($e->getCode()==102)
1096
			{
1097
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
1098
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1099
			}
1100
			unset($e);
1101
			$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1102
		}
1103
		Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),$HierarchyDelimiter, 60*60*24*5);
1104
		return $HierarchyDelimiter[$this->icServer->ImapServerId];
1105
	}
1106
1107
	/**
1108
	 * getSpecialUseFolders
1109
	 * @ToDo: could as well be static, when icServer is passed
1110
	 * @return mixed null/array
1111
	 */
1112
	function getSpecialUseFolders()
1113
	{
1114
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$this->icServer->ImapServerId.' Connected:'.$this->icServer->_connected);
1115
		static $_specialUseFolders = null;
1116
		if (is_null($_specialUseFolders)||empty($_specialUseFolders)) $_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
1117
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_trash));
1118
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
1119
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
1120
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
1121
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1122
		if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
1123
			return $_specialUseFolders[$this->icServer->ImapServerId];
1124
		$_specialUseFolders[$this->icServer->ImapServerId]=array();
1125
		//if (!empty($this->icServer->acc_folder_trash) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]))
1126
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]='Trash';
1127
		//if (!empty($this->icServer->acc_folder_draft) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]))
1128
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]='Drafts';
1129
		//if (!empty($this->icServer->acc_folder_sent) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]))
1130
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent';
1131
		//if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]))
1132
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates';
1133
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk';
1134
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_archive]='Archive';
0 ignored issues
show
Bug Best Practice introduced by
The property acc_folder_archive does not exist on EGroupware\Api\Mail\Imap. Since you implemented __get, consider adding a @property annotation.
Loading history...
1135
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer));
1136
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1137
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, 60*60*24*5);
1138
		return $_specialUseFolders[$this->icServer->ImapServerId];
1139
	}
1140
1141
	/**
1142
	 * get IMAP folder status regarding NoSelect
1143
	 *
1144
	 * @param foldertoselect string the foldername
1145
	 *
1146
	 * @return boolean true or false regarding the noselect attribute
1147
	 */
1148
	function folderIsSelectable($folderToSelect)
1149
	{
1150
		$retval = true;
1151
		if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) {
1152
			if (!empty($folderStatus['attributes']) && stripos(array2string($folderStatus['attributes']),'noselect')!==false)
1153
			{
1154
				$retval = false;
1155
			}
1156
		}
1157
		return $retval;
1158
	}
1159
1160
	/**
1161
	 * get IMAP folder status, wrapper to store results within a single request
1162
	 *
1163
	 * returns an array information about the imap folder
1164
	 *
1165
	 * @param folderName string the foldername
1166
	 * @param ignoreStatusCache bool ignore the cache used for counters
1167
	 *
1168
	 * @return array
1169
	 *
1170
	 * @throws Exception
1171
	 */
1172
	function _getStatus($folderName,$ignoreStatusCache=false)
1173
	{
1174
		static $folderStatus = null;
1175
		if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName]))
1176
		{
1177
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName]));
1178
			return $folderStatus[$this->icServer->ImapServerId][$folderName];
1179
		}
1180
		try
1181
		{
1182
			$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName,$ignoreStatusCache);
1183
		}
1184
		catch (\Exception $e)
1185
		{
1186
			throw new Exception(__METHOD__.' ('.__LINE__.') '." failed for $folderName with error:".$e->getMessage().($e->details?', '.$e->details:''));
1187
		}
1188
		return $folderStatus[$this->icServer->ImapServerId][$folderName];
1189
	}
1190
1191
	/**
1192
	 * get IMAP folder status
1193
	 *
1194
	 * returns an array information about the imap folder, may be used as  wrapper to retrieve results from cache
1195
	 *
1196
	 * @param _folderName string the foldername
1197
	 * @param ignoreStatusCache bool ignore the cache used for counters
1198
	 * @param basicInfoOnly bool retrieve only names and stuff returned by getMailboxes
1199
	 * @param fetchSubscribedInfo bool fetch Subscribed Info on folder
1200
	 * @return array
1201
	 */
1202
	function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false,$fetchSubscribedInfo=true)
1203
	{
1204
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." called with:$_folderName,$ignoreStatusCache,$basicInfoOnly");
1205
		if (!is_string($_folderName) || empty($_folderName)||(isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID])))
0 ignored issues
show
introduced by
The condition is_string($_folderName) is always true.
Loading history...
1206
		{
1207
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
1208
			return false;
1209
		}
1210
		static $folderInfoCache = null; // reduce traffic on single request
1211
		static $folderBasicInfo = null;
1212
		if (isset($folderBasicInfo[$this->profileID]))
1213
		{
1214
			$folderInfoCache = $folderBasicInfo[$this->profileID];
1215
		}
1216
		if (isset($folderInfoCache[$_folderName]) && $ignoreStatusCache==false && $basicInfoOnly) return $folderInfoCache[$_folderName];
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1217
		$retValue = array();
1218
		$retValue['subscribed'] = false;
1219
/*
1220
		if(!$icServer = Mail\Account::read($this->profileID)) {
1221
			if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName);
1222
			return false;
1223
		}
1224
*/
1225
		//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string(array_keys($folderInfoCache)));
1226
		// does the folder exist???
1227
		if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName]))
1228
		{
1229
			try
1230
			{
1231
				$ret = $this->icServer->getMailboxes($_folderName, 1, true);
1232
			}
1233
			catch (\Exception $e)
1234
			{
1235
				//error_log(__METHOD__.array2string($e));
1236
				//error_log(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1237
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
1238
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1239
				throw new Exception(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1240
			}
1241
			//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string($ret));
1242
			if (is_array($ret))
1243
			{
1244
				$retkeys = array_keys($ret);
1245
				if ($retkeys[0]==$_folderName) $folderInfoCache[$_folderName] = $ret[$retkeys[0]];
1246
			}
1247
			else
1248
			{
1249
				$folderInfoCache[$_folderName]=false;
1250
			}
1251
		}
1252
		$folderInfo = $folderInfoCache[$_folderName];
1253
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo).'->'.function_backtrace());
1254
		if($ignoreStatusCache||!$folderInfo|| !is_array($folderInfo)) {
1255
			try
1256
			{
1257
				$folderInfo = $this->_getStatus($_folderName,$ignoreStatusCache);
1258
			}
1259
			catch (\Exception $e)
1260
			{
1261
				//error_log(__METHOD__.array2string($e));
1262
				error_log(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1263
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
1264
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1265
				//throw new Exception(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1266
				$folderInfo=null;
1267
			}
1268
			if (!is_array($folderInfo))
1269
			{
1270
				// no folder info, but there is a status returned for the folder: something is wrong, try to cope with it
1271
				$folderInfo = is_array($folderInfo)?$folderInfo:array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(),
0 ignored issues
show
introduced by
The condition is_array($folderInfo) is always false.
Loading history...
1272
					'ATTRIBUTES' => '');
1273
				if (!isset($folderInfo['HIERACHY_DELIMITER']) || empty($folderInfo['HIERACHY_DELIMITER']) || (isset($folderInfo['delimiter']) && empty($folderInfo['delimiter'])))
1274
				{
1275
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo));
1276
					$folderInfo['HIERACHY_DELIMITER'] = $this->getHierarchyDelimiter();
1277
				}
1278
			}
1279
		}
1280
		#if(!is_array($folderInfo)) {
1281
		#	return false;
1282
		#}
1283
		$retValue['delimiter']		= (isset($folderInfo['HIERACHY_DELIMITER']) && $folderInfo['HIERACHY_DELIMITER']?$folderInfo['HIERACHY_DELIMITER']:$folderInfo['delimiter']);
1284
		$retValue['attributes']		= (isset($folderInfo['ATTRIBUTES']) && $folderInfo['ATTRIBUTES']?$folderInfo['ATTRIBUTES']:$folderInfo['attributes']);
1285
		$shortNameParts			= explode($retValue['delimiter'], $_folderName);
1286
		$retValue['shortName']		= array_pop($shortNameParts);
1287
		$retValue['displayName']	= $_folderName;
1288
		$retValue['shortDisplayName']	= $retValue['shortName'];
1289
		if(strtoupper($retValue['shortName']) == 'INBOX') {
1290
			$retValue['displayName']	= lang('INBOX');
1291
			$retValue['shortDisplayName']	= lang('INBOX');
1292
		}
1293
		// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
1294
		elseif (in_array($retValue['shortName'],self::$autoFolders))
1295
		{
1296
			$retValue['displayName'] = $retValue['shortDisplayName'] = lang($retValue['shortName']);
1297
		}
1298
		if ($folderInfo) $folderBasicInfo[$this->profileID][$_folderName]=$retValue;
1299
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.$_folderName.array2string($retValue['attributes']));
1300
		if ($basicInfoOnly || (isset($retValue['attributes']) && stripos(array2string($retValue['attributes']),'noselect')!==false))
1301
		{
1302
			return $retValue;
1303
		}
1304
		// fetch all in one go for one request, instead of querying them one by one
1305
		// cache it for a minute 60*60*1
1306
		// this should reduce communication to the imap server
1307
		static $subscribedFolders = null;
1308
		static $nameSpace = null;
1309
		static $prefix = null;
1310
		if (is_null($nameSpace) || empty($nameSpace[$this->profileID])) $nameSpace[$this->profileID] = $this->_getNameSpaces();
1311
		if (!empty($nameSpace[$this->profileID]))
1312
		{
1313
			$nsNoPersonal=array();
1314
			foreach($nameSpace[$this->profileID] as &$ns)
1315
			{
1316
				if ($ns['type']!='personal') $nsNoPersonal[]=$ns;
1317
			}
1318
			$nameSpace[$this->profileID]=$nsNoPersonal;
1319
		}
1320
		if (is_null($prefix) || empty($prefix[$this->profileID]) || empty($prefix[$this->profileID][$_folderName])) $prefix[$this->profileID][$_folderName] = $this->getFolderPrefixFromNamespace($nameSpace[$this->profileID], $_folderName);
1321
1322
		if ($fetchSubscribedInfo && is_null($subscribedFolders) || empty($subscribedFolders[$this->profileID]))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($fetchSubscribedInfo &&...ders[$this->profileID]), Probably Intended Meaning: $fetchSubscribedInfo && ...ers[$this->profileID]))
Loading history...
1323
		{
1324
			$subscribedFolders[$this->profileID] = $this->icServer->listSubscribedMailboxes();
1325
		}
1326
1327
		if($fetchSubscribedInfo && is_array($subscribedFolders[$this->profileID]) && in_array($_folderName,$subscribedFolders[$this->profileID])) {
1328
			$retValue['subscribed'] = true;
1329
		}
1330
1331
		try
1332
		{
1333
			//$folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache);
1334
			$folderStatus = $this->getMailBoxCounters($_folderName,false);
1335
			$retValue['messages']		= $folderStatus['MESSAGES'];
1336
			$retValue['recent']		= $folderStatus['RECENT'];
1337
			$retValue['uidnext']		= $folderStatus['UIDNEXT'];
1338
			$retValue['uidvalidity']	= $folderStatus['UIDVALIDITY'];
1339
			$retValue['unseen']		= $folderStatus['UNSEEN'];
1340
			if (//$retValue['unseen']==0 &&
1341
				(isset($this->mailPreferences['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information
1342
				$this->mailPreferences['trustServersUnseenInfo']==false) ||
1343
				(isset($this->mailPreferences['trustServersUnseenInfo']) &&
1344
				$this->mailPreferences['trustServersUnseenInfo']==2 &&
1345
				$prefix[$this->profileID][$_folderName] != '' && stripos($_folderName,$prefix[$this->profileID][$_folderName]) !== false)
1346
			)
1347
			{
1348
				//error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences['trustServersUnseenInfo']);
1349
				// we filter for the combined status of unseen and undeleted, as this is what we show in list
1350
				try
1351
				{
1352
					$byUid=true;
1353
					$_reverse=1;
1354
					$sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse, array('status'=>array('UNSEEN','UNDELETED')),$byUid,false);
1355
					$retValue['unseen'] = $sortResult['count'];
1356
				}
1357
				catch (\Exception $ee)
1358
				{
1359
					if (self::$debug) error_log(__METHOD__." could not fetch/calculate unseen counter for $_folderName Reason:'".$ee->getMessage()."' but requested.");
1360
				}
1361
			}
1362
		}
1363
		catch (\Exception $e)
1364
		{
1365
			if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($e->getMessage(),true));
1366
		}
1367
1368
		return $retValue;
1369
	}
1370
1371
	/**
1372
	 * Convert Horde_Mime_Headers object to an associative array like Horde_Mime_Array::toArray()
1373
	 *
1374
	 * Catches Horde_Idna_Exception and returns raw header instead eg. for invalid domains like "[email protected]".
1375
	 *
1376
	 * @param Horde_Mime_Headers $headers
1377
	 * @return array
1378
	 */
1379
	protected static function headers2array(Horde_Mime_Headers $headers)
1380
	{
1381
		try {
1382
			$arr = $headers->toArray();
1383
		}
1384
		catch(\Horde_Idna_Exception $e) {
1385
			$arr = array();
1386
			foreach($headers as $header)
1387
			{
1388
				try {
1389
					$val = $header->sendEncode();
1390
				} catch (\Horde_Idna_Exception $e) {
1391
					$val = (array)$header->value;
1392
				}
1393
				$arr[$header->name] = count($val) == 1 ? reset($val) : $val;
1394
			}
1395
		}
1396
		return $arr;
1397
	}
1398
1399
	/**
1400
	 * getHeaders
1401
	 *
1402
	 * this function is a wrapper function for getSortedList and populates the resultList thereof with headerdata
1403
	 *
1404
	 * @param string $_folderName
1405
	 * @param int $_startMessage
1406
	 * @param int $_numberOfMessages number of messages to return
1407
	 * @param array $_sort sort by criteria
1408
	 * @param boolean $_reverse reverse sorting of the result array (may be switched, as it is passed to getSortedList by reference)
1409
	 * @param array $_filter filter to apply to getSortedList
1410
	 * @param mixed $_thisUIDOnly = null, if given fetch the headers of this uid only (either one, or array of uids)
1411
	 * @param boolean $_cacheResult = true try touse the cache of getSortedList
1412
	 * @param mixed $_fetchPreviews = false (boolean/int) fetch part of the body of the messages requested (if integer the number is assumed to be the number of chars to be returned for preview; if set to true 300 chars are returned (when available))
1413
	 * @return array result as array(header=>array,total=>int,first=>int,last=>int)
1414
	 */
1415
	function getHeaders($_folderName, $_startMessage, $_numberOfMessages, $_sort, $_reverse, $_filter, $_thisUIDOnly=null, $_cacheResult=true, $_fetchPreviews=false)
1416
	{
1417
		//self::$debug=true;
1418
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.function_backtrace());
1419
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName,$_startMessage, $_numberOfMessages, $_sort, $_reverse, ".array2string($_filter).", $_thisUIDOnly");
1420
		$reverse = (bool)$_reverse;
1421
		// get the list of messages to fetch
1422
		$this->reopen($_folderName);
1423
		//$currentFolder = $this->icServer->getCurrentMailbox();
1424
		//if ($currentFolder != $_folderName); $this->icServer->openMailbox($_folderName);
1425
		$rByUid = true; // try searching by uid. this var will be passed by reference to getSortedList, and may be set to false, if UID retrieval fails
1426
		#print "<pre>";
1427
		#$this->icServer->setDebug(true);
1428
		$total=0;
1429
		if ($_thisUIDOnly === null)
1430
		{
1431
			if (($_startMessage || $_numberOfMessages) && !isset($_filter['range']))
1432
			{
1433
				// this will not work we must calculate the range we want to retieve as e.g.: 0:20 retirieves the first 20 mails and sorts them
1434
				// if sort capability is applied to the range fetched, not sort first and fetch the range afterwards
1435
				//$start = $_startMessage-1;
1436
				//$end = $_startMessage-1+$_numberOfMessages;
1437
				//$_filter['range'] ="$start:$end";
1438
				//$_filter['range'] ="$_startMessage:*";
1439
			}
1440
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_sort, $reverse, ".array2string($_filter).", $rByUid");
1441
			if (self::$debug||self::$debugTimes) $starttime = microtime (true);
1442
			//see this example below for a 12 week datefilter (since)
1443
			//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $starttime-(3600*24*7*12)));
1444
			$_sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
1445
			$sortResult = $_sortResult['match']->ids;
1446
			//$modseq = $_sortResult['modseq'];
1447
			//error_log(__METHOD__.' ('.__LINE__.') '.'Modsequence:'.$modseq);
1448
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' call getSortedList for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly),__METHOD__.' ('.__LINE__.') ');
1449
1450
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1451
			#$this->icServer->setDebug(false);
1452
			#print "</pre>";
1453
			// nothing found
1454
			if(!is_array($sortResult) || empty($sortResult)) {
1455
				$retValue = array();
1456
				$retValue['info']['total']	= 0;
1457
				$retValue['info']['first']	= 0;
1458
				$retValue['info']['last']	= 0;
1459
				return $retValue;
1460
			}
1461
1462
			$total = $_sortResult['count'];
1463
			#_debug_array($sortResult);
1464
			#_debug_array(array_slice($sortResult, -5, -2));
1465
			//error_log("REVERSE: $reverse");
1466
			if($reverse === true) {
1467
				if  ($_startMessage<=$total)
1468
				{
1469
					$startMessage = $_startMessage-1;
1470
				}
1471
				else
1472
				{
1473
					//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1474
					if ($_startMessage+$_numberOfMessages>$total)
1475
					{
1476
						$numberOfMessages = $total%$_numberOfMessages;
1477
						//$numberOfMessages = abs($_startMessage-$total-1);
1478
						if ($numberOfMessages>0 && $numberOfMessages<=$_numberOfMessages) $_numberOfMessages = $numberOfMessages;
1479
						//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1480
					}
1481
					$startMessage=($total-$_numberOfMessages)-1;
1482
					//$retValue['info']['first'] = $startMessage;
1483
					//$retValue['info']['last'] = $total;
1484
1485
				}
1486
				if ($startMessage+$_numberOfMessages>$total)
1487
				{
1488
					$_numberOfMessages = $_numberOfMessages-($total-($startMessage+$_numberOfMessages));
1489
					//$retValue['info']['first'] = $startMessage;
1490
					//$retValue['info']['last'] = $total;
1491
				}
1492
				if($startMessage > 0) {
1493
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+$startMessage)).', '.-$startMessage.' Number of Messages:'.count($sortResult));
1494
					$sortResult = array_slice($sortResult, -($_numberOfMessages+$startMessage), -$startMessage);
1495
				} else {
1496
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+($_startMessage-1))).', AllTheRest, Number of Messages:'.count($sortResult));
1497
					$sortResult = array_slice($sortResult, -($_numberOfMessages+($_startMessage-1)));
1498
				}
1499
				$sortResult = array_reverse($sortResult);
1500
			} else {
1501
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.($_startMessage-1).', '.$_numberOfMessages.' Number of Messages:'.count($sortResult));
1502
				$sortResult = array_slice($sortResult, $_startMessage-1, $_numberOfMessages);
1503
			}
1504
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1505
		}
1506
		else
1507
		{
1508
			$sortResult = (is_array($_thisUIDOnly) ? $_thisUIDOnly:(array)$_thisUIDOnly);
1509
		}
1510
1511
1512
		// fetch the data for the selected messages
1513
		if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1514
		try
1515
		{
1516
			$uidsToFetch = new Horde_Imap_Client_Ids();
1517
			$uidsToFetch->add($sortResult);
1518
1519
			$fquery = new Horde_Imap_Client_Fetch_Query();
1520
1521
			// Pre-cache the headers we want, 'fetchHeaders' is a label into the cache
1522
			$fquery->headers('fetchHeaders',array(
1523
				'DISPOSITION-NOTIFICATION-TO','RETURN-RECEIPT-TO','X-CONFIRM-READING-TO',
1524
				'DATE','SUBJECT','FROM','TO','CC','REPLY-TO',
1525
				'X-PRIORITY'
1526
			),array(
1527
				// Cache headers, we'll look at them below
1528
				'cache' => true,//$_cacheResult,
1529
				// Set peek so messages are not flagged as read
1530
				'peek' => true
1531
			));
1532
			$fquery->size();
1533
			$fquery->structure();
1534
			$fquery->flags();
1535
			$fquery->imapDate();// needed to ensure getImapDate fetches the internaldate, not the current time
1536
			// if $_fetchPreviews is activated fetch part of the messages too
1537
			if ($_fetchPreviews) $fquery->fullText(array('peek'=>true,'length'=>((int)$_fetchPreviews<5000?5000:$_fetchPreviews),'start'=>0));
1538
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
1539
				'ids' => $uidsToFetch,
1540
			));
1541
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headersNew->ids()));
1542
		}
1543
		catch (\Exception $e)
1544
		{
1545
			$headersNew = array();
1546
			$sortResult = array();
1547
		}
1548
		if (self::$debug||self::$debugTimes)
1549
		{
1550
			self::logRunTimes($starttime,null,'HordeFetch: for Folder:'.$_folderName.' Filter:'.array2string($_filter),__METHOD__.' ('.__LINE__.') ');
1551
			if (self::$debug)
1552
			{
1553
				$queryString = implode(',', $sortResult);
1554
				error_log(__METHOD__.' ('.__LINE__.') '.' Query:'.$queryString.' Result:'.array2string($headersNew));
1555
			}
1556
		}
1557
1558
		$cnt = 0;
1559
1560
		foreach((array)$sortResult as $uid) {
1561
			$sortOrder[$uid] = $cnt++;
1562
		}
1563
1564
		$count = 0;
1565
		if (is_object($headersNew)) {
1566
			if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1567
			foreach($headersNew->ids() as $id) {
1568
				$_headerObject = $headersNew->get($id);
1569
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject));
1570
				$headerObject = array();
1571
				$bodyPreview = null;
1572
				$uid = $headerObject['UID']= ($_headerObject->getUid()?$_headerObject->getUid():$id);
1573
				$headerObject['MSG_NUM'] = $_headerObject->getSeq();
1574
				$headerObject['SIZE'] = $_headerObject->getSize();
1575
				$headerObject['INTERNALDATE'] = $_headerObject->getImapDate();
1576
1577
					// Get already cached headers, 'fetchHeaders' is a label matchimg above
1578
				$headerForPrio = self::headers2array($_headerObject->getHeaders('fetchHeaders',Horde_Imap_Client_Data_Fetch::HEADER_PARSE));
1579
				// Try to fetch header with key='' as some servers might have no fetchHeaders index. e.g. yandex.com
1580
				if (empty($headerForPrio)) $headerForPrio = self::headers2array($_headerObject->getHeaders('',Horde_Imap_Client_Data_Fetch::HEADER_PARSE));
1581
				//fetch the fullMsg part if all conditions match to be available in case $_headerObject->getHeaders returns
1582
				//nothing worthwhile (as it does for googlemail accounts, when preview is switched on
1583
				if ($_fetchPreviews)
1584
				{
1585
					// on enabled preview $bodyPreview is needed lateron. fetched here, for fallback-reasons
1586
					// in case of failed Header-Retrieval
1587
					$bodyPreview = $_headerObject->getFullMsg();
1588
					if (empty($headerForPrio)||(is_array($headerForPrio)&&count($headerForPrio)===1&&$headerForPrio['']))
1589
					{
1590
						$length = strpos($bodyPreview, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
1591
						if ($length===false) $length = strlen($bodyPreview);
1592
						$headerForPrio = self::headers2array(Horde_Mime_Headers::parseHeaders(substr($bodyPreview, 0,$length)));
1593
					}
1594
				}
1595
				$headerForPrio = array_change_key_case($headerForPrio, CASE_UPPER);
1596
				if (self::$debug) {
1597
					error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject).'UID:'.$_headerObject->getUid().' Size:'.$_headerObject->getSize().' Date:'.$_headerObject->getImapDate().'/'.DateTime::to($_headerObject->getImapDate(),'Y-m-d H:i:s'));
1598
					error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerForPrio));
1599
				}
1600
				// message deleted from server but cache still reporting its existence ; may happen on QRESYNC with No permanent modsequences
1601
				if (empty($headerForPrio))
1602
				{
1603
					$total--;
1604
					continue;
1605
				}
1606
				if ( isset($headerForPrio['DISPOSITION-NOTIFICATION-TO']) ) {
1607
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['DISPOSITION-NOTIFICATION-TO']));
1608
				} else if ( isset($headerForPrio['RETURN-RECEIPT-TO']) ) {
1609
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['RETURN-RECEIPT-TO']));
1610
				} else if ( isset($headerForPrio['X-CONFIRM-READING-TO']) ) {
1611
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['X-CONFIRM-READING-TO']));
1612
				} /*else $sent_not = "";*/
1613
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1614
				$headerObject['DATE'] = $headerForPrio['DATE'];
1615
				$headerObject['SUBJECT'] = (is_array($headerForPrio['SUBJECT'])?$headerForPrio['SUBJECT'][0]:$headerForPrio['SUBJECT']);
1616
				$headerObject['FROM'] = (array)($headerForPrio['FROM']?$headerForPrio['FROM']:($headerForPrio['REPLY-TO']?$headerForPrio['REPLY-TO']:$headerForPrio['RETURN-PATH']));
1617
				$headerObject['TO'] = (array)$headerForPrio['TO'];
1618
				$headerObject['CC'] = isset($headerForPrio['CC'])?(array)$headerForPrio['CC']:array();
1619
				$headerObject['REPLY-TO'] = isset($headerForPrio['REPLY-TO'])?(array)$headerForPrio['REPLY-TO']:array();
1620
				$headerObject['PRIORITY'] = isset($headerForPrio['X-PRIORITY'])?$headerForPrio['X-PRIORITY']:null;
1621
				foreach (array('FROM','TO','CC','REPLY-TO') as $key)
1622
				{
1623
					$address = array();
1624
					foreach ($headerObject[$key] as $k => $ad)
1625
					{
1626
						//the commented section below IS a simplified version of the section "make sure ..."
1627
						/*
1628
						if (stripos($ad,'@')===false)
1629
						{
1630
							$remember=$k;
1631
						}
1632
						else
1633
						{
1634
							$address[] = (!is_null($remember)?$headerObject[$key][$remember].' ':'').$ad;
1635
							$remember=null;
1636
						}
1637
						*/
1638
						// make sure addresses are real emailaddresses one by one in the array as expected
1639
						$rfcAddr = self::parseAddressList($ad); // does some fixing of known problems too
1640
						foreach ($rfcAddr as $_rfcAddr)
1641
						{
1642
							if (!$_rfcAddr->valid)	continue; // skip. not a valid address
1643
							$address[] = imap_rfc822_write_address($_rfcAddr->mailbox,$_rfcAddr->host,$_rfcAddr->personal);
1644
						}
1645
					}
1646
					$headerObject[$key] = $address;
1647
				}
1648
				$headerObject['FLAGS'] = $_headerObject->getFlags();
1649
				$headerObject['BODYPREVIEW']=null;
1650
				// this section fetches part of the message-body (if enabled) for some kind of preview
1651
				// if we fail to succeed, we fall back to the retrieval of the message-body with
1652
				// fetchPartContents (see below, when we iterate over the structure to determine the
1653
				// existance (and the details) for attachments)
1654
				if ($_fetchPreviews)
1655
				{
1656
					// $bodyPreview is populated at the beginning of the loop, as it may be
1657
					// needed to parse the Headers of the Message
1658
					if (empty($bodyPreview)) $bodyPreview = $_headerObject->getFullMsg();
1659
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($bodyPreview));
1660
					$base = Horde_Mime_Part::parseMessage($bodyPreview);
1661
					foreach($base->partIterator() as $part)
1662
					{
1663
						//error_log(__METHOD__.__LINE__.'Part:'.$part->getPrimaryType());
1664
						if (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'text')
1665
						{
1666
							$charset = $part->getContentTypeParameter('charset');
1667
							$buffer = Mail\Html::convertHTMLToText($part->toString(array(
1668
												'encode' => Horde_Mime_Part::ENCODE_BINARY,	// otherwise we cant recode charset
1669
											)), $charset, 'utf-8');
1670
							$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Translation::convert_jsonsafe($buffer),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));
1671
						} elseif (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'multipart')
1672
						{
1673
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part));
1674
						}
1675
					}
1676
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject['BODYPREVIEW']));
1677
				}
1678
				$mailStructureObject = $_headerObject->getStructure();
1679
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1680
				//error_log(__METHOD__.' ('.__LINE__.') '.' MimeMap:'.array2string($mailStructureObject->contentTypeMap()));
1681
				//foreach ($_headerObject->getStructure()->getParts() as $p => $part)
1682
				$headerObject['ATTACHMENTS']=null;
1683
				$skipParts=array();
1684
				$messageMimeType='';
1685
				foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1685
				foreach (/** @scrutinizer ignore-deprecated */ $mailStructureObject->contentTypeMap() as $mime_id => $mime_type)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1686
				{
1687
					if ($mime_id==0 || $messageMimeType==='') $messageMimeType = $mime_type;
1688
					$part = $mailStructureObject->getPart($mime_id);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1688
					$part = /** @scrutinizer ignore-deprecated */ $mailStructureObject->getPart($mime_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1689
					$partdisposition = $part->getDisposition();
1690
					$partPrimaryType = $part->getPrimaryType();
1691
					// this section fetches the body for the purpose of previewing a few lines
1692
					// drawback here it is talking to the mailserver for each mail thus consuming
1693
					// more time than expected; so we call this section only when there is no
1694
					// bodypreview could be found (multipart/....)
1695
					if ($_fetchPreviews && empty($headerObject['BODYPREVIEW'])&&($partPrimaryType == 'text') &&
1696
						((intval($mime_id) === 1) || !$mime_id) &&
1697
						($partdisposition !== 'attachment')) {
1698
							$_structure=$part;
1699
							$this->fetchPartContents($uid, $_structure, false,true);
1700
							$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Mail\Html::convertHTMLToText($_structure->getContents()),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));
0 ignored issues
show
Bug introduced by
The method getContents() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1700
							$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Mail\Html::convertHTMLToText($_structure->/** @scrutinizer ignore-call */ getContents()),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1701
							$charSet = $part->getCharset();
1702
							// check if client set a wrong charset and content is utf-8 --> use utf-8
1703
							if (strtolower($charSet) !='utf-8' && preg_match('//u', $headerObject['BODYPREVIEW']))
1704
							{
1705
								$charSet['charSet'] = 'UTF-8';
1706
							}
1707
							// add line breaks to $bodyParts
1708
							//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
1709
							$headerObject['BODYPREVIEW']  = Translation::convert_jsonsafe($headerObject['BODYPREVIEW'], $charSet);
1710
							//error_log(__METHOD__.__LINE__.$headerObject['BODYPREVIEW']);
1711
					}
1712
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType);
1713
					$cid = $part->getContentId();
1714
					if (empty($partdisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
1715
					{
1716
						// the presence of an cid does not necessarily indicate its inline. it may lack the needed
1717
						// link to show the image. Considering this: we "list" everything that matches the above criteria
1718
						// as attachment in order to not loose/miss information on our data
1719
						$partdisposition='attachment';//($partPrimaryType == 'image'&&!empty($cid)?'inline':'attachment');
1720
					}
1721
					if ($mime_type=='message/rfc822')
1722
					{
1723
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
1724
						foreach($part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1724
						foreach(/** @scrutinizer ignore-deprecated */ $part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1725
					}
1726
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType.' Skip:'.array2string($skipParts));
1727
					if (array_key_exists($mime_id,$skipParts)) continue;
1728
					if ($partdisposition=='attachment' ||
1729
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&$mime_type=='image/tiff') || // as we are not able to display tiffs
1730
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&empty($cid)) ||
1731
						($partdisposition=='inline' && $partPrimaryType != 'image' && $partPrimaryType != 'multipart' && $partPrimaryType != 'text'))
1732
					{
1733
						$headerObject['ATTACHMENTS'][$mime_id]=$part->getAllDispositionParameters();
1734
						$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=$mime_type;
1735
						$headerObject['ATTACHMENTS'][$mime_id]['uid']=$uid;
1736
						$headerObject['ATTACHMENTS'][$mime_id]['cid'] = $cid;
1737
						$headerObject['ATTACHMENTS'][$mime_id]['partID']=$mime_id;
1738
						if (!isset($headerObject['ATTACHMENTS'][$mime_id]['name']))
1739
						{
1740
							$headerObject['ATTACHMENTS'][$mime_id]['name']= $part->getName() ? $part->getName() :
1741
								($mime_type == "message/rfc822" ? lang('forwarded message') : lang('attachment'));
1742
						}
1743
						if (!strcasecmp($headerObject['ATTACHMENTS'][$mime_id]['name'],'winmail.dat') ||
1744
							$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=='application/ms-tnef')
1745
						{
1746
							$headerObject['ATTACHMENTS'][$mime_id]['is_winmail'] = true;
1747
						}
1748
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getName()));
1749
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getAllDispositionParameters()));
1750
						//error_log(__METHOD__.' ('.__LINE__.') '.' Attachment:'.$mime_id.'->'.array2string($headerObject['ATTACHMENTS'][$mime_id]));
1751
					}
1752
				}
1753
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (plain):'.array2string($mailStructureObject->findBody('plain')));
1754
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (html):'.array2string($mailStructureObject->findBody('html')));
1755
				//if($count == 0) error_log(__METHOD__.array2string($headerObject));
1756
				if (empty($headerObject['UID'])) continue;
1757
				//$uid = ($rByUid ? $headerObject['UID'] : $headerObject['MSG_NUM']);
1758
				// make dates like "Mon, 23 Apr 2007 10:11:06 UT" working with strtotime
1759
				if(substr($headerObject['DATE'],-2) === 'UT') {
1760
					$headerObject['DATE'] .= 'C';
1761
				}
1762
				if(substr($headerObject['INTERNALDATE'],-2) === 'UT') {
1763
					$headerObject['INTERNALDATE'] .= 'C';
1764
				}
1765
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$headerObject['SUBJECT'].'->'.$headerObject['DATE'].'<->'.$headerObject['INTERNALDATE'] .'#');
1766
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']);
1767
				if (isset($headerObject['ATTACHMENTS']) && count($headerObject['ATTACHMENTS'])) foreach ($headerObject['ATTACHMENTS'] as &$a) { $retValue['header'][$sortOrder[$uid]]['attachments'][]=$a;}
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sortOrder seems to be defined by a foreach iteration on line 1560. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1768
				$retValue['header'][$sortOrder[$uid]]['subject']	= $this->decode_subject($headerObject['SUBJECT']);
1769
				$retValue['header'][$sortOrder[$uid]]['size'] 		= $headerObject['SIZE'];
1770
				$retValue['header'][$sortOrder[$uid]]['date']		= self::_strtotime(($headerObject['DATE']&&!($headerObject['DATE']=='NIL')?$headerObject['DATE']:$headerObject['INTERNALDATE']),'ts',true);
1771
				$retValue['header'][$sortOrder[$uid]]['internaldate']= self::_strtotime($headerObject['INTERNALDATE'],'ts',true);
1772
				$retValue['header'][$sortOrder[$uid]]['mimetype']	= $messageMimeType;
1773
				$retValue['header'][$sortOrder[$uid]]['id']		= $headerObject['MSG_NUM'];
1774
				$retValue['header'][$sortOrder[$uid]]['uid']		= $headerObject['UID'];
1775
				$retValue['header'][$sortOrder[$uid]]['bodypreview']		= $headerObject['BODYPREVIEW'];
1776
				$retValue['header'][$sortOrder[$uid]]['priority']		= ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3);
1777
				$retValue['header'][$sortOrder[$uid]]['smimeType']		= Mail\Smime::getSmimeType($mailStructureObject);
1778
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.array2string($retValue['header'][$sortOrder[$uid]]));
1779
				if (isset($headerObject['DISPOSITION-NOTIFICATION-TO'])) $retValue['header'][$sortOrder[$uid]]['disposition-notification-to'] = $headerObject['DISPOSITION-NOTIFICATION-TO'];
1780
				if (is_array($headerObject['FLAGS'])) {
1781
					$retValue['header'][$sortOrder[$uid]] = array_merge($retValue['header'][$sortOrder[$uid]],self::prepareFlagsArray($headerObject));
1782
				}
1783
				//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].'->'.array2string($_headerObject->getEnvelope()->__get('from')));
1784
				if(is_array($headerObject['FROM']) && $headerObject['FROM'][0]) {
1785
					$retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0],true);
1786
					if (count($headerObject['FROM'])>1)
1787
					{
1788
						$ki=0;
1789
						foreach($headerObject['FROM'] as $k => $add)
1790
						{
1791
							if ($k==0) continue;
1792
							$retValue['header'][$sortOrder[$uid]]['additional_from_addresses'][$ki] = self::decode_header($add,true);
1793
							$ki++;
1794
						}
1795
					}
1796
				}
1797
				if(is_array($headerObject['REPLY-TO']) && $headerObject['REPLY-TO'][0]) {
1798
					$retValue['header'][$sortOrder[$uid]]['reply_to_address'] = self::decode_header($headerObject['REPLY-TO'][0],true);
1799
				}
1800
				if(is_array($headerObject['TO']) && $headerObject['TO'][0]) {
1801
					$retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0],true);
1802
					if (count($headerObject['TO'])>1)
1803
					{
1804
						$ki=0;
1805
						foreach($headerObject['TO'] as $k => $add)
1806
						{
1807
							if ($k==0) continue;
1808
							//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1809
							$retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki] = self::decode_header($add,true);
1810
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1811
							$ki++;
1812
						}
1813
					}
1814
				}
1815
				if(is_array($headerObject['CC']) && count($headerObject['CC'])>0) {
1816
					$ki=0;
1817
					foreach($headerObject['CC'] as $k => $add)
1818
					{
1819
						//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1820
						$retValue['header'][$sortOrder[$uid]]['cc_addresses'][$ki] = self::decode_header($add,true);
1821
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1822
						$ki++;
1823
					}
1824
				}
1825
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]));
1826
1827
				$count++;
1828
			}
1829
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' fetching Headers and stuff for Folder:'.$_folderName,__METHOD__.' ('.__LINE__.') ');
1830
			//self::$debug=false;
1831
			// sort the messages to the requested displayorder
1832
			if(is_array($retValue['header'])) {
1833
				$countMessages = $total;
0 ignored issues
show
Unused Code introduced by
The assignment to $countMessages is dead and can be removed.
Loading history...
1834
				if (isset($_filter['range'])) $countMessages = self::$folderStatusCache[$this->profileID][$_folderName]['messages'];
1835
				ksort($retValue['header']);
1836
				$retValue['info']['total']	= $total;
1837
				//if ($_startMessage>$total) $_startMessage = $total-($count-1);
1838
				$retValue['info']['first']	= $_startMessage;
1839
				$retValue['info']['last']	= $_startMessage + $count - 1 ;
1840
				return $retValue;
1841
			} else {
1842
				$retValue = array();
1843
				$retValue['info']['total']	= 0;
1844
				$retValue['info']['first']	= 0;
1845
				$retValue['info']['last']	= 0;
1846
				return $retValue;
1847
			}
1848
		} else {
1849
			if ($headersNew == null && empty($_thisUIDOnly)) error_log(__METHOD__." -> retrieval of Message Details to Query $queryString failed: ".print_r($headersNew,TRUE));
1850
			$retValue = array();
1851
			$retValue['info']['total']  = 0;
1852
			$retValue['info']['first']  = 0;
1853
			$retValue['info']['last']   = 0;
1854
			return $retValue;
1855
		}
1856
	}
1857
1858
	/**
1859
	 * static function prepareFlagsArray
1860
	 * prepare headerObject to return some standardized array to tell which flags are set for a message
1861
	 * @param array $headerObject  - array to process, a full return array from icServer->getSummary
1862
	 * @return array array of flags
1863
	 */
1864
	static function prepareFlagsArray($headerObject)
1865
	{
1866
		if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']);
1867
		$retValue = array();
1868
		$retValue['recent']		= in_array('\\recent', $headerFlags);
1869
		$retValue['flagged']	= in_array('\\flagged', $headerFlags);
1870
		$retValue['answered']	= in_array('\\answered', $headerFlags);
1871
		$retValue['forwarded']   = in_array('$forwarded', $headerFlags);
1872
		$retValue['deleted']	= in_array('\\deleted', $headerFlags);
1873
		$retValue['seen']		= in_array('\\seen', $headerFlags);
1874
		$retValue['draft']		= in_array('\\draft', $headerFlags);
1875
		$retValue['mdnsent']	= in_array('$mdnsent', $headerFlags)||in_array('mdnsent', $headerFlags);
1876
		$retValue['mdnnotsent']	= in_array('$mdnnotsent', $headerFlags)||in_array('mdnnotsent', $headerFlags);
1877
		$retValue['label1']   = in_array('$label1', $headerFlags);
1878
		$retValue['label2']   = in_array('$label2', $headerFlags);
1879
		$retValue['label3']   = in_array('$label3', $headerFlags);
1880
		$retValue['label4']   = in_array('$label4', $headerFlags);
1881
		$retValue['label5']   = in_array('$label5', $headerFlags);
1882
		//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].':'.array2string($retValue));
1883
		return $retValue;
1884
	}
1885
1886
	/**
1887
	 * fetches a sorted list of messages from the imap server
1888
	 * private function
1889
	 *
1890
	 * @todo implement sort based on Net_IMAP
1891
	 * @param string $_folderName the name of the folder in which the messages get searched
1892
	 * @param integer $_sort the primary sort key
1893
	 * @param bool $_reverse sort the messages ascending or descending
1894
	 * @param array $_filter the search filter
1895
	 * @param bool $resultByUid if set to true, the result is to be returned by uid, if the server does not reply
1896
	 * 			on a query for uids, the result may be returned by IDs only, this will be indicated by this param
1897
	 * @param bool $setSession if set to true the session will be populated with the result of the query
1898
	 * @return mixed bool/array false or array of ids
1899
	 */
1900
	function getSortedList($_folderName, $_sort, &$_reverse, $_filter, &$resultByUid=true, $setSession=true)
1901
	{
1902
		static $cachedFolderStatus = null;
1903
		// in the past we needed examineMailbox to figure out if the server with the serverID support keywords
1904
		// this information is filled/provided by examineMailbox; but caching within one request seems o.k.
1905
		if (is_null($cachedFolderStatus) || !isset($cachedFolderStatus[$this->profileID][$_folderName]) )
1906
		{
1907
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName] = $this->icServer->examineMailbox($_folderName);
1908
		}
1909
		else
1910
		{
1911
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName];
1912
		}
1913
		//error_log(__METHOD__.' ('.__LINE__.') '.' F:'.$_folderName.' S:'.array2string($folderStatus));
1914
		//error_log(__METHOD__.' ('.__LINE__.') '.' Filter:'.array2string($_filter));
1915
		$try2useCache = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $try2useCache is dead and can be removed.
Loading history...
1916
		static $eMailListContainsDeletedMessages = null;
1917
		if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
1918
		// this indicates, that there is no Filter set, and the returned set/subset should not contain DELETED Messages, nor filtered for UNDELETED
1919
		if ($setSession==true && ((strpos(array2string($_filter), 'UNDELETED') === false && strpos(array2string($_filter), 'DELETED') === false)))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1920
		{
1921
			if (self::$debugTimes) $starttime = microtime(true);
1922
			if (is_null($eMailListContainsDeletedMessages) || empty($eMailListContainsDeletedMessages[$this->profileID]) || empty($eMailListContainsDeletedMessages[$this->profileID][$_folderName])) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
1923
			$five=true;
1924
			$dReverse=1;
1925
			$deletedMessages = $this->getSortedList($_folderName, 0, $dReverse, array('status'=>array('DELETED')),$five,false);
1926
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') Found DeletedMessages:'.array2string($eMailListContainsDeletedMessages));
1927
			$eMailListContainsDeletedMessages[$this->profileID][$_folderName] =$deletedMessages['count'];
1928
			Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, 60*60*1);
1929
			if (self::$debugTimes) self::logRunTimes($starttime,null,'setting eMailListContainsDeletedMessages for Profile:'.$this->profileID.' Folder:'.$_folderName.' to '.$eMailListContainsDeletedMessages[$this->profileID][$_folderName],__METHOD__.' ('.__LINE__.') ');			//error_log(__METHOD__.' ('.__LINE__.') '.' Profile:'.$this->profileID.' Folder:'.$_folderName.' -> EXISTS/SessStat:'.array2string($folderStatus['MESSAGES']).'/'.self::$folderStatusCache[$this->profileID][$_folderName]['messages'].' ListContDelMsg/SessDeleted:'.$eMailListContainsDeletedMessages[$this->profileID][$_folderName].'/'.self::$folderStatusCache[$this->profileID][$_folderName]['deleted']);
1930
		}
1931
		$try2useCache = false;
1932
		//self::$supportsORinQuery[$this->profileID]=true;
1933
		if (is_null(self::$supportsORinQuery) || !isset(self::$supportsORinQuery[$this->profileID]))
1934
		{
1935
			self::$supportsORinQuery = Cache::getCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
1936
			if (!isset(self::$supportsORinQuery[$this->profileID])) self::$supportsORinQuery[$this->profileID]=true;
1937
		}
1938
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_filter).' SupportsOrInQuery:'.self::$supportsORinQuery[$this->profileID]);
1939
		$filter = $this->createIMAPFilter($_folderName, $_filter,self::$supportsORinQuery[$this->profileID]);
1940
		if (self::$debug)
1941
		{
1942
			$query_str = $filter->build();
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Imap_Client_Search_Query::build() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1942
			$query_str = /** @scrutinizer ignore-deprecated */ $filter->build();
Loading history...
1943
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query']);
1944
		}
1945
		//_debug_array($filter);
1946
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($filter).'#'.array2string($this->icServer->capability()));
1947
		if($this->icServer->hasCapability('SORT')) {
1948
			// when using an orQuery and we sort by date. sort seems to fail on certain servers => ZIMBRA with Horde_Imap_Client
1949
			// thus we translate the search request from date to Horde_Imap_Client::SORT_SEQUENCE (which should be the same, if
1950
			// there is no messing with the dates)
1951
			//if (self::$supportsORinQuery[$this->profileID]&&$_sort=='date'&&$_filter['type']=='quick'&&!empty($_filter['string']))$_sort='INTERNALDATE';
1952
			if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: ".array2string($_sort)." Reverse: $_reverse");
1953
			$sortOrder = $this->_getSortString($_sort, $_reverse);
1954
			if ($_reverse && in_array(Horde_Imap_Client::SORT_REVERSE,$sortOrder)) $_reverse=false; // as we reversed the result already
1955
			if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy:".array2string($_sort)."->".array2string($sortOrder)." Filter: ".array2string($filter));
1956
			try
1957
			{
1958
				$sortResult = $this->icServer->search($_folderName, $filter, array(
1959
					'sort' => $sortOrder,));
1960
1961
				// Attempt another search without sorting filter if first try failed with
1962
				// no result, as may some servers do not coupe well with sort option
1963
				// eventhough they claim to support SORT capability.
1964
				if (!isset($sortResult['count'])) $sortResult = $this->icServer->search($_folderName, $filter);
1965
1966
			// if there is an Error, we assume that the server is not capable of sorting
1967
			}
1968
			catch(\Exception $e)
1969
			{
1970
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1971
				$resultByUid = false;
1972
				$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1973
				if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1974
				try
1975
				{
1976
					$sortResult = $this->icServer->search($_folderName, $filter, array(
1977
						'sort' => $sortOrder));
1978
				}
1979
				catch(\Exception $e)
1980
				{
1981
					error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1982
					$sortResult = self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'];
1983
				}
1984
			}
1985
			if (self::$debug) error_log(__METHOD__.print_r($sortResult,true));
1986
		} else {
1987
			if (self::$debug) error_log(__METHOD__." Mailserver has NO SORT Capability");
1988
			//$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1989
			//if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1990
			try
1991
			{
1992
				$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1993
					'sort' => $sortOrder)*/);
1994
			}
1995
			catch(\Exception $e)
1996
			{
1997
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1998
				// possible error OR Query. But Horde gives no detailed Info :-(
1999
				self::$supportsORinQuery[$this->profileID]=false;
2000
				Cache::setCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),self::$supportsORinQuery,60*60*10);
2001
				if (self::$debug) error_log(__METHOD__.__LINE__." Mailserver seems to have NO OR Capability for Search:".$sortResult->message);
2002
				$filter = $this->createIMAPFilter($_folderName, $_filter, self::$supportsORinQuery[$this->profileID]);
2003
				try
2004
				{
2005
					$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
2006
						'sort' => $sortOrder)*/);
2007
				}
2008
				catch(\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
2009
				{
2010
				}
2011
			}
2012
			if(is_array($sortResult['match'])) {
2013
					// not sure that this is going so succeed as $sortResult['match'] is a hordeObject
2014
					sort($sortResult['match'], SORT_NUMERIC);
2015
			}
2016
			if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
2017
		}
2018
		if ($setSession)
2019
		{
2020
			self::$folderStatusCache[$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY'];
2021
			self::$folderStatusCache[$this->profileID][$_folderName]['messages']	= $folderStatus['MESSAGES'];
2022
			self::$folderStatusCache[$this->profileID][$_folderName]['deleted']	= $eMailListContainsDeletedMessages[$this->profileID][$_folderName];
2023
			self::$folderStatusCache[$this->profileID][$_folderName]['uidnext']	= $folderStatus['UIDNEXT'];
2024
			self::$folderStatusCache[$this->profileID][$_folderName]['filter']	= $_filter;
2025
			self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'] = $sortResult;
2026
			self::$folderStatusCache[$this->profileID][$_folderName]['sort']	= $_sort;
2027
		}
2028
		//error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
2029
		//_debug_array($sortResult['match']->ids);
2030
		return $sortResult;
2031
	}
2032
2033
	/**
2034
	 * convert the sort value from the gui(integer) into a string
2035
	 *
2036
	 * @param mixed _sort the integer sort order / or a valid and handeled SORTSTRING (right now: UID/ARRIVAL/INTERNALDATE (->ARRIVAL))
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_sort was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2037
	 * @param bool _reverse wether to add REVERSE to the Sort String or not
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_reverse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2038
	 * @return the sort sequence for horde search
2039
	 */
2040
	function _getSortString($_sort, $_reverse=false)
2041
	{
2042
		$_reverse=false;
2043
		if (is_numeric($_sort))
2044
		{
2045
			switch($_sort) {
2046
				case 2:
2047
					$retValue = array(Horde_Imap_Client::SORT_FROM);
2048
					break;
2049
				case 4:
2050
					$retValue = array(Horde_Imap_Client::SORT_TO);
2051
					break;
2052
				case 3:
2053
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
2054
					break;
2055
				case 6:
2056
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
2057
					break;
2058
				case 0:
2059
				default:
2060
					$retValue = array(Horde_Imap_Client::SORT_DATE);
2061
					//$retValue = 'ARRIVAL';
2062
					break;
2063
			}
2064
		}
2065
		else
2066
		{
2067
			switch(strtoupper($_sort)) {
2068
				case 'FROMADDRESS':
2069
					$retValue = array(Horde_Imap_Client::SORT_FROM);
2070
					break;
2071
				case 'TOADDRESS':
2072
					$retValue = array(Horde_Imap_Client::SORT_TO);
2073
					break;
2074
				case 'SUBJECT':
2075
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
2076
					break;
2077
				case 'SIZE':
2078
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
2079
					break;
2080
				case 'ARRIVAL':
2081
					$retValue = array(Horde_Imap_Client::SORT_ARRIVAL);
2082
					break;
2083
				case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date
2084
				case 'INTERNALDATE':
2085
					$retValue = array(Horde_Imap_Client::SORT_SEQUENCE);
2086
					break;
2087
				case 'DATE':
2088
				default:
2089
					$retValue = array(Horde_Imap_Client::SORT_DATE);
2090
					break;
2091
			}
2092
		}
2093
		if ($_reverse) array_unshift($retValue,Horde_Imap_Client::SORT_REVERSE);
0 ignored issues
show
introduced by
The condition $_reverse is always false.
Loading history...
2094
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue);
2095
		return $retValue;
2096
	}
2097
2098
	/**
2099
	 * this function creates an IMAP filter from the criterias given
2100
	 *
2101
	 * @param string $_folder used to determine the search to TO or FROM on QUICK Search wether it is a send-folder or not
2102
	 * @param array $_criterias contains the search/filter criteria
2103
	 * @param boolean $_supportsOrInQuery wether to use the OR Query on QuickSearch
2104
	 * @return Horde_Imap_Client_Search_Query the IMAP filter
2105
	 */
2106
	function createIMAPFilter($_folder, $_criterias, $_supportsOrInQuery=true)
2107
	{
2108
		$imapFilter = new Horde_Imap_Client_Search_Query();
2109
		$imapFilter->charset('UTF-8');
2110
2111
		//_debug_array($_criterias);
2112
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
0 ignored issues
show
introduced by
The condition is_array($_criterias) is always true.
Loading history...
2113
		if((!is_array($_criterias) || $_criterias['status']=='any') &&
0 ignored issues
show
introduced by
The condition is_array($_criterias) is always true.
Loading history...
2114
			(!isset($_criterias['string']) || empty($_criterias['string'])) &&
2115
			(!isset($_criterias['range'])|| empty($_criterias['range']) ||
2116
			( !empty($_criterias['range'])&& ($_criterias['range']!='BETWEEN' && empty($_criterias['date'])||
2117
			($_criterias['range']=='BETWEEN' && empty($_criterias['since'])&& empty($_criterias['before']))))))
2118
		{
2119
			//error_log(__METHOD__.' ('.__LINE__.') returning early Criterias:'.print_r($_criterias, true));
2120
			$imapFilter->flag('DELETED', $set=false);
2121
			return $imapFilter;
2122
		}
2123
		$queryValid = false;
2124
		// statusQuery MUST be placed first, as search for subject/mailbody and such is
2125
		// depending on charset. flagSearch is not BUT messes the charset if called afterwards
2126
		$statusQueryValid = false;
2127
		foreach((array)$_criterias['status'] as $k => $criteria) {
2128
			$imapStatusFilter = new Horde_Imap_Client_Search_Query();
2129
			$imapStatusFilter->charset('UTF-8');
2130
			$criteria = strtoupper($criteria);
2131
			switch ($criteria) {
2132
				case 'ANSWERED':
2133
				case 'DELETED':
2134
				case 'FLAGGED':
2135
				case 'RECENT':
2136
				case 'SEEN':
2137
					$imapStatusFilter->flag($criteria, $set=true);
2138
					$queryValid = $statusQueryValid =true;
2139
					break;
2140
				case 'READ':
2141
					$imapStatusFilter->flag('SEEN', $set=true);
2142
					$queryValid = $statusQueryValid =true;
2143
					break;
2144
				case 'LABEL1':
2145
				case 'KEYWORD1':
2146
				case 'LABEL2':
2147
				case 'KEYWORD2':
2148
				case 'LABEL3':
2149
				case 'KEYWORD3':
2150
				case 'LABEL4':
2151
				case 'KEYWORD4':
2152
				case 'LABEL5':
2153
				case 'KEYWORD5':
2154
					$imapStatusFilter->flag(str_ireplace('KEYWORD','$LABEL',$criteria), $set=true);
2155
					$queryValid = $statusQueryValid =true;
2156
					break;
2157
				case 'NEW':
2158
					$imapStatusFilter->flag('RECENT', $set=true);
2159
					$imapStatusFilter->flag('SEEN', $set=false);
2160
					$queryValid = $statusQueryValid =true;
2161
					break;
2162
				case 'OLD':
2163
					$imapStatusFilter->flag('RECENT', $set=false);
2164
					$queryValid = $statusQueryValid =true;
2165
					break;
2166
// operate only on system flags
2167
//        $systemflags = array(
2168
//            'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
2169
//        );
2170
				case 'UNANSWERED':
2171
					$imapStatusFilter->flag('ANSWERED', $set=false);
2172
					$queryValid = $statusQueryValid =true;
2173
					break;
2174
				case 'UNDELETED':
2175
					$imapFilter->flag('DELETED', $set=false);
2176
					$queryValid = true;
2177
					break;
2178
				case 'UNFLAGGED':
2179
					$imapStatusFilter->flag('FLAGGED', $set=false);
2180
					$queryValid = $statusQueryValid =true;
2181
					break;
2182
				case 'UNREAD':
2183
				case 'UNSEEN':
2184
					$imapStatusFilter->flag('SEEN', $set=false);
2185
					$queryValid = $statusQueryValid =true;
2186
					break;
2187
				case 'UNLABEL1':
2188
				case 'UNKEYWORD1':
2189
				case 'UNLABEL2':
2190
				case 'UNKEYWORD2':
2191
				case 'UNLABEL3':
2192
				case 'UNKEYWORD3':
2193
				case 'UNLABEL4':
2194
				case 'UNKEYWORD4':
2195
				case 'UNLABEL5':
2196
				case 'UNKEYWORD5':
2197
					$imapStatusFilter->flag(str_ireplace(array('UNKEYWORD','UNLABEL'),'$LABEL',$criteria), $set=false);
2198
					$queryValid = $statusQueryValid =true;
2199
					break;
2200
				default:
2201
					$statusQueryValid = false;
2202
			}
2203
			if ($statusQueryValid)
2204
			{
2205
				$imapFilter->andSearch($imapStatusFilter);
2206
			}
2207
		}
2208
2209
2210
		//error_log(__METHOD__.' ('.__LINE__.') '.print_r($_criterias, true));
2211
		$imapSearchFilter = new Horde_Imap_Client_Search_Query();
2212
		$imapSearchFilter->charset('UTF-8');
2213
2214
		if(!empty($_criterias['string'])) {
2215
			$criteria = strtoupper($_criterias['type']);
2216
			switch ($criteria) {
2217
				case 'BYDATE':
2218
				case 'QUICK':
2219
				case 'QUICKWITHCC':
2220
					$imapSearchFilter->headerText('SUBJECT', $_criterias['string'], $not=false);
2221
					//$imapSearchFilter->charset('UTF-8');
2222
					$imapFilter2 = new Horde_Imap_Client_Search_Query();
2223
					$imapFilter2->charset('UTF-8');
2224
					if($this->isSentFolder($_folder)) {
2225
						$imapFilter2->headerText('TO', $_criterias['string'], $not=false);
2226
					} else {
2227
						$imapFilter2->headerText('FROM', $_criterias['string'], $not=false);
2228
					}
2229
					if ($_supportsOrInQuery)
2230
					{
2231
						$imapSearchFilter->orSearch($imapFilter2);
2232
					}
2233
					else
2234
					{
2235
						$imapSearchFilter->andSearch($imapFilter2);
2236
					}
2237
					if ($_supportsOrInQuery && $criteria=='QUICKWITHCC')
2238
					{
2239
						$imapFilter3 = new Horde_Imap_Client_Search_Query();
2240
						$imapFilter3->charset('UTF-8');
2241
						$imapFilter3->headerText('CC', $_criterias['string'], $not=false);
2242
						$imapSearchFilter->orSearch($imapFilter3);
2243
					}
2244
					$queryValid = true;
2245
					break;
2246
				case 'LARGER':
2247
				case 'SMALLER':
2248
					if (strlen(trim($_criterias['string'])) != strlen((float) trim($_criterias['string'])))
2249
					{
2250
						//examine string to evaluate size
2251
						$unit = strtoupper(trim(substr(trim($_criterias['string']),strlen((float) trim($_criterias['string'])))));
2252
						$multipleBy = array('KB'=>1024,'K'=>1024,
2253
											'MB'=>1024*1000,'M'=>1024*1000,
2254
											'GB'=>1024*1000*1000,'G'=>1024*1000*1000,
2255
											'TB'=>1024*1000*1000*1000,'T'=>1024*1000*1000*1000);
2256
						$numberinBytes=(float)$_criterias['string'];
2257
						if (isset($multipleBy[$unit])) $numberinBytes=(float)$_criterias['string']*$multipleBy[$unit];
2258
						//error_log(__METHOD__.__LINE__.'#'.$_criterias['string'].'->'.(float)$_criterias['string'].'#'.$unit.' ='.$numberinBytes);
2259
						$_criterias['string']=$numberinBytes;
2260
					}
2261
					$imapSearchFilter->size( $_criterias['string'], ($criteria=='LARGER'?true:false), $not=false);
2262
					//$imapSearchFilter->charset('UTF-8');
2263
					$queryValid = true;
2264
					break;
2265
				case 'FROM':
2266
				case 'TO':
2267
				case 'CC':
2268
				case 'BCC':
2269
				case 'SUBJECT':
2270
					$imapSearchFilter->headerText($criteria, $_criterias['string'], $not=false);
2271
					//$imapSearchFilter->charset('UTF-8');
2272
					$queryValid = true;
2273
					break;
2274
				case 'BODY':
2275
				case 'TEXT':
2276
					$imapSearchFilter->text($_criterias['string'],($criteria=='BODY'?true:false), $not=false);
2277
					//$imapSearchFilter->charset('UTF-8');
2278
					$queryValid = true;
2279
					break;
2280
				case 'SINCE':
2281
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2282
					$queryValid = true;
2283
					break;
2284
				case 'BEFORE':
2285
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2286
					$queryValid = true;
2287
					break;
2288
				case 'ON':
2289
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2290
					$queryValid = true;
2291
					break;
2292
			}
2293
		}
2294
		if ($statusQueryValid && !$queryValid) $queryValid=true;
2295
		if ($queryValid) $imapFilter->andSearch($imapSearchFilter);
2296
2297
		if (isset($_criterias['range']) && !empty($_criterias['range']))
2298
		{
2299
			$rangeValid = false;
2300
			$imapRangeFilter = new Horde_Imap_Client_Search_Query();
2301
			$imapRangeFilter->charset('UTF-8');
2302
			$criteria = strtoupper($_criterias['range']);
2303
			if ($_criterias['range'] == "BETWEEN" && isset($_criterias['since']) && isset($_criterias['before']) && $_criterias['since']==$_criterias['before'])
2304
			{
2305
				$_criterias['date']=$_criterias['since'];
2306
				unset($_criterias['since']);
2307
				unset($_criterias['before']);
2308
				$criteria=$_criterias['range']='ON';
2309
			}
2310
			switch ($criteria) {
2311
				case 'BETWEEN':
2312
					//try to be smart about missing
2313
					//enddate
2314
					if ($_criterias['since'])
2315
					{
2316
						$imapRangeFilter->dateSearch(new DateTime($_criterias['since']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2317
						$rangeValid = true;
2318
					}
2319
					//startdate
2320
					if ($_criterias['before'])
2321
					{
2322
						$imapRangeFilter2 = new Horde_Imap_Client_Search_Query();
2323
						$imapRangeFilter2->charset('UTF-8');
2324
						//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2325
						$_criterias['before'] = date("d-M-Y",DateTime::to($_criterias['before'],'ts')+(3600*24));
2326
						$imapRangeFilter2->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2327
						$imapRangeFilter->andSearch($imapRangeFilter2);
2328
						$rangeValid = true;
2329
					}
2330
					break;
2331
				case 'SINCE'://enddate
2332
					$imapRangeFilter->dateSearch(new DateTime(($_criterias['since']?$_criterias['since']:$_criterias['date'])), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2333
					$rangeValid = true;
2334
					break;
2335
				case 'BEFORE'://startdate
2336
					//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2337
					$_criterias['before'] = date("d-M-Y",DateTime::to(($_criterias['before']?$_criterias['before']:$_criterias['date']),'ts')+(3600*24));
2338
					$imapRangeFilter->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2339
					$rangeValid = true;
2340
					break;
2341
				case 'ON':
2342
					$imapRangeFilter->dateSearch(new DateTime($_criterias['date']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2343
					$rangeValid = true;
2344
					break;
2345
			}
2346
			if ($rangeValid && !$queryValid) $queryValid=true;
2347
			if ($rangeValid) $imapFilter->andSearch($imapRangeFilter);
2348
		}
2349
		if (self::$debug)
2350
		{
2351
			//$imapFilter->charset('UTF-8');
2352
			$query_str = $imapFilter->build();
0 ignored issues
show
Unused Code introduced by
The assignment to $query_str is dead and can be removed.
Loading history...
Deprecated Code introduced by
The function Horde_Imap_Client_Search_Query::build() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2352
			$query_str = /** @scrutinizer ignore-deprecated */ $imapFilter->build();
Loading history...
2353
			//error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query'].' created by Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2354
		}
2355
		if($queryValid==false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2356
			$imapFilter->flag('DELETED', $set=false);
2357
			return $imapFilter;
2358
		} else {
2359
			return $imapFilter;
2360
		}
2361
	}
2362
2363
	/**
2364
	 * decode header (or envelope information)
2365
	 * if array given, note that only values will be converted
2366
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2367
	 * @param  boolean|string $_tryIDNConversion (true/false AND 'FORCE'): try IDN Conversion on domainparts of emailADRESSES
2368
	 * @return mixed - based on the input type
2369
	 */
2370
	static function decode_header($_string, $_tryIDNConversion=false)
2371
	{
2372
		if (is_array($_string))
2373
		{
2374
			foreach($_string as $k=>$v)
2375
			{
2376
				$_string[$k] = self::decode_header($v, $_tryIDNConversion);
2377
			}
2378
			return $_string;
2379
		}
2380
		else
2381
		{
2382
			$_string = Mail\Html::decodeMailHeader($_string,self::$displayCharset);
2383
			$test = @json_encode($_string);
2384
			//error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#');
2385
			if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2386
			{
2387
				// try to fix broken utf8
2388
				$x = utf8_encode($_string);
2389
				$test = @json_encode($x);
2390
				if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2391
				{
2392
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
2393
					$_string = (function_exists('mb_convert_encoding')?mb_convert_encoding($_string,'UTF-8','UTF-8'):(function_exists('iconv')?@iconv("UTF-8","UTF-8//IGNORE",$_string):$_string));
2394
				}
2395
				else
2396
				{
2397
					$_string = $x;
2398
				}
2399
			}
2400
2401
			if ($_tryIDNConversion===true && stripos($_string,'@')!==false)
2402
			{
2403
				$rfcAddr = self::parseAddressList($_string);
2404
				$stringA = array();
2405
				foreach ($rfcAddr as $_rfcAddr)
2406
				{
2407
					if (!$_rfcAddr->valid)
2408
					{
2409
						$stringA = array();
2410
						break; // skip idna conversion if we encounter an error here
2411
					}
2412
					try {
2413
						$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox,Horde_Idna::decode($_rfcAddr->host),$_rfcAddr->personal);
2414
					}
2415
					// if Idna conversation fails, leave address unchanged
2416
					catch(\Exception $e) {
2417
						unset($e);
2418
						$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox, $_rfcAddr->host, $_rfcAddr->personal);
2419
					}
2420
				}
2421
				if (!empty($stringA)) $_string = implode(',',$stringA);
2422
			}
2423
			if ($_tryIDNConversion==='FORCE')
2424
			{
2425
				//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_string.'='.Horde_Idna::decode($_string));
2426
				$_string = Horde_Idna::decode($_string);
2427
			}
2428
			return $_string;
2429
		}
2430
	}
2431
2432
	/**
2433
	 * decode subject
2434
	 * if array given, note that only values will be converted
2435
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2436
	 * @param  boolean $decode try decoding
2437
	 * @return mixed - based on the input type
2438
	 */
2439
	function decode_subject($_string,$decode=true)
2440
	{
2441
		#$string = $_string;
2442
		if($_string=='NIL')
2443
		{
2444
			return 'No Subject';
2445
		}
2446
		if ($decode) $_string = self::decode_header($_string);
2447
		// make sure its utf-8
2448
		$test = @json_encode($_string);
2449
		if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2450
		{
2451
			$_string = utf8_encode($_string);
2452
		}
2453
		return $_string;
2454
2455
	}
2456
2457
	/**
2458
	 * decodeEntityFolderName - remove html entities
2459
	 * @param string _folderName the foldername
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_folderName was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2460
	 * @return string the converted string
2461
	 */
2462
	function decodeEntityFolderName($_folderName)
2463
	{
2464
		return html_entity_decode($_folderName, ENT_QUOTES, self::$displayCharset);
2465
	}
2466
2467
	/**
2468
	 * convert a mailboxname from utf7-imap to displaycharset
2469
	 *
2470
	 * @param string _folderName the foldername
2471
	 * @return string the converted string
2472
	 */
2473
	function encodeFolderName($_folderName)
2474
	{
2475
		return Translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset);
2476
	}
2477
2478
	/**
2479
	 * convert the foldername from display charset to UTF-7
2480
	 *
2481
	 * @param string _parent the parent foldername
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_parent was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2482
	 * @return ISO-8859-1 / UTF7-IMAP encoded string
0 ignored issues
show
Documentation Bug introduced by
The doc comment ISO-8859-1 at position 0 could not be parsed: Unknown type name 'ISO-8859-1' at position 0 in ISO-8859-1.
Loading history...
2483
	 */
2484
	function _encodeFolderName($_folderName) {
2485
		return Translation::convert($_folderName, self::$displayCharset, 'ISO-8859-1');
2486
		#return Translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
2487
	}
2488
2489
	/**
2490
	 * create a new folder under given parent folder
2491
	 *
2492
	 * @param string _parent the parent foldername
2493
	 * @param string _folderName the new foldername
2494
	 * @param string _error pass possible error back to caller
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2495
	 *
2496
	 * @return mixed name of the newly created folder or false on error
2497
	 */
2498
	function createFolder($_parent, $_folderName, &$_error)
2499
	{
2500
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."->"."$_parent, $_folderName called from:".function_backtrace());
2501
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2502
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2503
2504
		if(empty($parent)) {
2505
			$newFolderName = $folderName;
2506
		} else {
2507
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2508
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2509
		}
2510
		if (empty($newFolderName)) return false;
2511
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.'->'.$newFolderName);
2512
		if ($this->folderExists($newFolderName,true))
2513
		{
2514
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." Folder $newFolderName already exists.");
2515
			return $newFolderName;
2516
		}
2517
		try
2518
		{
2519
			$opts = array();
2520
			// if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too
2521
			if (isset(self::$specialUseFolders[$newFolderName]))
2522
			{
2523
				$opts['special_use'] = self::$specialUseFolders[$newFolderName];
2524
			}
2525
			$this->icServer->createMailbox($newFolderName, $opts);
2526
		}
2527
		catch (\Exception $e)
2528
		{
2529
			$_error = lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $newFolderName. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2529
			$_error = /** @scrutinizer ignore-call */ lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2530
			error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details.') Namespace:'.array2string($this->icServer->getNameSpaces()).function_backtrace());
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Imap_Client_Base::getNamespaces() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2530
			error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details.') Namespace:'.array2string(/** @scrutinizer ignore-deprecated */ $this->icServer->getNameSpaces()).function_backtrace());
Loading history...
2531
			return false;
2532
		}
2533
		try
2534
		{
2535
			$this->icServer->subscribeMailbox($newFolderName);
2536
		}
2537
		catch (\Exception $e)
2538
		{
2539
			error_log(__METHOD__.' ('.__LINE__.') '.' subscribe to new folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details);
2540
			return false;
2541
		}
2542
2543
		return $newFolderName;
2544
	}
2545
2546
	/**
2547
	 * rename a folder
2548
	 *
2549
	 * @param string _oldFolderName the old foldername
2550
	 * @param string _parent the parent foldername
2551
	 * @param string _folderName the new foldername
2552
	 *
2553
	 * @return mixed name of the newly created folder or false on error
2554
	 * @throws Exception
2555
	 */
2556
	function renameFolder($_oldFolderName, $_parent, $_folderName)
2557
	{
2558
		$oldFolderName	= $_oldFolderName;//$this->_encodeFolderName($_oldFolderName);
2559
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2560
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2561
2562
		if(empty($parent)) {
2563
			$newFolderName = $folderName;
2564
		} else {
2565
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2566
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2567
		}
2568
		if (self::$debug) error_log("create folder: $newFolderName");
2569
		try
2570
		{
2571
			$this->icServer->renameMailbox($oldFolderName, $newFolderName);
2572
		}
2573
		catch (\Exception $e)
2574
		{
2575
			throw new Exception(__METHOD__." failed for $oldFolderName (rename to: $newFolderName) with error:".$e->getMessage());;
2576
		}
2577
		// clear FolderExistsInfoCache
2578
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folderInfo seems to be never defined.
Loading history...
2579
2580
		return $newFolderName;
2581
2582
	}
2583
2584
	/**
2585
	 * delete an existing folder
2586
	 *
2587
	 * @param string _folderName the name of the folder to be deleted
2588
	 *
2589
	 * @return bool true on success, PEAR Error on failure
2590
	 * @throws Exception
2591
	 */
2592
	function deleteFolder($_folderName)
2593
	{
2594
		//$folderName = $this->_encodeFolderName($_folderName);
2595
		try
2596
		{
2597
			$this->icServer->subscribeMailbox($_folderName,false);
2598
			$this->icServer->deleteMailbox($_folderName);
2599
		}
2600
		catch (\Exception $e)
2601
		{
2602
			throw new Exception("Deleting Folder $_folderName failed! Error:".$e->getMessage());;
2603
		}
2604
		// clear FolderExistsInfoCache
2605
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folderInfo seems to be never defined.
Loading history...
2606
2607
		return true;
2608
	}
2609
2610
	/**
2611
	 * fetchUnSubscribedFolders: get unsubscribed IMAP folder list
2612
	 *
2613
	 * returns an array of unsubscribed IMAP folder names.
2614
	 *
2615
	 * @return array with folder names. eg.: 1 => INBOX/TEST
2616
	 */
2617
	function fetchUnSubscribedFolders()
2618
	{
2619
		$unSubscribedMailboxes = $this->icServer->listUnSubscribedMailboxes();
2620
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($unSubscribedMailboxes));
2621
		return $unSubscribedMailboxes;
2622
	}
2623
2624
	/**
2625
	 * get IMAP folder objects
2626
	 *
2627
	 * returns an array of IMAP folder objects. Put INBOX folder in first
2628
	 * position. Preserves the folder seperator for later use. The returned
2629
	 * array is indexed using the foldername. Use cachedObjects when retrieving subscribedFolders
2630
	 *
2631
	 * @param boolean _subscribedOnly  get subscribed or all folders
2632
	 * @param boolean _getCounters   get get messages counters
2633
	 * @param boolean _alwaysGetDefaultFolders  this triggers to ignore the possible notavailableautofolders - preference
2634
	 *			as activeSync needs all folders like sent, trash, drafts, templates and outbox - if not present devices may crash
2635
	 *			-> autoFolders should be created if needed / accessed (if possible and configured)
2636
	 * @param boolean _useCacheIfPossible  - if set to false cache will be ignored and reinitialized
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_useCacheIfPossible was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2637
	 *
2638
	 * @return array with folder objects. eg.: INBOX => {inbox object}
2639
	 */
2640
	function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false,$_useCacheIfPossible=true)
2641
	{
2642
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' ServerID:'.$this->icServer->ImapServerId.", subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders, _useCacheIfPossible:$_useCacheIfPossible");
2643
		if (self::$debugTimes) $starttime = microtime (true);
2644
		static $folders2return;
2645
		//$_subscribedOnly=false;
2646
		// always use static on single request if info is available;
2647
		// so if you require subscribed/unsubscribed results on a single request you MUST
2648
		// set $_useCacheIfPossible to false !
2649
		if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2650
		{
2651
			if (self::$debugTimes) self::logRunTimes($starttime,null,'using static',__METHOD__.' ('.__LINE__.') ');
2652
			return $folders2return[$this->icServer->ImapServerId];
2653
		}
2654
2655
		if ($_subscribedOnly && $_getCounters===false)
2656
		{
2657
			if (is_null($folders2return)) $folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
2658
			if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2659
			{
2660
				//error_log(__METHOD__.' ('.__LINE__.') '.' using Cached folderObjects'.array2string($folders2return[$this->icServer->ImapServerId]));
2661
				if (self::$debugTimes) self::logRunTimes($starttime,null,'from Cache',__METHOD__.' ('.__LINE__.') ');
2662
				return $folders2return[$this->icServer->ImapServerId];
2663
			}
2664
		}
2665
		// use $folderBasicInfo for holding attributes and other basic folderinfo $folderBasicInfo[$this->icServer->ImapServerId]
2666
		static $folderBasicInfo;
2667
		if (is_null($folderBasicInfo)||!isset($folderBasicInfo[$this->icServer->ImapServerId])) $folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
2668
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($folderBasicInfo[$this->icServer->ImapServerId])));
2669
2670
		$delimiter = $this->getHierarchyDelimiter();
2671
2672
		$inboxData = new \stdClass;
2673
		$inboxData->name 		= 'INBOX';
2674
		$inboxData->folderName		= 'INBOX';
2675
		$inboxData->displayName		= lang('INBOX');
2676
		$inboxData->delimiter 		= $delimiter;
2677
		$inboxData->shortFolderName	= 'INBOX';
2678
		$inboxData->shortDisplayName	= lang('INBOX');
2679
		$inboxData->subscribed = true;
2680
		if($_getCounters == true) {
2681
			$inboxData->counter = $this->getMailBoxCounters('INBOX');
2682
		}
2683
		// force unsubscribed by preference showAllFoldersInFolderPane
2684
		if ($_subscribedOnly == true &&
2685
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2686
			$this->mailPreferences['showAllFoldersInFolderPane']==1)
2687
		{
2688
			$_subscribedOnly = false;
2689
		}
2690
		$inboxFolderObject = array('INBOX' => $inboxData);
2691
2692
		//$nameSpace = $this->icServer->getNameSpaces();
2693
		$nameSpace = $this->_getNameSpaces();
2694
		$fetchedAllInOneGo = false;
2695
		$subscribedFoldersForCache = $foldersNameSpace = array();
2696
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2697
		if (is_array($nameSpace))
0 ignored issues
show
introduced by
The condition is_array($nameSpace) is always true.
Loading history...
2698
		{
2699
			foreach($nameSpace as $k => $singleNameSpace) {
2700
				$type = $singleNameSpace['type'];
2701
				// the following line (assumption that for the same namespace the delimiter should be equal) may be wrong
2702
				$foldersNameSpace[$type]['delimiter']  = $singleNameSpace['delimiter'];
2703
2704
				if(is_array($singleNameSpace)&&$fetchedAllInOneGo==false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2705
					// fetch and sort the subscribed folders
2706
					// we alway fetch the subscribed, as this provides the only way to tell
2707
					// if a folder is subscribed or not
2708
					if ($_subscribedOnly == true)
2709
					{
2710
						try
2711
						{
2712
							$subscribedMailboxes = $this->icServer->listSubscribedMailboxes('',0,true);
2713
							if (!empty($subscribedMailboxes))
2714
							{
2715
								$fetchedAllInOneGo = true;
2716
							}
2717
							else
2718
							{
2719
								$subscribedMailboxes = $this->icServer->listSubscribedMailboxes($singleNameSpace['prefix'],0,true);
2720
							}
2721
						}
2722
						catch(Exception $e)
2723
						{
2724
							continue;
2725
						}
2726
						//echo "subscribedMailboxes";_debug_array($subscribedMailboxes);
2727
						$subscribedFoldersPerNS = (!empty($subscribedMailboxes)?array_keys($subscribedMailboxes):array());
2728
						//if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']);
2729
						//_debug_array($foldersNameSpace);
2730
						//error_log(__METHOD__.__LINE__.array2string($singleNameSpace).':#:'.array2string($subscribedFoldersPerNS));
2731
						if (!empty($subscribedFoldersPerNS) && !empty($subscribedMailboxes))
2732
						{
2733
							//error_log(__METHOD__.' ('.__LINE__.') '." $type / subscribed:". array2string($subscribedMailboxes));
2734
							foreach ($subscribedMailboxes as $k => $finfo)
0 ignored issues
show
Comprehensibility Bug introduced by
$k is overwriting a variable from outer foreach loop.
Loading history...
2735
							{
2736
								//error_log(__METHOD__.__LINE__.$k.':#:'.array2string($finfo));
2737
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$k]=
2738
								$folderBasicInfo[$this->icServer->ImapServerId][$k]=array(
2739
									'MAILBOX'=>$finfo['MAILBOX'],
2740
									'ATTRIBUTES'=>$finfo['ATTRIBUTES'],
2741
									'delimiter'=>$finfo['delimiter'],//lowercase for some reason???
2742
									'SUBSCRIBED'=>$finfo['SUBSCRIBED'],//seeded by getMailboxes
2743
								);
2744
								if (empty($foldersNameSpace[$type]['subscribed']) || !in_array($k,$foldersNameSpace[$type]['subscribed']))
2745
								{
2746
									$foldersNameSpace[$type]['subscribed'][] = $k;
2747
								}
2748
								if (empty($foldersNameSpace[$type]['all']) || !in_array($k,$foldersNameSpace[$type]['all']))
2749
								{
2750
									$foldersNameSpace[$type]['all'][] = $k;
2751
								}
2752
							}
2753
						}
2754
						//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($foldersNameSpace[$type]['subscribed']));
2755
						if (!is_array($foldersNameSpace[$type]['all'])) $foldersNameSpace[$type]['all'] = array();
2756
						if ($_subscribedOnly == true && !empty($foldersNameSpace[$type]['subscribed'])) {
2757
							continue;
2758
						}
2759
2760
					}
2761
2762
					// fetch and sort all folders
2763
					//echo $type.'->'.$singleNameSpace['prefix'].'->'.($type=='shared'?0:2)."<br>";
2764
					try
2765
					{
2766
						// calling with 2 lists all mailboxes on that level with fetches all
2767
						// we switch to all, to avoid further calls for subsequent levels
2768
						// that may produce problems, when encountering recursions probably
2769
						// horde is handling that, so we do not; keep that in mind!
2770
						//$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],2,true);
2771
						$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],0,true);
2772
					}
2773
					catch (\Exception $e)
2774
					{
2775
						error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve all Boxes:'.$e->getMessage());
2776
						$allMailboxesExt = array();
2777
					}
2778
					if (!is_array($allMailboxesExt))
2779
					{
2780
						//error_log(__METHOD__.' ('.__LINE__.') '.' Expected Array but got:'.array2string($allMailboxesExt). 'Type:'.$type.' Prefix:'.$singleNameSpace['prefix']);
2781
						continue;
2782
						//$allMailboxesExt=array();
2783
					}
2784
2785
					//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($allMailboxesExt));
2786
					foreach ($allMailboxesExt as $mbx) {
2787
						if (!isset($folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2788
						{
2789
							$folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]=array(
2790
								'MAILBOX'=>$mbx['MAILBOX'],
2791
								'ATTRIBUTES'=>$mbx['ATTRIBUTES'],
2792
								'delimiter'=>$mbx['delimiter'],//lowercase for some reason???
2793
								'SUBSCRIBED'=>$mbx['SUBSCRIBED'],//seeded by getMailboxes
2794
							);
2795
							if ($mbx['SUBSCRIBED'] && !isset($subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2796
							{
2797
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']] = $folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']];
2798
							}
2799
						}
2800
						if ($mbx['SUBSCRIBED'] && (empty($foldersNameSpace[$type]['subscribed']) || !in_array($mbx['MAILBOX'],$foldersNameSpace[$type]['subscribed'])))
2801
						{
2802
							$foldersNameSpace[$type]['subscribed'][] = $mbx['MAILBOX'];
2803
						}
2804
						//echo __METHOD__;_debug_array($mbx);
2805
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
2806
						if (isset($allMailBoxesExtSorted[$mbx['MAILBOX']])||
2807
							isset($allMailBoxesExtSorted[$mbx['MAILBOX'].$foldersNameSpace[$type]['delimiter']])||
2808
							(substr($mbx['MAILBOX'],-1)==$foldersNameSpace[$type]['delimiter'] && isset($allMailBoxesExtSorted[substr($mbx['MAILBOX'],0,-1)]))
2809
						) continue;
2810
2811
						//echo '#'.$mbx['MAILBOX'].':'.array2string($mbx)."#<br>";
2812
						$allMailBoxesExtSorted[$mbx['MAILBOX']] = $mbx;
2813
					}
2814
					if (is_array($allMailBoxesExtSorted)) ksort($allMailBoxesExtSorted);
2815
					//_debug_array(array_keys($allMailBoxesExtSorted));
2816
					$allMailboxes = array();
2817
					foreach ((array)$allMailBoxesExtSorted as $mbx) {
2818
						if (!in_array($mbx['MAILBOX'],$allMailboxes)) $allMailboxes[] = $mbx['MAILBOX'];
2819
						//echo "Result:";_debug_array($allMailboxes);
2820
					}
2821
					$foldersNameSpace[$type]['all'] = $allMailboxes;
2822
					if (is_array($foldersNameSpace[$type]['all'])) sort($foldersNameSpace[$type]['all']);
2823
				}
2824
			}
2825
		}
2826
		//subscribed folders may be used in getFolderStatus
2827
		Cache::setCache(Cache::INSTANCE,'email','subscribedFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$subscribedFoldersForCache,$expiration=60*60*1);
2828
		//echo "<br>FolderNameSpace To Process:";_debug_array($foldersNameSpace);
2829
		$autoFolderObjects = $folders = array();
2830
		$autofolder_exists = array();
2831
		foreach( array('personal', 'others', 'shared') as $type) {
2832
			if(isset($foldersNameSpace[$type])) {
2833
				if($_subscribedOnly) {
2834
					if( !empty($foldersNameSpace[$type]['subscribed']) ) $listOfFolders = $foldersNameSpace[$type]['subscribed'];
2835
				} else {
2836
					if( !empty($foldersNameSpace[$type]['all'])) $listOfFolders = $foldersNameSpace[$type]['all'];
2837
				}
2838
				foreach((array)$listOfFolders as $folderName) {
2839
					//echo "<br>FolderToCheck:$folderName<br>";
2840
					//error_log(__METHOD__.__LINE__.'#Delimiter:'.$delimiter.':#'.$folderName);
2841
					if ($_subscribedOnly && empty($foldersNameSpace[$type]['all'])) continue;//when subscribedonly, we fetch all folders in one go.
2842
					if($_subscribedOnly && !(in_array($folderName, $foldersNameSpace[$type]['all'])||in_array($folderName.$foldersNameSpace[$type]['delimiter'], $foldersNameSpace[$type]['all']))) {
2843
						#echo "$folderName failed to be here <br>";
2844
						continue;
2845
					}
2846
					if (isset($folders[$folderName])) continue;
2847
					if (isset($autoFolderObjects[$folderName])) continue;
2848
					if (empty($delimiter)||$delimiter != $foldersNameSpace[$type]['delimiter']) $delimiter = $foldersNameSpace[$type]['delimiter'];
2849
					$folderParts = explode($delimiter, $folderName);
2850
					$shortName = array_pop($folderParts);
2851
2852
					$folderObject = new \stdClass;
2853
					$folderObject->delimiter	= $delimiter;
2854
					$folderObject->folderName	= $folderName;
2855
					$folderObject->shortFolderName	= $shortName;
2856
					if(!$_subscribedOnly) {
2857
						#echo $folderName."->".$type."<br>";
2858
						#_debug_array($foldersNameSpace[$type]['subscribed']);
2859
						$folderObject->subscribed = in_array($folderName, (array)$foldersNameSpace[$type]['subscribed']);
2860
					}
2861
2862
					if($_getCounters == true) {
2863
						//error_log(__METHOD__.' ('.__LINE__.') '.' getCounter forFolder:'.$folderName);
2864
						$folderObject->counter = $this->getMailBoxCounters($folderName);
2865
					}
2866
					if(strtoupper($folderName) == 'INBOX') {
2867
						$folderName = 'INBOX';
2868
						$folderObject->folderName	= 'INBOX';
2869
						$folderObject->shortFolderName	= 'INBOX';
2870
						$folderObject->displayName	= lang('INBOX');
2871
						$folderObject->shortDisplayName = lang('INBOX');
2872
						$folderObject->subscribed	= true;
2873
					// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
2874
					} elseif (in_array($shortName,self::$autoFolders)) {
2875
						$tmpfolderparts = explode($delimiter,$folderObject->folderName);
2876
						array_pop($tmpfolderparts);
2877
						$folderObject->displayName = implode($delimiter,$tmpfolderparts).$delimiter.lang($shortName);
2878
						$folderObject->shortDisplayName = lang($shortName);
2879
						unset($tmpfolderparts);
2880
					} else {
2881
						$folderObject->displayName = $folderObject->folderName;
2882
						$folderObject->shortDisplayName = $shortName;
2883
					}
2884
					//$folderName = $folderName;
2885
					if (in_array($shortName,self::$autoFolders)&&self::searchValueInFolderObjects($shortName,$autoFolderObjects)===false) {
2886
						$autoFolderObjects[$folderName] = $folderObject;
2887
					} else {
2888
						$folders[$folderName] = $folderObject;
2889
					}
2890
					//error_log(__METHOD__.' ('.__LINE__.') '.':'.$folderObject->folderName);
2891
					if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders ();
2892
					if (isset(self::$specialUseFolders[$folderName]))
2893
					{
2894
						$autofolder_exists[$folderName] = self::$specialUseFolders[$folderName];
2895
					}
2896
				}
2897
			}
2898
		}
2899
		if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) {
2900
			uasort($autoFolderObjects,array($this,"sortByAutoFolderPos"));
2901
		}
2902
		// check if some standard folders are missing and need to be created
2903
		if (count($autofolder_exists) < count(self::$autoFolders) && $this->check_create_autofolders($autofolder_exists))
2904
		{
2905
			// if new folders have been created, re-read folders ignoring the cache
2906
			return $this->getFolderObjects($_subscribedOnly, $_getCounters, $_alwaysGetDefaultFolders, false);	// false = do NOT use cache
2907
		}
2908
		if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName"));
2909
		//$folders2return = array_merge($autoFolderObjects,$folders);
2910
		//_debug_array($folders2return); #exit;
2911
		$folders2return[$this->icServer->ImapServerId] = array_merge((array)$inboxFolderObject,(array)$autoFolderObjects,(array)$folders);
2912
		if (($_subscribedOnly && $_getCounters===false) ||
2913
			($_subscribedOnly == false && $_getCounters===false &&
2914
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2915
			$this->mailPreferences['showAllFoldersInFolderPane']==1))
2916
		{
2917
			Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return,$expiration=60*60*1);
2918
		}
2919
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1);
2920
		if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') ');
2921
		return $folders2return[$this->icServer->ImapServerId];
2922
	}
2923
2924
	/**
2925
	 * Get IMAP folders for a mailbox
2926
	 *
2927
	 * @param string $_nodePath = null folder name to fetch from IMAP,
2928
	 *			null means all folders
2929
	 * @param boolean $_onlyTopLevel if set to true only top level objects
2930
	 *			will be return and nodePath would be ignored
2931
	 * @param int $_search = 2 search restriction in given mailbox
2932
	 *	0:All folders recursively from the $_nodePath
2933
	 *  1:Only folder of specified $_nodePath
2934
	 *	2:All folders of $_nodePath in the same heirachy level
2935
	 *
2936
	 * @param boolean $_subscribedOnly = false Command to fetch only the subscribed folders
2937
	 * @param boolean $_getCounter = false Command to fetch mailbox counter
2938
	 *
2939
	 * @return array arrays of folders
2940
	 */
2941
	function getFolderArrays ($_nodePath = null, $_onlyTopLevel = false, $_search= 2, $_subscribedOnly = false, $_getCounter = false)
2942
	{
2943
		// delimiter
2944
		$delimiter = $this->getHierarchyDelimiter();
2945
2946
		$folders = $nameSpace =  array();
2947
		$nameSpaceTmp = $this->_getNameSpaces();
2948
		foreach($nameSpaceTmp as $k => $singleNameSpace) {
2949
			$nameSpace[$singleNameSpace['type']]=$singleNameSpace;
2950
		}
2951
		unset($nameSpaceTmp);
2952
2953
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2954
		// Get special use folders
2955
		if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders (); // Set self::$specialUseFolders
2956
		// topLevelQueries generally ignore the $_search param. Except for Config::examineNamespace
2957
		if ($_onlyTopLevel) // top level leaves
2958
		{
2959
			// Get top mailboxes of icServer
2960
			$topFolders = $this->icServer->getMailboxes("", 2, true);
2961
			// Trigger examination of namespace to retrieve
2962
			// folders located in other and shared; needed only for some servers
2963
			if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
2964
			if (self::$mailConfig['examineNamespace'])
2965
			{
2966
				$prefixes=array();
2967
				if (is_array($nameSpace))
0 ignored issues
show
introduced by
The condition is_array($nameSpace) is always true.
Loading history...
2968
				{
2969
					foreach($nameSpace as $k => $singleNameSpace) {
2970
						$type = $singleNameSpace['type'];
2971
2972
						if(is_array($singleNameSpace) && $singleNameSpace['prefix']){
2973
							$prefixes[$type] = $singleNameSpace['prefix'];
2974
							//regard extra care for nameSpacequeries when configured AND respect $_search
2975
							$result = $this->icServer->getMailboxes($singleNameSpace['prefix'], $_search==0?0:2, true);
2976
							if (is_array($result))
2977
							{
2978
								ksort($result);
2979
								$topFolders = array_merge($topFolders,$result);
2980
							}
2981
						}
2982
					}
2983
				}
2984
			}
2985
2986
			$autofolders = array();
2987
2988
			foreach(self::$specialUseFolders as $path => $folder)
2989
			{
2990
				if ($this->folderExists($path))
2991
				{
2992
					$autofolders[$folder] = $folder;
2993
				}
2994
			}
2995
			// Check if the special use folders are there, otherwise try to create them
2996
			if (count($autofolders) < count(self::$autoFolders) && $this->check_create_autofolders ($autofolders))
2997
			{
2998
				return $this->getFolderArrays ($_nodePath, $_onlyTopLevel, $_search, $_subscribedOnly, $_getCounter);
2999
			}
3000
3001
			// now process topFolders for next level
3002
			foreach ($topFolders as &$node)
3003
			{
3004
				$pattern = "/\\".$delimiter."/";
3005
				$reference = preg_replace($pattern, '', $node['MAILBOX']);
3006
				if(!empty($prefixes))
3007
				{
3008
					$reference = '';
3009
					$tmpArray = explode($delimiter,$node['MAILBOX']);
3010
					foreach($tmpArray as $p)
3011
					{
3012
						$reference = empty($reference)?$p:$reference.$delimiter.$p;
3013
					}
3014
				}
3015
				$mainFolder = $subFolders = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $mainFolder is dead and can be removed.
Loading history...
3016
3017
				if ($_subscribedOnly)
3018
				{
3019
					$mainFolder = $this->icServer->listSubscribedMailboxes($reference, 1, true);
3020
					$subFolders = $this->icServer->listSubscribedMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
3021
				}
3022
				else
3023
				{
3024
					$mainFolder = $this->icServer->getMailboxes($reference, 1, true);
3025
					$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
3026
				}
3027
3028
				if (is_array($mainFolder['INBOX']))
3029
				{
3030
					// Array container of auto folders
3031
					$aFolders = array();
3032
3033
					// Array container of non auto folders
3034
					$nFolders = array();
3035
3036
					foreach ((array)$subFolders as $path => $folder)
3037
					{
3038
						$folderInfo = self::pathToFolderData($folder['MAILBOX'], $folder['delimiter']);
3039
						if (in_array(trim($folderInfo['name']), $autofolders) || in_array(trim($folderInfo['name']), self::$autoFolders))
3040
						{
3041
							$aFolders [$path] = $folder;
3042
						}
3043
						else
3044
						{
3045
							$nFolders [$path] = $folder;
3046
						}
3047
					}
3048
					if (is_array($aFolders)) uasort ($aFolders, array($this,'sortByAutofolder'));
3049
					//ksort($aFolders);
3050
3051
					// Sort none auto folders base on mailbox name
3052
					uasort($nFolders,array($this,'sortByMailbox'));
3053
3054
					$subFolders = array_merge($aFolders,$nFolders);
3055
				}
3056
				else
3057
				{
3058
					if (is_array($subFolders)) ksort($subFolders);
3059
				}
3060
				$folders = array_merge($folders,(array)$mainFolder, (array)$subFolders);
3061
			}
3062
		}
3063
		elseif ($_nodePath) // single node
3064
		{
3065
			switch ($_search)
3066
			{
3067
				// Including children
3068
				case 0:
3069
				case 2:
3070
					$path = $_nodePath.''.$delimiter;
3071
					break;
3072
				// Node itself
3073
				// shouldn't contain next level delimiter
3074
				case 1:
3075
					$path = $_nodePath;
3076
					break;
3077
			}
3078
			if ($_subscribedOnly)
3079
			{
3080
				$folders = $this->icServer->listSubscribedMailboxes($path, $_search, true);
3081
			}
3082
			else
3083
			{
3084
				$folders = $this->icServer->getMailboxes($path, $_search, true);
3085
			}
3086
3087
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
3088
		}
3089
		elseif(!$_nodePath) // all
3090
		{
3091
			if ($_subscribedOnly)
3092
			{
3093
				$folders = $this->icServer->listSubscribedMailboxes('', 0, true);
3094
			}
3095
			else
3096
			{
3097
				$folders = $this->icServer->getMailboxes('', 0, true);
3098
			}
3099
		}
3100
		// only sort (autofolders, shared, others ...) when retrieving all folders or toplevelquery
3101
		if ($_onlyTopLevel || !$_nodePath)
3102
		{
3103
			// SORTING FOLDERS
3104
			//self::$debugTimes=true;
3105
			if (self::$debugTimes) $starttime = microtime (true);
3106
			// Merge of all auto folders and specialusefolders
3107
			$autoFoldersTmp = array_unique((array_merge(self::$autoFolders, array_values(self::$specialUseFolders))));
3108
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
3109
			$tmpFolders = $folders;
3110
			$inboxFolderObject=$inboxSubFolderObjects=$autoFolderObjects=$typeFolderObject=$mySpecialUseFolders=array();
3111
			$googleMailFolderObject=$googleAutoFolderObjects=$googleSubFolderObjects=array();
3112
			$isGoogleMail=false;
3113
			foreach($autoFoldersTmp as $afk=>$aF)
3114
			{
3115
				if (!isset($mySpecialUseFolders[$aF]) && $aF) $mySpecialUseFolders[$aF]=$this->getFolderByType($aF,false);
3116
				//error_log($afk.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3117
			}
3118
			//error_log(array2string($mySpecialUseFolders));
3119
			foreach ($tmpFolders as $k => $f) {
3120
				$sorted=false;
3121
				if (strtoupper(substr($k,0,5))=='INBOX') {
3122
					if (strtoupper($k)=='INBOX') {
3123
						//error_log(__METHOD__.__LINE__.':'.strtoupper(substr($k,0,5)).':'.$k);
3124
						$inboxFolderObject[$k]=$f;
3125
						unset($folders[$k]);
3126
						$sorted=true;
3127
					} else {
3128
						$isAutoFolder=false;
3129
						foreach($autoFoldersTmp as $afk=>$aF)
3130
						{
3131
							//error_log(__METHOD__.__LINE__.$k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3132
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3133
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter || //k may be child of an autofolder
3134
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3135
							{
3136
								//error_log(__METHOD__.__LINE__.$k.'->'.$mySpecialUseFolders[$aF]);
3137
								$isAutoFolder=true;
3138
								$autoFolderObjects[$k]=$f;
3139
								break;
3140
							}
3141
						}
3142
						if ($isAutoFolder==false) $inboxSubFolderObjects[$k]=$f;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
3143
						unset($folders[$k]);
3144
						$sorted=true;
3145
					}
3146
				} elseif (strtoupper(substr($k,0,13))=='[GOOGLE MAIL]') {
3147
					$isGoogleMail=true;
3148
					if (strtoupper($k)=='[GOOGLE MAIL]') {
3149
						$googleMailFolderObject[$k]=$f;
3150
						unset($folders[$k]);
3151
						$sorted=true;
3152
					} else {
3153
						$isAutoFolder=false;
3154
						foreach($autoFoldersTmp as $afk=>$aF)
3155
						{
3156
							//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3157
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3158
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3159
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3160
							{
3161
								//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3162
								$isAutoFolder=true;
3163
								$googleAutoFolderObjects[$k]=$f;
3164
								break;
3165
							}
3166
						}
3167
						if ($isAutoFolder==false) $googleSubFolderObjects[$k]=$f;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
3168
						unset($folders[$k]);
3169
						$sorted=true;
3170
					}
3171
				} else {
3172
					$isAutoFolder=false;
3173
					foreach($autoFoldersTmp as $afk=>$aF)
3174
					{
3175
						//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3176
						if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3177
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3178
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3179
						{
3180
							//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3181
							$isAutoFolder=true;
3182
							$autoFolderObjects[$k]=$f;
3183
							unset($folders[$k]);
3184
							$sorted=true;
3185
							break;
3186
						}
3187
					}
3188
				}
3189
3190
				if ($sorted==false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
3191
				{
3192
					foreach(array('others','shared') as $type)
3193
					{
3194
						if ($nameSpace[$type]['prefix_present']&&$nameSpace[$type]['prefix'])
3195
						{
3196
							if (substr($k,0,strlen($nameSpace[$type]['prefix']))==$nameSpace[$type]['prefix']||
3197
								substr($k,0,strlen($nameSpace[$type]['prefix'])-strlen($nameSpace[$type]['delimiter']))==substr($nameSpace[$type]['prefix'],0,strlen($nameSpace[$type]['delimiter'])*-1)) {
3198
								//error_log(__METHOD__.__LINE__.':'.substr($k,0,strlen($nameSpace[$type]['prefix'])).':'.$k);
3199
								$typeFolderObject[$type][$k]=$f;
3200
								unset($folders[$k]);
3201
							}
3202
						}
3203
					}
3204
				}
3205
			}
3206
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3207
			// avoid calling sortByAutoFolder as it is not regarding subfolders
3208
			$autoFolderObjectsTmp = $autoFolderObjects;
3209
			unset($autoFolderObjects);
3210
			uasort($autoFolderObjectsTmp, array($this,'sortByMailbox'));
3211
			foreach($autoFoldersTmp as $afk=>$aF)
3212
			{
3213
				foreach($autoFolderObjectsTmp as $k => $f)
3214
				{
3215
					if($aF && ($mySpecialUseFolders[$aF]==$k ||
3216
						substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter ||
3217
						stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false))
3218
					{
3219
						$autoFolderObjects[$k]=$f;
3220
					}
3221
				}
3222
			}
3223
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3224
			if (!$isGoogleMail) {
3225
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3226
			} else {
3227
				// avoid calling sortByAutoFolder as it is not regarding subfolders
3228
				$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
3229
				unset($googleAutoFolderObjects);
3230
				uasort($gAutoFolderObjectsTmp, array($this,'sortByMailbox'));
3231
				foreach($autoFoldersTmp as $afk=>$aF)
3232
				{
3233
					foreach($gAutoFolderObjectsTmp as $k => $f)
3234
					{
3235
						if($aF && ($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter))
3236
						{
3237
							$googleAutoFolderObjects[$k]=$f;
3238
						}
3239
					}
3240
				}
3241
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$folders,(array)$googleMailFolderObject,$googleAutoFolderObjects,$googleSubFolderObjects,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3242
			}
3243
			if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') Sorting:');
3244
			//self::$debugTimes=false;
3245
		}
3246
		// Get counter information and add them to each fetched folders array
3247
		// TODO:  do not fetch counters for user .... as in shared / others
3248
		if ($_getCounter)
3249
		{
3250
			foreach ($folders as &$folder)
3251
			{
3252
				$folder['counter'] = $this->icServer->getMailboxCounters($folder['MAILBOX']);
3253
			}
3254
		}
3255
		return $folders;
3256
	}
3257
3258
3259
	/**
3260
	 * Check if all automatic folders exist and create them if not
3261
	 *
3262
	 * @param array $autofolders_exists existing folders, no need to check their existance again
3263
	 * @return int number of new folders created
3264
	 */
3265
	function check_create_autofolders(array $autofolders_exists=array())
3266
	{
3267
		$num_created = 0;
3268
		foreach(self::$autoFolders as $folder)
3269
		{
3270
			$created = false;
3271
			if (!in_array($folder, $autofolders_exists) && $this->_getSpecialUseFolder($folder, true, $created) &&
3272
				$created && $folder != 'Outbox')
3273
			{
3274
				$num_created++;
3275
			}
3276
		}
3277
		return $num_created;
3278
	}
3279
3280
	/**
3281
	 * search Value In FolderObjects
3282
	 *
3283
	 * Helper function to search for a specific value within the foldertree objects
3284
	 * @param string $needle
3285
	 * @param array $haystack array of folderobjects
3286
	 * @return MIXED false or key
3287
	 */
3288
	static function searchValueInFolderObjects($needle, $haystack)
3289
	{
3290
		$rv = false;
3291
		foreach ($haystack as $k => $v)
3292
		{
3293
			foreach($v as &$sv) {if (trim($sv)==trim($needle)) return $k;}
3294
		}
3295
		return $rv;
3296
	}
3297
3298
	/**
3299
	 * sortByMailbox
3300
	 *
3301
	 * Helper function to sort folders array by mailbox
3302
	 * @param array $a
3303
	 * @param array $b array of folders
3304
	 * @return int expect values (0, 1 or -1)
3305
	 */
3306
	function sortByMailbox($a,$b)
3307
	{
3308
		return strcasecmp($a['MAILBOX'],$b['MAILBOX']);
3309
	}
3310
3311
	/**
3312
	 * Get folder data from path
3313
	 *
3314
	 * @param string $_path a node path
3315
	 * @param string $_hDelimiter hierarchy delimiter
3316
	 * @return array returns an array of data extracted from given node path
3317
	 */
3318
	static function pathToFolderData ($_path, $_hDelimiter)
3319
	{
3320
		if (!strpos($_path, self::DELIMITER)) $_path = self::DELIMITER.$_path;
3321
		list(,$path) = explode(self::DELIMITER, $_path);
3322
		$path_chain = $parts = explode($_hDelimiter, $path);
3323
		$name = array_pop($parts);
3324
		return array (
3325
			'name' => $name,
3326
			'mailbox' => $path,
3327
			'parent' => implode($_hDelimiter, $parts),
3328
			'text' => $name,
3329
			'tooltip' => $name,
3330
			'path' => $path_chain
3331
		);
3332
	}
3333
3334
	/**
3335
	 * sortByAutoFolder
3336
	 *
3337
	 * Helper function to sort folder-objects by auto Folder Position
3338
	 * @param array $_a
3339
	 * @param array $_b
3340
	 * @return int expect values (0, 1 or -1)
3341
	 */
3342
	function sortByAutoFolder($_a, $_b)
3343
	{
3344
		// 0, 1 und -1
3345
		$a = self::pathToFolderData($_a['MAILBOX'], $_a['delimiter']);
3346
		$b = self::pathToFolderData($_b['MAILBOX'], $_b['delimiter']);
3347
		$pos1 = array_search(trim($a['name']),self::$autoFolders);
3348
		$pos2 = array_search(trim($b['name']),self::$autoFolders);
3349
		if ($pos1 == $pos2) return 0;
3350
		return ($pos1 < $pos2) ? -1 : 1;
3351
	}
3352
3353
	/**
3354
	 * sortByDisplayName
3355
	 *
3356
	 * Helper function to sort folder-objects by displayname
3357
	 * @param object $a
3358
	 * @param object $b array of folderobjects
3359
	 * @return int expect values (0, 1 or -1)
3360
	 */
3361
	function sortByDisplayName($a,$b)
3362
	{
3363
		// 0, 1 und -1
3364
		return strcasecmp($a->displayName,$b->displayName);
3365
	}
3366
3367
	/**
3368
	 * sortByAutoFolderPos
3369
	 *
3370
	 * Helper function to sort folder-objects by auto Folder Position
3371
	 * @param object $a
3372
	 * @param object $b array of folderobjects
3373
	 * @return int expect values (0, 1 or -1)
3374
	 */
3375
	function sortByAutoFolderPos($a,$b)
3376
	{
3377
		// 0, 1 und -1
3378
		$pos1 = array_search(trim($a->shortFolderName),self::$autoFolders);
3379
		$pos2 = array_search(trim($b->shortFolderName),self::$autoFolders);
3380
		if ($pos1 == $pos2) return 0;
3381
		return ($pos1 < $pos2) ? -1 : 1;
3382
	}
3383
3384
	/**
3385
	 * getMailBoxCounters
3386
	 *
3387
	 * function to retrieve the counters for a given folder
3388
	 * @param string $folderName
3389
	 * @param boolean $_returnObject return the counters as object rather than an array
3390
	 * @return mixed false or array of counters array(MESSAGES,UNSEEN,RECENT,UIDNEXT,UIDVALIDITY) or object
3391
	 */
3392
	function getMailBoxCounters($folderName,$_returnObject=true)
3393
	{
3394
		try
3395
		{
3396
			$folderStatus = $this->icServer->getMailboxCounters($folderName);
3397
			//error_log(__METHOD__.' ('.__LINE__.') '.$folderName.": FolderStatus:".array2string($folderStatus).function_backtrace());
3398
		}
3399
		catch (\Exception $e)
3400
		{
3401
			if (self::$debug) error_log(__METHOD__." returned FolderStatus for Folder $folderName:".$e->getMessage());
3402
			return false;
3403
		}
3404
		if(is_array($folderStatus)) {
0 ignored issues
show
introduced by
The condition is_array($folderStatus) is always true.
Loading history...
3405
			if ($_returnObject===false) return $folderStatus;
3406
			$status =  new \stdClass;
3407
			$status->messages   = $folderStatus['MESSAGES'];
3408
			$status->unseen     = $folderStatus['UNSEEN'];
3409
			$status->recent     = $folderStatus['RECENT'];
3410
			$status->uidnext        = $folderStatus['UIDNEXT'];
3411
			$status->uidvalidity    = $folderStatus['UIDVALIDITY'];
3412
3413
			return $status;
3414
		}
3415
		return false;
3416
	}
3417
3418
	/**
3419
	 * getMailBoxesRecursive
3420
	 *
3421
	 * function to retrieve mailboxes recursively from given mailbox
3422
	 * @param string $_mailbox
3423
	 * @param string $delimiter
3424
	 * @param string $prefix
3425
	 * @param string $reclevel 0, counter to keep track of the current recursionlevel
3426
	 * @return array of mailboxes
3427
	 */
3428
	function getMailBoxesRecursive($_mailbox, $delimiter, $prefix, $reclevel=0)
3429
	{
3430
		#echo __METHOD__." retrieve SubFolders for $_mailbox$delimiter <br>";
3431
		$maxreclevel=25;
3432
		if ($reclevel > $maxreclevel) {
3433
			error_log( __METHOD__." Recursion Level Exeeded ($reclevel) while looking up $_mailbox$delimiter ");
3434
			return array();
3435
		}
3436
		$reclevel++;
3437
		// clean up double delimiters
3438
		$_mailbox = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$_mailbox);
3439
		//get that mailbox in question
3440
		$mbx = $this->icServer->getMailboxes($_mailbox,1,true);
3441
		$mbxkeys = array_keys($mbx);
3442
		#_debug_array($mbx);
3443
//error_log(__METHOD__.' ('.__LINE__.') '.' Delimiter:'.array2string($delimiter));
3444
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
3445
		// Example: Array([INBOX/GaGa] => Array([MAILBOX] => INBOX/GaGa[ATTRIBUTES] => Array([0] => \\unmarked)[delimiter] => /))
3446
		if (is_array($mbx[$mbxkeys[0]]["ATTRIBUTES"]) && (in_array('\HasChildren',$mbx[$mbxkeys[0]]["ATTRIBUTES"]) || in_array('\Haschildren',$mbx[$mbxkeys[0]]["ATTRIBUTES"]) || in_array('\haschildren',$mbx[$mbxkeys[0]]["ATTRIBUTES"]))) {
3447
			// if there are children fetch them
3448
			//echo $mbx[$mbxkeys[0]]['MAILBOX']."<br>";
3449
3450
			$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'].($mbx[$mbxkeys[0]]['MAILBOX'] == $prefix ? '':$delimiter),2,false);
3451
			//$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'],2,false);
3452
			//_debug_array($buff);
3453
			$allMailboxes = array();
3454
			foreach ($buff as $mbxname) {
3455
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbxname));
3456
				$mbxname = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$mbxname['MAILBOX']);
3457
				#echo "About to recur in level $reclevel:".$mbxname."<br>";
3458
				if ( $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'] && $mbxname != $prefix  && $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'].$delimiter)
3459
				{
3460
					$allMailboxes = array_merge($allMailboxes, self::getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel));
0 ignored issues
show
Bug Best Practice introduced by
The method EGroupware\Api\Mail::getMailBoxesRecursive() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3460
					$allMailboxes = array_merge($allMailboxes, self::/** @scrutinizer ignore-call */ getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel));
Loading history...
3461
				}
3462
			}
3463
			if (!(in_array('\NoSelect',$mbx[$mbxkeys[0]]["ATTRIBUTES"]) || in_array('\Noselect',$mbx[$mbxkeys[0]]["ATTRIBUTES"]) || in_array('\noselect',$mbx[$mbxkeys[0]]["ATTRIBUTES"]))) $allMailboxes[] = $mbx[$mbxkeys[0]]['MAILBOX'];
3464
			return $allMailboxes;
3465
		} else {
3466
			return array($_mailbox);
3467
		}
3468
	}
3469
3470
	/**
3471
	 * _getSpecialUseFolder
3472
	 * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder
3473
	 * @param string $_type the type to fetch (Drafts|Template|Trash|Sent)
3474
	 * @param boolean $_checkexistance trigger check for existance
3475
	 * @param boolean& $created =null on return true: if folder was just created, false if not
3476
	 * @return mixed string or false
3477
	 */
3478
	function _getSpecialUseFolder($_type, $_checkexistance=TRUE, &$created=null)
3479
	{
3480
		static $types = array(
3481
			'Drafts'   => array('profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'),
3482
			'Template' => array('profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'),
3483
			'Trash'    => array('profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'),
3484
			'Sent'     => array('profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'),
3485
			'Junk'     => array('profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'),
3486
			'Outbox'   => array('profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'),
3487
			'Archive'   => array('profileKey'=>'acc_folder_archive','autoFolderName'=>'Archive'),
3488
		);
3489
		if ($_type == 'Templates') $_type = 'Template';	// for some reason self::$autofolders uses 'Templates'!
3490
		$created = false;
3491
		if (!isset($types[$_type]))
3492
		{
3493
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$_type.' not supported for '.__METHOD__);
3494
			return false;
3495
		}
3496
		if (is_null(self::$specialUseFolders) || empty(self::$specialUseFolders)) self::$specialUseFolders = $this->getSpecialUseFolders();
3497
3498
		//highest precedence
3499
		try
3500
		{
3501
			$_folderName = $this->icServer->{$types[$_type]['profileKey']};
3502
		}
3503
		catch (\Exception $e)
3504
		{
3505
			// we know that outbox is not supported, but we use this here, as we autocreate expected SpecialUseFolders in this function
3506
			if ($_type != 'Outbox') error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve Folder'.$_folderName." for ".array2string($types[$_type]).":".$e->getMessage());
3507
			$_folderName = false;
3508
		}
3509
		// do not try to autocreate configured Archive-Folder. Return false if configured folder does not exist
3510
		if ($_type == 'Archive') {
3511
			if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3512
				return false;
3513
			} else {
3514
				return $_folderName;
3515
			}
3516
3517
		}
3518
		// does the folder exist??? (is configured/preset, but non-existent)
3519
		if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3520
			try
3521
			{
3522
				$error = null;
3523
				if (($_folderName = $this->createFolder('', $_folderName, $error))) $created = true;
3524
				if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3525
			}
3526
			catch(Exception $e)
3527
			{
3528
				error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage().':'.function_backtrace());
3529
				$_folderName = false;
3530
			}
3531
		}
3532
		// not sure yet if false is the correct behavior on none
3533
		if ($_folderName =='none') return 'none' ; //false;
3534
		//no (valid) folder found yet; try specialUseFolders
3535
		if (empty($_folderName) && is_array(self::$specialUseFolders) && ($f = array_search($_type,self::$specialUseFolders))) $_folderName = $f;
3536
		//no specialUseFolder; try some Defaults
3537
		if (empty($_folderName) && isset($types[$_type]))
3538
		{
3539
			$nameSpace = $this->_getNameSpaces();
3540
			$prefix='';
3541
			foreach ($nameSpace as $nSp)
3542
			{
3543
				if ($nSp['type']=='personal')
3544
				{
3545
					//error_log(__METHOD__.__LINE__.array2string($nSp));
3546
					$prefix = $nSp['prefix'];
3547
					break;
3548
				}
3549
			}
3550
			if ($this->folderExists($prefix.$types[$_type]['autoFolderName'],true))
3551
			{
3552
				$_folderName = $prefix.$types[$_type]['autoFolderName'];
3553
			}
3554
			else
3555
			{
3556
				try
3557
				{
3558
					$error = null;
3559
					$this->createFolder('', $prefix.$types[$_type]['autoFolderName'],$error);
3560
					$_folderName = $prefix.$types[$_type]['autoFolderName'];
3561
					if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3562
				}
3563
				catch(Exception $e)
3564
				{
3565
					error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage());
3566
					$_folderName = false;
3567
				}
3568
			}
3569
		}
3570
		return $_folderName;
3571
	}
3572
3573
	/**
3574
	 * getFolderByType wrapper for _getSpecialUseFolder Type as param
3575
	 * @param string $type foldertype to look for
3576
	 * @param boolean $_checkexistance trigger check for existance
3577
	 * @return mixed string or false
3578
	 */
3579
	function getFolderByType($type, $_checkexistance=false)
3580
	{
3581
		return $this->_getSpecialUseFolder($type, $_checkexistance);
3582
	}
3583
3584
	/**
3585
	 * getJunkFolder wrapper for _getSpecialUseFolder Type Junk
3586
	 * @param boolean $_checkexistance trigger check for existance
3587
	 * @return mixed string or false
3588
	 */
3589
	function getJunkFolder($_checkexistance=TRUE)
3590
	{
3591
		return $this->_getSpecialUseFolder('Junk', $_checkexistance);
3592
	}
3593
3594
	/**
3595
	 * getDraftFolder wrapper for _getSpecialUseFolder Type Drafts
3596
	 * @param boolean $_checkexistance trigger check for existance
3597
	 * @return mixed string or false
3598
	 */
3599
	function getDraftFolder($_checkexistance=TRUE)
3600
	{
3601
		return $this->_getSpecialUseFolder('Drafts', $_checkexistance);
3602
	}
3603
3604
	/**
3605
	 * getTemplateFolder wrapper for _getSpecialUseFolder Type Template
3606
	 * @param boolean $_checkexistance trigger check for existance
3607
	 * @return mixed string or false
3608
	 */
3609
	function getTemplateFolder($_checkexistance=TRUE)
3610
	{
3611
		return $this->_getSpecialUseFolder('Template', $_checkexistance);
3612
	}
3613
3614
	/**
3615
	 * getTrashFolder wrapper for _getSpecialUseFolder Type Trash
3616
	 * @param boolean $_checkexistance trigger check for existance
3617
	 * @return mixed string or false
3618
	 */
3619
	function getTrashFolder($_checkexistance=TRUE)
3620
	{
3621
		return $this->_getSpecialUseFolder('Trash', $_checkexistance);
3622
	}
3623
3624
	/**
3625
	 * getSentFolder wrapper for _getSpecialUseFolder Type Sent
3626
	 * @param boolean $_checkexistance trigger check for existance
3627
	 * @return mixed string or false
3628
	 */
3629
	function getSentFolder($_checkexistance=TRUE)
3630
	{
3631
		return $this->_getSpecialUseFolder('Sent', $_checkexistance);
3632
	}
3633
3634
	/**
3635
	 * getOutboxFolder wrapper for _getSpecialUseFolder Type Outbox
3636
	 * @param boolean $_checkexistance trigger check for existance
3637
	 * @return mixed string or false
3638
	 */
3639
	function getOutboxFolder($_checkexistance=TRUE)
3640
	{
3641
		return $this->_getSpecialUseFolder('Outbox', $_checkexistance);
3642
	}
3643
3644
	/**
3645
	 * getArchiveFolder wrapper for _getSpecialUseFolder Type Archive
3646
	 * @param boolean $_checkexistance trigger check for existance . We do no autocreation for configured Archive folder
3647
	 * @return mixed string or false
3648
	 */
3649
	function getArchiveFolder($_checkexistance=TRUE)
3650
	{
3651
		return $this->_getSpecialUseFolder('Archive', $_checkexistance);
3652
	}
3653
3654
	/**
3655
	 * isSentFolder is the given folder the sent folder or at least a subfolder of it
3656
	 * @param string $_folderName folder to perform the check on
3657
	 * @param boolean $_checkexistance trigger check for existance
3658
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3659
	 * @return boolean
3660
	 */
3661
	function isSentFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3662
	{
3663
		$sentFolder = $this->getSentFolder($_checkexistance);
3664
		if(empty($sentFolder)) {
3665
			return false;
3666
		}
3667
		// does the folder exist???
3668
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3669
			return false;
3670
		}
3671
3672
		if ($_exactMatch)
3673
		{
3674
			if(false !== stripos($_folderName, $sentFolder)&& strlen($_folderName)==strlen($sentFolder)) {
3675
				return true;
3676
			} else {
3677
				return false;
3678
			}
3679
		} else {
3680
			if(false !== stripos($_folderName, $sentFolder)) {
3681
				return true;
3682
			} else {
3683
				return false;
3684
			}
3685
		}
3686
	}
3687
3688
	/**
3689
	 * checks if the Outbox folder exists and is part of the foldername to be checked
3690
	 * @param string $_folderName folder to perform the check on
3691
	 * @param boolean $_checkexistance trigger check for existance
3692
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3693
	 * @return boolean
3694
	 */
3695
	function isOutbox($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3696
	{
3697
		if (stripos($_folderName, 'Outbox')===false) {
3698
			return false;
3699
		}
3700
		// does the folder exist???
3701
		if ($_checkexistance && $GLOBALS['egw_info']['user']['apps']['activesync'] && !$this->folderExists($_folderName)) {
3702
			$outboxFolder = $this->getOutboxFolder($_checkexistance);
3703
			if ($_exactMatch)
3704
			{
3705
				if(false !== stripos($_folderName, $outboxFolder)&& strlen($_folderName)==strlen($outboxFolder)) {
3706
					return true;
3707
				} else {
3708
					return false;
3709
				}
3710
			} else {
3711
				if(false !== stripos($_folderName, $outboxFolder)) {
3712
					return true;
3713
				} else {
3714
					return false;
3715
				}
3716
			}
3717
		}
3718
		return true;
3719
	}
3720
3721
	/**
3722
	 * isDraftFolder is the given folder the sent folder or at least a subfolder of it
3723
	 * @param string $_folderName folder to perform the check on
3724
	 * @param boolean $_checkexistance trigger check for existance
3725
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3726
	 * @return boolean
3727
	 */
3728
	function isDraftFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3729
	{
3730
		$draftFolder = $this->getDraftFolder($_checkexistance);
3731
		if(empty($draftFolder)) {
3732
			return false;
3733
		}
3734
		// does the folder exist???
3735
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3736
			return false;
3737
		}
3738
		if (is_a($_folderName,"Horde_Imap_Client_Mailbox")) $_folderName = $_folderName->utf8;
3739
		if ($_exactMatch)
3740
		{
3741
			if(false !== stripos($_folderName, $draftFolder)&& strlen($_folderName)==strlen($draftFolder)) {
3742
				return true;
3743
			} else {
3744
				return false;
3745
			}
3746
		} else {
3747
			if(false !== stripos($_folderName, $draftFolder)) {
3748
				return true;
3749
			} else {
3750
				return false;
3751
			}
3752
		}
3753
	}
3754
3755
	/**
3756
	 * isTrashFolder is the given folder the sent folder or at least a subfolder of it
3757
	 * @param string $_folderName folder to perform the check on
3758
	 * @param boolean $_checkexistance trigger check for existance
3759
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3760
	 * @return boolean
3761
	 */
3762
	function isTrashFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3763
	{
3764
		$trashFolder = $this->getTrashFolder($_checkexistance);
3765
		if(empty($trashFolder)) {
3766
			return false;
3767
		}
3768
		// does the folder exist???
3769
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3770
			return false;
3771
		}
3772
3773
		if ($_exactMatch)
3774
		{
3775
			if(false !== stripos($_folderName, $trashFolder)&& strlen($_folderName)==strlen($trashFolder)) {
3776
				return true;
3777
			} else {
3778
				return false;
3779
			}
3780
		} else {
3781
			if(false !== stripos($_folderName, $trashFolder)) {
3782
				return true;
3783
			} else {
3784
				return false;
3785
			}
3786
		}
3787
	}
3788
3789
	/**
3790
	 * isTemplateFolder is the given folder the sent folder or at least a subfolder of it
3791
	 * @param string $_folderName folder to perform the check on
3792
	 * @param boolean $_checkexistance trigger check for existance
3793
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3794
	 * @return boolean
3795
	 */
3796
	function isTemplateFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3797
	{
3798
		$templateFolder = $this->getTemplateFolder($_checkexistance);
3799
		if(empty($templateFolder)) {
3800
			return false;
3801
		}
3802
		// does the folder exist???
3803
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3804
			return false;
3805
		}
3806
		if ($_exactMatch)
3807
		{
3808
			if(false !== stripos($_folderName, $templateFolder)&& strlen($_folderName)==strlen($templateFolder)) {
3809
				return true;
3810
			} else {
3811
				return false;
3812
			}
3813
		} else {
3814
			if(false !== stripos($_folderName, $templateFolder)) {
3815
				return true;
3816
			} else {
3817
				return false;
3818
			}
3819
		}
3820
	}
3821
3822
	/**
3823
	 * folderExists checks for existance of a given folder
3824
	 * @param string $_folder folder to perform the check on
3825
	 * @param boolean $_forceCheck trigger check for existance on icServer
3826
	 * @return mixed string or false
3827
	 */
3828
	function folderExists($_folder, $_forceCheck=false)
3829
	{
3830
		static $folderInfo;
3831
		$forceCheck = $_forceCheck;
3832
		if (empty($_folder))
3833
		{
3834
			// this error is more or less without significance, unless we force the check
3835
			if ($_forceCheck===true) error_log(__METHOD__.' ('.__LINE__.') '.' Called with empty Folder:'.$_folder.function_backtrace());
3836
			return false;
3837
		}
3838
		// when check is not enforced , we assume a folder represented as Horde_Imap_Client_Mailbox as existing folder
3839
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")&&$_forceCheck===false) return true;
3840
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")) $_folder =  $_folder->utf8;
3841
		// reduce traffic within the Instance per User; Expire every 5 hours
3842
		//error_log(__METHOD__.' ('.__LINE__.') '.' Called with Folder:'.$_folder.function_backtrace());
3843
		if (is_null($folderInfo)) $folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*5);
3844
		//error_log(__METHOD__.' ('.__LINE__.') '.'Cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.($forceCheck?'(forcedCheck)':'').':'.array2string($folderInfo));
3845
		if (!empty($folderInfo) && isset($folderInfo[$this->profileID]) && isset($folderInfo[$this->profileID][$_folder]) && $forceCheck===false)
3846
		{
3847
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID);
3848
			return $folderInfo[$this->profileID][$_folder];
3849
		}
3850
		else
3851
		{
3852
			if ($forceCheck === false)
3853
			{
3854
				//error_log(__METHOD__.' ('.__LINE__.') '.' No cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.' FolderExistsInfoCache:'.array2string($folderInfo[$this->profileID]));
3855
				$forceCheck = true; // try to force the check, in case there is no connection, we may need that
3856
			}
3857
		}
3858
3859
		// does the folder exist???
3860
		//error_log(__METHOD__."->Connected?".$this->icServer->_connected.", ".$_folder.", ".($forceCheck?' forceCheck activated':'dont check on server'));
3861
		if ( $forceCheck || empty($folderInfo) || !isset($folderInfo[$this->profileID]) || !isset($folderInfo[$this->profileID][$_folder])) {
0 ignored issues
show
introduced by
The condition $forceCheck is always true.
Loading history...
3862
			//error_log(__METHOD__."->NotConnected and forceCheck with profile:".$this->profileID);
3863
			//return false;
3864
			//try to connect
3865
		}
3866
		try
3867
		{
3868
			$folderInfo[$this->profileID][$_folder] = $this->icServer->mailboxExist($_folder);
3869
		}
3870
		catch (\Exception $e)
3871
		{
3872
			error_log(__METHOD__.__LINE__.$e->getMessage().($e->details?', '.$e->details:''));
3873
			self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
3874
			$folderInfo[$this->profileID][$_folder] = false;
3875
		}
3876
		//error_log(__METHOD__.' ('.__LINE__.') '.' Folder Exists:'.$folderInfo[$this->profileID][$_folder].function_backtrace());
3877
3878
		if(!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) &&
3879
			$folderInfo[$this->profileID][$_folder] !== true)
3880
		{
3881
			$folderInfo[$this->profileID][$_folder] = false; // set to false, whatever it was (to have a valid returnvalue for the static return)
3882
		}
3883
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*60*5);
3884
		return (!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) ? $folderInfo[$this->profileID][$_folder] : false);
3885
	}
3886
3887
	/**
3888
	 * remove any messages which are marked as deleted or
3889
	 * remove any messages from the trashfolder
3890
	 *
3891
	 * @param string _folderName the foldername
3892
	 * @return nothing
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\nothing was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3893
	 */
3894
	function compressFolder($_folderName = false)
3895
	{
3896
		$folderName	= ($_folderName ? $_folderName : $this->sessionData['mailbox']);
3897
		$deleteOptions	= $GLOBALS['egw_info']['user']['preferences']['mail']['deleteOptions'];
3898
		$trashFolder	= $this->getTrashFolder();
3899
3900
		$this->icServer->openMailbox($folderName);
3901
3902
		if(strtolower($folderName) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") {
3903
			$this->deleteMessages('all',$folderName,'remove_immediately');
3904
		} else {
3905
			$this->icServer->expunge($folderName);
3906
		}
3907
	}
3908
3909
	/**
3910
	 * delete a Message
3911
	 *
3912
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/string at position 0 could not be parsed: Unknown type name 'array/string' at position 0 in array/string.
Loading history...
3913
	 * @param string _folder foldername
3914
	 * @param string _forceDeleteMethod - "no", or deleteMethod like 'move_to_trash',"mark_as_deleted","remove_immediately"
3915
	 *
3916
	 * @return bool true, as we do not handle return values yet
3917
	 * @throws Exception
3918
	 */
3919
	function deleteMessages($_messageUID, $_folder=NULL, $_forceDeleteMethod='no')
3920
	{
3921
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.array2string($_folder).', '.$_forceDeleteMethod);
3922
		$oldMailbox = '';
3923
		if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
3924
		if (empty($_messageUID))
3925
		{
3926
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3927
			return false;
3928
		}
3929
		elseif ($_messageUID==='all')
3930
		{
3931
			$_messageUID= null;
3932
		}
3933
		else
3934
		{
3935
			$uidsToDelete = new Horde_Imap_Client_Ids();
3936
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3937
			$uidsToDelete->add($_messageUID);
3938
		}
3939
		$deleteOptions = $_forceDeleteMethod; // use forceDeleteMethod if not "no", or unknown method
3940
		if ($_forceDeleteMethod === 'no' || !in_array($_forceDeleteMethod,array('move_to_trash',"mark_as_deleted","remove_immediately"))) $deleteOptions  = ($this->mailPreferences['deleteOptions']?$this->mailPreferences['deleteOptions']:"mark_as_deleted");
3941
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3942
		$trashFolder    = $this->getTrashFolder();
3943
		$draftFolder	= $this->getDraftFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['draftFolder'];
0 ignored issues
show
Unused Code introduced by
The assignment to $draftFolder is dead and can be removed.
Loading history...
3944
		$templateFolder = $this->getTemplateFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['templateFolder'];
0 ignored issues
show
Unused Code introduced by
The assignment to $templateFolder is dead and can be removed.
Loading history...
3945
		if((strtolower($_folder) == strtolower($trashFolder) && $deleteOptions == "move_to_trash")) {
3946
			$deleteOptions = "remove_immediately";
3947
		}
3948
		if($this->icServer->getCurrentMailbox() != $_folder) {
3949
			$oldMailbox = $this->icServer->getCurrentMailbox();
3950
			$this->icServer->openMailbox($_folder);
3951
		}
3952
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3953
		$updateCache = false;
3954
		switch($deleteOptions) {
3955
			case "move_to_trash":
3956
				//error_log(__METHOD__.' ('.__LINE__.') ');
3957
				$updateCache = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $updateCache is dead and can be removed.
Loading history...
3958
				if(!empty($trashFolder)) {
3959
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.implode(' : ', $_messageUID));
3960
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$trashFolder <= $_folder / ". $this->sessionData['mailbox']);
3961
					// copy messages
3962
					try
3963
					{
3964
						$this->icServer->copy($_folder, $trashFolder, array('ids'=>$uidsToDelete,'move'=>true));
3965
					}
3966
					catch (\Exception $e)
3967
					{
3968
						throw new Exception("Failed to move Messages (".array2string($uidsToDelete).") from Folder $_folder to $trashFolder Error:".$e->getMessage());
3969
					}
3970
				}
3971
				break;
3972
3973
			case "mark_as_deleted":
3974
				//error_log(__METHOD__.' ('.__LINE__.') ');
3975
				// mark messages as deleted
3976
				if (is_null($_messageUID)) $_messageUID='all';
3977
				foreach((array)$_messageUID as $key =>$uid)
3978
				{
3979
					//flag messages, that are flagged for deletion as seen too
3980
					$this->flagMessages('read', $uid, $_folder);
3981
					$flags = $this->getFlags($uid);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $flags is correct as $this->getFlags($uid) targeting EGroupware\Api\Mail::getFlags() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3982
					$this->flagMessages('delete', $uid, $_folder);
3983
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
3984
					if (strpos( array2string($flags),'Deleted')!==false) $undelete[] = $uid;
3985
					unset($flags);
3986
				}
3987
				foreach((array)$undelete as $key =>$uid)
3988
				{
3989
					$this->flagMessages('undelete', $uid, $_folder);
3990
				}
3991
				break;
3992
3993
			case "remove_immediately":
3994
				//error_log(__METHOD__.' ('.__LINE__.') ');
3995
				$updateCache = true;
3996
				if (is_null($_messageUID)) $_messageUID='all';
3997
				if (is_object($_messageUID))
3998
				{
3999
					$this->flagMessages('delete', $_messageUID, $_folder);
4000
				}
4001
				else
4002
				{
4003
					foreach((array)$_messageUID as $key =>$uid)
4004
					{
4005
						//flag messages, that are flagged for deletion as seen too
4006
						$this->flagMessages('delete', $uid, $_folder);
4007
					}
4008
				}
4009
				$examineMailbox = $this->icServer->examineMailbox($_folder);
4010
				// examine the folder and if there are messages then try to delete the messages finaly
4011
				if (is_array($examineMailbox) && $examineMailbox['MESSAGES'] > 0) $this->icServer->expunge($_folder);
4012
				break;
4013
		}
4014
		if($oldMailbox != '') {
4015
			$this->icServer->openMailbox($oldMailbox);
4016
		}
4017
4018
		return true;
4019
	}
4020
4021
	/**
4022
	 * get flags for a Message
4023
	 *
4024
	 * @param mixed string _messageUID array of id to retrieve the flags for
4025
	 *
4026
	 * @return null/array flags
0 ignored issues
show
Documentation Bug introduced by
The doc comment null/array at position 0 could not be parsed: Unknown type name 'null/array' at position 0 in null/array.
Loading history...
4027
	 */
4028
	function getFlags ($_messageUID) {
4029
		try
4030
		{
4031
			$uidsToFetch = new Horde_Imap_Client_Ids();
4032
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4033
			$uidsToFetch->add($_messageUID);
4034
			$_folderName = $this->icServer->getCurrentMailbox();
4035
			$fquery = new Horde_Imap_Client_Fetch_Query();
4036
			$fquery->flags();
4037
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
4038
				'ids' => $uidsToFetch,
4039
			));
4040
			if (is_object($headersNew)) {
4041
				foreach($headersNew->ids() as $id) {
4042
					$_headerObject = $headersNew->get($id);
4043
					$flags = $_headerObject->getFlags();
4044
				}
4045
			}
4046
		}
4047
		catch (\Exception $e)
4048
		{
4049
			error_log(__METHOD__.' ('.__LINE__.') '."Failed to fetch flags for ".array2string($_messageUID)." Error:".$e->getMessage());
4050
			return null;
4051
			//throw new Exception("Failed to fetch flags for ".array2string($_messageUID)" Error:".$e->getMessage());
4052
		}
4053
		return $flags;
4054
	}
4055
4056
	/**
4057
	 * get and parse the flags response for the Notifyflag for a Message
4058
	 *
4059
	 * @param string _messageUID array of id to retrieve the flags for
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_messageUID was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
4060
	 * @param array flags - to avoid additional server call
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\flags was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
4061
	 *
4062
	 * @return null/boolean
0 ignored issues
show
Documentation Bug introduced by
The doc comment null/boolean at position 0 could not be parsed: Unknown type name 'null/boolean' at position 0 in null/boolean.
Loading history...
4063
	 */
4064
	function getNotifyFlags ($_messageUID, $flags=null)
4065
	{
4066
		if (self::$debug) error_log(__METHOD__.$_messageUID.' Flags:'.array2string($flags));
4067
		try
4068
		{
4069
			if($flags===null) $flags =  $this->getFlags($_messageUID);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $flags is correct as $this->getFlags($_messageUID) targeting EGroupware\Api\Mail::getFlags() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
4070
		}
4071
		catch (\Exception $e)
4072
		{
4073
			return null;
4074
		}
4075
4076
		if ( stripos( array2string($flags),'MDNSent')!==false)
4077
			return true;
4078
4079
		if ( stripos( array2string($flags),'MDNnotSent')!==false)
4080
			return false;
4081
4082
		return null;
4083
	}
4084
4085
	/**
4086
	 * flag a Message
4087
	 *
4088
	 * @param string _flag (readable name)
4089
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/string at position 0 could not be parsed: Unknown type name 'array/string' at position 0 in array/string.
Loading history...
4090
	 * @param string _folder foldername
4091
	 *
4092
	 * @todo handle handle icserver->setFlags returnValue
4093
	 *
4094
	 * @return bool true, as we do not handle icserver->setFlags returnValue
4095
	 */
4096
	function flagMessages($_flag, $_messageUID,$_folder=NULL)
4097
	{
4098
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']);
4099
		if (empty($_messageUID))
4100
		{
4101
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
4102
			return false;
4103
		}
4104
		$this->icServer->openMailbox(($_folder?$_folder:$this->sessionData['mailbox']));
4105
		$folder = $this->icServer->getCurrentMailbox();
4106
		if (is_array($_messageUID)&& count($_messageUID)>50)
4107
		{
4108
			$count = $this->getMailBoxCounters($folder,true);
4109
			if ($count->messages == count($_messageUID)) $_messageUID='all';
4110
		}
4111
4112
		if ($_messageUID==='all')
4113
		{
4114
			$messageUIDs = array('all');
4115
		}
4116
		else
4117
		{
4118
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4119
			$messageUIDs = array_chunk($_messageUID,50,true);
4120
		}
4121
		try
4122
		{
4123
			foreach($messageUIDs as &$uids)
4124
			{
4125
				if ($uids==='all')
4126
				{
4127
					$uidsToModify=null;
4128
				}
4129
				else
4130
				{
4131
					$uidsToModify = new Horde_Imap_Client_Ids();
4132
					$uidsToModify->add($uids);
4133
				}
4134
				switch($_flag) {
4135
					case "delete":
4136
						$ret = $this->icServer->store($folder, array('add'=>array('\\Deleted'), 'ids'=> $uidsToModify));
0 ignored issues
show
Unused Code introduced by
The assignment to $ret is dead and can be removed.
Loading history...
4137
						break;
4138
					case "undelete":
4139
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Deleted'), 'ids'=> $uidsToModify));
4140
						break;
4141
					case "flagged":
4142
						$ret = $this->icServer->store($folder, array('add'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4143
						break;
4144
					case "read":
4145
					case "seen":
4146
						$ret = $this->icServer->store($folder, array('add'=>array('\\Seen'), 'ids'=> $uidsToModify));
4147
						break;
4148
					case "forwarded":
4149
						$ret = $this->icServer->store($folder, array('add'=>array('$Forwarded'), 'ids'=> $uidsToModify));
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
4150
					case "answered":
4151
						$ret = $this->icServer->store($folder, array('add'=>array('\\Answered'), 'ids'=> $uidsToModify));
4152
						break;
4153
					case "unflagged":
4154
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4155
						break;
4156
					case "unread":
4157
					case "unseen":
4158
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Seen','\\Answered','$Forwarded'), 'ids'=> $uidsToModify));
4159
						break;
4160
					case "mdnsent":
4161
						$ret = $this->icServer->store($folder, array('add'=>array('MDNSent'), 'ids'=> $uidsToModify));
4162
						break;
4163
					case "mdnnotsent":
4164
						$ret = $this->icServer->store($folder, array('add'=>array('MDNnotSent'), 'ids'=> $uidsToModify));
4165
						break;
4166
					case "label1":
4167
					case "labelone":
4168
						$ret = $this->icServer->store($folder, array('add'=>array('$label1'), 'ids'=> $uidsToModify));
4169
						break;
4170
					case "unlabel1":
4171
					case "unlabelone":
4172
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4173
						break;
4174
					case "label2":
4175
					case "labeltwo":
4176
						$ret = $this->icServer->store($folder, array('add'=>array('$label2'), 'ids'=> $uidsToModify));
4177
						break;
4178
					case "unlabel2":
4179
					case "unlabeltwo":
4180
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4181
						break;
4182
					case "label3":
4183
					case "labelthree":
4184
						$ret = $this->icServer->store($folder, array('add'=>array('$label3'), 'ids'=> $uidsToModify));
4185
						break;
4186
					case "unlabel3":
4187
					case "unlabelthree":
4188
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4189
						break;
4190
					case "label4":
4191
					case "labelfour":
4192
						$ret = $this->icServer->store($folder, array('add'=>array('$label4'), 'ids'=> $uidsToModify));
4193
						break;
4194
					case "unlabel4":
4195
					case "unlabelfour":
4196
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4197
						break;
4198
					case "label5":
4199
					case "labelfive":
4200
						$ret = $this->icServer->store($folder, array('add'=>array('$label5'), 'ids'=> $uidsToModify));
4201
						break;
4202
					case "unlabel5":
4203
					case "unlabelfive":
4204
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4205
						break;
4206
					case "unlabel":
4207
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4208
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4209
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4210
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4211
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4212
						break;
4213
				}
4214
			}
4215
		}
4216
		catch(Exception $e)
4217
		{
4218
			error_log(__METHOD__.__LINE__.' Error, could not flag messages in folder '.$folder.' Reason:'.$e->getMessage());
4219
		}
4220
		if ($folder instanceof Horde_Imap_Client_Mailbox) $_folder = $folder->utf8;
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\Horde_Imap_Client_Mailbox was not found. Did you mean Horde_Imap_Client_Mailbox? If so, make sure to prefix the type with \.
Loading history...
4221
		//error_log(__METHOD__.__LINE__.'#'.$this->icServer->ImapServerId.'#'.array2string($_folder).'#');
4222
		self::$folderStatusCache[$this->icServer->ImapServerId][(!empty($_folder)?$_folder: $this->sessionData['mailbox'])]['uidValidity'] = 0;
4223
4224
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
4225
		return true; // as we do not catch/examine setFlags returnValue
4226
	}
4227
4228
	/**
4229
	 * move Message(s)
4230
	 *
4231
	 * @param string _foldername target folder
4232
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/string at position 0 could not be parsed: Unknown type name 'array/string' at position 0 in array/string.
Loading history...
4233
	 * @param boolean $deleteAfterMove - decides if a mail is moved (true) or copied (false)
4234
	 * @param string $currentFolder
4235
	 * @param boolean $returnUIDs - control wether or not the action called should return the new uids
4236
	 *						caveat: not all servers do support that
4237
	 * @param int $_sourceProfileID - source profile ID, should be handed over, if not $this->icServer->ImapServerId is used
4238
	 * @param int $_targetProfileID - target profile ID, should only be handed over when target server is different from source
4239
	 *
4240
	 * @return mixed/bool true,false or new uid
0 ignored issues
show
Documentation Bug introduced by
The doc comment mixed/bool at position 0 could not be parsed: Unknown type name 'mixed/bool' at position 0 in mixed/bool.
Loading history...
4241
	 * @throws Exception
4242
	 */
4243
	function moveMessages($_foldername, $_messageUID, $deleteAfterMove=true, $currentFolder = Null, $returnUIDs = false, $_sourceProfileID = Null, $_targetProfileID = Null)
4244
	{
4245
		$source = Mail\Account::read(($_sourceProfileID?$_sourceProfileID:$this->icServer->ImapServerId))->imapServer();
4246
		//$deleteOptions  = $GLOBALS['egw_info']["user"]["preferences"]["mail"]["deleteOptions"];
4247
		if (empty($_messageUID))
4248
		{
4249
			if (self::$debug) error_log(__METHOD__." no Message(s): ".implode(',',$_messageUID));
4250
			return false;
4251
		}
4252
		elseif ($_messageUID==='all')
4253
		{
4254
			//error_log(__METHOD__." all Message(s): ".implode(',',$_messageUID));
4255
			$uidsToMove= null;
4256
		}
4257
		else
4258
		{
4259
			//error_log(__METHOD__." Message(s): ".implode(',',$_messageUID));
4260
			$uidsToMove = new Horde_Imap_Client_Ids();
4261
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4262
			$uidsToMove->add($_messageUID);
4263
		}
4264
		$sourceFolder = (!empty($currentFolder)?$currentFolder: $this->sessionData['mailbox']);
4265
		//error_log(__METHOD__.__LINE__."$_targetProfileID !== ".array2string($source->ImapServerId));
4266
		if (!is_null($_targetProfileID) && $_targetProfileID !== $source->ImapServerId)
4267
		{
4268
			$sourceFolder = $source->getMailbox($sourceFolder);
4269
			$source->openMailbox($sourceFolder);
4270
			$uidsToFetch = new Horde_Imap_Client_Ids();
4271
			$uidsToFetch->add($_messageUID);
4272
			$fquery = new Horde_Imap_Client_Fetch_Query();
4273
			$fquery->flags();
4274
			$fquery->headerText(array('peek'=>true));
4275
			$fquery->fullText(array('peek'=>true));
4276
			$fquery->imapDate();
4277
			$headersNew = $source->fetch($sourceFolder, $fquery, array(
4278
				'ids' => $uidsToFetch,
4279
			));
4280
4281
			//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' mailheaders:'.array2string($headersNew));
4282
4283
			if (is_object($headersNew)) {
4284
				$c=0;
4285
				$retUid = new Horde_Imap_Client_Ids();
4286
				// we copy chunks of 5 to avoid too much memory and/or server stress
4287
				// some servers seem not to allow/support the appendig of multiple messages. so we are down to one
4288
				foreach($headersNew as &$_headerObject) {
4289
					$c++;
4290
					$flags = $_headerObject->getFlags(); //unseen status seems to be lost when retrieving the full message
4291
					$date = $_headerObject->getImapDate();
4292
					$currentDate =  new Horde_Imap_Client_DateTime();
4293
					// if the internal Date of the message equals the current date; try using the header date
4294
					if ($date==$currentDate)
4295
					{
4296
						$headerForPrio = array_change_key_case($_headerObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray(), CASE_UPPER);
4297
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#'.$headerForPrio['DATE']);
4298
						$date = new Horde_Imap_Client_DateTime($headerForPrio['DATE']);
4299
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#');
4300
					}
4301
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject)));
4302
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
4303
					$body = $_headerObject->getFullMsg();
4304
					$dataNflags[] = array('data'=>$body, 'flags'=>$flags, 'internaldate'=>$date);
4305
					if ($c==1)
4306
					{
4307
						$target = Mail\Account::read($_targetProfileID)->imapServer();
4308
						//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($_foldername));
4309
						$foldername = $target->getMailbox($_foldername);
4310
						// make sure the target folder is open and ready
4311
						$target->openMailbox($foldername);
4312
						$ret = $target->append($foldername,$dataNflags);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $dataNflags seems to be defined later in this foreach loop on line 4304. Are you sure it is defined here?
Loading history...
4313
						$retUid->add($ret);
4314
						unset($dataNflags);
4315
						// sleep 500 miliseconds; AS some sERVERs seem not to be capable of the load this is
4316
						// inflicting in them. they "reply" with an unspecific IMAP Error
4317
						time_nanosleep(0,500000);
4318
						$c=0;
4319
					}
4320
				}
4321
				if (isset($dataNflags))
4322
				{
4323
					$target = Mail\Account::read($_targetProfileID)->imapServer();
4324
					//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($foldername));
4325
					$foldername = $target->getMailbox($_foldername);
4326
					// make sure the target folder is open and ready
4327
					$target->openMailbox($foldername);
4328
					$ret = $target->append($foldername,$dataNflags);
4329
					$retUid->add($ret);
4330
					unset($dataNflags);
4331
				}
4332
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4333
				// make sure we are back to source
4334
				$source->openMailbox($sourceFolder);
4335
				if ($deleteAfterMove)
4336
				{
4337
					$remember = $this->icServer;
4338
					$this->icServer = $source;
4339
					$this->deleteMessages($_messageUID, $sourceFolder, $_forceDeleteMethod='remove_immediately');
4340
					$this->icServer = $remember;
4341
				}
4342
			}
4343
		}
4344
		else
4345
		{
4346
			try
4347
			{
4348
				$retUid = $source->copy($sourceFolder, $_foldername, array('ids'=>$uidsToMove,'move'=>$deleteAfterMove));
4349
			}
4350
			catch (exception $e)
4351
			{
4352
				error_log(__METHOD__.' ('.__LINE__.') '."Copying to Folder $_foldername failed! Error:".$e->getMessage());
4353
				throw new Exception("Copying to Folder $_foldername failed! Error:".$e->getMessage());
4354
			}
4355
		}
4356
4357
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4358
		return ($returnUIDs ? $retUid : true);
4359
	}
4360
4361
	/**
4362
	 * Parse dates, also handle wrong or unrecognized timezones, falling back to current time
4363
	 *
4364
	 * @param string $_date to be parsed/formatted
4365
	 * @param string $format ='' if none is passed, use user prefs
4366
	 * @return string returns the date as it is parseable by strtotime, or current timestamp if everything fails
4367
	 */
4368
	static function _strtotime($_date='', $format='', $convert2usertime=false)
4369
	{
4370
		try {
4371
			$date = new DateTime($_date);	// parse date & time including timezone (throws exception, if not parsable)
4372
			if ($convert2usertime) $date->setUser();	// convert to user-time
4373
			$date2return = $date->format($format);
4374
		}
4375
		catch(\Exception $e)
4376
		{
4377
			unset($e);	// not used
4378
4379
			// remove last space-separated part and retry
4380
			$parts = explode(' ',$_date);
4381
			// try only 10 times to prevent of causing error by reaching
4382
			// maximum function nesting level.
4383
			if (count($parts) > 1 && count($parts)<10)
4384
			{
4385
				array_pop($parts);
4386
				$date2return = self::_strtotime(implode(' ', $parts), $format, $convert2usertime);
4387
			}
4388
			else	// not last part, use current time
4389
			{
4390
				$date2return = DateTime::to('now', $format);
4391
			}
4392
		}
4393
		return $date2return;
4394
	}
4395
4396
	/**
4397
	 * htmlentities
4398
	 * helperfunction to cope with wrong encoding in strings
4399
	 * @param string $_string  input to be converted
4400
	 * @param mixed $_charset false or string -> Target charset, if false Mail displayCharset will be used
4401
	 * @return string
4402
	 */
4403
	static function htmlentities($_string, $_charset=false)
4404
	{
4405
		//setting the charset (if not given)
4406
		if ($_charset===false) $_charset = self::$displayCharset;
4407
		$string = @htmlentities($_string, ENT_QUOTES, $_charset, false);
4408
		if (empty($string) && !empty($_string)) $string = @htmlentities(Translation::convert($_string,Translation::detect_encoding($_string),$_charset),ENT_QUOTES | ENT_IGNORE,$_charset, false);
4409
		return $string;
4410
	}
4411
4412
	/**
4413
	 * clean a message from elements regarded as potentially harmful
4414
	 * param string/reference $_html is the text to be processed
4415
	 * return nothing
4416
	 */
4417
	static function getCleanHTML(&$_html)
4418
	{
4419
		// remove CRLF and TAB as it is of no use in HTML.
4420
		// but they matter in <pre>, so we rather don't
4421
		//$_html = str_replace("\r\n",' ',$_html);
4422
		//$_html = str_replace("\t",' ',$_html);
4423
		//error_log(__METHOD__.__LINE__.':'.$_html);
4424
		//repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on
4425
		$_html = str_replace(array('&amp;amp;','<DIV><BR></DIV>',"<DIV>&nbsp;</DIV>",'<div>&nbsp;</div>','</td></font>','<br><td>','<tr></tr>','<o:p></o:p>','<o:p>','</o:p>'),
4426
							 array('&amp;',    '<BR>',           '<BR>',             '<BR>',             '</font></td>','<td>',    '',         '',           '',  ''),$_html);
4427
		//$_html = str_replace(array('&amp;amp;'),array('&amp;'),$_html);
4428
		if (stripos($_html,'style')!==false) Mail\Html::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags
4429
		if (stripos($_html,'head')!==false) Mail\Html::replaceTagsCompletley($_html,'head'); // Strip out stuff in head
4430
		//if (stripos($_html,'![if')!==false && stripos($_html,'<![endif]>')!==false) Mail\Html::replaceTagsCompletley($_html,'!\[if','<!\[endif\]>',false); // Strip out stuff in ifs
4431
		//if (stripos($_html,'!--[if')!==false && stripos($_html,'<![endif]-->')!==false) Mail\Html::replaceTagsCompletley($_html,'!--\[if','<!\[endif\]-->',false); // Strip out stuff in ifs
4432
		//error_log(__METHOD__.' ('.__LINE__.') '.$_html);
4433
4434
		if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html);
4435
		// Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards
4436
		if (stripos($_html,'!doctype')!==false) Mail\Html::replaceTagsCompletley($_html,'!doctype');
4437
		if (stripos($_html,'?xml:namespace')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml:namespace','/>',false);
4438
		if (stripos($_html,'?xml version')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml version','\?>',false);
4439
		if (strpos($_html,'!CURSOR')!==false) Mail\Html::replaceTagsCompletley($_html,'!CURSOR');
4440
		// htmLawed filter only the 'body'
4441
		//preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $_html, $matches);
4442
		//if ($matches[2])
4443
		//{
4444
		//	$hasOther = true;
4445
		//	$_html = $matches[2];
4446
		//}
4447
		// purify got switched to htmLawed
4448
		// some testcode to test purifying / htmlawed
4449
		//$_html = "<BLOCKQUOTE>hi <div> there </div> kram <br> </blockquote>".$_html;
4450
		$_html = Html\HtmLawed::purify($_html,self::$htmLawed_config,array(),true);
4451
		//if ($hasOther) $_html = $matches[1]. $_html. $matches[3];
4452
		// clean out comments , should not be needed as purify should do the job.
4453
		$search = array(
4454
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
4455
			'@<!--[\s\S]*?[ \t\n\r]*-->@',         // Strip multi-line comments including CDATA
4456
		);
4457
		$_html = preg_replace($search,"",$_html);
4458
		// remove non printable chars
4459
		$_html = preg_replace('/([\000-\011])/','',$_html);
4460
		//error_log(__METHOD__.':'.__LINE__.':'.$_html);
4461
	}
4462
4463
	/**
4464
	 * Header and Bodystructure stuff
4465
	 */
4466
4467
	/**
4468
	 * getMimePartCharset - fetches the charset mimepart if it exists
4469
	 * @param $_mimePartObject structure object
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\structure was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
4470
	 * @return mixed mimepart or false if no CHARSET is found, the missing charset has to be handled somewhere else,
4471
	 *		as we cannot safely assume any charset as we did earlier
4472
	 */
4473
	function getMimePartCharset($_mimePartObject)
4474
	{
4475
		//$charSet = 'iso-8859-1';//self::$displayCharset; //'iso-8859-1'; // self::displayCharset seems to be asmarter fallback than iso-8859-1
4476
		$CharsetFound=false;
4477
		//echo "#".$_mimePartObject->encoding.'#<br>';
4478
		if(is_array($_mimePartObject->parameters)) {
4479
			if(isset($_mimePartObject->parameters['CHARSET'])) {
4480
				$charSet = $_mimePartObject->parameters['CHARSET'];
4481
				$CharsetFound=true;
4482
			}
4483
		}
4484
		// this one is dirty, but until I find something that does the trick of detecting the encoding, ....
4485
		//if ($CharsetFound == false && $_mimePartObject->encoding == "QUOTED-PRINTABLE") $charSet = 'iso-8859-1'; //assume quoted-printable to be ISO
4486
		//if ($CharsetFound == false && $_mimePartObject->encoding == "BASE64") $charSet = 'utf-8'; // assume BASE64 to be UTF8
4487
		return ($CharsetFound ? $charSet : $CharsetFound);
4488
	}
4489
4490
	/**
4491
	 * decodeMimePart - fetches the charset mimepart if it exists
4492
	 * @param string $_mimeMessage - the message to be decoded
4493
	 * @param string $_encoding - the encoding used BASE64 and QUOTED-PRINTABLE is supported
4494
	 * @param string $_charset - not used
4495
	 * @return string decoded mimePart
4496
	 */
4497
	function decodeMimePart($_mimeMessage, $_encoding, $_charset = '')
4498
	{
4499
		// decode the part
4500
		if (self::$debug) error_log(__METHOD__."() with $_encoding and $_charset:".print_r($_mimeMessage,true));
4501
		switch (strtoupper($_encoding))
4502
		{
4503
			case 'BASE64':
4504
				// use imap_base64 to decode, not any longer, as it is strict, and fails if it encounters invalid chars
4505
				return base64_decode($_mimeMessage);
4506
4507
			case 'QUOTED-PRINTABLE':
4508
				// use imap_qprint to decode
4509
				return quoted_printable_decode($_mimeMessage);
4510
4511
			case 'WEDONTKNOWTHEENCODING':
4512
				// try base64
4513
				$r = base64_decode($_mimeMessage);
4514
				if (json_encode($r))
4515
				{
4516
					return $r;
4517
				}
4518
				//we do not know the encoding, so we do not decode
4519
			default:
4520
				// it is either not encoded or we don't know about it
4521
				return $_mimeMessage;
4522
		}
4523
	}
4524
4525
	/**
4526
	 * get part of the message, if its stucture is indicating its of multipart alternative style
4527
	 * a wrapper for multipartmixed
4528
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
4529
	 * @param Horde_Mime_Part $_structure structure for parsing
4530
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4531
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4532
	 * @param Horde_Mime_Part& $partCalendar =null on return text/calendar part, if one was contained or false
4533
	 * @return array containing the desired part
4534
	 */
4535
	function getMultipartAlternative($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$partCalendar=null)
4536
	{
4537
		// a multipart/alternative has exactly 2 parts (text and html  OR  text and something else)
4538
		// sometimes there are 3 parts, when there is an ics/ical attached/included-> we want to show that
4539
		// as attachment AND as abstracted ical information (we use our notification style here).
4540
		$partText = $partCalendar = $partHTML = null;
4541
		if (self::$debug) _debug_array(array("METHOD"=>__METHOD__,"LINE"=>__LINE__,"STRUCTURE"=>$_structure));
4542
		//error_log(__METHOD__.' ('.__LINE__.') ');
4543
		$ignore_first_part = true;
4544
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4544
		foreach(/** @scrutinizer ignore-deprecated */ $_structure->contentTypeMap() as $mime_id => $mime_type)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4545
		{
4546
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type"." ignoreFirstPart:".$ignore_first_part);
4547
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4548
4549
			if ($ignore_first_part)
4550
			{
4551
				$ignore_first_part = false;
4552
				continue;	// ignore multipart/alternative itself
4553
			}
4554
4555
			$mimePart = $_structure->getPart($mime_id);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4555
			$mimePart = /** @scrutinizer ignore-deprecated */ $_structure->getPart($mime_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4556
4557
			switch($mimePart->getPrimaryType())
4558
			{
4559
				case 'text':
4560
					switch($mimePart->getSubType())
4561
					{
4562
						case 'plain':
4563
							if ($mimePart->getBytes() > 0) $partText = $mimePart;
4564
							break;
4565
4566
						case 'html':
4567
							if ($mimePart->getBytes() > 0)  $partHTML = $mimePart;
4568
							break;
4569
4570
						case 'calendar':
4571
							if ($mimePart->getBytes() > 0)  $partCalendar = $mimePart;
4572
							break;
4573
					}
4574
					break;
4575
4576
				case 'multipart':
4577
					switch($mimePart->getSubType())
4578
					{
4579
						case 'related':
4580
						case 'mixed':
4581
							if (count($mimePart->getParts()) > 1)
4582
							{
4583
								// in a multipart alternative we treat the multipart/related as html part
4584
								if (self::$debug) error_log(__METHOD__." process MULTIPART/".$mimePart->getSubType()." with array as subparts");
4585
								$partHTML = $mimePart;
4586
								break 3; // GET OUT OF LOOP, will be processed according to type
4587
							}
4588
							break;
4589
						case 'alternative':
4590
							if (count($mimePart->getParts()) > 1)
4591
							{
4592
								//cascading multipartAlternative structure, assuming only the first one is to be used
4593
								return $this->getMultipartAlternative($_uid, $mimePart, $_htmlMode, $_preserveSeen);
4594
							}
4595
					}
4596
			}
4597
		}
4598
4599
		switch($_htmlMode)
4600
		{
4601
			case 'html_only':
4602
			case 'always_display':
4603
				if ($partHTML)
4604
				{
4605
					switch($partHTML->getSubType())
4606
					{
4607
						case 'related':
4608
							return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4609
4610
						case 'mixed':
4611
							return $this->getMultipartMixed($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4612
4613
						default:
4614
							return $this->getTextPart($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4615
					}
4616
				}
4617
				elseif ($partText && $_htmlMode=='always_display')
4618
				{
4619
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4620
				}
4621
				break;
4622
4623
			case 'only_if_no_text':
4624
				if ($partText)
4625
				{
4626
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4627
				}
4628
				if ($partHTML)
4629
				{
4630
					if ($partHTML->getPrimaryType())
4631
					{
4632
						return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4633
					}
4634
					return $this->getTextPart($_uid, $partHTML, 'always_display', $_preserveSeen);
4635
				}
4636
				break;
4637
4638
			default:
4639
				if ($partText)
4640
				{
4641
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4642
				}
4643
				$bodyPart = array(
4644
					'body'		=> lang("no plain text part found"),
4645
					'mimeType'	=> 'text/plain',
4646
					'charSet'	=> self::$displayCharset,
4647
				);
4648
				break;
4649
		}
4650
		return $bodyPart;
4651
	}
4652
4653
	/**
4654
	 * Get part of the message, if its stucture is indicating its of multipart mixed style
4655
	 *
4656
	 * @param int $_uid the messageuid,
4657
	 * @param Horde_Mime_Part $_structure = '' if given use structure for parsing
4658
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4659
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4660
	 * @param array& $skipParts - passed by reference to have control/knowledge which parts are already fetched
4661
	 * @param Horde_Mime_Part& $partCalendar =null on return text/calendar part, if one was contained or false
4662
	 * @return array containing the desired part
4663
	 */
4664
	function getMultipartMixed($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$skipParts=array(), &$partCalendar=null)
4665
	{
4666
		if (self::$debug) echo __METHOD__."$_uid, $_htmlMode<br>";
4667
		$bodyPart = array();
4668
		if (self::$debug) _debug_array($_structure);
4669
4670
		$ignore_first_part = true;
4671
		//$skipParts = array();
4672
		//error_log(__METHOD__.__LINE__.array2string($_structure->contentTypeMap()));
4673
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4673
		foreach(/** @scrutinizer ignore-deprecated */ $_structure->contentTypeMap() as $mime_id => $mime_type)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4674
		{
4675
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type");
4676
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4677
			if ($ignore_first_part)
4678
			{
4679
				$ignore_first_part = false;
4680
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED FirstPart $mime_id: $mime_type");
4681
				continue;	// ignore multipart/mixed itself
4682
			}
4683
			if (array_key_exists($mime_id,$skipParts))
4684
			{
4685
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED $mime_id: $mime_type");
4686
				continue;
4687
			}
4688
4689
			$part = $_structure->getPart($mime_id);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4689
			$part = /** @scrutinizer ignore-deprecated */ $_structure->getPart($mime_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4690
4691
			switch($part->getPrimaryType())
4692
			{
4693
				case 'multipart':
4694
					if ($part->getDisposition() == 'attachment') continue 2;	// +1 for switch
4695
					switch($part->getSubType())
4696
					{
4697
						case 'alternative':
4698
							return array($this->getMultipartAlternative($_uid, $part, $_htmlMode, $_preserveSeen, $partCalendar));
4699
4700
						case 'mixed':
4701
						case 'signed':
4702
							$bodyPart = array_merge($bodyPart, $this->getMultipartMixed($_uid, $part, $_htmlMode, $_preserveSeen, $skipParts, $partCalendar));
4703
							break;
4704
4705
						case 'related':
4706
							$bodyPart = array_merge($bodyPart, $this->getMultipartRelated($_uid, $part, $_htmlMode, $_preserveSeen, $partCalendar, $skipParts));
4707
							break;
4708
					}
4709
					break;
4710
				case 'application':
4711
					switch($part->getSubType())
4712
					{
4713
						case 'pgp-encrypted':
4714
							if (($part = $_structure->getPart($mime_id+1)) &&
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4714
							if (($part = /** @scrutinizer ignore-deprecated */ $_structure->getPart($mime_id+1)) &&

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4715
								$part->getType() == 'application/octet-stream')
4716
							{
4717
								$this->fetchPartContents($_uid, $part);
4718
								$skipParts[$mime_id]=$mime_type;
4719
								$skipParts[$mime_id+1]=$part->getType();
4720
								$bodyPart[] = array(
4721
									'body'		=> $part->getContents(array(
4722
										'stream' => false,
4723
									)),
4724
									'mimeType'  => 'text/plain',
4725
									'charSet'	=> $_structure->getCharset(),
4726
								);
4727
							}
4728
							break;
4729
					}
4730
					break;
4731
4732
				case 'text':
4733
					switch($part->getSubType())
4734
					{
4735
						case 'plain':
4736
						case 'html':
4737
						case 'calendar': // inline ics/ical files
4738
							if($part->getDisposition() != 'attachment')
4739
							{
4740
								$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4741
								$skipParts[$mime_id]=$mime_type;
4742
							}
4743
							//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$part->type."/".$part->subType.' -> BodyPart:'.array2string($bodyPart[count($bodyPart)-1]));
4744
							break;
4745
					}
4746
					break;
4747
4748
				case 'message':
4749
					//skip attachments
4750
					if($part->getSubType() == 'delivery-status' && $part->getDisposition() != 'attachment')
4751
					{
4752
						$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4753
						$skipParts[$mime_id]=$mime_type;
4754
					}
4755
					// do not descend into attached Messages
4756
					if($part->getSubType() == 'rfc822' || $part->getDisposition() == 'attachment')
4757
					{
4758
						$skipParts[$mime_id.'.0'] = $mime_type;
4759
						foreach($part->contentTypeMap() as $sub_id => $sub_type){ $skipParts[$sub_id] = $sub_type;}
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4759
						foreach(/** @scrutinizer ignore-deprecated */ $part->contentTypeMap() as $sub_id => $sub_type){ $skipParts[$sub_id] = $sub_type;}

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4760
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$_uid.' Part:'.$mime_id.':'.array2string($skipParts));
4761
						//break 2;
4762
					}
4763
					break;
4764
4765
				default:
4766
					// do nothing
4767
					// the part is a attachment
4768
			}
4769
		}
4770
		return $bodyPart;
4771
	}
4772
4773
	/**
4774
	 * getMultipartRelated
4775
	 * get part of the message, if its stucture is indicating its of multipart related style
4776
	 * a wrapper for multipartmixed
4777
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
4778
	 * @param Horde_Mime_Part $_structure if given use structure for parsing
4779
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4780
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4781
	 * @param Horde_Mime_Part& $partCalendar =null on return text/calendar part, if one was contained or false
4782
	 * @param array& $skipParts - passed by reference to have control/knowledge which parts are already fetched
4783
	 * @return array containing the desired part
4784
	 */
4785
	function getMultipartRelated($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen=false, &$partCalendar=null, &$skipParts=array())
4786
	{
4787
		return $this->getMultipartMixed($_uid, $_structure, $_htmlMode, $_preserveSeen, $skipParts, $partCalendar);
4788
	}
4789
4790
	/**
4791
	 * Fetch a body part
4792
	 *
4793
	 * @param int $_uid
4794
	 * @param string $_partID = null
4795
	 * @param string $_folder = null
4796
	 * @param boolean $_preserveSeen = false
4797
	 * @param boolean $_stream = false true return a stream, false return string
4798
	 * @param string &$_encoding = null on return: transfer encoding of returned part
4799
	 * @param boolean $_tryDecodingServerside = true; wether to try to fetch Data with BINARY instead of BODY
4800
	 * @return string|resource
4801
	 */
4802
	function getBodyPart($_uid, $_partID=null, $_folder=null, $_preserveSeen=false, $_stream=false, &$_encoding=null, $_tryDecodingServerside=true)
4803
	{
4804
		if (self::$debug) error_log( __METHOD__.__LINE__."(".array2string($_uid).", $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, $_tryDecodingServerside)");
4805
4806
		if (empty($_folder))
4807
		{
4808
			$_folder = (isset($this->sessionData['mailbox'])&&$this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
4809
		}
4810
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_folder).'/'.$this->icServer->getCurrentMailbox().'/'. $this->sessionData['mailbox']);
4811
		// querying contents of body part
4812
		$uidsToFetch = new Horde_Imap_Client_Ids();
4813
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
0 ignored issues
show
introduced by
The condition is_array($_uid) is always false.
Loading history...
4814
		$uidsToFetch->add($_uid);
4815
4816
		$fquery = new Horde_Imap_Client_Fetch_Query();
4817
		$fetchParams = array(
4818
			'peek' => $_preserveSeen,
4819
			'decode' => true,	// try decode on server, does NOT neccessary work
4820
		);
4821
		if ($_tryDecodingServerside===false)// || ($_tryDecodingServerside&&$this->isDraftFolder($_folder)))
4822
		{
4823
			$_tryDecodingServerside=false;
4824
			$fetchParams = array(
4825
				'peek' => $_preserveSeen,
4826
			);
4827
		}
4828
		$fquery->bodyPart($_partID, $fetchParams);
4829
4830
		$part = $this->icServer->fetch($_folder, $fquery, array(
4831
			'ids' => $uidsToFetch,
4832
		))->first();
4833
		$partToReturn = null;
4834
		if ($part)
4835
		{
4836
			$_encoding = $part->getBodyPartDecode($_partID);
4837
			//error_log(__METHOD__.__LINE__.':'.$_encoding.'#');
4838
			$partToReturn = $part->getBodyPart($_partID, $_stream);
4839
			//error_log(__METHOD__.__LINE__.':'.$partToReturn.'#');
4840
		}
4841
		// if we get an empty result, server may have trouble fetching data with UID FETCH $_uid (BINARY.PEEK[$_partID])
4842
		// thus we trigger a second go with UID FETCH $_uid (BODY.PEEK[$_partID])
4843
		if (empty($partToReturn)&&$_tryDecodingServerside===true)
4844
		{
4845
			error_log(__METHOD__.__LINE__.' failed to fetch bodyPart in  BINARY. Try BODY');
4846
			$partToReturn = $this->getBodyPart($_uid, $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, false);
4847
		}
4848
		return ($partToReturn?$partToReturn:null);
4849
	}
4850
4851
	/**
4852
	 * Get Body from message
4853
	 *
4854
	 * @param int $_uid the messageuid
4855
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4856
	 * @param string $_htmlMode how to display a message: 'html_only', 'always_display', 'only_if_no_text' or ''
4857
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4858
	 * @param boolean $_stream = false true return a stream, false return string
4859
	 * @return array containing the desired text part, mimeType and charset
4860
	 */
4861
	function getTextPart($_uid, Horde_Mime_Part $_structure, $_htmlMode='', $_preserveSeen=false, $_stream=false)
4862
	{
4863
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_uid.':'.array2string($_structure).' '.function_backtrace());
4864
		$bodyPart = array();
4865
		if (self::$debug) _debug_array(array($_structure,function_backtrace()));
4866
4867
		if($_structure->getSubType() == 'html' && !in_array($_htmlMode, array('html_only', 'always_display', 'only_if_no_text')))
4868
		{
4869
			$bodyPart = array(
4870
				'error'		=> 1,
4871
				'body'		=> lang("displaying html messages is disabled"),
4872
				'mimeType'	=> 'text/html',
4873
				'charSet'	=> self::$displayCharset,
4874
			);
4875
		}
4876
		elseif ($_structure->getSubType() == 'plain' && $_htmlMode == 'html_only')
4877
		{
4878
			$bodyPart = array(
4879
				'error'		=> 1,
4880
				'body'      => lang("displaying plain messages is disabled"),
4881
				'mimeType'  => 'text/plain', // make sure we do not return mimeType text/html
4882
				'charSet'   => self::$displayCharset,
4883
			);
4884
		}
4885
		else
4886
		{
4887
			// some Servers append PropertyFile___ ; strip that here for display
4888
			// RB: not sure what this is: preg_replace('/PropertyFile___$/','',$this->decodeMimePart($mimePartBody, $_structure->encoding, $this->getMimePartCharset($_structure))),
4889
4890
			// Should not try to fetch if the content is already there (e.g. Smime encrypted message)
4891
			if (empty($_structure->getContents())) $this->fetchPartContents($_uid, $_structure, $_stream, $_preserveSeen);
4892
4893
			$bodyPart = array(
4894
				'body'		=> $_structure->getContents(array(
4895
					'stream' => $_stream,
4896
				)),
4897
				'mimeType'  => $_structure->getType() == 'text/html' ? 'text/html' : 'text/plain',
4898
				'charSet'	=> $_structure->getCharset(),
4899
			);
4900
		}
4901
		return $bodyPart;
4902
	}
4903
4904
	/**
4905
	 * Get Body of message
4906
	 *
4907
	 * @param int $_uid the messageuid,
4908
	 * @param string $_htmlOptions how to display a message, html, plain text, ...
4909
	 * @param string $_partID = null the partID, may be omitted
4910
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4911
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4912
	 * @param string $_folder folder to work on
4913
	 * @param Horde_Mime_part& $calendar_part =null on return calendar-part or null, if there is none
4914
	 * @return array containing the message body, mimeType and charset
4915
	 */
4916
	function getMessageBody($_uid, $_htmlOptions='', $_partID=null, Horde_Mime_Part $_structure=null, $_preserveSeen = false, $_folder = '', &$calendar_part=null)
4917
	{
4918
		if (self::$debug) echo __METHOD__."$_uid, $_htmlOptions, $_partID<br>";
4919
		if($_htmlOptions != '') {
4920
			$this->htmlOptions = $_htmlOptions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_htmlOptions of type string is incompatible with the declared type array of property $htmlOptions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
4921
		}
4922
		if (empty($_folder))
4923
		{
4924
			$_folder = $this->sessionData['mailbox'];
4925
		}
4926
		if (empty($this->sessionData['mailbox']) && !empty($_folder))
4927
		{
4928
			$this->sessionData['mailbox'] = $_folder;
0 ignored issues
show
Bug Best Practice introduced by
The property sessionData does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
4929
		}
4930
4931
		if (!isset($_structure))
4932
		{
4933
			$_structure = $this->getStructure($_uid, $_partID, $_folder, $_preserveSeen);
4934
		}
4935
		if (!is_object($_structure))
4936
		{
4937
			return array(
4938
				array(
4939
					'error'		=> 1,
4940
					'body'		=> 'Error: Could not fetch structure on mail:'.$_uid." as $_htmlOptions". 'for Mailprofile'.$this->icServer->ImapServerId.' User:'.$GLOBALS['egw_info']['user']['account_lid'],
4941
					'mimeType'	=> 'text/plain',
4942
					'charSet'	=> self::$displayCharset,
4943
				)
4944
			);
4945
		}
4946
		if (!empty($_partID))
4947
		{
4948
			$_structure->contentTypeMap();
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4948
			/** @scrutinizer ignore-deprecated */ $_structure->contentTypeMap();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4949
			$_structure = $_structure->getPart($_partID);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4949
			$_structure = /** @scrutinizer ignore-deprecated */ $_structure->getPart($_partID);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4950
			//_debug_array($_structure->getMimeId()); exit;
4951
		}
4952
4953
		switch($_structure->getPrimaryType())
4954
		{
4955
			case 'application':
4956
				return array(
4957
					array(
4958
						'body'		=> '',
4959
						'mimeType'	=> 'text/plain',
4960
						'charSet'	=> 'iso-8859-1',
4961
					)
4962
				);
4963
4964
			case 'multipart':
4965
				switch($_structure->getSubType())
4966
				{
4967
					case 'alternative':
4968
						$bodyParts = array($this->getMultipartAlternative($_uid, $_structure, $this->htmlOptions, $_preserveSeen, $calendar_part));
4969
						break;
4970
4971
					case 'nil': // multipart with no Alternative
4972
					case 'mixed':
4973
					case 'report':
4974
					case 'signed':
4975
					case 'encrypted':
4976
						$skipParts = array();
4977
						$bodyParts = $this->getMultipartMixed($_uid, $_structure, $this->htmlOptions, $_preserveSeen, $skipParts, $calendar_part);
4978
						break;
4979
4980
					case 'related':
4981
						$bodyParts = $this->getMultipartRelated($_uid, $_structure, $this->htmlOptions, $_preserveSeen, $calendar_part);
4982
						break;
4983
				}
4984
				return self::normalizeBodyParts($bodyParts);
4985
4986
			case 'video':
4987
			case 'audio': // some servers send audiofiles and imagesfiles directly, without any stuff surround it
4988
			case 'image': // they are displayed as Attachment NOT INLINE
4989
				return array(
4990
					array(
4991
						'body'      => '',
4992
						'mimeType'  => $_structure->getSubType(),
4993
					),
4994
				);
4995
4996
			case 'text':
4997
				$bodyPart = array();
4998
				if ($_structure->getDisposition() != 'attachment')
4999
				{
5000
					switch($_structure->getSubType())
5001
					{
5002
						case 'calendar':
5003
							$calendar_part = $_structure;
5004
							// fall through in case user has no calendar rights
5005
						case 'html':
5006
						case 'plain':
5007
						default:
5008
							$bodyPart = array($this->getTextPart($_uid, $_structure, $this->htmlOptions, $_preserveSeen));
5009
					}
5010
				} else {
5011
					// what if the structure->disposition is attachment ,...
5012
				}
5013
				return self::normalizeBodyParts($bodyPart);
5014
5015
			case 'attachment':
5016
			case 'message':
5017
				switch($_structure->getSubType())
5018
				{
5019
					case 'rfc822':
5020
						$newStructure = $_structure->getParts();
5021
						if (self::$debug) {echo __METHOD__." Message -> RFC -> NewStructure:"; _debug_array($newStructure[0]);}
5022
						return self::normalizeBodyParts($this->getMessageBody($_uid, $_htmlOptions, $newStructure[0]->getMimeId(), $newStructure[0], $_preserveSeen, $_folder));
5023
				}
5024
				break;
5025
5026
			default:
5027
				if (self::$debug) _debug_array($_structure);
5028
				return array(
5029
					array(
5030
						'body'		=> lang('The mimeparser can not parse this message.').$_structure->getType(),
5031
						'mimeType'	=> 'text/plain',
5032
						'charSet'	=> self::$displayCharset,
5033
					)
5034
				);
5035
		}
5036
	}
5037
5038
	/**
5039
	 * normalizeBodyParts - function to gather and normalize all body Information
5040
	 * as we may recieve a bodyParts structure from within getMessageBody nested deeper than expected
5041
	 * so this is used to normalize the output, so we are able to rely on our expectation
5042
	 * @param _bodyParts - Body Array
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
5043
	 * @return array - a normalized Bodyarray
5044
	 */
5045
	static function normalizeBodyParts($_bodyParts)
5046
	{
5047
		if (is_array($_bodyParts))
5048
		{
5049
			foreach($_bodyParts as $singleBodyPart)
5050
			{
5051
				if (!isset($singleBodyPart['body'])) {
5052
					$buff = self::normalizeBodyParts($singleBodyPart);
5053
					foreach ((array)$buff as $val) { $body2return[] = $val;}
5054
					continue;
5055
				}
5056
				$body2return[] = $singleBodyPart;
5057
			}
5058
		}
5059
		else
5060
		{
5061
			$body2return = $_bodyParts;
5062
		}
5063
		return $body2return;
5064
	}
5065
5066
	/**
5067
	 * getdisplayableBody - creates the bodypart of the email as textual representation
5068
	 * @param object $mailClass the mailClass object to be used
5069
	 * @param array $bodyParts  with the bodyparts
5070
	 * @param boolean $preserveHTML  switch to preserve HTML
5071
	 * @param boolean $useTidy  switch to use tidy
5072
	 * @return string a preformatted string with the mails converted to text
5073
	 */
5074
	static function &getdisplayableBody(&$mailClass, $bodyParts, $preserveHTML = false,  $useTidy = true)
5075
	{
5076
		$message='';
5077
		for($i=0; $i<count($bodyParts); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5078
		{
5079
			if (!isset($bodyParts[$i]['body'])) {
5080
				$bodyParts[$i]['body'] = self::getdisplayableBody($mailClass, $bodyParts[$i], $preserveHTML, $useTidy);
5081
				$message .= empty($bodyParts[$i]['body'])?'':$bodyParts[$i]['body'];
5082
				continue;
5083
			}
5084
			if (isset($bodyParts[$i]['error'])) continue;
5085
			if (empty($bodyParts[$i]['body'])) continue;
5086
			// some characterreplacements, as they fail to translate
5087
			$sar = array(
5088
				'@(\x84|\x93|\x94)@',
5089
				'@(\x96|\x97|\x1a)@',
5090
				'@(\x82|\x91|\x92)@',
5091
				'@(\x85)@',
5092
				'@(\x86)@',
5093
				'@(\x99)@',
5094
				'@(\xae)@',
5095
			);
5096
			$rar = array(
5097
				'"',
5098
				'-',
5099
				'\'',
5100
				'...',
5101
				'&',
5102
				'(TM)',
5103
				'(R)',
5104
			);
5105
5106
			if(($bodyParts[$i]['mimeType'] == 'text/html' || $bodyParts[$i]['mimeType'] == 'text/plain') &&
5107
				strtoupper($bodyParts[$i]['charSet']) != 'UTF-8')
5108
			{
5109
				$bodyParts[$i]['body'] = preg_replace($sar,$rar,$bodyParts[$i]['body']);
5110
			}
5111
5112
			if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Translation::detect_encoding($bodyParts[$i]['body']);
5113
			// add line breaks to $bodyParts
5114
			//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
5115
			$newBody  = Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
5116
			//error_log(__METHOD__.' ('.__LINE__.') '.' MimeType:'.$bodyParts[$i]['mimeType'].'->'.$newBody);
5117
			$mailClass->activeMimeType = 'text/plain';
5118
			if ($bodyParts[$i]['mimeType'] == 'text/html') {
5119
				$mailClass->activeMimeType = $bodyParts[$i]['mimeType'];
5120
				if (!$preserveHTML)
5121
				{
5122
					$alreadyHtmlLawed=false;
5123
					// as Translation::convert reduces \r\n to \n and purifier eats \n -> peplace it with a single space
5124
					$newBody = str_replace("\n"," ",$newBody);
5125
					// convert HTML to text, as we dont want HTML in infologs
5126
					if ($useTidy && extension_loaded('tidy'))
5127
					{
5128
						$tidy = new tidy();
5129
						$cleaned = $tidy->repairString($newBody, self::$tidy_config,'utf8');
5130
						// Found errors. Strip it all so there's some output
5131
						if($tidy->getStatus() == 2)
5132
						{
5133
							error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$tidy->errorBuffer);
5134
						}
5135
						else
5136
						{
5137
							$newBody = $cleaned;
5138
						}
5139
						if (!$preserveHTML)
5140
						{
5141
							// filter only the 'body', as we only want that part, if we throw away the html
5142
							preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $newBody, $matches=array());
0 ignored issues
show
Bug introduced by
$matches = array() cannot be passed to preg_match() as the parameter $matches expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5142
							preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $newBody, /** @scrutinizer ignore-type */ $matches=array());
Loading history...
5143
							if ($matches[2])
5144
							{
5145
								$hasOther = true;
5146
								$newBody = $matches[2];
5147
							}
5148
						}
5149
					}
5150
					else
5151
					{
5152
						// htmLawed filter only the 'body'
5153
						preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $newBody, $matches=array());
5154
						if ($matches[2])
5155
						{
5156
							$hasOther = true;
5157
							$newBody = $matches[2];
5158
						}
5159
						$htmLawed = new Html\HtmLawed();
5160
						// the next line should not be needed, but produces better results on HTML 2 Text conversion,
5161
						// as we switched off HTMLaweds tidy functionality
5162
						$newBody = str_replace(array('&amp;amp;','<DIV><BR></DIV>',"<DIV>&nbsp;</DIV>",'<div>&nbsp;</div>'),array('&amp;','<BR>','<BR>','<BR>'),$newBody);
5163
						$newBody = $htmLawed->run($newBody,self::$htmLawed_config);
5164
						if ($hasOther && $preserveHTML) $newBody = $matches[1]. $newBody. $matches[3];
5165
						$alreadyHtmlLawed=true;
0 ignored issues
show
Unused Code introduced by
The assignment to $alreadyHtmlLawed is dead and can be removed.
Loading history...
5166
					}
5167
					//error_log(__METHOD__.' ('.__LINE__.') '.' after purify:'.$newBody);
5168
					if ($preserveHTML==false) $newBody = Mail\Html::convertHTMLToText($newBody,self::$displayCharset,true,true);
5169
					//error_log(__METHOD__.' ('.__LINE__.') '.' after convertHTMLToText:'.$newBody);
5170
					if ($preserveHTML==false) $newBody = nl2br($newBody); // we need this, as htmLawed removes \r\n
5171
					/*if (!$alreadyHtmlLawed) */ $mailClass->getCleanHTML($newBody); // remove stuff we regard as unwanted
5172
					if ($preserveHTML==false) $newBody = str_replace("<br />","\r\n",$newBody);
5173
					//error_log(__METHOD__.' ('.__LINE__.') '.' after getClean:'.$newBody);
5174
				}
5175
				$message .= $newBody;
5176
				continue;
5177
			}
5178
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after specialchars):'.$newBody);
5179
			//use Mail\Html::convertHTMLToText instead of strip_tags, (even message is plain text) as strip_tags eats away too much
5180
			//$newBody = strip_tags($newBody); //we need to fix broken tags (or just stuff like "<800 USD/p" )
5181
			$newBody = Mail\Html::convertHTMLToText($newBody,self::$displayCharset,false,false);
5182
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after strip tags):'.$newBody);
5183
			$newBody = htmlspecialchars_decode($newBody,ENT_QUOTES);
5184
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body (after hmlspc_decode):'.$newBody);
5185
			$message .= $newBody;
5186
			//continue;
5187
		}
5188
		return $message;
5189
	}
5190
5191
	static function wordwrap($str, $cols, $cut, $dontbreaklinesstartingwith=false)
5192
	{
5193
		$lines = explode("\n", $str);
5194
		$newStr = '';
5195
		foreach($lines as $line)
5196
		{
5197
			// replace tabs by 8 space chars, or any tab only counts one char
5198
			//$line = str_replace("\t","        ",$line);
5199
			//$newStr .= wordwrap($line, $cols, $cut);
5200
			$allowedLength = $cols-strlen($cut);
5201
			//dont try to break lines with links, chance is we mess up the text is way too big
5202
			if (strlen($line) > $allowedLength && stripos($line,'href=')===false &&
5203
				($dontbreaklinesstartingwith==false ||
5204
				 ($dontbreaklinesstartingwith &&
5205
				  strlen($dontbreaklinesstartingwith)>=1 &&
5206
				  substr($line,0,strlen($dontbreaklinesstartingwith)) != $dontbreaklinesstartingwith
5207
				 )
5208
				)
5209
			   )
5210
			{
5211
				$s=explode(" ", $line);
5212
				$line = "";
5213
				$linecnt = 0;
5214
				foreach ($s as &$v) {
5215
					$cnt = strlen($v);
5216
					// only break long words within the wordboundaries,
5217
					// but it may destroy links, so we check for href and dont do it if we find one
5218
					// we check for any html within the word, because we do not want to break html by accident
5219
					if($cnt > $allowedLength && stripos($v,'href=')===false && stripos($v,'onclick=')===false && $cnt == strlen(html_entity_decode($v)))
5220
					{
5221
						$v=wordwrap($v, $allowedLength, $cut, true);
5222
					}
5223
					// the rest should be broken at the start of the new word that exceeds the limit
5224
					if ($linecnt+$cnt > $allowedLength) {
5225
						$v=$cut.$v;
5226
						#$linecnt = 0;
5227
						$linecnt =strlen($v)-strlen($cut);
5228
					} else {
5229
						$linecnt += $cnt;
5230
					}
5231
					if (strlen($v)) $line .= (strlen($line) ? " " : "").$v;
5232
				}
5233
			}
5234
			$newStr .= $line . "\n";
5235
		}
5236
		return $newStr;
5237
	}
5238
5239
	/**
5240
	 * getMessageEnvelope
5241
	 * get parsed headers from message
5242
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
5243
	 * @param string/int $_partID = '' , the partID, may be omitted
5244
	 * @param boolean $decode flag to do the decoding on the fly
5245
	 * @param string $_folder folder to work on
5246
	 * @param boolean $_useHeaderInsteadOfEnvelope - force getMessageHeader method to be used for fetching Envelope Information
5247
	 * @return array the message header
5248
	 */
5249
	function getMessageEnvelope($_uid, $_partID = '',$decode=false, $_folder='', $_useHeaderInsteadOfEnvelope=false)
5250
	{
5251
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder".function_backtrace());
5252
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5253
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder");
5254
		if((empty($_partID)||$_partID=='null')&&$_useHeaderInsteadOfEnvelope===false) {
5255
			$uidsToFetch = new Horde_Imap_Client_Ids();
5256
			if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5257
			$uidsToFetch->add($_uid);
5258
5259
			$fquery = new Horde_Imap_Client_Fetch_Query();
5260
			$envFields = new Horde_Mime_Headers();
5261
			$fquery->envelope();
5262
			$fquery->size();
5263
			$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5264
				'ids' => $uidsToFetch,
5265
			));
5266
			if (is_object($headersNew)) {
5267
				foreach($headersNew as &$_headerObject) {
5268
					$env = $_headerObject->getEnvelope();
5269
					//_debug_array($envFields->singleFields());
5270
					$singleFields = $envFields->singleFields();
5271
					foreach ($singleFields as &$v)
5272
					{
5273
						switch ($v)
5274
						{
5275
							case 'to':
5276
							case 'reply-to':
5277
							case 'from':
5278
							case 'cc':
5279
							case 'bcc':
5280
							case 'sender':
5281
								//error_log(__METHOD__.' ('.__LINE__.') '.$v.'->'.array2string($env->$v->addresses));
5282
								$envelope[$v]=$env->$v->addresses;
5283
								$address = array();
5284
								if (!is_array($envelope[$v])) break;
5285
								foreach ($envelope[$v] as $k => $ad)
5286
								{
5287
									if (stripos($ad,'@')===false)
5288
									{
5289
										$remember=$k;
5290
									}
5291
									else
5292
									{
5293
										$address[] = (!is_null($remember)?$envelope[$v][$remember].' ':'').$ad;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $envelope seems to be defined later in this foreach loop on line 5282. Are you sure it is defined here?
Loading history...
5294
										$remember=null;
5295
									}
5296
								}
5297
								$envelope[$v] = $address;
5298
								break;
5299
							case 'date':
5300
								$envelope[$v]=DateTime::to($env->$v);
5301
								break;
5302
							default:
5303
								$envelope[$v]=$env->$v;
5304
						}
5305
					}
5306
					$envelope['size']=$_headerObject->getSize();
5307
				}
5308
			}
5309
			$envelope = array_change_key_case($envelope,CASE_UPPER);
5310
			//if ($decode) _debug_array($envelope);
5311
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($envelope));
5312
			if ($decode)
5313
			{
5314
				foreach ($envelope as $key => $rvV)
5315
				{
5316
					//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5317
					$envelope[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5318
				}
5319
			}
5320
			return $envelope;
5321
		} else {
5322
5323
			$headers = $this->getMessageHeader($_uid, $_partID, true,true,$_folder);
5324
5325
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($headers));
5326
			//_debug_array($headers);
5327
			$newData = array(
5328
				'DATE'		=> $headers['DATE'],
5329
				'SUBJECT'	=> ($decode ? self::decode_header($headers['SUBJECT']):$headers['SUBJECT']),
5330
				'MESSAGE_ID'	=> $headers['MESSAGE-ID']
5331
			);
5332
			if (isset($headers['IN-REPLY-TO'])) $newData['IN-REPLY-TO'] = $headers['IN-REPLY-TO'];
5333
			if (isset($headers['REFERENCES'])) $newData['REFERENCES'] = $headers['REFERENCES'];
5334
			if (isset($headers['THREAD-TOPIC'])) $newData['THREAD-TOPIC'] = $headers['THREAD-TOPIC'];
5335
			if (isset($headers['THREAD-INDEX'])) $newData['THREAD-INDEX'] = $headers['THREAD-INDEX'];
5336
			if (isset($headers['LIST-ID'])) $newData['LIST-ID'] = $headers['LIST-ID'];
5337
			if (isset($headers['SIZE'])) $newData['SIZE'] = $headers['SIZE'];
5338
			//_debug_array($newData);
5339
			$recepientList = array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO');
5340
			foreach($recepientList as $recepientType) {
5341
				if(isset($headers[$recepientType])) {
5342
					if ($decode) $headers[$recepientType] =  self::decode_header($headers[$recepientType],true);
5343
					//error_log(__METHOD__.__LINE__." ".$recepientType."->".array2string($headers[$recepientType]));
5344
					foreach(self::parseAddressList($headers[$recepientType]) as $singleAddress) {
5345
						$addressData = array(
5346
							'PERSONAL_NAME'		=> $singleAddress->personal ? $singleAddress->personal : 'NIL',
5347
							'AT_DOMAIN_LIST'	=> $singleAddress->adl ? $singleAddress->adl : 'NIL',
5348
							'MAILBOX_NAME'		=> $singleAddress->mailbox ? $singleAddress->mailbox : 'NIL',
5349
							'HOST_NAME'		=> $singleAddress->host ? $singleAddress->host : 'NIL',
5350
							'EMAIL'			=> $singleAddress->host ? $singleAddress->mailbox.'@'.$singleAddress->host : $singleAddress->mailbox,
5351
						);
5352
						if($addressData['PERSONAL_NAME'] != 'NIL') {
5353
							$addressData['RFC822_EMAIL'] = imap_rfc822_write_address($singleAddress->mailbox, $singleAddress->host, $singleAddress->personal);
5354
						} else {
5355
							$addressData['RFC822_EMAIL'] = 'NIL';
5356
						}
5357
						$newData[$recepientType][] = ($addressData['RFC822_EMAIL']!='NIL'?$addressData['RFC822_EMAIL']:$addressData['EMAIL']);//$addressData;
5358
					}
5359
				} else {
5360
					if($recepientType == 'SENDER' || $recepientType == 'REPLY-TO') {
5361
						$newData[$recepientType] = $newData['FROM'];
5362
					} else {
5363
						$newData[$recepientType] = array();
5364
					}
5365
				}
5366
			}
5367
			//if ($decode) _debug_array($newData);
5368
			return $newData;
5369
		}
5370
	}
5371
5372
	/**
5373
	 * Get parsed headers from message
5374
	 *
5375
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
5376
	 * @param string/int $_partID ='' , the partID, may be omitted
5377
	 * @param boolean|string $decode flag to do the decoding on the fly or "object"
5378
	 * @param boolean $preserveUnSeen flag to preserve the seen flag where applicable
5379
	 * @param string $_folder folder to work on
5380
	 * @return array|Horde_Mime_Headers message header as array or object
5381
	 */
5382
	function getMessageHeader($_uid, $_partID = '',$decode=false, $preserveUnSeen=false, $_folder='')
5383
	{
5384
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$_uid.', '.$_partID.', '.$decode.', '.$preserveUnSeen.', '.$_folder);
5385
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5386
		$uidsToFetch = new Horde_Imap_Client_Ids();
5387
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5388
		$uidsToFetch->add($_uid);
5389
5390
		$fquery = new Horde_Imap_Client_Fetch_Query();
5391
		if ($_partID != '')
5392
		{
5393
			$fquery->headerText(array('id'=>$_partID,'peek'=>$preserveUnSeen));
5394
			$fquery->structure();
5395
		}
5396
		else
5397
		{
5398
			$fquery->headerText(array('peek'=>$preserveUnSeen));
5399
		}
5400
		$fquery->size();
5401
5402
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5403
			'ids' => $uidsToFetch,
5404
		));
5405
		if (is_object($headersNew)) {
5406
			foreach($headersNew as $_fetchObject)
5407
			{
5408
				$headers = $_fetchObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5409
				if ($_partID != '')
5410
				{
5411
					$mailStructureObject = $_fetchObject->getStructure();
5412
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5413
					{
5414
						if ($mime_id==$_partID)
5415
						{
5416
							//error_log(__METHOD__.' ('.__LINE__.') '."$mime_id == $_partID".array2string($_headerObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray()));
5417
							$headers = $_fetchObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5418
							break;
5419
						}
5420
					}
5421
				}
5422
				$size = $_fetchObject->getSize();
5423
				//error_log(__METHOD__.__LINE__.'#'.$size);
5424
			}
5425
			if ($decode === 'object')
5426
			{
5427
				if (is_object($headers)) $headers->setUserAgent('EGroupware API '.$GLOBALS['egw_info']['server']['versions']['phpgwapi']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $headers seems to be defined by a foreach iteration on line 5406. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
5428
				return $headers;
5429
			}
5430
			$retValue = is_object($headers) ? $headers->toArray():array();
5431
			if ($size) $retValue['size'] = $size;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $size seems to be defined by a foreach iteration on line 5406. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
5432
		}
5433
		$retValue = array_change_key_case($retValue,CASE_UPPER);
5434
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue));
5435
		// if SUBJECT is an array, use thelast one, as we assume something with the unfolding for the subject did not work
5436
		if (is_array($retValue['SUBJECT']))
5437
		{
5438
			$retValue['SUBJECT'] = $retValue['SUBJECT'][count($retValue['SUBJECT'])-1];
5439
		}
5440
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($decode ? self::decode_header($retValue,true):$retValue));
5441
		if ($decode)
5442
		{
5443
			foreach ($retValue as $key => $rvV)
5444
			{
5445
				//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5446
				$retValue[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5447
			}
5448
		}
5449
		return $retValue;
5450
	}
5451
5452
	/**
5453
	 * getMessageRawHeader
5454
	 * get messages raw header data
5455
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
5456
	 * @param string/int $_partID = '' , the partID, may be omitted
5457
	 * @param string $_folder folder to work on
5458
	 * @return string the message header
5459
	 */
5460
	function getMessageRawHeader($_uid, $_partID = '', $_folder = '')
5461
	{
5462
		static $rawHeaders;
5463
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5464
		//error_log(__METHOD__.' ('.__LINE__.') '." Try Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5465
5466
		if (is_null($rawHeaders)||!is_array($rawHeaders)) $rawHeaders = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
5467
		if (isset($rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5468
		{
5469
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5470
			return $rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5471
		}
5472
		$uidsToFetch = new Horde_Imap_Client_Ids();
5473
		$uid = $_uid;
5474
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5475
		$uidsToFetch->add($uid);
5476
5477
		$fquery = new Horde_Imap_Client_Fetch_Query();
5478
		if ($_partID != '')
5479
		{
5480
			$fquery->headerText(array('id'=>$_partID,'peek'=>true));
5481
			$fquery->structure();
5482
		}
5483
		else
5484
		{
5485
			$fquery->headerText(array('peek'=>true));
5486
		}
5487
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5488
			'ids' => $uidsToFetch,
5489
		));
5490
		if (is_object($headersNew)) {
5491
			foreach($headersNew as &$_headerObject) {
5492
				$retValue = $_headerObject->getHeaderText();
5493
				if ($_partID != '')
5494
				{
5495
					$mailStructureObject = $_headerObject->getStructure();
5496
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5497
					{
5498
						if ($mime_id==$_partID)
5499
						{
5500
							$retValue = $_headerObject->getHeaderText($mime_id);
5501
						}
5502
					}
5503
				}
5504
			}
5505
		}
5506
		$rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]=$retValue;
5507
		Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($GLOBALS['egw_info']['user']['account_id']),$rawHeaders,60*60*1);
5508
		return $retValue;
5509
	}
5510
5511
	/**
5512
	 * getStyles - extracts the styles from the given bodyparts
5513
	 * @param array $_bodyParts  with the bodyparts
5514
	 * @return string a preformatted string with the mails converted to text
5515
	 */
5516
	static function &getStyles($_bodyParts)
5517
	{
5518
		$style = '';
5519
		if (empty($_bodyParts)) return "";
5520
		foreach((array)$_bodyParts as $singleBodyPart) {
5521
			if (!isset($singleBodyPart['body'])) {
5522
				$singleBodyPart['body'] = self::getStyles($singleBodyPart);
5523
				$style .= $singleBodyPart['body'];
5524
				continue;
5525
			}
5526
5527
			if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = Translation::detect_encoding($singleBodyPart['body']);
5528
			$singleBodyPart['body'] = Translation::convert(
5529
				$singleBodyPart['body'],
5530
				strtolower($singleBodyPart['charSet'])
5531
			);
5532
			$ct = 0;
5533
			$newStyle=array();
5534
			if (stripos($singleBodyPart['body'],'<style')!==false)  $ct = preg_match_all('#<style(?:\s.*)?>(.+)</style>#isU', $singleBodyPart['body'], $newStyle);
5535
			if ($ct>0)
5536
			{
5537
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$ct.'#'.array2string($newStyle));
5538
				$style2buffer = implode('',$newStyle[0]);
5539
			}
5540
			if ($style2buffer && strtoupper(self::$displayCharset) == 'UTF-8')
5541
			{
5542
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($style2buffer));
5543
				$test = json_encode($style2buffer);
5544
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$test.'# ->'.strlen($style2buffer).' Error:'.json_last_error());
5545
				//if (json_last_error() != JSON_ERROR_NONE && strlen($style2buffer)>0)
5546
				if ($test=="null" && strlen($style2buffer)>0)
5547
				{
5548
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
5549
					error_log(__METHOD__.' ('.__LINE__.') '.' Found Invalid sequence for utf-8 in CSS:'.$style2buffer.' Charset Reported:'.$singleBodyPart['charSet'].' Carset Detected:'.Translation::detect_encoding($style2buffer));
5550
					$style2buffer = utf8_encode($style2buffer);
5551
				}
5552
			}
5553
			$style .= $style2buffer;
5554
		}
5555
		// clean out comments and stuff
5556
		$search = array(
5557
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
5558
//			'@<!--[\s\S]*?[ \t\n\r]*-->@',   // Strip multi-line comments including CDATA
5559
//			'@<!--[\s\S]*?[ \t\n\r]*--@',    // Strip broken multi-line comments including CDATA
5560
		);
5561
		$style = preg_replace($search,"",$style);
5562
5563
		// CSS Security
5564
		// http://code.google.com/p/browsersec/wiki/Part1#Cascading_stylesheets
5565
		$css = preg_replace('/(javascript|expression|-moz-binding)/i','',$style);
5566
		if (stripos($css,'script')!==false) Mail\Html::replaceTagsCompletley($css,'script'); // Strip out script that may be included
5567
		// we need this, as styledefinitions are enclosed with curly brackets; and template stuff tries to replace everything between curly brackets that is having no horizontal whitespace
5568
		// as the comments as <!-- styledefinition --> in stylesheet are outdated, and ck-editor does not understand it, we remove it
5569
		$css = str_replace(array(':','<!--','-->'),array(': ','',''),$css);
5570
		//error_log(__METHOD__.' ('.__LINE__.') '.$css);
5571
		// TODO: we may have to strip urls and maybe comments and ifs
5572
		return $css;
5573
	}
5574
5575
	/**
5576
	 * getMessageRawBody
5577
	 * get the message raw body
5578
	 * @param string/int $_uid the messageuid,
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
5579
	 * @param string/int $_partID = '' , the partID, may be omitted
5580
	 * @param string $_folder folder to work on
5581
	 * @param boolean $_stream =false true: return a stream, false: return string, stream suppresses any caching
5582
	 * @return string the message body
5583
	 */
5584
	function getMessageRawBody($_uid, $_partID = '', $_folder='', $_stream=false)
5585
	{
5586
		//TODO: caching einbauen static!
5587
		static $rawBody;
5588
		if (is_null($rawBody)) $rawBody = array();
5589
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5590
		if (!$_stream && isset($rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5591
		{
5592
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder");
5593
			return $rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5594
		}
5595
5596
		$uidsToFetch = new Horde_Imap_Client_Ids();
5597
		$uid = $_uid;
5598
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5599
		$uidsToFetch->add($uid);
5600
5601
		$fquery = new Horde_Imap_Client_Fetch_Query();
5602
		$fquery->fullText(array('peek'=>true));
5603
		if ($_partID != '')
5604
		{
5605
			$fquery->structure();
5606
			$fquery->bodyPart($_partID,array('peek'=>true));
5607
		}
5608
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5609
			'ids' => $uidsToFetch,
5610
		));
5611
		if (is_object($headersNew)) {
5612
			foreach($headersNew as &$_headerObject) {
5613
				$body = $_headerObject->getFullMsg($_stream);
5614
				if ($_partID != '')
5615
				{
5616
					$mailStructureObject = $_headerObject->getStructure();
5617
					//_debug_array($mailStructureObject->contentTypeMap());
5618
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5619
					{
5620
						if ($mime_id==$_partID)
5621
						{
5622
							$body = $_headerObject->getBodyPart($mime_id, $_stream);
5623
						}
5624
					}
5625
				}
5626
			}
5627
		}
5628
		if (!$_stream)
5629
		{
5630
			//error_log(__METHOD__.' ('.__LINE__.') '."[{$this->icServer->ImapServerId}][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]");
5631
			$rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)] = $body;
5632
		}
5633
		return $body;
5634
	}
5635
5636
	/**
5637
	 * Get structure of a mail or part of a mail
5638
	 *
5639
	 * @param int $_uid
5640
	 * @param string $_partID = null
5641
	 * @param string $_folder = null
5642
	 * @param boolean $_preserveSeen = false flag to preserve the seenflag by using body.peek
5643
	 * @param Horde_Imap_Client_Fetch_Query $fquery=null default query just structure
5644
	 * @return Horde_Mime_Part
5645
	 */
5646
	function getStructure($_uid, $_partID=null, $_folder=null, $_preserveSeen=false)
5647
	{
5648
		if (self::$debug) error_log( __METHOD__.' ('.__LINE__.') '.":$_uid, $_partID");
5649
5650
		if (empty($_folder))
5651
		{
5652
			$_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5653
		}
5654
		$uidsToFetch = new Horde_Imap_Client_Ids();
5655
5656
		if (!(is_object($_uid) || is_array($_uid)))
0 ignored issues
show
introduced by
The condition is_array($_uid) is always false.
Loading history...
5657
		{
5658
			$_uid = (array)$_uid;
5659
		}
5660
5661
		$uidsToFetch->add($_uid);
5662
5663
		try
5664
		{
5665
			$_fquery = new Horde_Imap_Client_Fetch_Query();
5666
	// not sure why Klaus add these, seem not necessary
5667
	//		$fquery->envelope();
5668
	//		$fquery->size();
5669
			$_fquery->structure();
5670
			if ($_partID) $_fquery->bodyPart($_partID, array('peek' => $_preserveSeen));
5671
5672
			$mail = $this->icServer->fetch($_folder, $_fquery, array(
5673
				'ids' => $uidsToFetch,
5674
			))->first();
5675
			if (is_object($mail))
5676
			{
5677
				$structure = $mail->getStructure();
5678
				$isSmime = Mail\Smime::isSmime(($mimeType = $structure->getType())) || Mail\Smime::isSmime(($protocol=$structure->getContentTypeParameter('protocol')));
5679
				if ($isSmime && !class_exists('mail_zpush', false))
5680
				{
5681
					return $this->resolveSmimeMessage($structure, array(
5682
						'uid' => $_uid,
5683
						'mailbox' => $_folder,
5684
						'mimeType' => Mail\Smime::isSmime($protocol) ? $protocol: $mimeType
5685
					));
5686
				}
5687
				return $mail->getStructure();
5688
			}
5689
			else
5690
			{
5691
				return null;
5692
			}
5693
		}
5694
		catch (Mail\Smime\PassphraseMissing $e)
5695
		{
5696
			// re-throw the exception to be caught on UI
5697
			throw $e;
5698
		}
5699
		catch (Exception $e)
5700
		{
5701
			error_log(__METHOD__.' ('.__LINE__.') '.' Could not fetch structure on mail:'.$_uid.' Serverprofile->'.$this->icServer->ImapServerId.' Message:'.$e->getMessage().' Stack:'.function_backtrace());
0 ignored issues
show
Bug introduced by
Are you sure $_uid of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

5701
			error_log(__METHOD__.' ('.__LINE__.') '.' Could not fetch structure on mail:'./** @scrutinizer ignore-type */ $_uid.' Serverprofile->'.$this->icServer->ImapServerId.' Message:'.$e->getMessage().' Stack:'.function_backtrace());
Loading history...
5702
			return null;
5703
		}
5704
	}
5705
5706
	/**
5707
	 * Parse the structure for attachments
5708
	 *
5709
	 * Returns not the attachments itself, but an array of information about the attachment
5710
	 *
5711
	 * @param int $_uid the messageuid,
5712
	 * @param string $_partID = null , the partID, may be omitted
5713
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
5714
	 * @param boolean $fetchEmbeddedImages = true,
5715
	 * @param boolean $fetchTextCalendar = false,
5716
	 * @param boolean $resolveTNEF = true
5717
	 * @param string $_folder folder to work on
5718
	 * @return array  an array of information about the attachment: array of array(name, size, mimeType, partID, encoding)
5719
	 */
5720
	function getMessageAttachments($_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folder='')
5721
	{
5722
		if (self::$debug) error_log( __METHOD__.":$_uid, $_partID");
5723
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5724
		$attachments = array();
5725
		if (!isset($_structure))
5726
		{
5727
			$_structure = $this->getStructure($_uid, $_partID,$_folder,true);
5728
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.print_r($_structure->contentTypeMap(),true));
5729
		}
5730
		if (!$_structure || !$_structure->contentTypeMap()) return array();
0 ignored issues
show
introduced by
$_structure is of type Horde_Mime_Part, thus it always evaluated to true.
Loading history...
Deprecated Code introduced by
The function Horde_Mime_Part::contentTypeMap() has been deprecated: Use iterator instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5730
		if (!$_structure || !/** @scrutinizer ignore-deprecated */ $_structure->contentTypeMap()) return array();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
5731
		if (!empty($_partID)) $_structure = $_structure->getPart($_partID);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::getPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5731
		if (!empty($_partID)) $_structure = /** @scrutinizer ignore-deprecated */ $_structure->getPart($_partID);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
5732
		$skipParts = array();
5733
		$tnefParts = array();
5734
		$skip = 0;
5735
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
5736
		{
5737
			// skip multipart/encrypted incl. its two sub-parts, as we show 2. sub-part as body to be decrypted client-side
5738
			if ($mime_type == 'multipart/encrypted')
5739
			{
5740
				$skip = 2;
5741
				continue;
5742
			}
5743
			elseif($skip)
5744
			{
5745
				$skip--;
5746
				continue;
5747
			}
5748
			$part = $_structure->getPart($mime_id);
5749
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getMimeId()));
5750
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.$part->getPrimaryType().'/'.$part->getSubType().'->'.$part->getDisposition());
5751
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllDispositionParameters()));
5752
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllContentTypeParameters()));
5753
			$partDisposition = $part->getDisposition();
5754
			$partPrimaryType = $part->getPrimaryType();
5755
			// we only want to retrieve the attachments of the current mail, not those of possible
5756
			// attached mails
5757
			if ($mime_type=='message/rfc822' && $_partID!=$mime_id)
5758
			{
5759
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
5760
				foreach($part->contentTypeMap() as $sub_id => $sub_type) {if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
5761
			}
5762
			if (empty($partDisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
5763
			{
5764
				// the absence of an partDisposition does not necessarily indicate there is no attachment. it may be an
5765
				// attachment with no link to show the attachment inline.
5766
				// Considering this: we "list" everything that matches the above criteria
5767
				// as attachment in order to not loose/miss information on our data
5768
				$partDisposition='attachment';
5769
			}
5770
			//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($skipParts));
5771
			if (array_key_exists($mime_id,$skipParts)) continue;
5772
5773
			if ($partDisposition == 'attachment' ||
5774
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image' && $part->getContentId()=='') ||
5775
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType != 'image' && $partPrimaryType != 'text' && $partPrimaryType != 'multipart') ||
5776
				($mime_type=='image/tiff') || //always fetch. even if $fetchEmbeddedImages is false. as we cannot display tiffs
5777
				($fetchEmbeddedImages && ($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image') ||
5778
				($fetchTextCalendar && $partPrimaryType == 'text' && $part->getSubType() == 'calendar'))
5779
			{
5780
				// if type is message/rfc822 and _partID is given, and MimeID equals partID
5781
				// we attempt to fetch "ourselves"
5782
				if ($_partID==$part->getMimeId() && $part->getPrimaryType()=='message') continue;
5783
				$attachment = $part->getAllDispositionParameters();
5784
				$attachment['disposition'] = $part->getDisposition();
5785
				$attachment['mimeType'] = $mime_type;
5786
				$attachment['uid'] = $_uid;
5787
				$attachment['partID'] = $mime_id;
5788
				if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName() ? $part->getName() : ($mime_type == "message/rfc822" ? lang('forwarded message') : lang('attachment'));;
5789
				if ($fetchTextCalendar)
5790
				{
5791
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part->getAllContentTypeParameters()));
5792
					$method = $part->getContentTypeParameter('method');
5793
					if ($method) $attachment['method'] = $method;
5794
					if (!isset($attachment['name'])) $attachment['name'] = 'event.ics';
5795
				}
5796
				$attachment['size'] = $part->getBytes();
5797
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5798
				if (empty($attachment['name'])) $attachment['name'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($mime_type);
5799
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($attachment));
5800
				//typical winmail.dat attachment is
5801
				//Array([size] => 1462762[filename] => winmail.dat[mimeType] => application/ms-tnef[uid] => 100[partID] => 2[name] => winmail.dat)
5802
				if ($resolveTNEF && ($attachment['mimeType']=='application/ms-tnef' || !strcasecmp($attachment['name'],'winmail.dat')))
5803
				{
5804
					$tnefParts[] = $attachment;
5805
				}
5806
				else
5807
				{
5808
					$attachments[] = $attachment;
5809
				}
5810
			}
5811
		}
5812
		if ($resolveTNEF && !empty($tnefParts))
5813
		{
5814
			//error_log(__METHOD__.__LINE__.array2string($tnefParts));
5815
			foreach ($tnefParts as $k => $tnp)
5816
			{
5817
				$tnefResolved=false;
5818
				$tnef_data = $this->getAttachment($tnp['uid'],$tnp['partID'],$k,false);
5819
				$myTnef = $this->tnef_decoder($tnef_data['attachment']);
5820
				//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5821
				// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5822
				// we need to build Something that meets the needs
5823
				if ($myTnef)
5824
				{
5825
					foreach($myTnef->getParts() as $mime_id => $part)
5826
					{
5827
						$tnefResolved=true;
5828
						$attachment = $part->getAllDispositionParameters();
5829
						$attachment['disposition'] = $part->getDisposition();
5830
						$attachment['mimeType'] = $part->getType();
5831
						$attachment['uid'] = $tnp['uid'];
5832
						$attachment['partID'] = $tnp['partID'];
5833
						$attachment['is_winmail'] = $tnp['uid'].'@'.$tnp['partID'].'@'.$mime_id;
5834
						if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5835
						$attachment['size'] = $part->getBytes();
5836
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5837
						if (empty($attachment['name'])) $attachment['name'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
5838
						$attachments[] = $attachment;
5839
					}
5840
				}
5841
				if ($tnefResolved===false) $attachments[]=$tnp;
5842
			}
5843
		}
5844
		//error_log(__METHOD__.__LINE__.array2string($attachments));
5845
		return $attachments;
5846
	}
5847
5848
	/**
5849
	 * Decode TNEF type attachment into Multipart/mixed attachment
5850
	 *
5851
	 * @param MIME object $data Mime part object
5852
	 *
5853
	 * @return boolean|Horde_Mime_part Multipart/Mixed part decoded attachments |
5854
	 *	return false if there's no attachments or failure
5855
	 */
5856
	public function tnef_decoder( $data )
5857
	{
5858
		foreach(array('Horde_Compress', 'Horde_Icalendar', 'Horde_Mapi') as $class)
5859
		{
5860
			if (!class_exists($class))
5861
			{
5862
				error_log(__METHOD__."() missing required PEAR package $class --> aborting");
5863
				return false;
5864
			}
5865
		}
5866
		$parts_obj = new Horde_Mime_part;
5867
		$parts_obj->setType('multipart/mixed');
5868
5869
		$tnef_object = Horde_Compress::factory('tnef');
5870
		try
5871
		{
5872
			$tnef_data = $tnef_object->decompress($data);
5873
		}
5874
		catch (Horde_Exception $ex)
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\Horde_Exception was not found. Did you mean Horde_Exception? If so, make sure to prefix the type with \.
Loading history...
5875
		{
5876
			error_log(__METHOD__."() ".$ex->getMessage().' --> aborting');
5877
			_egw_log_exception($ex);
5878
			return false;
5879
		}
5880
		if (is_array($tnef_data))
5881
		{
5882
			foreach ($tnef_data as &$data)
0 ignored issues
show
introduced by
$data is overwriting one of the parameters of this function.
Loading history...
5883
			{
5884
				$tmp_part = new Horde_Mime_part;
5885
5886
				$tmp_part->setName($data['name']);
5887
				$tmp_part->setContents($data['stream']);
5888
				$tmp_part->setDescription($data['name']);
5889
5890
				$type = $data['type'] . '/' . $data['subtype'];
5891
				if (in_array($type, array('application/octet-stream', 'application/base64')))
5892
				{
5893
					$type = Horde_Mime_Magic::filenameToMIME($data['name']);
5894
				}
5895
				$tmp_part->setType($type);
5896
				//error_log(__METHOD__.__LINE__.array2string($tmp_part));
5897
				$parts_obj->addPart($tmp_part);
0 ignored issues
show
Deprecated Code introduced by
The function Horde_Mime_Part::addPart() has been deprecated: Use array access instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5897
				/** @scrutinizer ignore-deprecated */ $parts_obj->addPart($tmp_part);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
5898
			}
5899
			$parts_obj->buildMimeIds();
5900
			return $parts_obj;
5901
		}
5902
		return false;
5903
	}
5904
5905
	/**
5906
	 * Get attachment data as string, to be used with Link::(get|set)_data()
5907
	 *
5908
	 * @param int $acc_id
5909
	 * @param string $_mailbox
5910
	 * @param int $_uid
5911
	 * @param string $_partID
5912
	 * @param int $_winmail_nr
5913
	 * @return resource stream with attachment content
5914
	 */
5915
	public static function getAttachmentAccount($acc_id, $_mailbox, $_uid, $_partID, $_winmail_nr)
5916
	{
5917
		$bo = self::getInstance(false, $acc_id);
5918
5919
		$attachment = $bo->getAttachment($_uid, $_partID, $_winmail_nr, false, true, $_mailbox);
5920
5921
		return $attachment['attachment'];
5922
	}
5923
5924
	/**
5925
	 * Retrieve tnef attachments
5926
	 *
5927
	 * @param int $_uid the uid of the message
5928
	 * @param string $_partID the id of the part, which holds the attachment
5929
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5930
	 * @param string $_folder =null folder to use if not current folder
5931
	 *
5932
	 * @return array returns an array of all resolved embeded attachments from winmail.dat
5933
	 */
5934
	function getTnefAttachments ($_uid, $_partID, $_stream=false, $_folder=null)
5935
	{
5936
		$tnef_data = $this->getAttachment($_uid, $_partID,0,false, false , $_folder);
5937
		$tnef_parts = $this->tnef_decoder($tnef_data['attachment']);
5938
		$attachments = array();
5939
		if ($tnef_parts)
5940
		{
5941
			foreach($tnef_parts->getParts() as $mime_id => $part)
5942
			{
5943
5944
				$attachment = $part->getAllDispositionParameters();
5945
				$attachment['mimeType'] = $part->getType();
5946
				if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5947
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5948
				if (empty($attachment['filename']))
5949
				{
5950
					$attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?
5951
						$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
5952
				}
5953
5954
				$attachment['attachment'] = $part->getContents(array('stream'=>$_stream));
5955
5956
				$attachments[$_uid.'@'.$_partID.'@'.$mime_id] = $attachment;
5957
			}
5958
		}
5959
		if (!is_array($attachments)) return false;
0 ignored issues
show
introduced by
The condition is_array($attachments) is always true.
Loading history...
5960
		return $attachments;
5961
	}
5962
5963
	/**
5964
	 * Retrieve a attachment
5965
	 *
5966
	 * @param int $_uid the uid of the message
5967
	 * @param string $_partID the id of the part, which holds the attachment
5968
	 * @param int $_winmail_nr = 0 winmail.dat attachment nr.
5969
	 * @param boolean $_returnPart =true flag to indicate if the attachment is to be returned as horde mime part object
5970
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5971
	 * @param string $_folder =null folder to use if not current folder
5972
	 *
5973
	 * @return array
5974
	 */
5975
	function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false, $_folder=null)
5976
	{
5977
		//error_log(__METHOD__.__LINE__."Uid:$_uid, PartId:$_partID, WinMailNr:$_winmail_nr, ReturnPart:$_returnPart, Stream:$_stream, Folder:$_folder".function_backtrace());
5978
		if (!isset($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5979
5980
		$uidsToFetch = new Horde_Imap_Client_Ids();
5981
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
0 ignored issues
show
introduced by
The condition is_array($_uid) is always false.
Loading history...
5982
		$uidsToFetch->add($_uid);
5983
5984
		$fquery = new Horde_Imap_Client_Fetch_Query();
5985
		$fquery->structure();
5986
		$fquery->bodyPart($_partID, array('peek'=>true));
5987
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5988
			'ids' => $uidsToFetch,
5989
		));
5990
		if (is_object($headersNew)) {
5991
			foreach($headersNew as $id=>$_headerObject) {
5992
				$body = $_headerObject->getFullMsg();
0 ignored issues
show
Unused Code introduced by
The assignment to $body is dead and can be removed.
Loading history...
5993
				if ($_partID != '')
5994
				{
5995
					$mailStructureObject = $_headerObject->getStructure();
5996
					if (!class_exists('mail_zpush', false) && (Mail\Smime::isSmime(($mimeType = $mailStructureObject->getType())) ||
5997
							Mail\Smime::isSmime(($protocol=$mailStructureObject->getContentTypeParameter('protocol')))))
5998
					{
5999
						$mailStructureObject = $this->resolveSmimeMessage($mailStructureObject, array(
6000
							'uid' => $_uid,
6001
							'mailbox' => $_folder,
6002
							'mimeType' => Mail\Smime::isSmime($protocol) ? $protocol : $mimeType
6003
6004
						));
6005
					}
6006
					$mailStructureObject->contentTypeMap();
6007
					$part = $mailStructureObject->getPart($_partID);
6008
					$partDisposition = ($part?$part->getDisposition():'failed');
6009
					if ($partDisposition=='failed')
6010
					{
6011
						error_log(__METHOD__.'('.__LINE__.'):'.array2string($_uid).','.$_partID.' ID:'.$id.' HObject:'.array2string($_headerObject).' StructureObject:'.array2string($mailStructureObject->contentTypeMap()).'->'.function_backtrace());
6012
					}
6013
					// if $partDisposition is empty, we assume attachment, and hope that the function
6014
					// itself is only triggered to fetch attachments
6015
					if (empty($partDisposition)) $partDisposition='attachment';
6016
					if ($part && ($partDisposition=='attachment' || $partDisposition=='inline' || ($part->getPrimaryType() == 'text' && $part->getSubType() == 'calendar')))
6017
					{
6018
						//$headerObject=$part->getAllDispositionParameters();//not used anywhere around here
6019
						$structure_mime = $part->getType();
6020
						$filename = $part->getName();
6021
						$charset = $part->getContentTypeParameter('charset');
6022
						//$structure_bytes = $part->getBytes(); $structure_partID=$part->getMimeId(); error_log(__METHOD__.__LINE__." fetchPartContents(".array2string($_uid).", $structure_partID, $_stream, $_preserveSeen,$structure_mime)" );
6023
						if (empty($part->getContents())) $this->fetchPartContents($_uid, $part, $_stream, $_preserveSeen=true,$structure_mime);
6024
						if ($_returnPart) return $part;
6025
					}
6026
				}
6027
			}
6028
		}
6029
		$ext = MimeMagic::mime2ext($structure_mime);
6030
		if ($ext && stripos($filename,'.')===false && stripos($filename,$ext)===false) $filename = trim($filename).'.'.$ext;
6031
		if (!$part)
6032
		{
6033
			throw new Exception\WrongParameter("Error: Could not fetch attachment for Uid=".array2string($_uid).", PartId=$_partID, WinMailNr=$_winmail_nr, folder=$_folder");
6034
		}
6035
		$attachmentData = array(
6036
			'type'		=> $structure_mime,
6037
			'charset' => $charset,
6038
			'filename'	=> $filename,
6039
			'attachment'	=> $part->getContents(array(
6040
				// tnef_decode needs strings not a stream
6041
				'stream' => $_stream && !($filename == 'winmail.dat' && $_winmail_nr)
6042
			)),
6043
		);
6044
6045
		// try guessing the mimetype, if we get the application/octet-stream
6046
		if (strtolower($attachmentData['type']) == 'application/octet-stream') $attachmentData['type'] = MimeMagic::filename2mime($attachmentData['filename']);
6047
		# if the attachment holds a winmail number and is a winmail.dat then we have to handle that.
6048
		if ( $filename == 'winmail.dat' && $_winmail_nr)
6049
		{
6050
			//by now _uid is of type array
6051
			$tnefResolved=false;
6052
			$wantedPart=$_uid[0].'@'.$_partID;
6053
			$myTnef = $this->tnef_decoder($attachmentData['attachment']);
6054
			//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
6055
			// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
6056
			// we need to build Something that meets the needs
6057
			if ($myTnef)
6058
			{
6059
				foreach($myTnef->getParts() as $mime_id => $part)
6060
				{
6061
					$tnefResolved=true;
6062
					$attachment = $part->getAllDispositionParameters();
6063
					$attachment['mimeType'] = $part->getType();
6064
					//error_log(__METHOD__.__LINE__.'#'.$mime_id.'#'.$filename.'#'.array2string($attachment));
6065
					//error_log(__METHOD__.__LINE__." $_winmail_nr == $wantedPart@$mime_id");
6066
					if ($_winmail_nr == $wantedPart.'@'.$mime_id)
6067
					{
6068
						//error_log(__METHOD__.__LINE__.'#'.$structure_mime.'#'.$filename.'#'.array2string($attachment));
6069
						if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
6070
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
6071
						if (empty($attachment['filename'])) $attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
0 ignored issues
show
Bug introduced by
Are you sure $_uid of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6071
						if (empty($attachment['filename'])) $attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?$attachment['cid']:lang("unknown").'_Uid'./** @scrutinizer ignore-type */ $_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
Loading history...
6072
						$wmattach = $attachment;
6073
						$wmattach['attachment'] = $part->getContents(array('stream'=>$_stream));
6074
6075
					}
6076
				}
6077
			}
6078
			if ($tnefResolved)
6079
			{
6080
				$ext = MimeMagic::mime2ext($wmattach['mimeType']);
6081
				if ($ext && stripos($wmattach['filename'],'.')===false && stripos($wmattach['filename'],$ext)===false) $wmattach['filename'] = trim($wmattach['filename']).'.'.$ext;
6082
				$attachmentData = array(
6083
					'type'       => $wmattach['mimeType'],
6084
					'filename'   => $wmattach['filename'],
6085
					'attachment' => $wmattach['attachment'],
6086
				);
6087
			}
6088
		}
6089
		return $attachmentData;
6090
	}
6091
6092
	/**
6093
	 * Fetch a specific attachment from a message by it's cid
6094
	 *
6095
	 * this function is based on a on "Building A PHP-Based Mail Client"
6096
	 * http://www.devshed.com
6097
	 *
6098
	 * @param string|int $_uid
6099
	 * @param string $_cid
6100
	 * @param string $_part
6101
	 * @param boolean $_stream = null null do NOT fetch content, use fetchPartContents later
6102
	 *	true:
6103
	 * @return Horde_Mime_Part
6104
	 */
6105
	function getAttachmentByCID($_uid, $_cid, $_part, $_stream=null)
6106
	{
6107
		// some static variables to avoid fetching the same mail multiple times
6108
		static $uid=null, $part=null, $structure=null;
6109
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid, $_cid, $_part");
6110
6111
		if(empty($_cid)) return false;
6112
6113
		if ($_uid != $uid || $_part != $part)
6114
		{
6115
			$structure = $this->getStructure($uid=$_uid, $part=$_part);
6116
		}
6117
		/** @var Horde_Mime_Part */
6118
		$attachment = null;
6119
		foreach($structure->contentTypeMap() as $mime_id => $mime_type)
6120
		{
6121
			$part = $structure->getPart($mime_id);
6122
6123
			if ($part->getPrimaryType() == 'image' &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($part->getPrimaryType()...$_cid, $name) !== false, Probably Intended Meaning: $part->getPrimaryType() ..._cid, $name) !== false)
Loading history...
6124
				(($cid = $part->getContentId()) &&
6125
				// RB: seem a bit fague to search for inclusion in both ways
6126
				(strpos($cid, $_cid) !== false || strpos($_cid, $cid) !== false)) ||
6127
				(($name = $part->getName()) &&
6128
				(strpos($name, $_cid) !== false || strpos($_cid, $name) !== false)))
6129
			{
6130
				// if we have a direct match, dont search any further
6131
				if ($cid == $_cid)
6132
				{
6133
					$attachment = $part;
6134
				}
6135
				// everything else we only consider after we checked all
6136
				if (!isset($attachment)) $attachment = $part;
6137
				// do we want content fetched, can be done later, if not needed
6138
				if (isset($_stream))
6139
				{
6140
					$this->fetchPartContents($_uid, $attachment, $_stream);
6141
				}
6142
				if (isset($attachment)) break;
6143
			}
6144
		}
6145
		// set name as filename, if not set
6146
		if ($attachment && !$attachment->getDispositionParameter('filename'))
6147
		{
6148
			$attachment->setDispositionParameter('filename', $attachment->getName());
6149
		}
6150
		// guess type, if not set
6151
		if ($attachment && $attachment->getType() == 'application/octet-stream')
6152
		{
6153
			$attachment->setType(MimeMagic::filename2mime($attachment->getDispositionParameter('filename')));
6154
		}
6155
		//error_log(__METHOD__."($_uid, '$_cid', '$_part') returning ".array2string($attachment));
6156
		return $attachment;
6157
	}
6158
6159
	/**
6160
	 * Fetch and add contents to a part
6161
	 *
6162
	 * To get contents you use $part->getContents();
6163
	 *
6164
	 * @param int $_uid
6165
	 * @param Horde_Mime_Part $part
6166
	 * @param boolean $_stream = false true return a stream, false a string
6167
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
6168
	 * @param string  $_mimetype to decide wether to try to fetch part as binary or not
6169
	 * @return Horde_Mime_Part
6170
	 */
6171
	public function fetchPartContents($_uid, Horde_Mime_Part $part=null, $_stream=false, $_preserveSeen=false, $_mimetype=null)
6172
	{
6173
		if (is_null($part)) return null;//new Horde_Mime_Part;
6174
		$encoding = null;
6175
		$fetchAsBinary = true;
6176
		if ($_mimetype && strtolower($_mimetype)=='message/rfc822') $fetchAsBinary = false;
6177
		// we need to set content on structure to decode transfer encoding
6178
		$part->setContents(
6179
			$this->getBodyPart($_uid, $part->getMimeId(), null, $_preserveSeen, $_stream, $encoding, $fetchAsBinary),
6180
			array('encoding' => (!$fetchAsBinary&&!$encoding?'8bit':$encoding)));
6181
6182
		return $part;
6183
	}
6184
6185
	/**
6186
	 * save a message in folder
6187
	 *	throws exception on failure
6188
	 * @todo set flags again
6189
	 *
6190
	 * @param string _folderName the foldername
6191
	 * @param string|resource _header header part of message or resource with hole message
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_header was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6192
	 * @param string _body body part of message, only used if _header is NO resource
6193
	 * @param string _flags = '\\Recent'the imap flags to set for the saved message
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\_flags was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6194
	 *
6195
	 * @return the id of the message appended or exception
6196
	 * @throws Exception\WrongUserinput
6197
	 */
6198
	function appendMessage($_folderName, $_header, $_body, $_flags='\\Recent')
6199
	{
6200
		if (!is_resource($_header))
6201
		{
6202
			if (stripos($_header,'message-id:')===false)
6203
			{
6204
				$_header = 'Message-ID: <'.self::getRandomString().'@localhost>'."\n".$_header;
6205
			}
6206
			//error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_header, $_body, $_flags");
6207
			$_header = ltrim(str_replace("\n","\r\n",$_header));
6208
			$_header .= str_replace("\n","\r\n",$_body);
6209
		}
6210
		// the recent flag is the default enforced here ; as we assume the _flags is always set,
6211
		// we default it to hordes default (Recent) (, other wise we should not pass the parameter
6212
		// for flags at all)
6213
		if (empty($_flags)) $_flags = '\\Recent';
6214
		//if (!is_array($_flags) && stripos($_flags,',')!==false) $_flags=explode(',',$_flags);
6215
		//if (!is_array($_flags)) $_flags = (array) $_flags;
6216
		try
6217
		{
6218
			$dataNflags = array();
6219
			// both methods below are valid for appending a message to a mailbox.
6220
			// the commented version fails in retrieving the uid of the created message if the server
6221
			// is not returning the uid upon creation, as the method in append for detecting the uid
6222
			// expects data to be a string. this string is parsed for message-id, and the mailbox
6223
			// searched for the message-id then returning the uid found
6224
			//$dataNflags[] = array('data'=>array(array('t'=>'text','v'=>"$header"."$body")), 'flags'=>array($_flags));
6225
			$dataNflags[] = array('data' => $_header, 'flags'=>array($_flags));
6226
			$messageid = $this->icServer->append($_folderName,$dataNflags);
6227
		}
6228
		catch (\Exception $e)
6229
		{
6230
			if (self::$debug) error_log("Could not append Message: ".$e->getMessage());
6231
			throw new Exception\WrongUserinput(lang("Could not append Message:").' '.$e->getMessage().': '.$e->details);
6232
			//return false;
6233
		}
6234
		//error_log(__METHOD__.' ('.__LINE__.') '.' appended UID:'.$messageid);
6235
		//$messageid = true; // for debug reasons only
6236
		if ($messageid === true || empty($messageid)) // try to figure out the message uid
6237
		{
6238
			$list = $this->getHeaders($_folderName, $_startMessage=1, 1, 'INTERNALDATE', true, array(),null, false);
6239
			if ($list)
0 ignored issues
show
Bug Best Practice introduced by
The expression $list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$list is a non-empty array, thus is always true.
Loading history...
6240
			{
6241
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' MessageUid:'.$messageid.' but found:'.array2string($list));
6242
				$messageid = $list['header'][0]['uid'];
6243
			}
6244
		}
6245
		return $messageid;
6246
	}
6247
6248
	/**
6249
	 * Get a random string of 32 chars
6250
	 *
6251
	 * @return string
6252
	 */
6253
	static function getRandomString()
6254
	{
6255
		return Auth::randomstring(32);
6256
	}
6257
6258
	/**
6259
	 * functions to allow access to mails through other apps to fetch content
6260
	 * used in infolog, tracker
6261
	 */
6262
6263
	/**
6264
	 * get_mailcontent - fetches the actual mailcontent, and returns it as well defined array
6265
	 * @param object mailClass the mailClassobject to be used
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\mailClass was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6266
	 * @param uid the uid of the email to be processed
6267
	 * @param partid the partid of the email
6268
	 * @param mailbox the mailbox, that holds the message
6269
	 * @param preserveHTML flag to pass through to getdisplayableBody, null for both text and HTML
6270
	 * @param addHeaderSection flag to be able to supress headersection
6271
	 * @param includeAttachments flag to be able to supress possible attachments
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\flag was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6272
	 * @return array/bool with 'mailaddress'=>$mailaddress,
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/bool at position 0 could not be parsed: Unknown type name 'array/bool' at position 0 in array/bool.
Loading history...
6273
	 *				'subject'=>$subject,
6274
	 *				'message'=>$message,
6275
	 *				'attachments'=>$attachments,
6276
	 *				'headers'=>$headers,; boolean false on failure
6277
	 */
6278
	static function get_mailcontent(&$mailClass,$uid,$partid='',$mailbox='', $preserveHTML = false, $addHeaderSection=true, $includeAttachments=true)
6279
	{
6280
			//echo __METHOD__." called for $uid,$partid <br>";
6281
			$headers = $mailClass->getMessageHeader($uid,$partid,true,false,$mailbox);
6282
			if (empty($headers)) return false;
6283
			// dont force retrieval of the textpart, let mailClass preferences decide
6284
			$bodyParts = $mailClass->getMessageBody($uid,($preserveHTML?'always_display':'only_if_no_text'),$partid,null,false,$mailbox);
6285
			if(is_null($preserveHTML))
6286
			{
6287
				$html = static::getdisplayablebody(
6288
						$mailClass,
6289
						$mailClass->getMessageBody($uid,'always_display',$partid,null,false,$mailbox),
6290
						true
6291
				);
6292
6293
			}
6294
			// if we do not want HTML but there is no TextRepresentation with the message itself, try converting
6295
			if ( !$preserveHTML && $bodyParts[0]['mimeType']=='text/html')
6296
			{
6297
				foreach($bodyParts as $i => $part)
6298
				{
6299
					if ($bodyParts[$i]['mimeType']=='text/html')
6300
					{
6301
						$bodyParts[$i]['body'] = Mail\Html::convertHTMLToText($bodyParts[$i]['body'],$bodyParts[$i]['charSet'],true,$stripalltags=true);
6302
						$bodyParts[$i]['mimeType']='text/plain';
6303
					}
6304
				}
6305
			}
6306
			//error_log(array2string($bodyParts));
6307
			$attachments = $includeAttachments?$mailClass->getMessageAttachments($uid,$partid,null,true,false,true,$mailbox):array();
6308
6309
			if ($mailClass->isSentFolder($mailbox)) $mailaddress = $headers['TO'];
6310
			elseif (isset($headers['FROM'])) $mailaddress = $headers['FROM'];
6311
			elseif (isset($headers['SENDER'])) $mailaddress = $headers['SENDER'];
6312
			if (isset($headers['CC'])) $mailaddress .= ','.$headers['CC'];
6313
			//_debug_array(array($headers,$mailaddress));
6314
			$subject = $headers['SUBJECT'];
6315
6316
			$message = self::getdisplayableBody($mailClass, $bodyParts, $preserveHTML);
6317
			if ($preserveHTML && $mailClass->activeMimeType == 'text/plain') $message = '<pre>'.$message.'</pre>';
6318
			$headdata = ($addHeaderSection ? self::createHeaderInfoSection($headers, '',$preserveHTML) : '');
6319
			$message = $headdata.$message;
6320
			//echo __METHOD__.'<br>';
6321
			//_debug_array($attachments);
6322
			if (is_array($attachments))
6323
			{
6324
				// For dealing with multiple files of the same name
6325
				$dupe_count = $file_list = array();
6326
6327
				foreach ($attachments as $num => $attachment)
6328
				{
6329
					if ($attachment['mimeType'] == 'MESSAGE/RFC822')
6330
					{
6331
						//_debug_array($mailClass->getMessageHeader($uid, $attachment['partID']));
6332
						//_debug_array($mailClass->getMessageBody($uid,'', $attachment['partID']));
6333
						//_debug_array($mailClass->getMessageAttachments($uid, $attachment['partID']));
6334
						$mailcontent = self::get_mailcontent($mailClass,$uid,$attachment['partID'],$mailbox);
6335
						$headdata ='';
6336
						if ($mailcontent['headers'])
6337
						{
6338
							$headdata = self::createHeaderInfoSection($mailcontent['headers'],'',$preserveHTML);
6339
						}
6340
						if ($mailcontent['message'])
6341
						{
6342
							$tempname =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6343
							$attachedMessages[] = array(
6344
								'type' => 'TEXT/PLAIN',
6345
								'name' => $mailcontent['subject'].'.txt',
6346
								'tmp_name' => $tempname,
6347
							);
6348
							$tmpfile = fopen($tempname,'w');
6349
							fwrite($tmpfile,$headdata.$mailcontent['message']);
6350
							fclose($tmpfile);
6351
						}
6352
						foreach($mailcontent['attachments'] as &$tmpval)
6353
						{
6354
							$attachedMessages[] = $tmpval;
6355
						}
6356
						unset($attachments[$num]);
6357
					}
6358
					else
6359
					{
6360
						$attachments[$num] = array_merge($attachments[$num],$mailClass->getAttachment($uid, $attachment['partID'],0,false,false));
6361
6362
						if (empty($attachments[$num]['attachment'])&&$attachments[$num]['cid'])
6363
						{
6364
							$c = $mailClass->getAttachmentByCID($uid, $attachment['cid'], $attachment['partID'],true);
6365
							$attachments[$num]['attachment'] = $c->getContents();
6366
						}
6367
						// no attempt to convert, if we dont know about the charset
6368
						if (isset($attachments[$num]['charset'])&&!empty($attachments[$num]['charset'])) {
6369
							// we do not try guessing the charset, if it is not set
6370
							//if ($attachments[$num]['charset']===false) $attachments[$num]['charset'] = Translation::detect_encoding($attachments[$num]['attachment']);
6371
							Translation::convert($attachments[$num]['attachment'],$attachments[$num]['charset']);
6372
						}
6373
						if(in_array($attachments[$num]['name'], $file_list))
6374
						{
6375
							$dupe_count[$attachments[$num]['name']]++;
6376
							$attachments[$num]['name'] = pathinfo($attachments[$num]['name'], PATHINFO_FILENAME) .
6377
								' ('.($dupe_count[$attachments[$num]['name']] + 1).')' . '.' .
6378
								pathinfo($attachments[$num]['name'], PATHINFO_EXTENSION);
6379
						}
6380
						$attachments[$num]['type'] = $attachments[$num]['mimeType'];
6381
						$attachments[$num]['tmp_name'] = tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6382
						$tmpfile = fopen($attachments[$num]['tmp_name'],'w');
6383
						fwrite($tmpfile,$attachments[$num]['attachment']);
6384
						fclose($tmpfile);
6385
						$file_list[] = $attachments[$num]['name'];
6386
						unset($attachments[$num]['attachment']);
6387
					}
6388
				}
6389
				if (is_array($attachedMessages)) $attachments = array_merge($attachments,$attachedMessages);
6390
			}
6391
			$return = array(
6392
					'mailaddress'=>$mailaddress,
6393
					'subject'=>$subject,
6394
					'message'=>$message,
6395
					'attachments'=>$attachments,
6396
					'headers'=>$headers,
6397
			);
6398
			if($html)
6399
			{
6400
				$return['html_message'] = $html;
6401
			}
6402
6403
			return $return;
6404
	}
6405
6406
	/**
6407
	 * getStandardIdentityForProfile
6408
	 * get either the first identity out of the given identities or the one matching the profile_id
6409
	 * @param object/array $_identities identity iterator object or array with identities from Mail\Account
0 ignored issues
show
Documentation Bug introduced by
The doc comment object/array at position 0 could not be parsed: Unknown type name 'object/array' at position 0 in object/array.
Loading history...
6410
	 * @param integer $_profile_id the acc_id/profileID the identity with the matching key is the standard one
6411
	 * @return array the identity
6412
	 */
6413
	static function getStandardIdentityForProfile($_identities, $_profile_id)
6414
	{
6415
		$c = 0;
6416
		// use the standardIdentity
6417
		foreach($_identities as $key => $acc) {
6418
			if ($c==0) $identity = $acc;
6419
			//error_log(__METHOD__.__LINE__." $key == $_profile_id ");
6420
			if ($key==$_profile_id) $identity = $acc;
6421
			$c++;
6422
		}
6423
		return $identity;
6424
	}
6425
	/**
6426
	 * createHeaderInfoSection - creates a textual headersection from headerobject
6427
	 * @param array header headerarray may contain SUBJECT,FROM,SENDER,TO,CC,BCC,DATE,PRIORITY,IMPORTANCE
6428
	 * @param string headline Text tom use for headline, if SUPPRESS, supress headline and footerline
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\headline was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6429
	 * @param bool createHTML do it with HTML breaks
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\createHTML was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6430
	 * @return string a preformatted string with the information of the header worked into it
6431
	 */
6432
	static function createHeaderInfoSection($header,$headline='', $createHTML = false)
6433
	{
6434
		$headdata = null;
6435
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($header).function_backtrace());
6436
		if ($header['SUBJECT']) $headdata = lang('subject').': '.$header['SUBJECT'].($createHTML?"<br />":"\n");
6437
		if ($header['FROM']) $headdata .= lang('from').': '.self::convertAddressArrayToString($header['FROM'], $createHTML).($createHTML?"<br />":"\n");
0 ignored issues
show
Unused Code introduced by
The call to EGroupware\Api\Mail::convertAddressArrayToString() has too many arguments starting with $createHTML. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6437
		if ($header['FROM']) $headdata .= lang('from').': '.self::/** @scrutinizer ignore-call */ convertAddressArrayToString($header['FROM'], $createHTML).($createHTML?"<br />":"\n");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
6438
		if ($header['SENDER']) $headdata .= lang('sender').': '.self::convertAddressArrayToString($header['SENDER'], $createHTML).($createHTML?"<br />":"\n");
6439
		if ($header['TO']) $headdata .= lang('to').': '.self::convertAddressArrayToString($header['TO'], $createHTML).($createHTML?"<br />":"\n");
6440
		if ($header['CC']) $headdata .= lang('cc').': '.self::convertAddressArrayToString($header['CC'], $createHTML).($createHTML?"<br />":"\n");
6441
		if ($header['BCC']) $headdata .= lang('bcc').': '.self::convertAddressArrayToString($header['BCC'], $createHTML).($createHTML?"<br />":"\n");
6442
		if ($header['DATE']) $headdata .= lang('date').': '.$header['DATE'].($createHTML?"<br />":"\n");
6443
		if ($header['PRIORITY'] && $header['PRIORITY'] != 'normal') $headdata .= lang('priority').': '.$header['PRIORITY'].($createHTML?"<br />":"\n");
6444
		if ($header['IMPORTANCE'] && $header['IMPORTANCE'] !='normal') $headdata .= lang('importance').': '.$header['IMPORTANCE'].($createHTML?"<br />":"\n");
6445
		//if ($mailcontent['headers']['ORGANIZATION']) $headdata .= lang('organization').': '.$mailcontent['headers']['ORGANIZATION']."\
6446
		if (!empty($headdata))
6447
		{
6448
			if (!empty($headline) && $headline != 'SUPPRESS') $headdata = "---------------------------- $headline ----------------------------".($createHTML?"<br />":"\n").$headdata;
6449
			if (empty($headline)) $headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'').$headdata;
6450
			$headdata .= ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6451
		}
6452
		else
6453
		{
6454
			$headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6455
		}
6456
		return $headdata;
6457
	}
6458
6459
	/**
6460
	 * Make the provided filename safe to store in the VFS
6461
	 *
6462
	 * Some characters found in subjects that cause problems if we try to put
6463
	 * them as filenames (Windows) so we remove any characters that might result
6464
	 * in additional directories, or issues on Windows.
6465
	 *
6466
	 * Under Windows the characters < > ? " : | \ / * are not allowed.
6467
	 * % causes problems with VFS UI
6468
	 *
6469
	 * 4-byte unicode is also unwanted, as our current MySQL collation can store it
6470
	 *
6471
	 * We also dont want empty filenames, using lang('empty') instead.
6472
	 *
6473
	 * @param string $filename
6474
	 * @return Cleaned filename, with problematic characters replaced with ' '.
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\Cleaned was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6475
	 */
6476
	static function clean_subject_for_filename($filename)
6477
	{
6478
		static $filter_pattern = '$[\f\n\t\x0b\:*#?<>%"\|/\x{10000}-\x{10FFFF}\\\\]$u';
6479
		$file = substr(trim(preg_replace($filter_pattern, ' ', $filename)), 0, 200);
6480
		if (empty($file)) $file = lang('empty');
6481
		return $file;
6482
	}
6483
6484
	/**
6485
	 * adaptSubjectForImport - strips subject from unwanted Characters, and does some normalization
6486
	 * to meet expectations
6487
	 * @param string $subject string to process
6488
	 * @return string
6489
	 */
6490
	static function adaptSubjectForImport($subject)
6491
	{
6492
		$subject = str_replace('$$','__',($subject?$subject:lang('(no subject)')));
6493
		$subject = str_ireplace(array('[FWD]','[',']','{','}','<','>'),array('Fwd:',' ',' ',' ',' ',' ',' '),trim($subject));
6494
		return $subject;
6495
	}
6496
6497
	/**
6498
	 * convertAddressArrayToString - converts an mail envelope Address Array To String
6499
	 * @param array $rfcAddressArray  an addressarray as provided by mail retieved via egw_pear....
6500
	 * @return string a comma separated string with the mailaddress(es) converted to text
6501
	 */
6502
	static function convertAddressArrayToString($rfcAddressArray)
6503
	{
6504
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($rfcAddressArray));
6505
		$returnAddr ='';
6506
		if (is_array($rfcAddressArray))
0 ignored issues
show
introduced by
The condition is_array($rfcAddressArray) is always true.
Loading history...
6507
		{
6508
			foreach((array)$rfcAddressArray as $addressData) {
6509
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressData));
6510
				if($addressData['MAILBOX_NAME'] == 'NIL') {
6511
					continue;
6512
				}
6513
				if(strtolower($addressData['MAILBOX_NAME']) == 'undisclosed-recipients') {
6514
					continue;
6515
				}
6516
				if ($addressData['RFC822_EMAIL'])
6517
				{
6518
					$addressObjectA = self::parseAddressList($addressData['RFC822_EMAIL']);
6519
				}
6520
				else
6521
				{
6522
					$emailaddress = ($addressData['PERSONAL_NAME']?$addressData['PERSONAL_NAME'].' <'.$addressData['EMAIL'].'>':$addressData['EMAIL']);
6523
					$addressObjectA = self::parseAddressList($emailaddress);
6524
				}
6525
				$addressObject = $addressObjectA[0];
6526
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressObject));
6527
				if (!$addressObject->valid) continue;
6528
				//$mb =(string)$addressObject->mailbox;
6529
				//$h = (string)$addressObject->host;
6530
				//$p = (string)$addressObject->personal;
6531
				$returnAddr .= (strlen($returnAddr)>0?',':'');
6532
				//error_log(__METHOD__.' ('.__LINE__.') '.$p.' <'.$mb.'@'.$h.'>');
6533
				try {
6534
					$buff = imap_rfc822_write_address($addressObject->mailbox, Horde_Idna::decode($addressObject->host), $addressObject->personal);
6535
				}
6536
				// if Idna conversation fails, leave address unchanged
6537
				catch (\Exception $e) {
6538
					unset($e);
6539
					$buff = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
6540
				}
6541
				$returnAddr .= str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$buff);
6542
				//error_log(__METHOD__.' ('.__LINE__.') '.' Address: '.$returnAddr);
6543
			}
6544
		}
6545
		else
6546
		{
6547
			// do not mess with strings, return them untouched /* ToDo: validate string as Address */
6548
			$rfcAddressArray = self::decode_header($rfcAddressArray,true);
6549
			$rfcAddressArray = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$rfcAddressArray);
6550
			if (is_string($rfcAddressArray)) return $rfcAddressArray;
6551
		}
6552
		return $returnAddr;
6553
	}
6554
6555
	/**
6556
	 * Merges a given content with contact data
6557
	 *
6558
	 * @param string $content
6559
	 * @param array $ids array with contact id(s)
6560
	 * @param string &$err error-message on error
6561
	 * @return string/boolean merged content or false on error
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/boolean at position 0 could not be parsed: Unknown type name 'string/boolean' at position 0 in string/boolean.
Loading history...
6562
	 */
6563
	static function merge($content,$ids,$mimetype='')
6564
	{
6565
		$mergeobj = new Contacts\Merge();
6566
6567
		if (empty($mimetype)) $mimetype = (strlen(strip_tags($content)) == strlen($content) ?'text/plain':'text/html');
6568
		$rv = $mergeobj->merge_string($content,$ids,$err='',$mimetype, array(), self::$displayCharset);
0 ignored issues
show
Bug introduced by
$err = '' cannot be passed to EGroupware\Api\Storage\Merge::merge_string() as the parameter $err expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

6568
		$rv = $mergeobj->merge_string($content,$ids,/** @scrutinizer ignore-type */ $err='',$mimetype, array(), self::$displayCharset);
Loading history...
6569
		if (empty($rv) && !empty($content) && !empty($err)) $rv = $content;
0 ignored issues
show
introduced by
The condition empty($err) is always true.
Loading history...
6570
		if (!empty($err) && !empty($content) && !empty($ids)) error_log(__METHOD__.' ('.__LINE__.') '.' Merge failed for Ids:'.array2string($ids).' ContentType:'.$mimetype.' Content:'.$content.' Reason:'.array2string($err));
0 ignored issues
show
introduced by
The condition empty($err) is always true.
Loading history...
6571
		return $rv;
6572
	}
6573
6574
	/**
6575
	 * Returns a string showing the size of the message/attachment
6576
	 *
6577
	 * @param integer $bytes
6578
	 * @return string formatted string
6579
	 */
6580
	static function show_readable_size($bytes)
6581
	{
6582
		$bytes /= 1024;
6583
		$type = 'k';
6584
6585
		if ($bytes / 1024 > 1)
6586
		{
6587
			$bytes /= 1024;
6588
			$type = 'M';
6589
6590
			if ($bytes / 1024 > 1)
6591
			{
6592
				$bytes *= 10;
6593
				settype($bytes, 'integer');
6594
				$bytes /= 10;
6595
				$bytes /= 1024;
6596
				$type = 'G';
6597
			}
6598
6599
		}
6600
6601
		if ($bytes < 10)
6602
		{
6603
			$bytes *= 10;
6604
			settype($bytes, 'integer');
6605
			$bytes /= 10;
6606
		}
6607
		else
6608
			settype($bytes, 'integer');
6609
6610
		return $bytes . ' ' . $type ;
6611
	}
6612
6613
	static function detect_qp(&$sting) {
6614
		$needle = '/(=[0-9][A-F])|(=[A-F][0-9])|(=[A-F][A-F])|(=[0-9][0-9])/';
6615
		return preg_match("$needle",$string);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $string does not exist. Did you maybe mean $sting?
Loading history...
6616
	}
6617
6618
	/**
6619
	 * logRunTimes
6620
	 *	logs to the error log all parameters given; output only if self::$debugTimes is true
6621
	 *
6622
	 * @param int $_starttime starttime of the action measured based on microtime(true)
6623
	 * @param int $_endtime endtime of the action measured, if not given microtime(true) is used
6624
	 * @param string $_message message to output details or params, whatever seems neccesary
6625
	 * @param string $_methodNline - Information where the log was taken
6626
	 * @return void
6627
	 */
6628
	static function logRunTimes($_starttime,$_endtime=null,$_message='',$_methodNline='')
6629
	{
6630
		if (is_null($_endtime)) $_endtime = microtime(true);
0 ignored issues
show
Unused Code introduced by
The assignment to $_endtime is dead and can be removed.
Loading history...
6631
		$usagetime = microtime(true) - $_starttime;
6632
		if (self::$debugTimes) error_log($_methodNline.' took:'.number_format($usagetime,5).'(s) '.($_message?'Details:'.$_message:''));
6633
	}
6634
6635
	/**
6636
	 * check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6637
	 *
6638
	 * @param array $_formData passed by reference Array with information of name, type, file and size, mimetype may be adapted
6639
	 * @param string $IDtoAddToFileName id to enrich the returned tmpfilename
6640
	 * @param string $reqMimeType /(default message/rfc822, if set to false, mimetype check will not be performed
6641
	 * @return mixed $fullPathtoFile or exception
6642
	 *
6643
	 * @throws Exception\WrongUserinput
6644
	 */
6645
	static function checkFileBasics(&$_formData, $IDtoAddToFileName='', $reqMimeType='message/rfc822')
6646
	{
6647
		if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'egw-data') return $_formData['file'];
6648
6649
		//error_log(__METHOD__.__FILE__.array2string($_formData).' Id:'.$IDtoAddToFileName.' ReqMimeType:'.$reqMimeType);
6650
		$importfailed = $tmpFileName = false;
6651
		// ignore empty files, but allow to share vfs directories (which can have 0 size)
6652
		if ($_formData['size'] == 0 && parse_url($_formData['file'], PHP_URL_SCHEME) != 'vfs' && is_dir($_formData['file']))
6653
		{
6654
			$importfailed = true;
6655
			$alert_msg .= lang("Empty file %1 ignored.", $_formData['name']);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $_formData['name']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6655
			$alert_msg .= /** @scrutinizer ignore-call */ lang("Empty file %1 ignored.", $_formData['name']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Comprehensibility Best Practice introduced by
The variable $alert_msg seems to be never defined.
Loading history...
6656
		}
6657
		elseif (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs' || is_uploaded_file($_formData['file']) ||
6658
			realpath(dirname($_formData['file'])) == realpath($GLOBALS['egw_info']['server']['temp_dir']))
6659
		{
6660
			// ensure existance of eGW temp dir
6661
			// note: this is different from apache temp dir,
6662
			// and different from any other temp file location set in php.ini
6663
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6664
			{
6665
				@mkdir($GLOBALS['egw_info']['server']['temp_dir'],0700);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

6665
				/** @scrutinizer ignore-unhandled */ @mkdir($GLOBALS['egw_info']['server']['temp_dir'],0700);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
6666
			}
6667
6668
			// if we were NOT able to create this temp directory, then make an ERROR report
6669
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6670
			{
6671
				$alert_msg .= 'Error:'.'<br>'
6672
					.'Server is unable to access EGroupware tmp directory'.'<br>'
6673
					.$GLOBALS['egw_info']['server']['temp_dir'].'<br>'
6674
					.'Please check your configuration'.'<br>'
6675
					.'<br>';
6676
			}
6677
6678
			// sometimes PHP is very clue-less about MIME types, and gives NO file_type
6679
			// rfc default for unknown MIME type is:
6680
			if ($reqMimeType == 'message/rfc822')
6681
			{
6682
				$mime_type_default = 'message/rfc';
6683
			}
6684
			else
6685
			{
6686
				$mime_type_default = $reqMimeType;
6687
			}
6688
			// check the mimetype by extension. as browsers seem to report crap
6689
			// maybe its application/octet-stream -> this may mean that we could not determine the type
6690
			// so we check for the suffix too
6691
			// trust vfs mime-types, trust the mimetype if it contains a method
6692
			if ((substr($_formData['file'],0,6) !== 'vfs://' || $_formData['type'] == 'application/octet-stream') && stripos($_formData['type'],'method=')===false)
6693
			{
6694
				$buff = explode('.',$_formData['name']);
6695
				$suffix = '';
6696
				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
0 ignored issues
show
introduced by
The condition is_array($buff) is always true.
Loading history...
6697
				if (!empty($suffix)) $sfxMimeType = MimeMagic::ext2mime($suffix);
6698
				if (!empty($suffix) && !empty($sfxMimeType) &&
6699
					(strlen(trim($_formData['type']))==0 || (strtolower(trim($_formData['type'])) != $sfxMimeType)))
6700
				{
6701
					error_log(__METHOD__.' ('.__LINE__.') '.' Data:'.array2string($_formData));
6702
					error_log(__METHOD__.' ('.__LINE__.') '.' Form reported Mimetype:'.$_formData['type'].' but seems to be:'.$sfxMimeType);
6703
					$_formData['type'] = $sfxMimeType;
6704
				}
6705
			}
6706
			if (trim($_formData['type']) == '')
6707
			{
6708
				$_formData['type'] = 'application/octet-stream';
6709
			}
6710
			// if reqMimeType is set to false do not test for that
6711
			if ($reqMimeType)
6712
			{
6713
				// so if PHP did not pass any file_type info, then substitute the rfc default value
6714
				if (substr(strtolower(trim($_formData['type'])),0,strlen($mime_type_default)) != $mime_type_default)
6715
				{
6716
					if (!(strtolower(trim($_formData['type'])) == "application/octet-stream" && $sfxMimeType == $reqMimeType))
6717
					{
6718
						//error_log("Message rejected, no message/rfc. Is:".$_formData['type']);
6719
						$importfailed = true;
6720
						$alert_msg .= lang("File rejected, no %2. Is:%1",$_formData['type'],$reqMimeType);
6721
					}
6722
					if ((strtolower(trim($_formData['type'])) != $reqMimeType && $sfxMimeType == $reqMimeType))
6723
					{
6724
						$_formData['type'] = MimeMagic::ext2mime($suffix);
6725
					}
6726
				}
6727
			}
6728
			// as FreeBSD seems to have problems with the generated temp names we append some more random stuff
6729
			$randomString = chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90));
6730
			$tmpFileName = $GLOBALS['egw_info']['user']['account_id'].
6731
				trim($IDtoAddToFileName).basename($_formData['file']).'_'.$randomString;
6732
6733
			if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs')
6734
			{
6735
				$tmpFileName = $_formData['file'];	// no need to store it somewhere
6736
			}
6737
			elseif (is_uploaded_file($_formData['file']))
6738
			{
6739
				move_uploaded_file($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);	// requirement for safe_mode!
6740
			}
6741
			else
6742
			{
6743
				rename($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);
6744
			}
6745
		} else {
6746
			//error_log("Import of message ".$_formData['file']." failes to meet basic restrictions");
6747
			$importfailed = true;
6748
			$alert_msg .= lang("Processing of file %1 failed. Failed to meet basic restrictions.",$_formData['name']);
6749
		}
6750
		if ($importfailed == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
6751
		{
6752
			throw new Exception\WrongUserinput($alert_msg);
6753
		}
6754
		else
6755
		{
6756
			if (parse_url($tmpFileName,PHP_URL_SCHEME) == 'vfs')
6757
			{
6758
				Vfs::load_wrapper('vfs');
6759
			}
6760
			return $tmpFileName;
6761
		}
6762
	}
6763
6764
	/**
6765
	 * Parses a html text for images, and adds them as inline attachment
6766
	 *
6767
	 * Images can be data-urls, own VFS webdav.php urls or absolute path.
6768
	 *
6769
	 * @param Mailer $_mailObject instance of the Mailer Object to be used
6770
	 * @param string $_html2parse the html to parse and to be altered, if conditions meet
6771
	 * @param $mail_bo mail bo object
6772
	 * @return array|null return inline images stored as tmp file in vfs as array of attachments otherwise null
6773
	 */
6774
	static function processURL2InlineImages(Mailer $_mailObject, &$_html2parse, $mail_bo)
6775
	{
6776
		//error_log(__METHOD__."()");
6777
		$imageC = 0;
6778
		$images = null;
6779
		if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
6780
		{
6781
			foreach($images[2] as $i => $url)
6782
			{
6783
				//$isData = false;
6784
				$basedir = $data = '';
6785
				$needTempFile = true;
6786
6787
				try
6788
				{
6789
					// do not change urls for absolute images (thanks to corvuscorax)
6790
					if (substr($url, 0, 5) !== 'data:')
6791
					{
6792
						$filename = basename($url); // need to resolve all sort of url
6793
						if (($directory = dirname($url)) == '.') $directory = '';
6794
						$ext = pathinfo($filename, PATHINFO_EXTENSION);
6795
						$mimeType  = MimeMagic::ext2mime($ext);
6796
						if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; }
6797
						$myUrl = $directory.$filename;
6798
						if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
6799
						{
6800
							$basedir = Framework::getUrl('/');
6801
						}
6802
						// use vfs instead of url containing webdav.php
6803
						// ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign
6804
						// webdav.php urls as vfs
6805
						if (strpos($myUrl,'/webdav.php') !== false) // we have a webdav link, so we build a vfs/sqlfs link of it.
6806
						{
6807
							Vfs::load_wrapper('vfs');
6808
							list(,$myUrl) = explode('/webdav.php',$myUrl,2);
6809
							$basedir = 'vfs://default';
6810
							$needTempFile = false;
6811
						}
6812
6813
						// If it is an inline image url, we need to fetch the actuall attachment
6814
						// content and later on to be able to store its content as temp file
6815
						if (strpos($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage') !== false && $mail_bo)
6816
						{
6817
							$URI_params = array();
6818
							// Strips the url and store it into a temp for further procss
6819
							$tmp_url = html_entity_decode($myUrl);
6820
6821
							parse_str(parse_url($tmp_url, PHP_URL_QUERY),$URI_params);
6822
							if ($URI_params['mailbox'] && $URI_params['uid'] && $URI_params['cid'])
6823
							{
6824
								$mail_bo->reopen(base64_decode($URI_params['mailbox']));
6825
								$attachment = $mail_bo->getAttachmentByCID($URI_params['uid'], base64_decode($URI_params['cid']),base64_decode($URI_params['partID']),true);
6826
								$mail_bo->closeConnection();
6827
								if ($attachment)
6828
								{
6829
									$data = $attachment->getContents();
6830
									$mimeType = $attachment->getType();
6831
									$filename = $attachment->getDispositionParameter('filename');
6832
								}
6833
							}
6834
						}
6835
6836
						if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; }
6837
						if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl));
6838
					}
6839
					if (substr($url,0,strlen('data:'))=='data:')
6840
					{
6841
						//error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i]));
6842
						// we only support base64 encoded data
6843
						$tmp = substr($url,strlen('data:'));
6844
						list($mimeType,$data_base64) = explode(';base64,',$tmp);
6845
						$data = base64_decode($data_base64);
6846
						// FF currently does NOT add any mime-type
6847
						if (strtolower(substr($mimeType, 0, 6)) != 'image/')
6848
						{
6849
							$mimeType = MimeMagic::analyze_data($data);
6850
						}
6851
						list($what,$exactly) = explode('/',$mimeType);
6852
						$needTempFile = true;
6853
						$filename = ($what?$what:'data').$imageC++.'.'.$exactly;
6854
					}
6855
					if ($data || $needTempFile === false)
6856
					{
6857
						if ($needTempFile)
6858
						{
6859
							$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6860
							$tmpfile = fopen($attachment_file,'w');
6861
							fwrite($tmpfile,$data);
6862
							fclose($tmpfile);
6863
						}
6864
						else
6865
						{
6866
							$attachment_file = $basedir.urldecode($myUrl);
6867
						}
6868
						// we use $attachment_file as base for cid instead of filename, as it may be image.png
6869
						// (or similar) in all cases (when cut&paste). This may lead to more attached files, in case
6870
						// we use the same image multiple times, but, if we do this, we should try to detect that
6871
						// on upload. filename itself is not sufficient to determine the sameness of images
6872
						$cid = 'cid:' . md5($attachment_file);
6873
						if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null)
6874
						{
6875
							//$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse);
6876
							$_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse);
6877
						}
6878
					}
6879
				}
6880
				catch(\Exception $e)
6881
				{
6882
					// Something went wrong with this attachment.  Skip it.
6883
					error_log("Error adding inline attachment.  " . $e->getMessage());
6884
					error_log($e->getTraceAsString());
6885
				}
6886
				$attachments [] = array(
6887
					'name' => $filename,
6888
					'type' => $mimeType,
6889
					'file' => $attachment_file,
6890
					'tmp_name' => $attachment_file
6891
				);
6892
			}
6893
			return is_array($attachments) ? $attachments : null;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attachments seems to be defined by a foreach iteration on line 6781. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
6894
		}
6895
	}
6896
6897
	/**
6898
	 * importMessageToMergeAndSend
6899
	 *
6900
	 * @param Storage\Merge Storage\Merge bo_merge object
6901
	 * @param string $document the full filename
6902
	 * @param array $SendAndMergeTocontacts array of contact ids
6903
	 * @param string&|false $_folder (passed by reference) will set the folder used. must be set with a folder, but will hold modifications if
0 ignored issues
show
Documentation Bug introduced by
The doc comment string&|false at position 2 could not be parsed: Unknown type name '|' at position 2 in string&|false.
Loading history...
6904
	 *					folder is modified.  Set to false to not keep the message.
6905
	 * @param string& $importID ID for the imported message, used by attachments to identify them unambiguously
6906
	 * @return mixed array of messages with success and failed messages or exception
6907
	 */
6908
	function importMessageToMergeAndSend(Storage\Merge $bo_merge, $document, $SendAndMergeTocontacts, &$_folder, &$importID='')
6909
	{
6910
		$importfailed = false;
6911
		$processStats = array('success'=>array(),'failed'=>array());
6912
		if (empty($SendAndMergeTocontacts))
6913
		{
6914
			$importfailed = true;
6915
			$alert_msg .= lang("Import of message %1 failed. No Contacts to merge and send to specified.", '');
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with ''. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

6915
			$alert_msg .= /** @scrutinizer ignore-call */ lang("Import of message %1 failed. No Contacts to merge and send to specified.", '');

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Comprehensibility Best Practice introduced by
The variable $alert_msg seems to be never defined.
Loading history...
6916
		}
6917
6918
		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6919
		/* as the file is provided by Storage\Merge, we do not check
6920
		try
6921
		{
6922
			$tmpFileName = Mail::checkFileBasics($_formData,$importID);
6923
		}
6924
		catch (\Exception\WrongUserinput $e)
6925
		{
6926
			$importfailed = true;
6927
			$alert_msg .= $e->getMessage();
6928
		}
6929
		*/
6930
		$tmpFileName = $document;
6931
		// -----------------------------------------------------------------------
6932
		if ($importfailed === false)
6933
		{
6934
			$mailObject = new Mailer($this->profileID);
6935
			try
6936
			{
6937
				$this->parseFileIntoMailObject($mailObject, $tmpFileName);
6938
			}
6939
			catch (Exception\AssertionFailed $e)
6940
			{
6941
				$importfailed = true;
6942
				$alert_msg .= $e->getMessage();
6943
			}
6944
6945
			//_debug_array($Body);
6946
			$this->openConnection();
6947
			if (empty($_folder) && $_folder !== FALSE)
6948
			{
6949
				$_folder = $this->getSentFolder();
6950
			}
6951
			$delimiter = $this->getHierarchyDelimiter();
6952
			if($_folder=='INBOX'.$delimiter) $_folder='INBOX';
6953
			if ($importfailed === false)
6954
			{
6955
				$Subject = $mailObject->getHeader('Subject');
6956
				//error_log(__METHOD__.' ('.__LINE__.') '.' Subject:'.$Subject);
6957
				$Body = ($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null;
6958
				//error_log(__METHOD__.' ('.__LINE__.') '.' Body:'.$Body);
6959
				//error_log(__METHOD__.' ('.__LINE__.') '.' BodyContentType:'.$mailObject->BodyContentType);
6960
				$AltBody = ($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null;
6961
				//error_log(__METHOD__.' ('.__LINE__.') '.' AltBody:'.$AltBody);
6962
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject->GetReplyTo()));
6963
6964
				if(!$Body && !$AltBody)
6965
				{
6966
					throw new Exception\NotFound('No mail body in template "'.$document.'"');
6967
				}
6968
6969
				// Fetch ReplyTo - Address if existing to check if we are to replace it
6970
				$replyTo = $mailObject->getReplyTo();
6971
				if (isset($replyTo['[email protected]']))
6972
				{
6973
					$mailObject->clearReplyTos();
6974
					$activeMailProfiles = $this->mail->getAccountIdentities($this->profileID);
0 ignored issues
show
Bug introduced by
The property mail does not exist on EGroupware\Api\Mail. Did you mean mailbox?
Loading history...
6975
					$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6976
6977
					$mailObject->addReplyTo(Horde_Idna::encode($activeMailProfile['ident_email']),Mail::generateIdentityString($activeMailProfile,false));
6978
				}
6979
				if(count($SendAndMergeTocontacts) > 1)
6980
				{
6981
					foreach(Mailer::$type2header as $type => $h)
6982
					{
6983
						$header = $mailObject->getHeader(Mailer::$type2header[$type]);
6984
						if(is_array($header)) $header = implode(', ',$header);
6985
						$headers[$type] = $header;
6986
					}
6987
				}
6988
				foreach ($SendAndMergeTocontacts as $k => $val)
6989
				{
6990
					$errorInfo = $email = '';
6991
					$sendOK = $openComposeWindow = $openAsDraft = null;
6992
					//error_log(__METHOD__.' ('.__LINE__.') '.' Id To Merge:'.$val);
6993
					if (/*$GLOBALS['egw_info']['flags']['currentapp'] == 'addressbook' &&*/
6994
						(count($SendAndMergeTocontacts) > 1 || $_folder === FALSE) && $val &&
6995
						(is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val))) // do the merge
6996
					{
6997
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6998
6999
						// Parse destinations for placeholders
7000
						foreach(Mailer::$type2header as $type => $h)
7001
						{
7002
							//error_log('ID ' . $val . ' ' .$type . ': ' . $mailObject->getHeader(Mailer::$type2header[$type]) . ' -> ' .$bo_merge->merge_string($mailObject->getHeader(Mailer::$type2header[$type]),$val,$e,'text/plain',array(),self::$displayCharset));
7003
							$merged = $bo_merge->merge_string($headers[$type],$val,$e,'text/plain',array(),self::$displayCharset);
7004
							$mailObject->clearAddresses($type);
7005
							$mailObject->addAddress($merged,'',$type);
7006
							if($type == 'to')
7007
							{
7008
								$email = $merged;
7009
							}
7010
						}
7011
7012
						// No addresses from placeholders?  Treat it as just a contact ID
7013
						if (!$email)
7014
						{
7015
							$contact = $bo_merge->contacts->read($val);
7016
							//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
7017
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
7018
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
7019
							if($email)
7020
							{
7021
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
7022
							}
7023
						}
7024
7025
						$activeMailProfiles = $this->getAccountIdentities($this->profileID);
7026
						$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
7027
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($activeMailProfile));
7028
						$mailObject->setFrom($activeMailProfile['ident_email'],
7029
							self::generateIdentityString($activeMailProfile,false));
7030
7031
						$mailObject->removeHeader('Message-ID');
7032
						$mailObject->removeHeader('Date');
7033
						$mailObject->clearCustomHeaders();
7034
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
7035
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
7036
						if($text_body) $text_body->setContents($bo_merge->merge_string($Body, $val, $e, 'text/plain', array(), self::$displayCharset),array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
7037
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
7038
						if($html_body) $html_body->setContents($bo_merge->merge_string($AltBody, $val, $e, 'text/html', array(), self::$displayCharset),array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
7039
7040
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
7041
						// set a higher timeout for big messages
7042
						@set_time_limit(120);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

7042
						/** @scrutinizer ignore-unhandled */ @set_time_limit(120);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
7043
						$sendOK = true;
7044
						try {
7045
							$mailObject->send();
7046
							$message_id = $mailObject->getHeader('Message-ID');
0 ignored issues
show
Unused Code introduced by
The assignment to $message_id is dead and can be removed.
Loading history...
7047
							if($_folder)
7048
							{
7049
								$id = $this->appendMessage($_folder, $mailObject->getRaw(), '');
7050
								$importID = $id->current();
7051
							}
7052
						}
7053
						catch(Exception $e) {
7054
							$sendOK = false;
7055
							$errorInfo = $e->getMessage();
7056
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($errorInfo));
7057
						}
7058
					}
7059
					elseif (!$k)	// 1. entry, further entries will fail for apps other then addressbook
7060
					{
7061
						$openAsDraft = true;
7062
						$mailObject->removeHeader('Message-ID');
7063
						$mailObject->removeHeader('Date');
7064
						$mailObject->clearCustomHeaders();
7065
7066
						// Parse destinations for placeholders
7067
						foreach(Mailer::$type2header as $type => $h)
7068
						{
7069
							$header = $mailObject->getHeader(Mailer::$type2header[$type]);
7070
							if(is_array($header)) $header = implode(', ',$header);
7071
							$mailObject->clearAddresses($type);
7072
							$merged = $bo_merge->merge_string($header,$val,$e,'text/plain',array(),self::$displayCharset);
7073
							//error_log($type . ': ' . $mailObject->getHeader(Mailer::$type2header[$type]) . ' -> ' .$merged);
7074
							$mailObject->addAddress(trim($merged,'"'),'',$type);
7075
						}
7076
						$mailObject->forceBccHeader();
7077
7078
						// No addresses from placeholders?  Treat it as just a contact ID
7079
						if (count($mailObject->getAddresses('to',true)) == 0 &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (count($mailObject->getA...accounts->name2id($val), Probably Intended Meaning: count($mailObject->getAd...ccounts->name2id($val))
Loading history...
7080
							is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val)) // do the merge
7081
						{
7082
							$contact = $bo_merge->contacts->read($val);
7083
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($contact));
7084
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
7085
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
7086
							if($email)
7087
							{
7088
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
7089
							}
7090
						}
7091
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
7092
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
7093
						if (!empty($Body)) $text_body->setContents($bo_merge->merge_string($Body, $val, $e, 'text/plain', array(), self::$displayCharset),array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
7094
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
7095
						if (!empty($AltBody)) $html_body->setContents($bo_merge->merge_string($AltBody, $val, $e, 'text/html', array(), self::$displayCharset),array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
7096
						if(!$_folder !== false)
7097
						{
7098
							$_folder = $this->getDraftFolder();
7099
						}
7100
					}
7101
					if ($sendOK || $openAsDraft)
7102
					{
7103
						if ($openAsDraft)
7104
						{
7105
							if($this->folderExists($_folder,true))
7106
							{
7107
								if($this->isSentFolder($_folder))
7108
								{
7109
									$flags = '\\Seen';
7110
								} elseif($this->isDraftFolder($_folder)) {
7111
									$flags = '\\Draft';
7112
								} else {
7113
									$flags = '';
7114
								}
7115
								$savefailed = false;
7116
								try
7117
								{
7118
									$messageUid =$this->appendMessage($_folder,
7119
										$mailObject->getRaw(),
7120
										null,
7121
										$flags);
7122
								}
7123
								catch (\Exception\WrongUserinput $e)
0 ignored issues
show
Bug introduced by
The type Exception\WrongUserinput was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7124
								{
7125
									$savefailed = true;
7126
									$alert_msg .= lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",$Subject,$_folder,$e->getMessage());
7127
								}
7128
								// no send, save successful, and message_uid present
7129
								if ($savefailed===false && $messageUid && is_null($sendOK))
7130
								{
7131
									$importID = $messageUid;
7132
									$openComposeWindow = true;
7133
								}
7134
							}
7135
							else if ($_folder !== FALSE)
7136
							{
7137
								$savefailed = true;
7138
								$alert_msg .= lang("Saving of message %1 failed. Destination Folder %2 does not exist.",$Subject,$_folder);
7139
							}
7140
						}
7141
						if ($sendOK)
7142
						{
7143
							$processStats['success'][$val] = 'Send succeeded to '.$nfn.'<'.$email.'>'.($savefailed?' but failed to store to Folder:'.$_folder:'');
7144
						}
7145
						else
7146
						{
7147
							if (!$openComposeWindow) $processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
0 ignored issues
show
Bug Best Practice introduced by
The expression $openComposeWindow of type null|true is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
7148
						}
7149
					}
7150
					if (!is_null($sendOK) && $sendOK===false && is_null($openComposeWindow))
7151
					{
7152
						$processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
7153
					}
7154
				}
7155
			}
7156
			unset($mailObject);
7157
		}
7158
		// set the url to open when refreshing
7159
		if ($importfailed == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
7160
		{
7161
			throw new Exception\WrongUserinput($alert_msg);
7162
		}
7163
		else
7164
		{
7165
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($processStats));
7166
			return $processStats;
7167
		}
7168
	}
7169
7170
	/**
7171
	 * functions to allow the parsing of message/rfc files
7172
	 * used in felamimail to import mails, or parsev a message from file enrich it with addressdata (merge) and send it right away.
7173
	 */
7174
7175
	/**
7176
	 * Parses a message/rfc mail from file to the mailobject
7177
	 *
7178
	 * @param object $mailer instance of the SMTP Mailer Object
7179
	 * @param string $tmpFileName string that points/leads to the file to be imported
7180
	 * @throws Exception\NotFound if $fle is not found
7181
	 */
7182
	function parseFileIntoMailObject(Mailer $mailer, $tmpFileName)
7183
	{
7184
		switch (parse_url($tmpFileName, PHP_URL_SCHEME))
7185
		{
7186
			case 'vfs':
7187
				break;
7188
			case 'egw-data':
7189
				$message = ($host = parse_url($tmpFileName, PHP_URL_HOST)) ? Link::get_data($host, true) : false;
7190
				break;
7191
			default:
7192
				$tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($tmpFileName);
7193
				break;
7194
		}
7195
		if (!isset($message)) $message = fopen($tmpFileName, 'r');
7196
7197
		if (!$message)
7198
		{
7199
			throw new Exception\NotFound("File '$tmpFileName' not found!");
7200
		}
7201
		$this->parseRawMessageIntoMailObject($mailer, $message);
7202
7203
		fclose($message);
7204
	}
7205
7206
	/**
7207
	 * Check and fix headers of raw message for headers with line width
7208
	 * more than 998 chars per line, as none folding long headers might
7209
	 * break the mail content. RFC 2822 (2.2.3 Long Header fields)
7210
	 * https://www.ietf.org/rfc/rfc2822.txt
7211
	 *
7212
	 * @param string|resource $message
7213
	 * @return string
7214
	 */
7215
	static private function _checkAndfixLongHeaderFields($message)
7216
	{
7217
		$eol = Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL;
7218
		$needsFix = false;
7219
		if (is_resource($message))
7220
		{
7221
			fseek($message, 0, SEEK_SET);
7222
			$m = '';
7223
			while (!feof($message)) {
7224
				$m .= fread($message, 8192);
7225
			}
7226
			$message = $m;
7227
		}
7228
7229
		if (is_string($message))
0 ignored issues
show
introduced by
The condition is_string($message) is always true.
Loading history...
7230
		{
7231
			$start = substr($message,0, strpos($message, $eol));
7232
			$body = substr($message, strlen($start));
7233
			$hlength = strpos($start, $eol) ? strpos($start, $eol) : strlen($start);
7234
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$hlength));
7235
			foreach($headers->toArray() as $header => $value)
7236
			{
7237
				$needsReplacement = false;
7238
				foreach((array)$value as $val)
7239
				{
7240
					if (strlen($val)+ strlen($header) > 900)
7241
					{
7242
						$needsReplacement = $needsFix = true;
7243
					}
7244
				}
7245
				if ($needsReplacement) {
7246
					$headers->removeHeader($header);
7247
					$headers->addHeader($header, $value);
7248
				}
7249
			}
7250
		}
7251
		return $needsFix ? ($headers->toString(array('canonical'=>true)).$body) : $message;
7252
	}
7253
7254
	/**
7255
	 * Parses a message/rfc mail from file to the mailobject
7256
	 *
7257
	 * @param Mailer $mailer instance of SMTP Mailer object
7258
	 * @param string|ressource|Horde_Mime_Part $message string or resource containing the RawMessage / object Mail_mimeDecoded message (part))
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\ressource was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7259
	 * @param boolean $force8bitOnPrimaryPart (default false. force transferEncoding and charset to 8bit/utf8 if we have a textpart as primaryPart)
7260
	 * @throws Exception\WrongParameter when the required Horde_Mail_Part not found
7261
	 */
7262
	function parseRawMessageIntoMailObject(Mailer $mailer, $message, $force8bitOnPrimaryPart=false)
7263
	{
7264
		if (is_string($message) || is_resource($message))
7265
		{
7266
			// Check and fix long header fields
7267
			$message = self::_checkAndfixLongHeaderFields($message);
7268
7269
			// Default charset to utf-8, not us-ascii which Horde chooses
7270
			Horde_Mime_Part::$defaultCharset = 'utf-8';
7271
7272
			$structure = Horde_Mime_Part::parseMessage($message);
7273
			//error_log(__METHOD__.__LINE__.'#'.$structure->getPrimaryType().'#');
7274
			if ($force8bitOnPrimaryPart&&$structure->getPrimaryType()=='text')
7275
			{
7276
				$structure->setTransferEncoding('8bit');
7277
				$structure->setCharset('utf-8');
7278
			}
7279
			$mailer->setBasePart($structure);
7280
			//error_log(__METHOD__.__LINE__.':'.array2string($structure));
7281
7282
			// unfortunately parseMessage does NOT return parsed headers (we assume header is shorter then 8k)
7283
			// *** increase the header size limit to 32k to make sure most of the mails even with huge headers are
7284
			// covered. TODO: Not sure if we even need to cut of the header parts and not just passing the whole
7285
			// message to be parsed in order to get all headers, it needs more invetigation.
7286
			$start = is_string($message) ? substr($message, 0, 32768) :
0 ignored issues
show
introduced by
The condition is_string($message) is always true.
Loading history...
7287
				(fseek($message, 0, SEEK_SET) == -1 ? '' : fread($message, 32768));
7288
7289
			$length = strpos($start, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
7290
			if ($length===false) $length = strlen($start);
7291
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$length));
7292
7293
			foreach($headers->toArray(array('nowrap' => true)) as $header => $value)
7294
			{
7295
				foreach((array)$value as $n => $val)
7296
				{
7297
					$overwrite = !$n;
7298
					switch($header)
7299
					{
7300
						case 'Content-Transfer-Encoding':
7301
							//as we parse the message and this sets the part with a Content-Transfer-Encoding, we
7302
							//should not overwrite it with the header-values of the source-message as the encoding
7303
							//may be altered when retrieving the message e.g. from server
7304
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val.'<->'.$mailer->getHeader('Content-Transfer-Encoding'));
7305
							break;
7306
						case 'Bcc':
7307
						case 'bcc':
7308
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
7309
							$mailer->addBcc($val);
7310
							break;
7311
						default:
7312
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
7313
							$mailer->addHeader($header, $val, $overwrite);
7314
							//error_log(__METHOD__.__LINE__.':'.'getHeader('.$header.')'.array2string($mailer->getHeader($header)));
7315
					}
7316
				}
7317
			}
7318
		}
7319
		elseif (is_a($message, 'Horde_Mime_Part'))
7320
		{
7321
			$mailer->setBasePart($message);
7322
		}
7323
		else
7324
		{
7325
			if (($type = gettype($message)) == 'object') $type = get_class ($message);
7326
			throw new Exception\WrongParameter('Wrong parameter type for message: '.$type);
7327
		}
7328
	}
7329
7330
	/**
7331
	 * Parse an address-list
7332
	 *
7333
	 * Replaces imap_rfc822_parse_adrlist, which fails for utf-8, if not our replacement in common_functions is used!
7334
	 *
7335
	 * @param string $addresses
7336
	 * @param string $default_domain
7337
	 * @return Horde_Mail_Rfc822_List iteratable Horde_Mail_Rfc822_Address objects with attributes mailbox, host, personal and valid
7338
	 */
7339
	public static function parseAddressList($addresses, $default_domain=null)
7340
	{
7341
		$rfc822 = new Horde_Mail_Rfc822();
7342
		$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7343
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count.function_backtrace());
7344
		if ((empty($ret) || $ret->count()==0)&& is_string($addresses) && strlen($addresses)>0)
7345
		{
7346
			$matches = array();
7347
			preg_match_all("/[\w\.,-.,_.,0-9.]+@[\w\.,-.,_.,0-9.]+/",$addresses,$matches);
7348
			//error_log(__METHOD__.__LINE__.array2string($matches));
7349
			foreach ($matches[0] as &$match) {$match = trim($match,', ');}
7350
			$addresses = implode(',',$matches[0]);
7351
			//error_log(__METHOD__.__LINE__.array2string($addresses));
7352
			$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7353
			//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count);
7354
		}
7355
		$previousFailed=false;
7356
		$ret2 = new Horde_Mail_Rfc822_List();
7357
		// handle known problems on emailaddresses
7358
		foreach($ret as $i => $adr)
7359
		{
7360
			//mailaddresses enclosed in single quotes like '[email protected]' show up as 'me as mailbox and you.com' as host
7361
			if ($adr->mailbox && stripos($adr->mailbox,"'")== 0 &&
7362
					$adr->host && stripos($adr->host,"'")== (strlen($adr->host) -1))
7363
			{
7364
				$adr->mailbox = str_replace("'","",$adr->mailbox);
7365
				$adr->host = str_replace("'","",$adr->host);
7366
			}
7367
7368
7369
			// try to strip extra quoting or slashes from personal part
7370
			$adr->personal = stripslashes($adr->personal);
7371
			if ($adr->personal && (stripos($adr->personal, '"') == 0 &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($adr->personal && strip...->personal, -2) == '""', Probably Intended Meaning: $adr->personal && (strip...>personal, -2) == '""')
Loading history...
7372
					substr($adr->personal, -1) == '"') ||
7373
					(substr($adr->personal, -2) == '""'))
7374
			{
7375
				$adr->personal = str_replace('"', "", $adr->personal);
7376
			}
7377
7378
7379
			// no mailbox or host part as 'Xr\xc3\xa4hlyz, User <[email protected]>' is parsed as 2 addresses separated by ','
7380
			//#'Xr\xc3\xa4hlyz, User <[email protected]>'
7381
			//#Horde_Mail_Rfc822_List Object([_data:protected] => Array(
7382
			//[0] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => Xr\xc3\xa4hlyz[_host:protected] => [_personal:protected] => )
7383
			//[1] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => mailboxpart1.mailboxpart2[_host:protected] => youthost.com[_personal:protected] => User))[_filter:protected] => Array()[_ptr:protected] => )#2#,
7384
			if (strlen($adr->mailbox)==0||strlen($adr->host)==0)
7385
			{
7386
				$remember = ($adr->mailbox?$adr->mailbox:($adr->host?$adr->host:''));
7387
				$previousFailed=true;
7388
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7389
			}
7390
			else
7391
			{
7392
				if ($previousFailed && $remember) $adr->personal = $remember. ' ' . $adr->personal;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $remember seems to be defined later in this foreach loop on line 7386. Are you sure it is defined here?
Loading history...
7393
				$remember = '';
7394
				$previousFailed=false;
7395
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7396
				$ret2->add($adr);
7397
			}
7398
		}
7399
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret2).'#'.$ret2->count().'#'.$ret2->count);
7400
		return $ret2;
7401
	}
7402
7403
	/**
7404
	 * Send a read notification
7405
	 *
7406
	 * @param string $uid
7407
	 * @param string $_folder
7408
	 * @return boolean
7409
	 */
7410
	function sendMDN($uid,$_folder)
7411
	{
7412
		$acc = Mail\Account::read($this->profileID);
7413
		$identity = Mail\Account::read_identity($acc['ident_id'], true, null, $acc);
7414
		if (self::$debug) error_log(__METHOD__.__LINE__.array2string($identity));
7415
		$headers = $this->getMessageHeader($uid, '', 'object', true, $_folder);
7416
7417
		// Override Horde's translation with our own
7418
		Horde_Translation::setHandler('Horde_Mime', new Horde_Translation_Handler_Gettext('Horde_Mime', EGW_SERVER_ROOT.'/api/lang/locale'));
7419
		Preferences::setlocale();
7420
7421
		$mdn = new Horde_Mime_Mdn($headers);
7422
		$mdn->generate(true, true, 'displayed', php_uname('n'), $acc->smtpTransport(), array(
7423
			'charset' => 'utf-8',
7424
			'from_addr' => self::generateIdentityString($identity),
7425
		));
7426
7427
		return true;
7428
	}
7429
7430
	/**
7431
	 * Hook stuff
7432
	 */
7433
7434
	/**
7435
	 * hook to add account
7436
	 *
7437
	 * this function is a wrapper function for emailadmin
7438
	 *
7439
	 * @param _hookValues contains the hook values as array
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\contains was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7440
	 * @return nothing
7441
	 */
7442
	function addAccount($_hookValues)
7443
	{
7444
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7445
7446
	}
7447
7448
	/**
7449
	 * hook to delete account
7450
	 *
7451
	 * this function is a wrapper function for emailadmin
7452
	 *
7453
	 * @param _hookValues contains the hook values as array
7454
	 * @return nothing
7455
	 */
7456
	function deleteAccount($_hookValues)
7457
	{
7458
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7459
7460
	}
7461
7462
	/**
7463
	 * hook to update account
7464
	 *
7465
	 * this function is a wrapper function for emailadmin
7466
	 *
7467
	 * @param _hookValues contains the hook values as array
7468
	 * @return nothing
7469
	 */
7470
	function updateAccount($_hookValues)
7471
	{
7472
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7473
7474
	}
7475
7476
	/**
7477
	 * This function gets array of email addresses in RFC822 format
7478
	 * and tries to normalize the addresses into only email addresses.
7479
	 *
7480
	 * @param array $_addresses Addresses
7481
	 */
7482
	static function stripRFC822Addresses ($_addresses)
7483
	{
7484
		$matches = array();
7485
		foreach ($_addresses as &$address)
7486
		{
7487
			preg_match("/<([^\'\" <>]+)>$/", $address, $matches);
7488
			if ($matches[1]) $address = $matches[1];
7489
		}
7490
		return $_addresses;
7491
	}
7492
7493
7494
7495
	/**
7496
	 * Resolve certificate and encrypted message from smime attachment
7497
	 *
7498
	 * @param Horde_Mime_Part $_mime_part
7499
	 * @param array $_params
7500
	 *		params = array (
7501
	 *			mimeType			=> (string) // message mime type
7502
	 *			uid					=> (string) // message uid
7503
	 *			mailbox				=> (string) // the mailbox where message is stored
7504
	 *			passphrase			=> (string) // smime private key passphrase
7505
	 *		)
7506
	 *
7507
	 * @return Horde_Mime_Part returns a resolved mime part
7508
	 * @throws PassphraseMissing if private key passphrase is not provided
7509
	 * @throws Horde_Crypt_Exception if decryption fails
7510
	 */
7511
	function resolveSmimeMessage(Horde_Mime_Part $_mime_part, $_params)
7512
	{
7513
		// default params
7514
		$params = array_merge(array(
7515
 			'passphrase'	=> ''
7516
		), $_params);
7517
7518
		$metadata = array (
7519
			 'mimeType' => $params['mimeType']?$params['mimeType']:$_mime_part->getType()
7520
		);
7521
		$this->smime = new Mail\Smime;
0 ignored issues
show
Bug Best Practice introduced by
The property smime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
7522
		$message = $this->getMessageRawBody($params['uid'], null, $params['mailbox']);
7523
		if (!Mail\Smime::isSmimeSignatureOnly(Mail\Smime::getSmimeType($_mime_part)))
7524
		{
7525
			try{
7526
				$message = $this->_decryptSmimeBody($message, $params['passphrase'] !='' ?
7527
						$params['passphrase'] : Api\Cache::getSession('mail', 'smime_passphrase'));
7528
			}
7529
			catch(\Horde_Crypt_Exception $e)
7530
			{
7531
				throw new Mail\Smime\PassphraseMissing(lang('Could not decrypt '.
7532
						'S/MIME data. This message may not be encrypted by your '.
7533
						'public key and not being able to find corresponding private key.'));
7534
			}
7535
			$metadata['encrypted'] = true;
7536
		}
7537
7538
		try {
7539
			$cert = $this->smime->verifySignature($message);
7540
		} catch (\Exception $ex) {
7541
			// passphrase is required to decrypt the message
7542
			if (isset($message['password_required']))
7543
			{
7544
				throw new Mail\Smime\PassphraseMissing($message['msg']);
7545
			}
7546
			// verifivation failure either message has been tempered,
7547
			// signature is not valid or message has not ben signed
7548
			// but encrypted only.
7549
			else
7550
			{
7551
				$metadata['verify'] = false;
7552
				$metadata['signed'] = true;
7553
				$metadata['msg'] = $ex->getMessage();
7554
			}
7555
		}
7556
7557
		if ($cert) // signed message, it might be encrypted too
0 ignored issues
show
introduced by
$cert is of type EGroupware\Api\Mail\type, thus it always evaluated to true.
Loading history...
7558
		{
7559
			$envelope = $this->getMessageEnvelope($params['uid'], '', false, $params['mailbox']);
7560
			$from = $this->stripRFC822Addresses($envelope['FROM']);
7561
			$message_parts = $this->smime->extractSignedContents($message);
7562
			$cert_email = strtolower($cert->email);
7563
			//$f = $message_parts->_headers->getHeader('from');
7564
			$metadata = array_merge ($metadata, array (
7565
				'verify'		=> $cert->verify,
7566
				'cert'			=> $cert->cert,
7567
				'certDetails'	=> $this->smime->parseCert($cert->cert),
7568
				'msg'			=> $cert->msg,
7569
				'certHtml'		=> $this->smime->certToHTML($cert->cert),
7570
				'email'			=> $cert_email,
7571
				'signed'		=> true
7572
			));
7573
			// check for email address if both signer email address and
7574
			// email address of sender are the same. It also takes  subjectAltName emails into account.
7575
			if (is_array($from) && strcasecmp($from[0], $cert_email) != 0
7576
					&& stripos($metadata['certDetails']['extensions']['subjectAltName'],$from[0]) === false)
7577
			{
7578
				$metadata['unknownemail'] = true;
7579
				$metadata['msg'] .= ' '.lang('Email address of signer is different from the email address of sender!');
7580
			}
7581
7582
			$AB_bo   = new \addressbook_bo();
7583
			$certkey = $AB_bo->get_smime_keys($cert_email);
7584
			if (!is_array($certkey) || strcasecmp(trim($certkey[$cert_email]), trim($cert->cert)) != 0) $metadata['addtocontact'] = true;
0 ignored issues
show
introduced by
The condition is_array($certkey) is always true.
Loading history...
7585
		}
7586
		else // only encrypted message
7587
		{
7588
			$message_parts = Horde_Mime_Part::parseMessage($message, array('forcemime' => true));
7589
		}
7590
		$message_parts->setMetadata('X-EGroupware-Smime', $metadata);
7591
		return $message_parts;
7592
	}
7593
7594
	/**
7595
	 * decrypt given smime encrypted message
7596
	 *
7597
	 * @param string $_message
7598
	 * @param string $_passphrase
7599
	 * @return array|string return
7600
	 * @throws Horde_Crypt_Exception
7601
	 */
7602
	private function _decryptSmimeBody ($_message, $_passphrase = '')
7603
	{
7604
		$AB_bo   = new \addressbook_bo();
7605
		$acc_smime = Mail\Smime::get_acc_smime($this->profileID, $_passphrase);
7606
		$certkey = $AB_bo->get_smime_keys($acc_smime['acc_smime_username']);
7607
		if (!$this->smime->verifyPassphrase($acc_smime['pkey'], $_passphrase))
7608
		{
7609
			return array (
7610
				'password_required' => true,
7611
				'msg' => 'Authentication failure!'
7612
			);
7613
		}
7614
7615
		$params  = array (
7616
			'type'      => 'message',
7617
			'pubkey'    => $certkey[strtolower($acc_smime['acc_smime_username'])],
7618
			'privkey'   => $acc_smime['pkey'],
7619
			'passphrase'=> $_passphrase
7620
		);
7621
		return $this->smime->decrypt($_message, $params);
7622
	}
7623
}
7624