Completed
Push — master ( 93853c...0bd975 )
by Klaus
26:31 queued 06:23
created

Mail::getHierarchyDelimiter()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 24
Code Lines 16

Duplication

Lines 4
Ratio 16.67 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 16
c 1
b 0
f 0
nc 24
nop 1
dl 4
loc 24
rs 6.7272
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
31
use tidy;
32
33
/**
34
 * Mail worker class
35
 *  -provides backend functionality for all classes in Mail
36
 *  -provides classes that may be used by other apps too
37
 *
38
 * @link https://github.com/horde/horde/blob/master/imp/lib/Contents.php
39
 */
40
class Mail
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
41
{
42
	/**
43
	 * the current selected user profile
44
	 * @var int
45
	 */
46
	var $profileID = 0;
47
48
	/**
49
	 * delimiter - used to separate acc_id from mailbox / folder-tree-structure
50
	 *
51
	 * @var string
52
	 */
53
	const DELIMITER = '::';
54
55
	/**
56
	 * the current display char set
57
	 * @var string
58
	 */
59
	static $displayCharset;
60
	static $activeFolderCache;
61
	static $folderStatusCache;
62
	static $supportsORinQuery;
63
64
	/**
65
	 * Active preferences
66
	 *
67
	 * @var array
68
	 */
69
	var $mailPreferences;
70
71
	/**
72
	 * active html Options
73
	 *
74
	 * @var array
75
	 */
76
	var $htmlOptions;
77
78
	/**
79
	 * Active mimeType
80
	 *
81
	 * @var string
82
	 */
83
	var $activeMimeType;
84
85
	/**
86
	 * Active incomming (IMAP) Server Object
87
	 *
88
	 * @var Api\Mail\Imap
89
	 */
90
	var $icServer;
91
92
	/**
93
	 * Active outgoing (smtp) Server Object
94
	 *
95
	 * @var Api\Mail\Smtp
96
	 */
97
	var $ogServer;
98
99
	/**
100
	 * errorMessage
101
	 *
102
	 * @var string $errorMessage
103
	 */
104
	var $errorMessage;
105
106
	/**
107
	 * switch to enable debug; sometimes debuging is quite handy, to see things. check with the error log to see results
108
	 * @var boolean
109
	 */
110
	static $debug = false; //true;
111
	static $debugTimes = false; //true;
112
113
	/**
114
	 * static used to hold the mail Config values
115
	 * @array
116
	 */
117
	static $mailConfig;
118
119
	/**
120
	 * 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
121
	 *
122
	 * @array
123
	 */
124
	static $tidy_config = array('clean'=>false,'output-html'=>true,'join-classes'=>true,'join-styles'=>true,'show-body-only'=>"auto",'word-2000'=>true,'wrap'=>0);
125
126
	/**
127
	 * static used to configure htmLawed, for use with emails
128
	 *
129
	 * @array
130
	 */
131
	static $htmLawed_config = array('comment'=>1, //remove comments
132
		'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
133
		'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)
134
		'balance'=>1,//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)
135
		'direct_list_nest' => 1,
136
		'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
137
		// tidy eats away even some wanted whitespace, so we switch it off;
138
		// we used it for its compacting and beautifying capabilities, which resulted in better html for further processing
139
		'tidy'=>0,
140
		'elements' => "* -script",
141
		'deny_attribute' => 'on*',
142
		'schemes'=>'href: file, ftp, http, https, mailto, phone; src: cid, data, file, ftp, http, https; *:file, http, https, cid, src',
143
		'hook_tag' =>"hl_email_tag_transform",
144
	);
145
146
	/**
147
	 * static used define abbrevations for common access rights
148
	 *
149
	 * @array
150
	 */
151
	static $aclShortCuts = array('' => array('label'=>'none','title'=>'The user has no rights whatsoever.'),
152
		'lrs'		=> array('label'=>'readable','title'=>'Allows a user to read the contents of the mailbox.'),
153
		'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.'),
154
		'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.'),
155
		'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.'),
156
		'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.'),
157
		'custom'	=> array('label'=>'custom','title'=>'User defined combination of rights for the ACL'),
158
	);
159
160
	/**
161
	 * Folders that get automatic created AND get translated to the users language
162
	 * their creation is also controlled by users mailpreferences. if set to none / dont use folder
163
	 * the folder will not be automatically created. This is controlled in Mail->getFolderObjects
164
	 * so changing names here, must include a change of keywords there as well. Since these
165
	 * foldernames are subject to translation, keep that in mind too, if you change names here.
166
	 * lang('Drafts'), lang('Templates'), lang('Sent'), lang('Trash'), lang('Junk'), lang('Outbox')
167
	 * ActiveSync:
168
	 *  Outbox is needed by Nokia Clients to be able to send Mails
169
	 * @var array
170
	 */
171
	static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox');
172
173
	/**
174
	 * Array to cache the specialUseFolders, if existing
175
	 * @var array
176
	 */
177
	static $specialUseFolders;
178
179
	/**
180
	 * Hold instances by profileID for getInstance() singleton
181
	 *
182
	 * @var array
183
	 */
184
	private static $instances = array();
185
	private static $profileDefunct = array();
186
187
	/**
188
	 * Singleton for Mail
189
	 *
190
	 * @param boolean $_restoreSession = true
191
	 * @param int $_profileID = 0
192
	 * @param boolean $_validate = true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile
193
	 *                                  not matching the input profileID, if we can not find a profile matching the given ID
194
	 * @param mixed boolean/object $_icServerObject - if object, return instance with object set as icServer
195
	 *												  immediately, if boolean === true use oldImapServer in constructor
196
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
197
	 * @return Mail
198
	 */
199
	public static function getInstance($_restoreSession=true, &$_profileID=0, $_validate=true, $_oldImapServerObject=false, $_reuseCache=null)
200
	{
201
		//$_restoreSession=false;
202
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
203
		//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());
204
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_oldImapServerObject));
205
		if (isset(self::$profileDefunct[$_profileID]) && self::$profileDefunct[$_profileID]===true)
206
		{
207
			throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage().($e->details?', '.$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
208
		}
209
		if ($_oldImapServerObject instanceof Mail\Imap)
210
		{
211
			if (!is_object(self::$instances[$_profileID]))
212
			{
213
				self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
214
			}
215
			self::$instances[$_profileID]->icServer = $_oldImapServerObject;
216
			self::$instances[$_profileID]->accountid= $_oldImapServerObject->ImapServerId;
217
			self::$instances[$_profileID]->profileID= $_oldImapServerObject->ImapServerId;
218
			self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
219
			self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
220
			return self::$instances[$_profileID];
221
		}
222
		if ($_profileID == 0)
223
		{
224 View Code Duplication
			if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
225
			{
226
				$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
227
			}
228
			else
229
			{
230
				$profileID = Mail\Account::get_default_acc_id();
231
			}
232
			if ($profileID!=$_profileID) $_restoreSession==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...
233
			$_profileID=$profileID;
234 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace());
235
		}
236
		// no validation or restoreSession for old ImapServer Object, just fetch it and return it
237
		if ($_oldImapServerObject===true)
238
		{
239
			return new Mail('utf-8',false,$_profileID,true,$_reuseCache);
240
		}
241
		if ($_profileID != 0 && $_validate)
242
		{
243
			$profileID = self::validateProfileID($_profileID);
244
			if ($profileID != $_profileID)
245
			{
246
				if (self::$debug)
247
				{
248
					error_log(__METHOD__.' ('.__LINE__.') '.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.');
249
					error_log(__METHOD__.' ('.__LINE__.') '.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);
250
				}
251
				$_profileID = $profileID;
252
				//$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
253
				// save prefs
254
				//$GLOBALS['egw']->preferences->save_repository(true);
255
			}
256
			//Cache::setSession('mail','activeProfileID',$_profileID);
257
		}
258
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
259
		if ($_profileID && (!isset(self::$instances[$_profileID]) || $_restoreSession===false))
260
		{
261
			self::$instances[$_profileID] = new Mail('utf-8',$_restoreSession,$_profileID,false,$_reuseCache);
262
		}
263
		else
264
		{
265
			//refresh objects
266
			try
267
			{
268
				self::$instances[$_profileID]->icServer = Mail\Account::read($_profileID)->imapServer();
269
				self::$instances[$_profileID]->ogServer = Mail\Account::read($_profileID)->smtpServer();
270
				// TODO: merge mailprefs into userprefs, for easy treatment
271
				self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
272
				self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
273
			} catch (\Exception $e)
274
			{
275
				$newprofileID = Mail\Account::get_default_acc_id();
276
				// try loading the default profile for the user
277
				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());
278
				if ($newprofileID)
279
				{
280
					self::$instances[$newprofileID] = new Mail('utf-8',false,$newprofileID,false,$_reuseCache);
281
					$_profileID = $newprofileID;
282
				}
283
				else
284
				{
285
					throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_profileID / ".$this->profileID." with error:".$e->getMessage().($e->details?', '.$e->details:''));
286
				}
287
			}
288
			self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );
289
		}
290
		self::$instances[$_profileID]->profileID = $_profileID;
291
		if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new Horde_Idna;
292
		//if ($_profileID==0); error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID);
293
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
294
		return self::$instances[$_profileID];
295
	}
296
297
	/**
298
	 * store given ProfileID to Session and pref
299
	 *
300
	 * @param int $_profileID = 0
301
	 * @param boolean $_testConnection = 0
302
	 * @return mixed $_profileID or false on failed ConnectionTest
303
	 */
304
	public static function storeActiveProfileIDToPref($_icServerObject, $_profileID=0, $_testConnection=true)
305
	{
306 View Code Duplication
		if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
307
		{
308
			$oldProfileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
309
		}
310
		if ($_testConnection)
311
		{
312
			try
313
			{
314
				$_icServerObject->getCurrentMailbox();
315
			}
316
			catch (\Exception $e)
317
			{
318
				if ($_profileID != Mail\Account::get_default_acc_id()) $_profileID = Mail\Account::get_default_acc_id();
319
				error_log(__METHOD__.__LINE__.' '.$e->getMessage());
320
				return false;
321
			}
322
		}
323
		if ($oldProfileID != $_profileID)
324
		{
325
			if ($oldProfileID && $_profileID==0) $_profileID = $oldProfileID;
326
			$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
327
			// save prefs
328
			$GLOBALS['egw']->preferences->save_repository(true);
329
			$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $_profileID;
330
			Cache::setSession('mail','activeProfileID',$_profileID);
331
		}
332
		return $_profileID;
333
	}
334
335
	/**
336
	 * Validate given account acc_id to make sure account is valid for current user
337
	 *
338
	 * Validation checks:
339
	 * - non-empty imap-host
340
	 * - non-empty imap-username
341
	 *
342
	 * @param int $_acc_id = 0
343
	 * @return int validated acc_id -> either acc_id given, or first valid one
344
	 */
345
	public static function validateProfileID($_acc_id=0)
346
	{
347
		if ($_acc_id)
348
		{
349
			try {
350
				$account = Mail\Account::read($_acc_id);
351
				if ($account->is_imap())
352
				{
353
					return $_acc_id;
354
				}
355
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT valid, no imap-host!");
356
			}
357
			catch (\Exception $e) {
358
				unset($e);
359
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT found!");
360
			}
361
		}
362
		// no account specified or specified account not found or not valid
363
		// --> search existing account for first valid one and return that
364
		foreach(Mail\Account::search($only_current_user=true, 'acc_imap_host') as $acc_id => $imap_host)
365
		{
366
			if (!empty($imap_host) && ($account = Mail\Account::read($acc_id)) && $account->is_imap())
367
			{
368
				if (self::$debug && $_acc_id) error_log(__METHOD__."($_acc_id) using $acc_id instead");
369
				return $acc_id;
370
			}
371
		}
372
		if (self::$debug) error_log(__METHOD__."($_acc_id) NO valid account found!");
373
		return 0;
374
	}
375
376
377
	/**
378
	 * Private constructor, use Mail::getInstance() instead
379
	 *
380
	 * @param string $_displayCharset = 'utf-8'
381
	 * @param boolean $_restoreSession = true
382
	 * @param int $_profileID = 0 if not nummeric, we assume we only want an empty class object
383
	 * @param boolean $_oldImapServerObject = false
384
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
385
	 */
386
	private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0, $_oldImapServerObject=false, $_reuseCache=null)
387
	{
388
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
389
		if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset;
390
		// not nummeric, we assume we only want an empty class object
391
		if (!is_numeric($_profileID)) return true;
392
		if ($_restoreSession)
393
		{
394
			//error_log(__METHOD__." Session restore ".function_backtrace());
395
			$this->restoreSessionData();
396
			$lv_mailbox = $this->sessionData['mailbox'];
397
			$firstMessage = $this->sessionData['previewMessage'];
398
		}
399
		else
400
		{
401
			$this->restoreSessionData();
402
			$lv_mailbox = $this->sessionData['mailbox'];
403
			$firstMessage = $this->sessionData['previewMessage'];
404
			$this->sessionData = array();
405
		}
406
		if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
407
		try
408
		{
409
			$this->profileID = self::validateProfileID($_profileID);
0 ignored issues
show
Documentation Bug introduced by
It seems like self::validateProfileID($_profileID) can also be of type string. However, the property $profileID is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
410
			$this->accountid	= $GLOBALS['egw_info']['user']['account_id'];
411
412
			//error_log(__METHOD__.' ('.__LINE__.') '." ProfileID ".$this->profileID.' called from:'.function_backtrace());
413
			$acc = Mail\Account::read($this->profileID);
414
		}
415
		catch (\Exception $e)
416
		{
417
			throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage());
418
		}
419
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($acc->imapServer()));
420
		$this->icServer = ($_oldImapServerObject?$acc->oldImapServer():$acc->imapServer());
421
		$this->ogServer = $acc->smtpServer();
422
		// TODO: merge mailprefs into userprefs, for easy treatment
423
		$this->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
424
		$this->htmlOptions  = $this->mailPreferences['htmlOptions'];
425
		if (isset($this->icServer->ImapServerId) && !empty($this->icServer->ImapServerId))
426
		{
427
			$_profileID = $this->profileID = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->icServer->ImapServerId;
428
		}
429
430
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
431
	}
432
433
	/**
434
	 * forceEAProfileLoad
435
	 * used to force the load of a specific emailadmin profile; we assume administrative use only (as of now)
436
	 * @param int $_profile_id
437
	 * @return object instance of Mail (by reference)
438
	 */
439
	public static function &forceEAProfileLoad($_profile_id)
440
	{
441
		self::unsetCachedObjects($_profile_id);
442
		$mail = self::getInstance(false, $_profile_id,false);
443
		//_debug_array( $_profile_id);
444
		$mail->icServer = Mail\Account::read($_profile_id)->imapServer();
445
		$mail->ogServer = Mail\Account::read($_profile_id)->smtpServer();
446
		return $mail;
447
	}
448
449
	/**
450
	 * trigger the force of the reload of the SessionData by resetting the session to an empty array
451
	 * @param int $_profile_id
452
	 * @param boolean $_resetFolderObjects
453
	 */
454
	public static function forcePrefReload($_profile_id=null,$_resetFolderObjects=true)
455
	{
456
		// unset the mail_preferences session object, to force the reload/rebuild
457
		Cache::setSession('mail','mail_preferences',serialize(array()));
458
		Cache::setSession('emailadmin','session_data',serialize(array()));
459
		if ($_resetFolderObjects) self::resetFolderObjectCache($_profile_id);
460
	}
461
462
	/**
463
	 * restore the SessionData
464
	 */
465
	function restoreSessionData()
466
	{
467
		$this->sessionData = array();//Cache::getCache(Cache::SESSION,'mail','session_data',$callback=null,$callback_params=array(),$expiration=60*60*1);
468
		self::$activeFolderCache = Cache::getCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
469 View Code Duplication
		if (!empty(self::$activeFolderCache[$this->profileID])) $this->sessionData['mailbox'] = self::$activeFolderCache[$this->profileID];
470
	}
471
472
	/**
473
	 * saveSessionData saves session data
474
	 */
475
	function saveSessionData()
476
	{
477
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($this->sessionData)));
478 View Code Duplication
		if (!empty($this->sessionData['mailbox'])) self::$activeFolderCache[$this->profileID]=$this->sessionData['mailbox'];
479
		if (isset(self::$activeFolderCache) && is_array(self::$activeFolderCache))
480
		{
481
			Cache::setCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),self::$activeFolderCache, 60*60*10);
482
		}
483
	}
484
485
	/**
486
	 * unset certain CachedObjects for the given profile id, unsets the profile for default ID=0 as well
487
	 *
488
	 * 1) icServerIMAP_connectionError
489
	 * 2) icServerSIEVE_connectionError
490
	 * 3) INSTANCE OF MAIL_BO
491
	 * 4) HierarchyDelimiter
492
	 * 5) VacationNotice
493
	 *
494
	 * @param int $_profileID = null default profile of user as returned by getUserDefaultProfileID
495
	 * @return void
496
	 */
497
	static function unsetCachedObjects($_profileID=null)
498
	{
499
		if (is_null($_profileID)) $_profileID = Mail\Account::get_default_acc_id();
500
		if (is_array($_profileID) && $_profileID['account_id']) $account_id = $_profileID['account_id'];
501
		//error_log(__METHOD__.__LINE__.' called with ProfileID:'.array2string($_profileID).' from '.function_backtrace());
502
		if (!is_array($_profileID) && (is_numeric($_profileID) || !(stripos($_profileID,'tracker_')===false)))
503
		{
504
			self::resetConnectionErrorCache($_profileID);
505
			$rawHeadersCache = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
506 View Code Duplication
			if (isset($rawHeadersCache[$_profileID]))
507
			{
508
				unset($rawHeadersCache[$_profileID]);
509
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$rawHeadersCache, $expiration=60*60*1);
510
			}
511
			$HierarchyDelimiterCache = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*5);
512 View Code Duplication
			if (isset($HierarchyDelimiterCache[$_profileID]))
513
			{
514
				unset($HierarchyDelimiterCache[$_profileID]);
515
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$HierarchyDelimiterCache, $expiration=60*60*24*5);
516
			}
517
			//reset folderObject cache, to trigger reload
518
			self::resetFolderObjectCache($_profileID);
519
			//reset counter of deleted messages per folder
520
			$eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
521 View Code Duplication
			if (isset($eMailListContainsDeletedMessages[$_profileID]))
522
			{
523
				unset($eMailListContainsDeletedMessages[$_profileID]);
524
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$eMailListContainsDeletedMessages, $expiration=60*60*1);
525
			}
526
			$vacationCached = Cache::getCache(Cache::INSTANCE, 'email', 'vacationNotice'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*1);
527 View Code Duplication
			if (isset($vacationCached[$_profileID]))
528
			{
529
				unset($vacationCached[$_profileID]);
530
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),$vacationCached, $expiration=60*60*24*1);
531
			}
532
533
			if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
534
		}
535
		if (is_array($_profileID) && $_profileID['location'] == 'clear_cache')
536
		{
537
			// called via hook
538
			foreach($GLOBALS['egw']->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
539
			{
540
				//error_log(__METHOD__.__LINE__.array2string($account));
541
				$account_id = $account['account_id'];
542
				$_profileID = null;
543
				self::resetConnectionErrorCache($_profileID,$account_id);
544
				self::resetFolderObjectCache($_profileID,$account_id);
545
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),array(), 60*60*1);
546
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),array(), 60*60*24*5);
547
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),array(), 60*60*1);
548
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),array(), 60*60*24*1);
549
			}
550
		}
551
	}
552
553
	/**
554
	 * resets the various cache objects where connection error Objects may be cached
555
	 *
556
	 * @param int $_ImapServerId the profileID to look for
557
	 * @param int $account_id the egw account to look for
558
	 */
559
	static function resetConnectionErrorCache($_ImapServerId=null,$account_id=null)
560
	{
561
		//error_log(__METHOD__.' ('.__LINE__.') '.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($account_id));
562
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
563
		if (is_array($_ImapServerId))
564
		{
565
			// called via hook
566
			$account_id = $_ImapServerId['account_id'];
567
			unset($_ImapServerId);
568
			$_ImapServerId = null;
569
		}
570
		if (is_null($_ImapServerId))
571
		{
572
			$isConError = array();
573
			$waitOnFailure = array();
574
		}
575
		else
576
		{
577
			$isConError = Cache::getCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id));
578
			if (isset($isConError[$_ImapServerId]))
579
			{
580
				unset($isConError[$_ImapServerId]);
581
			}
582
			$waitOnFailure = Cache::getCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),null,array(),60*60*2);
583
			if (isset($waitOnFailure[$_ImapServerId]))
584
			{
585
				unset($waitOnFailure[$_ImapServerId]);
586
			}
587
		}
588
		Cache::setCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,60*15);
589
		Cache::setCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),$waitOnFailure,60*60*2);
590
	}
591
592
	/**
593
	 * resets the various cache objects where Folder Objects may be cached
594
	 *
595
	 * @param int $_ImapServerId the profileID to look for
596
	 * @param int $account_id the egw account to look for
597
	 */
598
	static function resetFolderObjectCache($_ImapServerId=null,$account_id=null)
599
	{
600
		//error_log(__METHOD__.' ('.__LINE__.') '.' called for Profile:'.array2string($_ImapServerId).'->'.function_backtrace());
601
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
602
		// on [location] => verify_settings we coud either use [prefs] => Array([ActiveProfileID] => 9, .. as $_ImapServerId
603
		// or treat it as not given. we try that path
604
		if (is_null($_ImapServerId)||is_array($_ImapServerId))
605
		{
606
			$folders2return = array();
607
			$folderInfo = array();
608
			$folderBasicInfo = array();
609
			$_specialUseFolders = array();
610
		}
611
		else
612
		{
613
			$folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),null,array(),60*60*1);
614
			if (!empty($folders2return) && isset($folders2return[$_ImapServerId]))
615
			{
616
				unset($folders2return[$_ImapServerId]);
617
			}
618
			$folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),null,array(),60*60*5);
619
			if (!empty($folderInfo) && isset($folderInfo[$_ImapServerId]))
620
			{
621
				unset($folderInfo[$_ImapServerId]);
622
			}
623
			/*
624
			$lastFolderUsedForMove = Cache::getCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),null,array(),$expiration=60*60*1);
625
			if (isset($lastFolderUsedForMove[$_ImapServerId]))
626
			{
627
				unset($lastFolderUsedForMove[$_ImapServerId]);
628
			}
629
			*/
630
			$folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),null,array(),60*60*1);
631
			if (!empty($folderBasicInfo) && isset($folderBasicInfo[$_ImapServerId]))
632
			{
633
				unset($folderBasicInfo[$_ImapServerId]);
634
			}
635
			$_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),null,array(),60*60*12);
636
			if (!empty($_specialUseFolders) && isset($_specialUseFolders[$_ImapServerId]))
637
			{
638
				unset($_specialUseFolders[$_ImapServerId]);
639
				self::$specialUseFolders=null;
640
			}
641
		}
642
		Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),$folders2return, 60*60*1);
643
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),$folderInfo,60*60*5);
644
		//Cache::setCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),$lastFolderUsedForMove,$expiration=60*60*1);
645
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),$folderBasicInfo,60*60*1);
646
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),$_specialUseFolders,60*60*12);
647
	}
648
649
	/**
650
	 * checks if the imap server supports a given capability
651
	 *
652
	 * @param string $_capability the name of the capability to check for
653
	 * @return bool
654
	 */
655
	function hasCapability($_capability)
656
	{
657
		$rv = $this->icServer->hasCapability(strtoupper($_capability));
658
		//error_log(__METHOD__.' ('.__LINE__.') '." $_capability:".array2string($rv));
659
		return $rv;
660
	}
661
662
	/**
663
	 * getUserEMailAddresses - function to gather the emailadresses connected to the current mail-account
664
	 * @param string $_profileID the ID of the mailaccount to check for identities, if null current mail-account is used
665
	 * @return array - array(email=>realname)
666
	 */
667
	function getUserEMailAddresses($_profileID=null)
668
	{
669
		$acc = Mail\Account::read((!empty($_profileID)?$_profileID:$this->profileID));
670
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($acc));
671
		$identities = Mail\Account::identities($acc);
672
673
		$userEMailAdresses = array($acc['ident_email']=>$acc['ident_realname']);
674
675
		foreach($identities as $ik => $ident) {
676
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
677
			$identity = Mail\Account::read_identity($ik);
678
			if (!empty($identity['ident_email']) && !isset($userEMailAdresses[$identity['ident_email']])) $userEMailAdresses[$identity['ident_email']] = $identity['ident_realname'];
679
		}
680
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
681
		return $userEMailAdresses;
682
	}
683
684
	/**
685
	 * getAllIdentities - function to gather the identities connected to the current user
686
	 * @param string/int $_accountToSearch = null if set search accounts for user specified
687
	 * @param boolean $resolve_placeholders wether or not resolve possible placeholders in identities
688
	 * @return array - array(email=>realname)
689
	 */
690
	static function getAllIdentities($_accountToSearch=null,$resolve_placeholders=false)
691
	{
692
		$userEMailAdresses = array();
693
		foreach(Mail\Account::search($only_current_user=($_accountToSearch?$_accountToSearch:true), $just_name=true) as $acc_id => $identity_name)
694
		{
695
			$acc = Mail\Account::read($acc_id,($_accountToSearch?$_accountToSearch:null));
696
			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']);
697
698
			foreach(Mail\Account::identities($acc) as $ik => $ident) {
699
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
700
				$identity = Mail\Account::read_identity($ik,$resolve_placeholders);
701
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
702 View Code Duplication
				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']);
703
			}
704
		}
705
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
706
		return $userEMailAdresses;
707
	}
708
709
	/**
710
	 * Get all identities of given mailaccount
711
	 *
712
	 * @param int|Mail\Account $account account-object or acc_id
713
	 * @return array - array(email=>realname)
714
	 */
715
	function getAccountIdentities($account)
716
	{
717
		if (!$account instanceof Mail\Account)
718
		{
719
			$account = Mail\Account::read($account);
720
		}
721
		$userEMailAdresses = array();
722
		foreach(Mail\Account::identities($account, true, 'params') as $ik => $ident) {
723
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
724
			$identity = Mail\Account::read_identity($ik,true,null,$account);
725
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
726
			// standardIdentity has ident_id==acc_id (as it is done within account->identities)
727
			if (empty($identity['ident_id'])) $identity['ident_id'] = $identity['acc_id'];
728 View Code Duplication
			if (!isset($userEMailAdresses[$identity['ident_id']]))
729
			{
730
				$userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$identity['acc_id'],
731
																'ident_id'=>$identity['ident_id'],
732
																'ident_email'=>$identity['ident_email'],
733
																'ident_org'=>$identity['ident_org'],
734
																'ident_realname'=>$identity['ident_realname'],
735
																'ident_signature'=>$identity['ident_signature'],
736
																'ident_name'=>$identity['ident_name']);
737
			}
738
		}
739
740
		return $userEMailAdresses;
741
	}
742
743
	/**
744
	 * Function to gather the default identitiy connected to the current mailaccount
745
	 *
746
	 * @return int - id of the identity
747
	 */
748
	function getDefaultIdentity()
749
	{
750
		// retrieve the signature accociated with the identity
751
		$id = $this->getIdentitiesWithAccounts($_accountData=array());
0 ignored issues
show
Bug introduced by
$_accountData = array() cannot be passed to getidentitieswithaccounts() as the parameter $identities expects a reference.
Loading history...
752
		foreach(Mail\Account::identities($_accountData[$this->profileID] ?
753
			$this->profileID : $_accountData[$id],false,'ident_id') as $accountData)
754
		{
755
			return $accountData;
756
		}
757
	}
758
759
	/**
760
	 * getIdentitiesWithAccounts
761
	 *
762
	 * @param array reference to pass all identities back
763
	 * @return the default Identity (active) or 0
764
	 */
765
	function getIdentitiesWithAccounts(&$identities)
766
	{
767
		// account select box
768
		$selectedID = $this->profileID;
769
		$allAccountData = Mail\Account::search($only_current_user=true, false, null);
770
		if ($allAccountData) {
771
			$rememberFirst=$selectedFound=null;
772
			foreach ($allAccountData as $tmpkey => $icServers)
773
			{
774
				if (is_null($rememberFirst)) $rememberFirst = $tmpkey;
775
				if ($tmpkey == $selectedID) $selectedFound=true;
776
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($icServers->acc_imap_host));
777
				$host = $icServers->acc_imap_host;
778
				if (empty($host)) continue;
779
				$identities[$icServers->acc_id] = $icServers['ident_realname'].' '.$icServers['ident_org'].' <'.$icServers['ident_email'].'>';
780
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($identities[$icServers->acc_id]));
781
			}
782
		}
783
		return ($selectedFound?$selectedID:$rememberFirst);
784
	}
785
786
	/**
787
	 * construct the string representing an Identity passed by $identity
788
	 *
789
	 * @var array/object $identity, identity object that holds realname, organization, emailaddress and signatureid
790
	 * @var boolean $fullString full or false=NamePart only is returned
791
	 * @return string - constructed of identity object data as defined in mailConfig
792
	 */
793
	static function generateIdentityString($identity, $fullString=true)
794
	{
795
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($identity));
796
		//if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
797
		// not set? -> use default, means full display of all available data
798
		//if (!isset(self::$mailConfig['how2displayIdentities'])) self::$mailConfig['how2displayIdentities']='';
799
		$how2displayIdentities = '';
800
		switch ($how2displayIdentities)
801
		{
802
			case 'email';
803
				//$retData = str_replace('@',' ',$identity->emailAddress).($fullString===true?' <'.$identity->emailAddress.'>':'');
804
				$retData = $identity['ident_email'].($fullString===true?' <'.$identity['ident_email'].'>':'');
805
				break;
806 View Code Duplication
			case 'nameNemail';
807
				$retData = (!empty($identity['ident_realname'])?$identity['ident_realname']:substr_replace($identity['ident_email'],'',strpos($identity['ident_email'],'@'))).($fullString===true?' <'.$identity['ident_email'].'>':'');
808
				break;
809 View Code Duplication
			case 'orgNemail';
810
				$retData = (!empty($identity['ident_org'])?$identity['ident_org']:substr_replace($identity['ident_email'],'',0,strpos($identity['ident_email'],'@')+1)).($fullString===true?' <'.$identity['ident_email'].'>':'');
811
				break;
812
			default:
813
				$retData = $identity['ident_realname'].(!empty($identity['ident_org'])?' '.$identity['ident_org']:'').($fullString===true?' <'.$identity['ident_email'].'>':'');
814
		}
815
		return $retData;
816
	}
817
818
	/**
819
	 * closes a connection on the active Server ($this->icServer)
820
	 *
821
	 * @return void
822
	 */
823
	function closeConnection()
824
	{
825
		//if ($icServer->_connected) error_log(__METHOD__.' ('.__LINE__.') '.' disconnect from Server');
826
		//error_log(__METHOD__."() ".function_backtrace());
827
		$this->icServer->disconnect();
828
	}
829
830
	/**
831
	 * reopens a connection for the active Server ($this->icServer), and selects the folder given
832
	 *
833
	 * @param string $_foldername folder to open/select
834
	 * @return void
835
	 */
836
	function reopen($_foldername)
837
	{
838
		if (self::$debugTimes) $starttime = microtime (true);
839
840
		//error_log(__METHOD__.' ('.__LINE__.') '."('$_foldername') ".function_backtrace());
841
		// TODO: trying to reduce traffic to the IMAP Server here, introduces problems with fetching the bodies of
842
		// eMails when not in "current-Folder" (folder that is selected by UI)
843
		static $folderOpened;
844
		//if (empty($folderOpened) || $folderOpened!=$_foldername)
845
		//{
846
			//error_log( __METHOD__.' ('.__LINE__.') '." $_foldername ".function_backtrace());
847
			//error_log(__METHOD__.' ('.__LINE__.') '.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true));
848
			if ($this->folderIsSelectable($_foldername)) {
849
				$this->icServer->openMailbox($_foldername);
850
			}
851
			$folderOpened = $_foldername;
852
		//}
853
		if (self::$debugTimes) self::logRunTimes($starttime,null,'Folder:'.$_foldername,__METHOD__.' ('.__LINE__.') ');
854
	}
855
856
857
	/**
858
	 * openConnection
859
	 *
860
	 * @param int $_icServerID = 0
861
	 * @throws Horde_Imap_Client_Exception on connection error or authentication failure
862
	 * @throws InvalidArgumentException on missing credentials
863
	 */
864
	function openConnection($_icServerID=0)
865
	{
866
		//error_log( "-------------------------->open connection ".function_backtrace());
867
		//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
868
		if (self::$debugTimes) $starttime = microtime (true);
869
		$mailbox=null;
870
		try
871
		{
872
			if(isset($this->sessionData['mailbox'])&&$this->folderExists($this->sessionData['mailbox'])) $mailbox = $this->sessionData['mailbox'];
873
			if (empty($mailbox)) $mailbox = $this->icServer->getCurrentMailbox();
874
/*
875
			if (isset(Mail\Imap::$supports_keywords[$_icServerID]))
876
			{
877
				$this->icServer->openMailbox($mailbox);
878
			}
879
			else
880
			{
881
				$this->icServer->examineMailbox($mailbox);
882
			}
883
*/
884
			// the above should detect if there is a known information about supporting KEYWORDS
885
			// but does not work as expected :-(
886
			$this->icServer->examineMailbox($mailbox);
887
			//error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true));
888
			//error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID.function_backtrace());
889
890
			//make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache
891
			$this->getHierarchyDelimiter(false);
892
			self::$specialUseFolders = $this->getSpecialUseFolders();
893
		}
894
		catch (\Exception $e)
895
		{
896
			error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID." trying to examine ($mailbox) failed!".$e->getMessage());
897
			throw new Exception(__METHOD__." failed to ".__METHOD__." on Profile to $_icServerID while trying to examine $mailbox:".$e->getMessage());
898
		}
899
		if (self::$debugTimes) self::logRunTimes($starttime,null,'ProfileID:'.$_icServerID,__METHOD__.' ('.__LINE__.') ');
900
	}
901
902
	/**
903
	 * getQuotaRoot
904
	 * return the qouta of the users INBOX
905
	 *
906
	 * @return mixed array/boolean
907
	 */
908
	function getQuotaRoot()
909
	{
910
		static $quota;
911
		if (isset($quota)) return $quota;
912
		if (isset(self::$profileDefunct[$this->profileID]) && self::$profileDefunct[$this->profileID]===true)
913
		{
914
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
915
			return false;
916
		}
917
		try
918
		{
919
			$this->icServer->getCurrentMailbox();
920
			if(!$this->icServer->hasCapability('QUOTA')) {
921
				$quota = false;
922
				return false;
923
			}
924
			$quota = $this->icServer->getStorageQuotaRoot('INBOX');
925
		}
926
		catch (Exception $e)
927
		{
928
			//error_log(__METHOD__.array2string($e));
929
			//error_log(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
930
			if ($e->getCode()==102)
931
			{
932
				self::$profileDefunct[$this->profileID]=true;
933
				throw new Exception(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in EGroupware\Api\Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
934
			}
935
		}
936
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($quota));
937
		if(is_array($quota)) {
938
			$quota = array(
939
				'usage'	=> $quota['USED'],
940
				'limit'	=> $quota['QMAX'],
941
			);
942
		} else {
943
			$quota = false;
944
		}
945
		return $quota;
946
	}
947
948
	/**
949
	 * getTimeOut
950
	 *
951
	 * @param string _use decide if the use is for IMAP or SIEVE, by now only the default differs
952
	 *
953
	 * @return int - timeout (either set or default 20/10)
954
	 */
955
	static function getTimeOut($_use='IMAP')
956
	{
957
		$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
958
		if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
959
		return $timeout;
960
	}
961
962
	/**
963
	 * Fetch the namespace from icServer
964
	 *
965
	 * An IMAPServer may present several namespaces under each key:
966
	 * so we return an array of namespacearrays for our needs
967
	 *
968
	 * @return array array(prefix_present=>mixed (bool/string) ,prefix=>string,delimiter=>string,type=>string (personal|others|shared))
969
	 */
970 View Code Duplication
	function _getNameSpaces()
971
	{
972
		static $nameSpace = null;
973
		$foldersNameSpace = array();
974
		$delimiter = $this->getHierarchyDelimiter();
975
		// TODO: cache by $this->icServer->ImapServerId
976
		if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaceArray();
977
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($nameSpace));
978
		if (is_array($nameSpace)) {
979
			foreach($nameSpace as $type => $singleNameSpaceArray)
980
			{
981
				foreach ($singleNameSpaceArray as $singleNameSpace)
982
				{
983
					$_foldersNameSpace = array();
984
					if($type == 'personal' && $singleNameSpace['name'] == '#mh/' && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
985
					{
986
						$_foldersNameSpace['prefix_present'] = 'forced';
987
						// uw-imap server with mailbox prefix or dovecot maybe
988
						$_foldersNameSpace['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace['name'])?$singleNameSpace['name']:''));
989
					}
990
					elseif($type == 'personal' && ($singleNameSpace['name'] == '#mh/') && $this->folderExists('mail'))
991
					{
992
						$_foldersNameSpace['prefix_present'] = 'forced';
993
						// uw-imap server with mailbox prefix or dovecot maybe
994
						$_foldersNameSpace['prefix'] = 'mail';
995
					} else {
996
						$_foldersNameSpace['prefix_present'] = !empty($singleNameSpace['name']);
997
						$_foldersNameSpace['prefix'] = $singleNameSpace['name'];
998
					}
999
					$_foldersNameSpace['delimiter'] = ($singleNameSpace['delimiter']?$singleNameSpace['delimiter']:$delimiter);
1000
					$_foldersNameSpace['type'] = $type;
1001
					$foldersNameSpace[] =$_foldersNameSpace;
1002
				}
1003
				//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################<br>";
1004
			}
1005
		}
1006
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($foldersNameSpace));
1007
		return $foldersNameSpace;
1008
	}
1009
1010
	/**
1011
	 * Wrapper to extract the folder prefix from folder compared to given namespace array
1012
	 *
1013
	 * @param array $nameSpace
1014
	 * @paam string $_folderName
1015
	 * @return string the prefix (may be an empty string)
1016
	 */
1017 View Code Duplication
	function getFolderPrefixFromNamespace($nameSpace, $folderName)
1018
	{
1019
		foreach($nameSpace as &$singleNameSpace)
1020
		{
1021
			//if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix'];
1022
			if (substr($folderName,0,strlen($singleNameSpace['prefix']))==$singleNameSpace['prefix']) return $singleNameSpace['prefix'];
1023
		}
1024
		return "";
1025
	}
1026
1027
	/**
1028
	 * getHierarchyDelimiter
1029
	 *
1030
	 * @var boolean $_useCache
1031
	 * @return string the hierarchyDelimiter
1032
	 */
1033
	function getHierarchyDelimiter($_useCache=true)
1034
	{
1035
		static $HierarchyDelimiter = null;
1036
		if (is_null($HierarchyDelimiter)) $HierarchyDelimiter = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
1037
		if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]);
1038 View Code Duplication
		if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId]))
1039
		{
1040
			return $HierarchyDelimiter[$this->icServer->ImapServerId];
1041
		}
1042
		$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1043
		try
1044
		{
1045
			$this->icServer->getCurrentMailbox();
1046
			$HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getDelimiter();
1047
		}
1048
		catch(\Exception $e)
1049
		{
1050
			if ($e->getCode()==102) self::$profileDefunct[$this->profileID]=true;
1051
			unset($e);
1052
			$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1053
		}
1054
		Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),$HierarchyDelimiter, 60*60*24*5);
1055
		return $HierarchyDelimiter[$this->icServer->ImapServerId];
1056
	}
1057
1058
	/**
1059
	 * getSpecialUseFolders
1060
	 * @ToDo: could as well be static, when icServer is passed
1061
	 * @return mixed null/array
1062
	 */
1063
	function getSpecialUseFolders()
1064
	{
1065
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$this->icServer->ImapServerId.' Connected:'.$this->icServer->_connected);
1066
		static $_specialUseFolders = null;
1067
		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);
1068
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_trash));
1069
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
1070
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
1071
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
1072
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1073 View Code Duplication
		if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
1074
			return $_specialUseFolders[$this->icServer->ImapServerId];
1075
		$_specialUseFolders[$this->icServer->ImapServerId]=array();
1076
		//if (!empty($this->icServer->acc_folder_trash) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]))
1077
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]='Trash';
1078
		//if (!empty($this->icServer->acc_folder_draft) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]))
1079
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]='Drafts';
1080
		//if (!empty($this->icServer->acc_folder_sent) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]))
1081
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent';
1082
		//if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]))
1083
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates';
1084
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk';
1085
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_archive]='Archive';
1086
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer));
1087
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1088
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, 60*60*24*5);
1089
		return $_specialUseFolders[$this->icServer->ImapServerId];
1090
	}
1091
1092
	/**
1093
	 * get IMAP folder status regarding NoSelect
1094
	 *
1095
	 * @param foldertoselect string the foldername
1096
	 *
1097
	 * @return boolean true or false regarding the noselect attribute
1098
	 */
1099
	function folderIsSelectable($folderToSelect)
1100
	{
1101
		$retval = true;
1102
		if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) {
1103 View Code Duplication
			if (!empty($folderStatus['attributes']) && stripos(array2string($folderStatus['attributes']),'noselect')!==false)
1104
			{
1105
				$retval = false;
1106
			}
1107
		}
1108
		return $retval;
1109
	}
1110
1111
	/**
1112
	 * get IMAP folder status, wrapper to store results within a single request
1113
	 *
1114
	 * returns an array information about the imap folder
1115
	 *
1116
	 * @param folderName string the foldername
1117
	 * @param ignoreStatusCache bool ignore the cache used for counters
1118
	 *
1119
	 * @return array
1120
	 *
1121
	 * @throws Exception
1122
	 */
1123
	function _getStatus($folderName,$ignoreStatusCache=false)
1124
	{
1125
		static $folderStatus = null;
1126
		if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName]))
1127
		{
1128
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName]));
1129
			return $folderStatus[$this->icServer->ImapServerId][$folderName];
1130
		}
1131
		try
1132
		{
1133
			$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName,$ignoreStatusCache);
1134
		}
1135
		catch (\Exception $e)
1136
		{
1137
			throw new Exception(__METHOD__.' ('.__LINE__.') '." failed for $folderName with error:".$e->getMessage().($e->details?', '.$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1138
		}
1139
		return $folderStatus[$this->icServer->ImapServerId][$folderName];
1140
	}
1141
1142
	/**
1143
	 * get IMAP folder status
1144
	 *
1145
	 * returns an array information about the imap folder, may be used as  wrapper to retrieve results from cache
1146
	 *
1147
	 * @param _folderName string the foldername
1148
	 * @param ignoreStatusCache bool ignore the cache used for counters
1149
	 * @param basicInfoOnly bool retrieve only names and stuff returned by getMailboxes
1150
	 * @param fetchSubscribedInfo bool fetch Subscribed Info on folder
1151
	 * @return array
1152
	 */
1153
	function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false,$fetchSubscribedInfo=true)
1154
	{
1155
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." called with:$_folderName,$ignoreStatusCache,$basicInfoOnly");
1156
		if (!is_string($_folderName) || empty($_folderName)||(isset(self::$profileDefunct[$this->profileID]) && self::$profileDefunct[$this->profileID]===true))
1157
		{
1158
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
1159
			return false;
1160
		}
1161
		static $folderInfoCache = null; // reduce traffic on single request
1162
		static $folderBasicInfo = null;
1163
		if (isset($folderBasicInfo[$this->profileID]))
1164
		{
1165
			$folderInfoCache = $folderBasicInfo[$this->profileID];
1166
		}
1167
		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...
1168
		$retValue = array();
1169
		$retValue['subscribed'] = false;
1170
/*
1171
		if(!$icServer = Mail\Account::read($this->profileID)) {
1172
			if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName);
1173
			return false;
1174
		}
1175
*/
1176
		//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string(array_keys($folderInfoCache)));
1177
		// does the folder exist???
1178
		if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName]))
1179
		{
1180
			try
1181
			{
1182
				$ret = $this->icServer->getMailboxes($_folderName, 1, true);
1183
			}
1184
			catch (\Exception $e)
1185
			{
1186
				//error_log(__METHOD__.array2string($e));
1187
				//error_log(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1188
				self::$profileDefunct[$this->profileID]=true;
1189
				throw new Exception(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1190
			}
1191
			//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string($ret));
1192
			if (is_array($ret))
1193
			{
1194
				$retkeys = array_keys($ret);
1195
				if ($retkeys[0]==$_folderName) $folderInfoCache[$_folderName] = $ret[$retkeys[0]];
1196
			}
1197
			else
1198
			{
1199
				$folderInfoCache[$_folderName]=false;
1200
			}
1201
		}
1202
		$folderInfo = $folderInfoCache[$_folderName];
1203
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo).'->'.function_backtrace());
1204
		if($ignoreStatusCache||!$folderInfo|| !is_array($folderInfo)) {
1205
			try
1206
			{
1207
				$folderInfo = $this->_getStatus($_folderName,$ignoreStatusCache);
1208
			}
1209
			catch (\Exception $e)
1210
			{
1211
				//error_log(__METHOD__.array2string($e));
1212
				error_log(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1213
				self::$profileDefunct[$this->profileID]=true;
1214
				//throw new Exception(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1215
				$folderInfo=null;
1216
			}
1217
			if (!is_array($folderInfo))
1218
			{
1219
				// no folder info, but there is a status returned for the folder: something is wrong, try to cope with it
1220
				$folderInfo = is_array($folderInfo)?$folderInfo:array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(),
1221
					'ATTRIBUTES' => '');
1222
				if (!isset($folderInfo['HIERACHY_DELIMITER']) || empty($folderInfo['HIERACHY_DELIMITER']) || (isset($folderInfo['delimiter']) && empty($folderInfo['delimiter'])))
1223
				{
1224
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo));
1225
					$folderInfo['HIERACHY_DELIMITER'] = $this->getHierarchyDelimiter();
1226
				}
1227
			}
1228
		}
1229
		#if(!is_array($folderInfo)) {
1230
		#	return false;
1231
		#}
1232
		$retValue['delimiter']		= (isset($folderInfo['HIERACHY_DELIMITER']) && $folderInfo['HIERACHY_DELIMITER']?$folderInfo['HIERACHY_DELIMITER']:$folderInfo['delimiter']);
1233
		$retValue['attributes']		= (isset($folderInfo['ATTRIBUTES']) && $folderInfo['ATTRIBUTES']?$folderInfo['ATTRIBUTES']:$folderInfo['attributes']);
1234
		$shortNameParts			= explode($retValue['delimiter'], $_folderName);
1235
		$retValue['shortName']		= array_pop($shortNameParts);
1236
		$retValue['displayName']	= $_folderName;
1237
		$retValue['shortDisplayName']	= $retValue['shortName'];
1238
		if(strtoupper($retValue['shortName']) == 'INBOX') {
1239
			$retValue['displayName']	= lang('INBOX');
1240
			$retValue['shortDisplayName']	= lang('INBOX');
1241
		}
1242
		// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
1243
		elseif (in_array($retValue['shortName'],self::$autoFolders))
1244
		{
1245
			$retValue['displayName'] = $retValue['shortDisplayName'] = lang($retValue['shortName']);
1246
		}
1247
		if ($folderInfo) $folderBasicInfo[$this->profileID][$_folderName]=$retValue;
1248
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.$_folderName.array2string($retValue['attributes']));
1249 View Code Duplication
		if ($basicInfoOnly || (isset($retValue['attributes']) && stripos(array2string($retValue['attributes']),'noselect')!==false))
1250
		{
1251
			return $retValue;
1252
		}
1253
		// fetch all in one go for one request, instead of querying them one by one
1254
		// cache it for a minute 60*60*1
1255
		// this should reduce communication to the imap server
1256
		static $subscribedFolders = null;
1257
		static $nameSpace = null;
1258
		static $prefix = null;
1259
		if (is_null($nameSpace) || empty($nameSpace[$this->profileID])) $nameSpace[$this->profileID] = $this->_getNameSpaces();
1260
		if (!empty($nameSpace[$this->profileID]))
1261
		{
1262
			$nsNoPersonal=array();
1263
			foreach($nameSpace[$this->profileID] as &$ns)
1264
			{
1265
				if ($ns['type']!='personal') $nsNoPersonal[]=$ns;
1266
			}
1267
			$nameSpace[$this->profileID]=$nsNoPersonal;
1268
		}
1269
		if (is_null($prefix) || empty($prefix[$this->profileID]) || empty($prefix[$this->profileID][$_folderName])) $prefix[$this->profileID][$_folderName] = $this->getFolderPrefixFromNamespace($nameSpace[$this->profileID], $_folderName);
1270
1271
		if ($fetchSubscribedInfo && is_null($subscribedFolders) || empty($subscribedFolders[$this->profileID]))
1272
		{
1273
			$subscribedFolders[$this->profileID] = $this->icServer->listSubscribedMailboxes();
1274
		}
1275
1276
		if($fetchSubscribedInfo && is_array($subscribedFolders[$this->profileID]) && in_array($_folderName,$subscribedFolders[$this->profileID])) {
1277
			$retValue['subscribed'] = true;
1278
		}
1279
1280
		try
1281
		{
1282
			//$folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache);
1283
			$folderStatus = $this->getMailBoxCounters($_folderName,false);
1284
			$retValue['messages']		= $folderStatus['MESSAGES'];
1285
			$retValue['recent']		= $folderStatus['RECENT'];
1286
			$retValue['uidnext']		= $folderStatus['UIDNEXT'];
1287
			$retValue['uidvalidity']	= $folderStatus['UIDVALIDITY'];
1288
			$retValue['unseen']		= $folderStatus['UNSEEN'];
1289
			if (//$retValue['unseen']==0 &&
1290
				(isset($this->mailPreferences['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information
1291
				$this->mailPreferences['trustServersUnseenInfo']==false) ||
1292
				(isset($this->mailPreferences['trustServersUnseenInfo']) &&
1293
				$this->mailPreferences['trustServersUnseenInfo']==2 &&
1294
				$prefix[$this->profileID][$_folderName] != '' && stripos($_folderName,$prefix[$this->profileID][$_folderName]) !== false)
1295
			)
1296
			{
1297
				//error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences['trustServersUnseenInfo']);
1298
				// we filter for the combined status of unseen and undeleted, as this is what we show in list
1299
				try
1300
				{
1301
					$sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse=1, array('status'=>array('UNSEEN','UNDELETED')),$byUid=true,false);
0 ignored issues
show
Bug introduced by
$_reverse = 1 cannot be passed to getsortedlist() as the parameter $_reverse expects a reference.
Loading history...
Bug introduced by
$byUid = true cannot be passed to getsortedlist() as the parameter $resultByUid expects a reference.
Loading history...
1302
					$retValue['unseen'] = $sortResult['count'];
1303
				}
1304
				catch (\Exception $ee)
1305
				{
1306
					if (self::$debug) error_log(__METHOD__." could not fetch/calculate unseen counter for $_folderName Reason:'".$ee->getMessage()."' but requested.");
1307
				}
1308
			}
1309
		}
1310
		catch (\Exception $e)
1311
		{
1312
			if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($e->getMessage(),true));
1313
		}
1314
1315
		return $retValue;
1316
	}
1317
1318
	/**
1319
	 * getHeaders
1320
	 *
1321
	 * this function is a wrapper function for getSortedList and populates the resultList thereof with headerdata
1322
	 *
1323
	 * @param string $_folderName
1324
	 * @param int $_startMessage
1325
	 * @param int $_numberOfMessages number of messages to return
1326
	 * @param array $_sort sort by criteria
1327
	 * @param boolean $_reverse reverse sorting of the result array (may be switched, as it is passed to getSortedList by reference)
1328
	 * @param array $_filter filter to apply to getSortedList
1329
	 * @param mixed $_thisUIDOnly = null, if given fetch the headers of this uid only (either one, or array of uids)
1330
	 * @param boolean $_cacheResult = true try touse the cache of getSortedList
1331
	 * @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))
1332
	 * @return array result as array(header=>array,total=>int,first=>int,last=>int)
1333
	 */
1334
	function getHeaders($_folderName, $_startMessage, $_numberOfMessages, $_sort, $_reverse, $_filter, $_thisUIDOnly=null, $_cacheResult=true, $_fetchPreviews=false)
1335
	{
1336
		//self::$debug=true;
1337
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.function_backtrace());
1338 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName,$_startMessage, $_numberOfMessages, $_sort, $_reverse, ".array2string($_filter).", $_thisUIDOnly");
1339
		$reverse = (bool)$_reverse;
1340
		// get the list of messages to fetch
1341
		$this->reopen($_folderName);
1342
		//$currentFolder = $this->icServer->getCurrentMailbox();
1343
		//if ($currentFolder != $_folderName); $this->icServer->openMailbox($_folderName);
1344
		$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
1345
		#print "<pre>";
1346
		#$this->icServer->setDebug(true);
1347
		$total=0;
1348
		if ($_thisUIDOnly === null)
1349
		{
1350
			if (($_startMessage || $_numberOfMessages) && !isset($_filter['range']))
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1351
			{
1352
				// 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
1353
				// if sort capability is applied to the range fetched, not sort first and fetch the range afterwards
1354
				//$start = $_startMessage-1;
1355
				//$end = $_startMessage-1+$_numberOfMessages;
1356
				//$_filter['range'] ="$start:$end";
1357
				//$_filter['range'] ="$_startMessage:*";
1358
			}
1359 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_sort, $reverse, ".array2string($_filter).", $rByUid");
1360
			if (self::$debug||self::$debugTimes) $starttime = microtime (true);
1361
			//see this example below for a 12 week datefilter (since)
1362
			//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $starttime-(3600*24*7*12)));
1363
			$_sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
1364
			$sortResult = $_sortResult['match']->ids;
1365
			//$modseq = $_sortResult['modseq'];
1366
			//error_log(__METHOD__.' ('.__LINE__.') '.'Modsequence:'.$modseq);
1367
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' call getSortedList for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly),__METHOD__.' ('.__LINE__.') ');
1368
1369
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1370
			#$this->icServer->setDebug(false);
1371
			#print "</pre>";
1372
			// nothing found
1373
			if(!is_array($sortResult) || empty($sortResult)) {
1374
				$retValue = array();
1375
				$retValue['info']['total']	= 0;
1376
				$retValue['info']['first']	= 0;
1377
				$retValue['info']['last']	= 0;
1378
				return $retValue;
1379
			}
1380
1381
			$total = $_sortResult['count'];
1382
			#_debug_array($sortResult);
1383
			#_debug_array(array_slice($sortResult, -5, -2));
1384
			//error_log("REVERSE: $reverse");
1385
			if($reverse === true) {
1386
				if  ($_startMessage<=$total)
1387
				{
1388
					$startMessage = $_startMessage-1;
1389
				}
1390
				else
1391
				{
1392
					//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1393
					if ($_startMessage+$_numberOfMessages>$total)
1394
					{
1395
						$numberOfMessages = $total%$_numberOfMessages;
1396
						//$numberOfMessages = abs($_startMessage-$total-1);
1397
						if ($numberOfMessages>0 && $numberOfMessages<=$_numberOfMessages) $_numberOfMessages = $numberOfMessages;
1398
						//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1399
					}
1400
					$startMessage=($total-$_numberOfMessages)-1;
1401
					//$retValue['info']['first'] = $startMessage;
1402
					//$retValue['info']['last'] = $total;
1403
1404
				}
1405
				if ($startMessage+$_numberOfMessages>$total)
1406
				{
1407
					$_numberOfMessages = $_numberOfMessages-($total-($startMessage+$_numberOfMessages));
1408
					//$retValue['info']['first'] = $startMessage;
1409
					//$retValue['info']['last'] = $total;
1410
				}
1411
				if($startMessage > 0) {
1412 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+$startMessage)).', '.-$startMessage.' Number of Messages:'.count($sortResult));
1413
					$sortResult = array_slice($sortResult, -($_numberOfMessages+$startMessage), -$startMessage);
1414
				} else {
1415 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+($_startMessage-1))).', AllTheRest, Number of Messages:'.count($sortResult));
1416
					$sortResult = array_slice($sortResult, -($_numberOfMessages+($_startMessage-1)));
1417
				}
1418
				$sortResult = array_reverse($sortResult);
1419
			} else {
1420 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.($_startMessage-1).', '.$_numberOfMessages.' Number of Messages:'.count($sortResult));
1421
				$sortResult = array_slice($sortResult, $_startMessage-1, $_numberOfMessages);
1422
			}
1423
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1424
		}
1425
		else
1426
		{
1427
			$sortResult = (is_array($_thisUIDOnly) ? $_thisUIDOnly:(array)$_thisUIDOnly);
1428
		}
1429
1430
1431
		// fetch the data for the selected messages
1432
		if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1433
		try
1434
		{
1435
			$uidsToFetch = new Horde_Imap_Client_Ids();
1436
			$uidsToFetch->add($sortResult);
1437
1438
			$fquery = new Horde_Imap_Client_Fetch_Query();
1439
1440
			// Pre-cache the headers we want, 'fetchHeaders' is a label into the cache
1441
			$fquery->headers('fetchHeaders',array(
1442
				'DISPOSITION-NOTIFICATION-TO','RETURN-RECEIPT-TO','X-CONFIRM-READING-TO',
1443
				'DATE','SUBJECT','FROM','TO','CC',
1444
				'X-PRIORITY'
1445
			),array(
1446
				// Cache headers, we'll look at them below
1447
				'cache' => true,//$_cacheResult,
1448
				// Set peek so messages are not flagged as read
1449
				'peek' => true
1450
			));
1451
			$fquery->size();
1452
			$fquery->structure();
1453
			$fquery->flags();
1454
			$fquery->imapDate();// needed to ensure getImapDate fetches the internaldate, not the current time
1455
			// if $_fetchPreviews is activated fetch part of the messages too
1456
			if ($_fetchPreviews) $fquery->fullText(array('peek'=>true,'length'=>((int)$_fetchPreviews<5000?5000:$_fetchPreviews),'start'=>0));
1457
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
1458
				'ids' => $uidsToFetch,
1459
			));
1460
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headersNew->ids()));
1461
		}
1462
		catch (\Exception $e)
1463
		{
1464
			$headersNew = array();
1465
			$sortResult = array();
1466
		}
1467
		if (self::$debug||self::$debugTimes)
1468
		{
1469
			self::logRunTimes($starttime,null,'HordeFetch: for Folder:'.$_folderName.' Filter:'.array2string($_filter),__METHOD__.' ('.__LINE__.') ');
1470
			if (self::$debug)
1471
			{
1472
				$queryString = implode(',', $sortResult);
1473
				error_log(__METHOD__.' ('.__LINE__.') '.' Query:'.$queryString.' Result:'.array2string($headersNew));
1474
			}
1475
		}
1476
1477
		$cnt = 0;
1478
1479
		foreach((array)$sortResult as $uid) {
1480
			$sortOrder[$uid] = $cnt++;
1481
		}
1482
1483
		$count = 0;
1484
		if (is_object($headersNew)) {
1485
			if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1486
			foreach($headersNew->ids() as $id) {
1487
				$_headerObject = $headersNew->get($id);
1488
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject));
1489
				$headerObject = array();
1490
				$bodyPreview = null;
1491
				$uid = $headerObject['UID']= ($_headerObject->getUid()?$_headerObject->getUid():$id);
1492
				$headerObject['MSG_NUM'] = $_headerObject->getSeq();
1493
				$headerObject['SIZE'] = $_headerObject->getSize();
1494
				$headerObject['INTERNALDATE'] = $_headerObject->getImapDate();
1495
1496
				// Get already cached headers, 'fetchHeaders' is a label matchimg above
1497
				$headerForPrio = $_headerObject->getHeaders('fetchHeaders',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1498
				// Try to fetch header with key='' as some servers might have no fetchHeaders index. e.g. yandex.com
1499
				if (empty($headerForPrio)) $headerForPrio = $_headerObject->getHeaders('',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1500
				//fetch the fullMsg part if all conditions match to be available in case $_headerObject->getHeaders returns
1501
				//nothing worthwhile (as it does for googlemail accounts, when preview is switched on
1502
				if ($_fetchPreviews)
1503
				{
1504
					// on enabled preview $bodyPreview is needed lateron. fetched here, for fallback-reasons
1505
					// in case of failed Header-Retrieval
1506
					$bodyPreview = $_headerObject->getFullMsg();
1507
					if (empty($headerForPrio)||(is_array($headerForPrio)&&count($headerForPrio)===1&&$headerForPrio['']))
1508
					{
1509
						$length = strpos($bodyPreview, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
1510
						if ($length===false) $length = strlen($bodyPreview);
1511
						$headerForPrio =  Horde_Mime_Headers::parseHeaders(substr($bodyPreview, 0,$length))->toArray();
1512
					}
1513
				}
1514
				$headerForPrio = array_change_key_case($headerForPrio, CASE_UPPER);
1515
				if (self::$debug) {
1516
					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'));
1517
					error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerForPrio));
1518
				}
1519
				// message deleted from server but cache still reporting its existence ; may happen on QRESYNC with No permanent modsequences
1520
				if (empty($headerForPrio))
1521
				{
1522
					$total--;
1523
					continue;
1524
				}
1525
				if ( isset($headerForPrio['DISPOSITION-NOTIFICATION-TO']) ) {
1526
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['DISPOSITION-NOTIFICATION-TO']));
1527
				} else if ( isset($headerForPrio['RETURN-RECEIPT-TO']) ) {
1528
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['RETURN-RECEIPT-TO']));
1529
				} else if ( isset($headerForPrio['X-CONFIRM-READING-TO']) ) {
1530
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['X-CONFIRM-READING-TO']));
1531
				} /*else $sent_not = "";*/
1532
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1533
				$headerObject['DATE'] = $headerForPrio['DATE'];
1534
				$headerObject['SUBJECT'] = (is_array($headerForPrio['SUBJECT'])?$headerForPrio['SUBJECT'][0]:$headerForPrio['SUBJECT']);
1535
				$headerObject['FROM'] = (array)($headerForPrio['FROM']?$headerForPrio['FROM']:($headerForPrio['REPLY-TO']?$headerForPrio['REPLY-TO']:$headerForPrio['RETURN-PATH']));
1536
				$headerObject['TO'] = (array)$headerForPrio['TO'];
1537
				$headerObject['CC'] = isset($headerForPrio['CC'])?(array)$headerForPrio['CC']:array();
1538
				$headerObject['PRIORITY'] = isset($headerForPrio['X-PRIORITY'])?$headerForPrio['X-PRIORITY']:null;
1539
				foreach (array('FROM','TO','CC') as $key)
1540
				{
1541
					$address = array();
1542
					foreach ($headerObject[$key] as $k => $ad)
1543
					{
1544
						//the commented section below IS a simplified version of the section "make sure ..."
1545
						/*
1546
						if (stripos($ad,'@')===false)
1547
						{
1548
							$remember=$k;
1549
						}
1550
						else
1551
						{
1552
							$address[] = (!is_null($remember)?$headerObject[$key][$remember].' ':'').$ad;
1553
							$remember=null;
1554
						}
1555
						*/
1556
						// make sure addresses are real emailaddresses one by one in the array as expected
1557
						$rfcAddr = self::parseAddressList($ad); // does some fixing of known problems too
1558
						foreach ($rfcAddr as $_rfcAddr)
1559
						{
1560
							if (!$_rfcAddr->valid)	continue; // skip. not a valid address
1561
							$address[] = imap_rfc822_write_address($_rfcAddr->mailbox,$_rfcAddr->host,$_rfcAddr->personal);
1562
						}
1563
					}
1564
					$headerObject[$key] = $address;
1565
				}
1566
				$headerObject['FLAGS'] = $_headerObject->getFlags();
1567
				$headerObject['BODYPREVIEW']=null;
1568
				// this section fetches part of the message-body (if enabled) for some kind of preview
1569
				// if we fail to succeed, we fall back to the retrieval of the message-body with
1570
				// fetchPartContents (see below, when we iterate over the structure to determine the
1571
				// existance (and the details) for attachments)
1572
				if ($_fetchPreviews)
1573
				{
1574
					// $bodyPreview is populated at the beginning of the loop, as it may be
1575
					// needed to parse the Headers of the Message
1576
					if (empty($bodyPreview)) $bodyPreview = $_headerObject->getFullMsg();
1577
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($bodyPreview));
1578
					$base = Horde_Mime_Part::parseMessage($bodyPreview);
1579
					foreach($base->partIterator() as $part)
1580
					{
1581
						//error_log(__METHOD__.__LINE__.'Part:'.$part->getPrimaryType());
1582
						if (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'text')
1583
						{
1584
							$charset = $part->getContentTypeParameter('charset');
1585
							$buffer = Mail\Html::convertHTMLToText($part->toString(array(
1586
												'encode' => Horde_Mime_Part::ENCODE_BINARY,	// otherwise we cant recode charset
1587
											)), $charset, 'utf-8');
1588
							$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Translation::convert_jsonsafe($buffer),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));
0 ignored issues
show
Bug introduced by
\EGroupware\Api\Translat...nvert_jsonsafe($buffer) cannot be passed to mb_substr() as the parameter $data expects a reference.
Loading history...
Bug introduced by
It seems like \EGroupware\Api\Translat...nvert_jsonsafe($buffer) targeting EGroupware\Api\Translation::convert_jsonsafe() can also be of type array; however, mb_substr() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1589
						} elseif (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'multipart')
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
1590
						{
1591
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part));
1592
						}
1593
					}
1594
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject['BODYPREVIEW']));
1595
				}
1596
				$mailStructureObject = $_headerObject->getStructure();
1597
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1598
				//error_log(__METHOD__.' ('.__LINE__.') '.' MimeMap:'.array2string($mailStructureObject->contentTypeMap()));
1599
				//foreach ($_headerObject->getStructure()->getParts() as $p => $part)
1600
				$headerObject['ATTACHMENTS']=null;
1601
				$skipParts=array();
1602
				$messageMimeType='';
1603
				foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
1604
				{
1605
					if ($mime_id==0 || $messageMimeType==='') $messageMimeType = $mime_type;
1606
					$part = $mailStructureObject->getPart($mime_id);
1607
					$partdisposition = $part->getDisposition();
1608
					$partPrimaryType = $part->getPrimaryType();
1609
					// this section fetches the body for the purpose of previewing a few lines
1610
					// drawback here it is talking to the mailserver for each mail thus consuming
1611
					// more time than expected; so we call this section only when there is no
1612
					// bodypreview could be found (multipart/....)
1613
					if ($_fetchPreviews && empty($headerObject['BODYPREVIEW'])&&($partPrimaryType == 'text') &&
1614
						((intval($mime_id) === 1) || !$mime_id) &&
1615
						($partdisposition !== 'attachment')) {
1616
							$_structure=$part;
1617
							$this->fetchPartContents($uid, $_structure, false,true);
1618
							$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
\EGroupware\Api\Mail\Htm...ructure->getContents()) cannot be passed to mb_substr() as the parameter $data expects a reference.
Loading history...
1619
							$charSet=Translation::detect_encoding($headerObject['BODYPREVIEW']);
1620
							// add line breaks to $bodyParts
1621
							//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
1622
							$headerObject['BODYPREVIEW']  = Translation::convert_jsonsafe($headerObject['BODYPREVIEW'], $charSet);
1623
							//error_log(__METHOD__.__LINE__.$headerObject['BODYPREVIEW']);
1624
					}
1625
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType);
1626
					$cid = $part->getContentId();
1627
					if (empty($partdisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
1628
					{
1629
						// the presence of an cid does not necessarily indicate its inline. it may lack the needed
1630
						// link to show the image. Considering this: we "list" everything that matches the above criteria
1631
						// as attachment in order to not loose/miss information on our data
1632
						$partdisposition='attachment';//($partPrimaryType == 'image'&&!empty($cid)?'inline':'attachment');
1633
					}
1634 View Code Duplication
					if ($mime_type=='message/rfc822')
1635
					{
1636
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
1637
						foreach($part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
1638
					}
1639
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType.' Skip:'.array2string($skipParts));
1640
					if (array_key_exists($mime_id,$skipParts)) continue;
1641
					if ($partdisposition=='attachment' ||
1642
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&$mime_type=='image/tiff') || // as we are not able to display tiffs
1643
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&empty($cid)) ||
1644
						($partdisposition=='inline' && $partPrimaryType != 'image' && $partPrimaryType != 'multipart' && $partPrimaryType != 'text'))
1645
					{
1646
						$headerObject['ATTACHMENTS'][$mime_id]=$part->getAllDispositionParameters();
1647
						$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=$mime_type;
1648
						$headerObject['ATTACHMENTS'][$mime_id]['uid']=$uid;
1649
						$headerObject['ATTACHMENTS'][$mime_id]['cid'] = $cid;
1650
						$headerObject['ATTACHMENTS'][$mime_id]['partID']=$mime_id;
1651
						if (!isset($headerObject['ATTACHMENTS'][$mime_id]['name']))$headerObject['ATTACHMENTS'][$mime_id]['name']=$part->getName();
1652
						if (!strcasecmp($headerObject['ATTACHMENTS'][$mime_id]['name'],'winmail.dat') ||
1653
							$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=='application/ms-tnef')
1654
						{
1655
							$headerObject['ATTACHMENTS'][$mime_id]['is_winmail'] = true;
1656
						}
1657
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getName()));
1658
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getAllDispositionParameters()));
1659
						//error_log(__METHOD__.' ('.__LINE__.') '.' Attachment:'.$mime_id.'->'.array2string($headerObject['ATTACHMENTS'][$mime_id]));
1660
					}
1661
				}
1662
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (plain):'.array2string($mailStructureObject->findBody('plain')));
1663
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (html):'.array2string($mailStructureObject->findBody('html')));
1664
				//if($count == 0) error_log(__METHOD__.array2string($headerObject));
1665
				if (empty($headerObject['UID'])) continue;
1666
				//$uid = ($rByUid ? $headerObject['UID'] : $headerObject['MSG_NUM']);
1667
				// make dates like "Mon, 23 Apr 2007 10:11:06 UT" working with strtotime
1668
				if(substr($headerObject['DATE'],-2) === 'UT') {
1669
					$headerObject['DATE'] .= 'C';
1670
				}
1671
				if(substr($headerObject['INTERNALDATE'],-2) === 'UT') {
1672
					$headerObject['INTERNALDATE'] .= 'C';
1673
				}
1674
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$headerObject['SUBJECT'].'->'.$headerObject['DATE'].'<->'.$headerObject['INTERNALDATE'] .'#');
1675
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']);
1676
				if (isset($headerObject['ATTACHMENTS']) && count($headerObject['ATTACHMENTS'])) foreach ($headerObject['ATTACHMENTS'] as &$a) { $retValue['header'][$sortOrder[$uid]]['attachments'][]=$a;}
1677
				$retValue['header'][$sortOrder[$uid]]['subject']	= $this->decode_subject($headerObject['SUBJECT']);
1678
				$retValue['header'][$sortOrder[$uid]]['size'] 		= $headerObject['SIZE'];
1679
				$retValue['header'][$sortOrder[$uid]]['date']		= self::_strtotime(($headerObject['DATE']&&!($headerObject['DATE']=='NIL')?$headerObject['DATE']:$headerObject['INTERNALDATE']),'ts',true);
1680
				$retValue['header'][$sortOrder[$uid]]['internaldate']= self::_strtotime($headerObject['INTERNALDATE'],'ts',true);
1681
				$retValue['header'][$sortOrder[$uid]]['mimetype']	= $messageMimeType;
1682
				$retValue['header'][$sortOrder[$uid]]['id']		= $headerObject['MSG_NUM'];
1683
				$retValue['header'][$sortOrder[$uid]]['uid']		= $headerObject['UID'];
1684
				$retValue['header'][$sortOrder[$uid]]['bodypreview']		= $headerObject['BODYPREVIEW'];
1685
				$retValue['header'][$sortOrder[$uid]]['priority']		= ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3);
1686
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.array2string($retValue['header'][$sortOrder[$uid]]));
1687
				if (isset($headerObject['DISPOSITION-NOTIFICATION-TO'])) $retValue['header'][$sortOrder[$uid]]['disposition-notification-to'] = $headerObject['DISPOSITION-NOTIFICATION-TO'];
1688
				if (is_array($headerObject['FLAGS'])) {
1689
					$retValue['header'][$sortOrder[$uid]] = array_merge($retValue['header'][$sortOrder[$uid]],self::prepareFlagsArray($headerObject));
1690
				}
1691
				//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].'->'.array2string($_headerObject->getEnvelope()->__get('from')));
1692
				if(is_array($headerObject['FROM']) && $headerObject['FROM'][0]) {
1693
					$retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0],true);
1694
				}
1695
				if(is_array($headerObject['TO']) && $headerObject['TO'][0]) {
1696
					$retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0],true);
1697
					if (count($headerObject['TO'])>1)
1698
					{
1699
						$ki=0;
1700 View Code Duplication
						foreach($headerObject['TO'] as $k => $add)
1701
						{
1702
							if ($k==0) continue;
1703
							//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1704
							$retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki] = self::decode_header($add,true);
1705
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1706
							$ki++;
1707
						}
1708
					}
1709
				}
1710
				if(is_array($headerObject['CC']) && count($headerObject['CC'])>0) {
1711
					$ki=0;
1712 View Code Duplication
					foreach($headerObject['CC'] as $k => $add)
1713
					{
1714
						//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1715
						$retValue['header'][$sortOrder[$uid]]['cc_addresses'][$ki] = self::decode_header($add,true);
1716
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1717
						$ki++;
1718
					}
1719
				}
1720
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]));
1721
1722
				$count++;
1723
			}
1724
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' fetching Headers and stuff for Folder:'.$_folderName,__METHOD__.' ('.__LINE__.') ');
1725
			//self::$debug=false;
1726
			// sort the messages to the requested displayorder
1727
			if(is_array($retValue['header'])) {
1728
				$countMessages = $total;
1729
				if (isset($_filter['range'])) $countMessages = self::$folderStatusCache[$this->profileID][$_folderName]['messages'];
1730
				ksort($retValue['header']);
1731
				$retValue['info']['total']	= $total;
1732
				//if ($_startMessage>$total) $_startMessage = $total-($count-1);
1733
				$retValue['info']['first']	= $_startMessage;
1734
				$retValue['info']['last']	= $_startMessage + $count - 1 ;
1735
				return $retValue;
1736
			} else {
1737
				$retValue = array();
1738
				$retValue['info']['total']	= 0;
1739
				$retValue['info']['first']	= 0;
1740
				$retValue['info']['last']	= 0;
1741
				return $retValue;
1742
			}
1743
		} else {
1744
			if ($headersNew == null && empty($_thisUIDOnly)) error_log(__METHOD__." -> retrieval of Message Details to Query $queryString failed: ".print_r($headersNew,TRUE));
1745
			$retValue = array();
1746
			$retValue['info']['total']  = 0;
1747
			$retValue['info']['first']  = 0;
1748
			$retValue['info']['last']   = 0;
1749
			return $retValue;
1750
		}
1751
	}
1752
1753
	/**
1754
	 * static function prepareFlagsArray
1755
	 * prepare headerObject to return some standardized array to tell which flags are set for a message
1756
	 * @param array $headerObject  - array to process, a full return array from icServer->getSummary
1757
	 * @return array array of flags
1758
	 */
1759
	static function prepareFlagsArray($headerObject)
1760
	{
1761
		if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']);
1762
		$retValue = array();
1763
		$retValue['recent']		= in_array('\\recent', $headerFlags);
1764
		$retValue['flagged']	= in_array('\\flagged', $headerFlags);
1765
		$retValue['answered']	= in_array('\\answered', $headerFlags);
1766
		$retValue['forwarded']   = in_array('$forwarded', $headerFlags);
1767
		$retValue['deleted']	= in_array('\\deleted', $headerFlags);
1768
		$retValue['seen']		= in_array('\\seen', $headerFlags);
1769
		$retValue['draft']		= in_array('\\draft', $headerFlags);
1770
		$retValue['mdnsent']	= in_array('$mdnsent', $headerFlags)||in_array('mdnsent', $headerFlags);
1771
		$retValue['mdnnotsent']	= in_array('$mdnnotsent', $headerFlags)||in_array('mdnnotsent', $headerFlags);
1772
		$retValue['label1']   = in_array('$label1', $headerFlags);
1773
		$retValue['label2']   = in_array('$label2', $headerFlags);
1774
		$retValue['label3']   = in_array('$label3', $headerFlags);
1775
		$retValue['label4']   = in_array('$label4', $headerFlags);
1776
		$retValue['label5']   = in_array('$label5', $headerFlags);
1777
		//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].':'.array2string($retValue));
1778
		return $retValue;
1779
	}
1780
1781
	/**
1782
	 * fetches a sorted list of messages from the imap server
1783
	 * private function
1784
	 *
1785
	 * @todo implement sort based on Net_IMAP
1786
	 * @param string $_folderName the name of the folder in which the messages get searched
1787
	 * @param integer $_sort the primary sort key
1788
	 * @param bool $_reverse sort the messages ascending or descending
1789
	 * @param array $_filter the search filter
1790
	 * @param bool $resultByUid if set to true, the result is to be returned by uid, if the server does not reply
1791
	 * 			on a query for uids, the result may be returned by IDs only, this will be indicated by this param
1792
	 * @param bool $setSession if set to true the session will be populated with the result of the query
1793
	 * @return mixed bool/array false or array of ids
1794
	 */
1795
	function getSortedList($_folderName, $_sort, &$_reverse, $_filter, &$resultByUid=true, $setSession=true)
1796
	{
1797
		static $cachedFolderStatus = null;
1798
		// in the past we needed examineMailbox to figure out if the server with the serverID support keywords
1799
		// this information is filled/provided by examineMailbox; but caching within one request seems o.k.
1800
		if (is_null($cachedFolderStatus) || !isset($cachedFolderStatus[$this->profileID][$_folderName]) )
1801
		{
1802
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName] = $this->icServer->examineMailbox($_folderName);
1803
		}
1804
		else
1805
		{
1806
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName];
1807
		}
1808
		//error_log(__METHOD__.' ('.__LINE__.') '.' F:'.$_folderName.' S:'.array2string($folderStatus));
1809
		//error_log(__METHOD__.' ('.__LINE__.') '.' Filter:'.array2string($_filter));
1810
		$try2useCache = true;
1811
		static $eMailListContainsDeletedMessages = null;
1812
		if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
1813
		// this indicates, that there is no Filter set, and the returned set/subset should not contain DELETED Messages, nor filtered for UNDELETED
1814
		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...
1815
		{
1816
			if (self::$debugTimes) $starttime = microtime(true);
1817
			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);
1818
			$deletedMessages = $this->getSortedList($_folderName, 0, $three=1, array('status'=>array('DELETED')),$five=true,false);
0 ignored issues
show
Bug introduced by
$three = 1 cannot be passed to getsortedlist() as the parameter $_reverse expects a reference.
Loading history...
Bug introduced by
$five = true cannot be passed to getsortedlist() as the parameter $resultByUid expects a reference.
Loading history...
1819
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') Found DeletedMessages:'.array2string($eMailListContainsDeletedMessages));
1820
			$eMailListContainsDeletedMessages[$this->profileID][$_folderName] =$deletedMessages['count'];
1821
			Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, 60*60*1);
1822
			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']);
1823
		}
1824
		$try2useCache = false;
1825
		//self::$supportsORinQuery[$this->profileID]=true;
1826
		if (is_null(self::$supportsORinQuery) || !isset(self::$supportsORinQuery[$this->profileID]))
1827
		{
1828
			self::$supportsORinQuery = Cache::getCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
1829
			if (!isset(self::$supportsORinQuery[$this->profileID])) self::$supportsORinQuery[$this->profileID]=true;
1830
		}
1831
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_filter).' SupportsOrInQuery:'.self::$supportsORinQuery[$this->profileID]);
1832
		$filter = $this->createIMAPFilter($_folderName, $_filter,self::$supportsORinQuery[$this->profileID]);
1833
		if (self::$debug)
1834
		{
1835
			$query_str = $filter->build();
1836
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query']);
1837
		}
1838
		//_debug_array($filter);
1839
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($filter).'#'.array2string($this->icServer->capability()));
1840
		if($this->icServer->hasCapability('SORT')) {
1841
			// when using an orQuery and we sort by date. sort seems to fail on certain servers => ZIMBRA with Horde_Imap_Client
1842
			// thus we translate the search request from date to Horde_Imap_Client::SORT_SEQUENCE (which should be the same, if
1843
			// there is no messing with the dates)
1844
			//if (self::$supportsORinQuery[$this->profileID]&&$_sort=='date'&&$_filter['type']=='quick'&&!empty($_filter['string']))$_sort='INTERNALDATE';
1845
			if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: ".array2string($_sort)." Reverse: $_reverse");
1846
			$sortOrder = $this->_getSortString($_sort, $_reverse);
1847
			if ($_reverse && in_array(Horde_Imap_Client::SORT_REVERSE,$sortOrder)) $_reverse=false; // as we reversed the result already
1848
			if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy:".array2string($_sort)."->".array2string($sortOrder)." Filter: ".array2string($filter));
1849
			try
1850
			{
1851
				$sortResult = $this->icServer->search($_folderName, $filter, array(
1852
					'sort' => $sortOrder,));
1853
			// if there is an Error, we assume that the server is not capable of sorting
1854
			}
1855
			catch(\Exception $e)
1856
			{
1857
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1858
				$resultByUid = false;
1859
				$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1860
				if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1861
				try
1862
				{
1863
					$sortResult = $this->icServer->search($_folderName, $filter, array(
1864
						'sort' => $sortOrder));
1865
				}
1866
				catch(\Exception $e)
1867
				{
1868
					error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1869
					$sortResult = self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'];
1870
				}
1871
			}
1872
			if (self::$debug) error_log(__METHOD__.print_r($sortResult,true));
1873
		} else {
1874
			if (self::$debug) error_log(__METHOD__." Mailserver has NO SORT Capability");
1875
			//$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1876
			//if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1877
			try
1878
			{
1879
				$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1880
					'sort' => $sortOrder)*/);
1881
			}
1882
			catch(\Exception $e)
1883
			{
1884
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1885
				// possible error OR Query. But Horde gives no detailed Info :-(
1886
				self::$supportsORinQuery[$this->profileID]=false;
1887
				Cache::setCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),self::$supportsORinQuery,60*60*10);
1888
				if (self::$debug) error_log(__METHOD__.__LINE__." Mailserver seems to have NO OR Capability for Search:".$sortResult->message);
1889
				$filter = $this->createIMAPFilter($_folderName, $_filter, self::$supportsORinQuery[$this->profileID]);
1890
				try
1891
				{
1892
					$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1893
						'sort' => $sortOrder)*/);
1894
				}
1895
				catch(\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1896
				{
1897
				}
1898
			}
1899
			if(is_array($sortResult['match'])) {
1900
					// not sure that this is going so succeed as $sortResult['match'] is a hordeObject
1901
					sort($sortResult['match'], SORT_NUMERIC);
1902
			}
1903
			if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1904
		}
1905
		if ($setSession)
1906
		{
1907
			self::$folderStatusCache[$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY'];
1908
			self::$folderStatusCache[$this->profileID][$_folderName]['messages']	= $folderStatus['MESSAGES'];
1909
			self::$folderStatusCache[$this->profileID][$_folderName]['deleted']	= $eMailListContainsDeletedMessages[$this->profileID][$_folderName];
1910
			self::$folderStatusCache[$this->profileID][$_folderName]['uidnext']	= $folderStatus['UIDNEXT'];
1911
			self::$folderStatusCache[$this->profileID][$_folderName]['filter']	= $_filter;
1912
			self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'] = $sortResult;
1913
			self::$folderStatusCache[$this->profileID][$_folderName]['sort']	= $_sort;
1914
		}
1915
		//error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1916
		//_debug_array($sortResult['match']->ids);
1917
		return $sortResult;
1918
	}
1919
1920
	/**
1921
	 * convert the sort value from the gui(integer) into a string
1922
	 *
1923
	 * @param mixed _sort the integer sort order / or a valid and handeled SORTSTRING (right now: UID/ARRIVAL/INTERNALDATE (->ARRIVAL))
1924
	 * @param bool _reverse wether to add REVERSE to the Sort String or not
1925
	 * @return the sort sequence for horde search
1926
	 */
1927
	function _getSortString($_sort, $_reverse=false)
1928
	{
1929
		$_reverse=false;
1930
		if (is_numeric($_sort))
1931
		{
1932
			switch($_sort) {
1933
				case 2:
1934
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1935
					break;
1936
				case 4:
1937
					$retValue = array(Horde_Imap_Client::SORT_TO);
1938
					break;
1939
				case 3:
1940
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
1941
					break;
1942
				case 6:
1943
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
1944
					break;
1945
				case 0:
1946
				default:
1947
					$retValue = array(Horde_Imap_Client::SORT_DATE);
1948
					//$retValue = 'ARRIVAL';
1949
					break;
1950
			}
1951
		}
1952
		else
1953
		{
1954
			switch(strtoupper($_sort)) {
1955
				case 'FROMADDRESS':
1956
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1957
					break;
1958
				case 'TOADDRESS':
1959
					$retValue = array(Horde_Imap_Client::SORT_TO);
1960
					break;
1961
				case 'SUBJECT':
1962
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
1963
					break;
1964
				case 'SIZE':
1965
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
1966
					break;
1967
				case 'ARRIVAL':
1968
					$retValue = array(Horde_Imap_Client::SORT_ARRIVAL);
1969
					break;
1970
				case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date
1971
				case 'INTERNALDATE':
1972
					$retValue = array(Horde_Imap_Client::SORT_SEQUENCE);
1973
					break;
1974
				case 'DATE':
1975
				default:
1976
					$retValue = array(Horde_Imap_Client::SORT_DATE);
1977
					break;
1978
			}
1979
		}
1980
		if ($_reverse) array_unshift($retValue,Horde_Imap_Client::SORT_REVERSE);
1981
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue);
1982
		return $retValue;
1983
	}
1984
1985
	/**
1986
	 * this function creates an IMAP filter from the criterias given
1987
	 *
1988
	 * @param string $_folder used to determine the search to TO or FROM on QUICK Search wether it is a send-folder or not
1989
	 * @param array $_criterias contains the search/filter criteria
1990
	 * @param boolean $_supportsOrInQuery wether to use the OR Query on QuickSearch
1991
	 * @return Horde_Imap_Client_Search_Query the IMAP filter
1992
	 */
1993
	function createIMAPFilter($_folder, $_criterias, $_supportsOrInQuery=true)
1994
	{
1995
		$imapFilter = new Horde_Imap_Client_Search_Query();
1996
		$imapFilter->charset('UTF-8');
1997
1998
		//_debug_array($_criterias);
1999 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2000
		if((!is_array($_criterias) || $_criterias['status']=='any') &&
2001
			(!isset($_criterias['string']) || empty($_criterias['string'])) &&
2002
			(!isset($_criterias['range'])|| empty($_criterias['range']) ||
2003
			( !empty($_criterias['range'])&& ($_criterias['range']!='BETWEEN' && empty($_criterias['date'])||
2004
			($_criterias['range']=='BETWEEN' && empty($_criterias['since'])&& empty($_criterias['before']))))))
2005
		{
2006
			//error_log(__METHOD__.' ('.__LINE__.') returning early Criterias:'.print_r($_criterias, true));
2007
			$imapFilter->flag('DELETED', $set=false);
2008
			return $imapFilter;
2009
		}
2010
		$queryValid = false;
2011
		// statusQuery MUST be placed first, as search for subject/mailbody and such is
2012
		// depending on charset. flagSearch is not BUT messes the charset if called afterwards
2013
		$statusQueryValid = false;
2014
		foreach((array)$_criterias['status'] as $k => $criteria) {
2015
			$imapStatusFilter = new Horde_Imap_Client_Search_Query();
2016
			$imapStatusFilter->charset('UTF-8');
2017
			$criteria = strtoupper($criteria);
2018
			switch ($criteria) {
2019
				case 'ANSWERED':
2020
				case 'DELETED':
2021
				case 'FLAGGED':
2022
				case 'RECENT':
2023
				case 'SEEN':
2024
					$imapStatusFilter->flag($criteria, $set=true);
2025
					$queryValid = $statusQueryValid =true;
2026
					break;
2027 View Code Duplication
				case 'READ':
2028
					$imapStatusFilter->flag('SEEN', $set=true);
2029
					$queryValid = $statusQueryValid =true;
2030
					break;
2031
				case 'LABEL1':
2032
				case 'KEYWORD1':
2033
				case 'LABEL2':
2034
				case 'KEYWORD2':
2035
				case 'LABEL3':
2036
				case 'KEYWORD3':
2037
				case 'LABEL4':
2038
				case 'KEYWORD4':
2039
				case 'LABEL5':
2040
				case 'KEYWORD5':
2041
					$imapStatusFilter->flag(str_ireplace('KEYWORD','$LABEL',$criteria), $set=true);
2042
					$queryValid = $statusQueryValid =true;
2043
					break;
2044
				case 'NEW':
2045
					$imapStatusFilter->flag('RECENT', $set=true);
2046
					$imapStatusFilter->flag('SEEN', $set=false);
2047
					$queryValid = $statusQueryValid =true;
2048
					break;
2049
				case 'OLD':
2050
					$imapStatusFilter->flag('RECENT', $set=false);
2051
					$queryValid = $statusQueryValid =true;
2052
					break;
2053
// operate only on system flags
2054
//        $systemflags = array(
2055
//            'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
2056
//        );
2057
				case 'UNANSWERED':
2058
					$imapStatusFilter->flag('ANSWERED', $set=false);
2059
					$queryValid = $statusQueryValid =true;
2060
					break;
2061
				case 'UNDELETED':
2062
					$imapFilter->flag('DELETED', $set=false);
2063
					$queryValid = true;
2064
					break;
2065
				case 'UNFLAGGED':
2066
					$imapStatusFilter->flag('FLAGGED', $set=false);
2067
					$queryValid = $statusQueryValid =true;
2068
					break;
2069
				case 'UNREAD':
2070 View Code Duplication
				case 'UNSEEN':
2071
					$imapStatusFilter->flag('SEEN', $set=false);
2072
					$queryValid = $statusQueryValid =true;
2073
					break;
2074
				case 'UNLABEL1':
2075
				case 'UNKEYWORD1':
2076
				case 'UNLABEL2':
2077
				case 'UNKEYWORD2':
2078
				case 'UNLABEL3':
2079
				case 'UNKEYWORD3':
2080
				case 'UNLABEL4':
2081
				case 'UNKEYWORD4':
2082
				case 'UNLABEL5':
2083
				case 'UNKEYWORD5':
2084
					$imapStatusFilter->flag(str_ireplace(array('UNKEYWORD','UNLABEL'),'$LABEL',$criteria), $set=false);
2085
					$queryValid = $statusQueryValid =true;
2086
					break;
2087
				default:
2088
					$statusQueryValid = false;
2089
			}
2090
			if ($statusQueryValid)
2091
			{
2092
				$imapFilter->andSearch($imapStatusFilter);
2093
			}
2094
		}
2095
2096
2097
		//error_log(__METHOD__.' ('.__LINE__.') '.print_r($_criterias, true));
2098
		$imapSearchFilter = new Horde_Imap_Client_Search_Query();
2099
		$imapSearchFilter->charset('UTF-8');
2100
2101
		if(!empty($_criterias['string'])) {
2102
			$criteria = strtoupper($_criterias['type']);
2103
			switch ($criteria) {
2104
				case 'BYDATE':
2105
				case 'QUICK':
2106
				case 'QUICKWITHCC':
2107
					$imapSearchFilter->headerText('SUBJECT', $_criterias['string'], $not=false);
2108
					//$imapSearchFilter->charset('UTF-8');
2109
					$imapFilter2 = new Horde_Imap_Client_Search_Query();
2110
					$imapFilter2->charset('UTF-8');
2111
					if($this->isSentFolder($_folder)) {
2112
						$imapFilter2->headerText('TO', $_criterias['string'], $not=false);
2113
					} else {
2114
						$imapFilter2->headerText('FROM', $_criterias['string'], $not=false);
2115
					}
2116
					if ($_supportsOrInQuery)
2117
					{
2118
						$imapSearchFilter->orSearch($imapFilter2);
2119
					}
2120
					else
2121
					{
2122
						$imapSearchFilter->andSearch($imapFilter2);
2123
					}
2124
					if ($_supportsOrInQuery && $criteria=='QUICKWITHCC')
2125
					{
2126
						$imapFilter3 = new Horde_Imap_Client_Search_Query();
2127
						$imapFilter3->charset('UTF-8');
2128
						$imapFilter3->headerText('CC', $_criterias['string'], $not=false);
2129
						$imapSearchFilter->orSearch($imapFilter3);
2130
					}
2131
					$queryValid = true;
2132
					break;
2133
				case 'LARGER':
2134
				case 'SMALLER':
2135
					if (strlen(trim($_criterias['string'])) != strlen((float) trim($_criterias['string'])))
2136
					{
2137
						//examine string to evaluate size
2138
						$unit = strtoupper(trim(substr(trim($_criterias['string']),strlen((float) trim($_criterias['string'])))));
2139
						$multipleBy = array('KB'=>1024,'K'=>1024,
2140
											'MB'=>1024*1000,'M'=>1024*1000,
2141
											'GB'=>1024*1000*1000,'G'=>1024*1000*1000,
2142
											'TB'=>1024*1000*1000*1000,'T'=>1024*1000*1000*1000);
2143
						$numberinBytes=(float)$_criterias['string'];
2144
						if (isset($multipleBy[$unit])) $numberinBytes=(float)$_criterias['string']*$multipleBy[$unit];
2145
						//error_log(__METHOD__.__LINE__.'#'.$_criterias['string'].'->'.(float)$_criterias['string'].'#'.$unit.' ='.$numberinBytes);
2146
						$_criterias['string']=$numberinBytes;
2147
					}
2148
					$imapSearchFilter->size( $_criterias['string'], ($criteria=='LARGER'?true:false), $not=false);
2149
					//$imapSearchFilter->charset('UTF-8');
2150
					$queryValid = true;
2151
					break;
2152
				case 'FROM':
2153
				case 'TO':
2154
				case 'CC':
2155
				case 'BCC':
2156
				case 'SUBJECT':
2157
					$imapSearchFilter->headerText($criteria, $_criterias['string'], $not=false);
2158
					//$imapSearchFilter->charset('UTF-8');
2159
					$queryValid = true;
2160
					break;
2161
				case 'BODY':
2162
				case 'TEXT':
2163
					$imapSearchFilter->text($_criterias['string'],($criteria=='BODY'?true:false), $not=false);
2164
					//$imapSearchFilter->charset('UTF-8');
2165
					$queryValid = true;
2166
					break;
2167
				case 'SINCE':
2168
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2169
					$queryValid = true;
2170
					break;
2171
				case 'BEFORE':
2172
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2173
					$queryValid = true;
2174
					break;
2175 View Code Duplication
				case 'ON':
2176
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2177
					$queryValid = true;
2178
					break;
2179
			}
2180
		}
2181
		if ($statusQueryValid && !$queryValid) $queryValid=true;
2182
		if ($queryValid) $imapFilter->andSearch($imapSearchFilter);
2183
2184
		if (isset($_criterias['range']) && !empty($_criterias['range']))
2185
		{
2186
			$rangeValid = false;
2187
			$imapRangeFilter = new Horde_Imap_Client_Search_Query();
2188
			$imapRangeFilter->charset('UTF-8');
2189
			$criteria = strtoupper($_criterias['range']);
2190
			if ($_criterias['range'] == "BETWEEN" && isset($_criterias['since']) && isset($_criterias['before']) && $_criterias['since']==$_criterias['before'])
2191
			{
2192
				$_criterias['date']=$_criterias['since'];
2193
				unset($_criterias['since']);
2194
				unset($_criterias['before']);
2195
				$criteria=$_criterias['range']='ON';
2196
			}
2197
			switch ($criteria) {
2198
				case 'BETWEEN':
2199
					//try to be smart about missing
2200
					//enddate
2201
					if ($_criterias['since'])
2202
					{
2203
						$imapRangeFilter->dateSearch(new DateTime($_criterias['since']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2204
						$rangeValid = true;
2205
					}
2206
					//startdate
2207
					if ($_criterias['before'])
2208
					{
2209
						$imapRangeFilter2 = new Horde_Imap_Client_Search_Query();
2210
						$imapRangeFilter2->charset('UTF-8');
2211
						//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2212
						$_criterias['before'] = date("d-M-Y",DateTime::to($_criterias['before'],'ts')+(3600*24));
2213
						$imapRangeFilter2->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2214
						$imapRangeFilter->andSearch($imapRangeFilter2);
2215
						$rangeValid = true;
2216
					}
2217
					break;
2218
				case 'SINCE'://enddate
2219
					$imapRangeFilter->dateSearch(new DateTime(($_criterias['since']?$_criterias['since']:$_criterias['date'])), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2220
					$rangeValid = true;
2221
					break;
2222
				case 'BEFORE'://startdate
2223
					//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2224
					$_criterias['before'] = date("d-M-Y",DateTime::to(($_criterias['before']?$_criterias['before']:$_criterias['date']),'ts')+(3600*24));
2225
					$imapRangeFilter->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2226
					$rangeValid = true;
2227
					break;
2228 View Code Duplication
				case 'ON':
2229
					$imapRangeFilter->dateSearch(new DateTime($_criterias['date']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2230
					$rangeValid = true;
2231
					break;
2232
			}
2233
			if ($rangeValid && !$queryValid) $queryValid=true;
2234
			if ($rangeValid) $imapFilter->andSearch($imapRangeFilter);
2235
		}
2236
		if (self::$debug)
2237
		{
2238
			//$imapFilter->charset('UTF-8');
2239
			$query_str = $imapFilter->build();
2240
			//error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query'].' created by Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2241
		}
2242
		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...
2243
			$imapFilter->flag('DELETED', $set=false);
2244
			return $imapFilter;
2245
		} else {
2246
			return $imapFilter;
2247
		}
2248
	}
2249
2250
	/**
2251
	 * decode header (or envelope information)
2252
	 * if array given, note that only values will be converted
2253
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2254
	 * @param  mixed/boolean $_tryIDNConversion (true/false AND FORCE): try IDN Conversion on domainparts of emailADRESSES
2255
	 * @return mixed - based on the input type
2256
	 */
2257
	static function decode_header($_string, $_tryIDNConversion=false)
2258
	{
2259
		if (is_array($_string))
2260
		{
2261
			foreach($_string as $k=>$v)
2262
			{
2263
				$_string[$k] = self::decode_header($v, $_tryIDNConversion);
2264
			}
2265
			return $_string;
2266
		}
2267
		else
2268
		{
2269
			$_string = Mail\Html::decodeMailHeader($_string,self::$displayCharset);
2270
			$test = @json_encode($_string);
2271
			//error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#');
2272
			if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2273
			{
2274
				// try to fix broken utf8
2275
				$x = utf8_encode($_string);
2276
				$test = @json_encode($x);
2277
				if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2278
				{
2279
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
2280
					$_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));
2281
				}
2282
				else
2283
				{
2284
					$_string = $x;
2285
				}
2286
			}
2287
2288
			if ($_tryIDNConversion===true && stripos($_string,'@')!==false)
2289
			{
2290
				$rfcAddr = self::parseAddressList($_string);
2291
				$stringA = array();
2292
				//$_string = str_replace($rfcAddr[0]->host,Horde_Idna::decode($rfcAddr[0]->host),$_string);
2293
				foreach ($rfcAddr as $_rfcAddr)
2294
				{
2295
					if (!$_rfcAddr->valid)
2296
					{
2297
						$stringA = array();
2298
						break; // skip idna conversion if we encounter an error here
2299
					}
2300
					$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox,Horde_Idna::decode($_rfcAddr->host),$_rfcAddr->personal);
2301
				}
2302
				if (!empty($stringA)) $_string = implode(',',$stringA);
2303
			}
2304
			if ($_tryIDNConversion==='FORCE')
2305
			{
2306
				//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_string.'='.Horde_Idna::decode($_string));
2307
				$_string = Horde_Idna::decode($_string);
2308
			}
2309
			return $_string;
2310
		}
2311
	}
2312
2313
	/**
2314
	 * decode subject
2315
	 * if array given, note that only values will be converted
2316
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2317
	 * @param  boolean $decode try decoding
2318
	 * @return mixed - based on the input type
2319
	 */
2320
	function decode_subject($_string,$decode=true)
2321
	{
2322
		#$string = $_string;
2323
		if($_string=='NIL')
2324
		{
2325
			return 'No Subject';
2326
		}
2327
		if ($decode) $_string = self::decode_header($_string);
2328
		// make sure its utf-8
2329
		$test = @json_encode($_string);
2330
		if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2331
		{
2332
			$_string = utf8_encode($_string);
2333
		}
2334
		return $_string;
2335
2336
	}
2337
2338
	/**
2339
	 * decodeEntityFolderName - remove html entities
2340
	 * @param string _folderName the foldername
2341
	 * @return string the converted string
2342
	 */
2343
	function decodeEntityFolderName($_folderName)
2344
	{
2345
		return html_entity_decode($_folderName, ENT_QUOTES, self::$displayCharset);
0 ignored issues
show
Unused Code introduced by
The call to html_entity_decode() has too many arguments starting with self::$displayCharset.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2346
	}
2347
2348
	/**
2349
	 * convert a mailboxname from utf7-imap to displaycharset
2350
	 *
2351
	 * @param string _folderName the foldername
2352
	 * @return string the converted string
2353
	 */
2354
	function encodeFolderName($_folderName)
2355
	{
2356
		return Translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset);
2357
	}
2358
2359
	/**
2360
	 * convert the foldername from display charset to UTF-7
2361
	 *
2362
	 * @param string _parent the parent foldername
2363
	 * @return ISO-8859-1 / UTF7-IMAP encoded string
2364
	 */
2365
	function _encodeFolderName($_folderName) {
2366
		return Translation::convert($_folderName, self::$displayCharset, 'ISO-8859-1');
2367
		#return Translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
2368
	}
2369
2370
	/**
2371
	 * create a new folder under given parent folder
2372
	 *
2373
	 * @param string _parent the parent foldername
2374
	 * @param string _folderName the new foldername
2375
	 * @param string _error pass possible error back to caller
2376
	 *
2377
	 * @return mixed name of the newly created folder or false on error
2378
	 */
2379
	function createFolder($_parent, $_folderName, &$_error)
2380
	{
2381 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."->"."$_parent, $_folderName called from:".function_backtrace());
2382
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2383
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2384
2385 View Code Duplication
		if(empty($parent)) {
2386
			$newFolderName = $folderName;
2387
		} else {
2388
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2389
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2390
		}
2391
		if (empty($newFolderName)) return false;
2392
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.'->'.$newFolderName);
2393
		if ($this->folderExists($newFolderName,true))
2394
		{
2395
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." Folder $newFolderName already exists.");
2396
			return $newFolderName;
2397
		}
2398
		try
2399
		{
2400
			$opts = array();
2401
			// if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too
2402
			if (isset(self::$specialUseFolders[$newFolderName]))
2403
			{
2404
				$opts['special_use'] = self::$specialUseFolders[$newFolderName];
2405
			}
2406
			$this->icServer->createMailbox($newFolderName, $opts);
2407
		}
2408
		catch (\Exception $e)
2409
		{
2410
			$_error = lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());
2411
			error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details.') Namespace:'.array2string($this->icServer->getNameSpaces()).function_backtrace());
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2412
			return false;
2413
		}
2414
		try
2415
		{
2416
			$this->icServer->subscribeMailbox($newFolderName);
2417
		}
2418
		catch (\Exception $e)
2419
		{
2420
			error_log(__METHOD__.' ('.__LINE__.') '.' subscribe to new folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details);
2421
			return false;
2422
		}
2423
2424
		return $newFolderName;
2425
	}
2426
2427
	/**
2428
	 * rename a folder
2429
	 *
2430
	 * @param string _oldFolderName the old foldername
2431
	 * @param string _parent the parent foldername
2432
	 * @param string _folderName the new foldername
2433
	 *
2434
	 * @return mixed name of the newly created folder or false on error
2435
	 * @throws Exception
2436
	 */
2437
	function renameFolder($_oldFolderName, $_parent, $_folderName)
2438
	{
2439
		$oldFolderName	= $_oldFolderName;//$this->_encodeFolderName($_oldFolderName);
2440
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2441
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2442
2443 View Code Duplication
		if(empty($parent)) {
2444
			$newFolderName = $folderName;
2445
		} else {
2446
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2447
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2448
		}
2449
		if (self::$debug) error_log("create folder: $newFolderName");
2450
		try
2451
		{
2452
			$this->icServer->renameMailbox($oldFolderName, $newFolderName);
2453
		}
2454
		catch (\Exception $e)
2455
		{
2456
			throw new Exception(__METHOD__." failed for $oldFolderName (rename to: $newFolderName) with error:".$e->getMessage());;
2457
		}
2458
		// clear FolderExistsInfoCache
2459
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2460
2461
		return $newFolderName;
2462
2463
	}
2464
2465
	/**
2466
	 * delete an existing folder
2467
	 *
2468
	 * @param string _folderName the name of the folder to be deleted
2469
	 *
2470
	 * @return bool true on success, PEAR Error on failure
2471
	 * @throws Exception
2472
	 */
2473
	function deleteFolder($_folderName)
2474
	{
2475
		//$folderName = $this->_encodeFolderName($_folderName);
2476
		try
2477
		{
2478
			$this->icServer->subscribeMailbox($_folderName,false);
2479
			$this->icServer->deleteMailbox($_folderName);
2480
		}
2481
		catch (\Exception $e)
2482
		{
2483
			throw new Exception("Deleting Folder $_folderName failed! Error:".$e->getMessage());;
2484
		}
2485
		// clear FolderExistsInfoCache
2486
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2487
2488
		return true;
2489
	}
2490
2491
	/**
2492
	 * fetchUnSubscribedFolders: get unsubscribed IMAP folder list
2493
	 *
2494
	 * returns an array of unsubscribed IMAP folder names.
2495
	 *
2496
	 * @return array with folder names. eg.: 1 => INBOX/TEST
2497
	 */
2498
	function fetchUnSubscribedFolders()
2499
	{
2500
		$unSubscribedMailboxes = $this->icServer->listUnSubscribedMailboxes();
2501
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($unSubscribedMailboxes));
2502
		return $unSubscribedMailboxes;
2503
	}
2504
2505
	/**
2506
	 * get IMAP folder objects
2507
	 *
2508
	 * returns an array of IMAP folder objects. Put INBOX folder in first
2509
	 * position. Preserves the folder seperator for later use. The returned
2510
	 * array is indexed using the foldername. Use cachedObjects when retrieving subscribedFolders
2511
	 *
2512
	 * @param boolean _subscribedOnly  get subscribed or all folders
2513
	 * @param boolean _getCounters   get get messages counters
2514
	 * @param boolean _alwaysGetDefaultFolders  this triggers to ignore the possible notavailableautofolders - preference
2515
	 *			as activeSync needs all folders like sent, trash, drafts, templates and outbox - if not present devices may crash
2516
	 *			-> autoFolders should be created if needed / accessed (if possible and configured)
2517
	 * @param boolean _useCacheIfPossible  - if set to false cache will be ignored and reinitialized
2518
	 *
2519
	 * @return array with folder objects. eg.: INBOX => {inbox object}
2520
	 */
2521
	function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false,$_useCacheIfPossible=true)
2522
	{
2523
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' ServerID:'.$this->icServer->ImapServerId.", subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders, _useCacheIfPossible:$_useCacheIfPossible");
2524
		if (self::$debugTimes) $starttime = microtime (true);
2525
		static $folders2return;
2526
		//$_subscribedOnly=false;
2527
		// always use static on single request if info is available;
2528
		// so if you require subscribed/unsubscribed results on a single request you MUST
2529
		// set $_useCacheIfPossible to false !
2530 View Code Duplication
		if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2531
		{
2532
			if (self::$debugTimes) self::logRunTimes($starttime,null,'using static',__METHOD__.' ('.__LINE__.') ');
2533
			return $folders2return[$this->icServer->ImapServerId];
2534
		}
2535
2536
		if ($_subscribedOnly && $_getCounters===false)
2537
		{
2538
			if (is_null($folders2return)) $folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
2539 View Code Duplication
			if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2540
			{
2541
				//error_log(__METHOD__.' ('.__LINE__.') '.' using Cached folderObjects'.array2string($folders2return[$this->icServer->ImapServerId]));
2542
				if (self::$debugTimes) self::logRunTimes($starttime,null,'from Cache',__METHOD__.' ('.__LINE__.') ');
2543
				return $folders2return[$this->icServer->ImapServerId];
2544
			}
2545
		}
2546
		// use $folderBasicInfo for holding attributes and other basic folderinfo $folderBasicInfo[$this->icServer->ImapServerId]
2547
		static $folderBasicInfo;
2548 View Code Duplication
		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);
2549
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($folderBasicInfo[$this->icServer->ImapServerId])));
2550
2551
		$delimiter = $this->getHierarchyDelimiter();
2552
2553
		$inboxData = new \stdClass;
2554
		$inboxData->name 		= 'INBOX';
2555
		$inboxData->folderName		= 'INBOX';
2556
		$inboxData->displayName		= lang('INBOX');
2557
		$inboxData->delimiter 		= $delimiter;
2558
		$inboxData->shortFolderName	= 'INBOX';
2559
		$inboxData->shortDisplayName	= lang('INBOX');
2560
		$inboxData->subscribed = true;
2561
		if($_getCounters == 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...
2562
			$inboxData->counter = $this->getMailBoxCounters('INBOX');
2563
		}
2564
		// force unsubscribed by preference showAllFoldersInFolderPane
2565
		if ($_subscribedOnly == 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...
2566
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2567
			$this->mailPreferences['showAllFoldersInFolderPane']==1)
2568
		{
2569
			$_subscribedOnly = false;
2570
		}
2571
		$inboxFolderObject = array('INBOX' => $inboxData);
2572
2573
		//$nameSpace = $this->icServer->getNameSpaces();
2574
		$nameSpace = $this->_getNameSpaces();
2575
		$fetchedAllInOneGo = false;
2576
		$subscribedFoldersForCache = $foldersNameSpace = array();
2577
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2578
		if (is_array($nameSpace))
2579
		{
2580
			foreach($nameSpace as $k => $singleNameSpace) {
2581
				$type = $singleNameSpace['type'];
2582
				// the following line (assumption that for the same namespace the delimiter should be equal) may be wrong
2583
				$foldersNameSpace[$type]['delimiter']  = $singleNameSpace['delimiter'];
2584
2585
				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...
2586
					// fetch and sort the subscribed folders
2587
					// we alway fetch the subscribed, as this provides the only way to tell
2588
					// if a folder is subscribed or not
2589
					if ($_subscribedOnly == 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...
2590
					{
2591
						try
2592
						{
2593
							$subscribedMailboxes = $this->icServer->listSubscribedMailboxes('',0,true);
2594
							if (!empty($subscribedMailboxes))
2595
							{
2596
								$fetchedAllInOneGo = true;
2597
							}
2598
							else
2599
							{
2600
								$subscribedMailboxes = $this->icServer->listSubscribedMailboxes($singleNameSpace['prefix'],0,true);
2601
							}
2602
						}
2603
						catch(Exception $e)
2604
						{
2605
							continue;
2606
						}
2607
						//echo "subscribedMailboxes";_debug_array($subscribedMailboxes);
2608
						$subscribedFoldersPerNS = (!empty($subscribedMailboxes)?array_keys($subscribedMailboxes):array());
2609
						//if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']);
2610
						//_debug_array($foldersNameSpace);
2611
						//error_log(__METHOD__.__LINE__.array2string($singleNameSpace).':#:'.array2string($subscribedFoldersPerNS));
2612
						if (!empty($subscribedFoldersPerNS) && !empty($subscribedMailboxes))
2613
						{
2614
							//error_log(__METHOD__.' ('.__LINE__.') '." $type / subscribed:". array2string($subscribedMailboxes));
2615
							foreach ($subscribedMailboxes as $k => $finfo)
2616
							{
2617
								//error_log(__METHOD__.__LINE__.$k.':#:'.array2string($finfo));
2618
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$k]=
2619
								$folderBasicInfo[$this->icServer->ImapServerId][$k]=array(
2620
									'MAILBOX'=>$finfo['MAILBOX'],
2621
									'ATTRIBUTES'=>$finfo['ATTRIBUTES'],
2622
									'delimiter'=>$finfo['delimiter'],//lowercase for some reason???
2623
									'SUBSCRIBED'=>$finfo['SUBSCRIBED'],//seeded by getMailboxes
2624
								);
2625 View Code Duplication
								if (empty($foldersNameSpace[$type]['subscribed']) || !in_array($k,$foldersNameSpace[$type]['subscribed']))
2626
								{
2627
									$foldersNameSpace[$type]['subscribed'][] = $k;
2628
								}
2629 View Code Duplication
								if (empty($foldersNameSpace[$type]['all']) || !in_array($k,$foldersNameSpace[$type]['all']))
2630
								{
2631
									$foldersNameSpace[$type]['all'][] = $k;
2632
								}
2633
							}
2634
						}
2635
						//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($foldersNameSpace[$type]['subscribed']));
2636
						if (!is_array($foldersNameSpace[$type]['all'])) $foldersNameSpace[$type]['all'] = array();
2637
						if ($_subscribedOnly == true && !empty($foldersNameSpace[$type]['subscribed'])) {
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...
2638
							continue;
2639
						}
2640
2641
					}
2642
2643
					// fetch and sort all folders
2644
					//echo $type.'->'.$singleNameSpace['prefix'].'->'.($type=='shared'?0:2)."<br>";
2645
					try
2646
					{
2647
						// calling with 2 lists all mailboxes on that level with fetches all
2648
						// we switch to all, to avoid further calls for subsequent levels
2649
						// that may produce problems, when encountering recursions probably
2650
						// horde is handling that, so we do not; keep that in mind!
2651
						//$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],2,true);
2652
						$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],0,true);
2653
					}
2654
					catch (\Exception $e)
2655
					{
2656
						error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve all Boxes:'.$e->getMessage());
2657
						$allMailboxesExt = array();
2658
					}
2659
					if (!is_array($allMailboxesExt))
2660
					{
2661
						//error_log(__METHOD__.' ('.__LINE__.') '.' Expected Array but got:'.array2string($allMailboxesExt). 'Type:'.$type.' Prefix:'.$singleNameSpace['prefix']);
2662
						continue;
2663
						//$allMailboxesExt=array();
2664
					}
2665
2666
					//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($allMailboxesExt));
2667
					foreach ($allMailboxesExt as $mbx) {
2668
						if (!isset($folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2669
						{
2670
							$folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]=array(
2671
								'MAILBOX'=>$mbx['MAILBOX'],
2672
								'ATTRIBUTES'=>$mbx['ATTRIBUTES'],
2673
								'delimiter'=>$mbx['delimiter'],//lowercase for some reason???
2674
								'SUBSCRIBED'=>$mbx['SUBSCRIBED'],//seeded by getMailboxes
2675
							);
2676
							if ($mbx['SUBSCRIBED'] && !isset($subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2677
							{
2678
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']] = $folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']];
2679
							}
2680
						}
2681
						if ($mbx['SUBSCRIBED'] && (empty($foldersNameSpace[$type]['subscribed']) || !in_array($mbx['MAILBOX'],$foldersNameSpace[$type]['subscribed'])))
2682
						{
2683
							$foldersNameSpace[$type]['subscribed'][] = $mbx['MAILBOX'];
2684
						}
2685
						//echo __METHOD__;_debug_array($mbx);
2686
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
2687
						if (isset($allMailBoxesExtSorted[$mbx['MAILBOX']])||
2688
							isset($allMailBoxesExtSorted[$mbx['MAILBOX'].$foldersNameSpace[$type]['delimiter']])||
2689
							(substr($mbx['MAILBOX'],-1)==$foldersNameSpace[$type]['delimiter'] && isset($allMailBoxesExtSorted[substr($mbx['MAILBOX'],0,-1)]))
2690
						) continue;
2691
2692
						//echo '#'.$mbx['MAILBOX'].':'.array2string($mbx)."#<br>";
2693
						$allMailBoxesExtSorted[$mbx['MAILBOX']] = $mbx;
2694
					}
2695
					if (is_array($allMailBoxesExtSorted)) ksort($allMailBoxesExtSorted);
2696
					//_debug_array(array_keys($allMailBoxesExtSorted));
2697
					$allMailboxes = array();
2698
					foreach ((array)$allMailBoxesExtSorted as $mbx) {
2699
						if (!in_array($mbx['MAILBOX'],$allMailboxes)) $allMailboxes[] = $mbx['MAILBOX'];
2700
						//echo "Result:";_debug_array($allMailboxes);
2701
					}
2702
					$foldersNameSpace[$type]['all'] = $allMailboxes;
2703
					if (is_array($foldersNameSpace[$type]['all'])) sort($foldersNameSpace[$type]['all']);
2704
				}
2705
			}
2706
		}
2707
		//subscribed folders may be used in getFolderStatus
2708
		Cache::setCache(Cache::INSTANCE,'email','subscribedFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$subscribedFoldersForCache,$expiration=60*60*1);
2709
		//echo "<br>FolderNameSpace To Process:";_debug_array($foldersNameSpace);
2710
		$autoFolderObjects = $folders = array();
2711
		$autofolder_exists = array();
2712
		foreach( array('personal', 'others', 'shared') as $type) {
2713
			if(isset($foldersNameSpace[$type])) {
2714
				if($_subscribedOnly) {
2715
					if( !empty($foldersNameSpace[$type]['subscribed']) ) $listOfFolders = $foldersNameSpace[$type]['subscribed'];
2716
				} else {
2717
					if( !empty($foldersNameSpace[$type]['all'])) $listOfFolders = $foldersNameSpace[$type]['all'];
2718
				}
2719
				foreach((array)$listOfFolders as $folderName) {
2720
					//echo "<br>FolderToCheck:$folderName<br>";
2721
					//error_log(__METHOD__.__LINE__.'#Delimiter:'.$delimiter.':#'.$folderName);
2722
					if ($_subscribedOnly && empty($foldersNameSpace[$type]['all'])) continue;//when subscribedonly, we fetch all folders in one go.
2723
					if($_subscribedOnly && !(in_array($folderName, $foldersNameSpace[$type]['all'])||in_array($folderName.$foldersNameSpace[$type]['delimiter'], $foldersNameSpace[$type]['all']))) {
2724
						#echo "$folderName failed to be here <br>";
2725
						continue;
2726
					}
2727
					if (isset($folders[$folderName])) continue;
2728
					if (isset($autoFolderObjects[$folderName])) continue;
2729
					if (empty($delimiter)||$delimiter != $foldersNameSpace[$type]['delimiter']) $delimiter = $foldersNameSpace[$type]['delimiter'];
2730
					$folderParts = explode($delimiter, $folderName);
2731
					$shortName = array_pop($folderParts);
2732
2733
					$folderObject = new \stdClass;
2734
					$folderObject->delimiter	= $delimiter;
2735
					$folderObject->folderName	= $folderName;
2736
					$folderObject->shortFolderName	= $shortName;
2737
					if(!$_subscribedOnly) {
2738
						#echo $folderName."->".$type."<br>";
2739
						#_debug_array($foldersNameSpace[$type]['subscribed']);
2740
						$folderObject->subscribed = in_array($folderName, (array)$foldersNameSpace[$type]['subscribed']);
2741
					}
2742
2743
					if($_getCounters == 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...
2744
						//error_log(__METHOD__.' ('.__LINE__.') '.' getCounter forFolder:'.$folderName);
2745
						$folderObject->counter = $this->getMailBoxCounters($folderName);
2746
					}
2747
					if(strtoupper($folderName) == 'INBOX') {
2748
						$folderName = 'INBOX';
2749
						$folderObject->folderName	= 'INBOX';
2750
						$folderObject->shortFolderName	= 'INBOX';
2751
						$folderObject->displayName	= lang('INBOX');
2752
						$folderObject->shortDisplayName = lang('INBOX');
2753
						$folderObject->subscribed	= true;
2754
					// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
2755
					} elseif (in_array($shortName,self::$autoFolders)) {
2756
						$tmpfolderparts = explode($delimiter,$folderObject->folderName);
2757
						array_pop($tmpfolderparts);
2758
						$folderObject->displayName = implode($delimiter,$tmpfolderparts).$delimiter.lang($shortName);
2759
						$folderObject->shortDisplayName = lang($shortName);
2760
						unset($tmpfolderparts);
2761
					} else {
2762
						$folderObject->displayName = $folderObject->folderName;
2763
						$folderObject->shortDisplayName = $shortName;
2764
					}
2765
					//$folderName = $folderName;
2766
					if (in_array($shortName,self::$autoFolders)&&self::searchValueInFolderObjects($shortName,$autoFolderObjects)===false) {
2767
						$autoFolderObjects[$folderName] = $folderObject;
2768
					} else {
2769
						$folders[$folderName] = $folderObject;
2770
					}
2771
					//error_log(__METHOD__.' ('.__LINE__.') '.':'.$folderObject->folderName);
2772
					if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders ();
2773
					if (isset(self::$specialUseFolders[$folderName]))
2774
					{
2775
						$autofolder_exists[$folderName] = self::$specialUseFolders[$folderName];
2776
					}
2777
				}
2778
			}
2779
		}
2780
		if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) {
2781
			uasort($autoFolderObjects,array($this,"sortByAutoFolderPos"));
2782
		}
2783
		// check if some standard folders are missing and need to be created
2784
		if (count($autofolder_exists) < count(self::$autoFolders) && $this->check_create_autofolders($autofolder_exists))
2785
		{
2786
			// if new folders have been created, re-read folders ignoring the cache
2787
			return $this->getFolderObjects($_subscribedOnly, $_getCounters, $_alwaysGetDefaultFolders, false);	// false = do NOT use cache
2788
		}
2789
		if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName"));
2790
		//$folders2return = array_merge($autoFolderObjects,$folders);
2791
		//_debug_array($folders2return); #exit;
2792
		$folders2return[$this->icServer->ImapServerId] = array_merge((array)$inboxFolderObject,(array)$autoFolderObjects,(array)$folders);
2793
		if (($_subscribedOnly && $_getCounters===false) ||
2794
			($_subscribedOnly == false && $_getCounters===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...
2795
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2796
			$this->mailPreferences['showAllFoldersInFolderPane']==1))
2797
		{
2798
			Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return,$expiration=60*60*1);
2799
		}
2800
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1);
2801
		if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') ');
2802
		return $folders2return[$this->icServer->ImapServerId];
2803
	}
2804
2805
	/**
2806
	 * Get IMAP folders for a mailbox
2807
	 *
2808
	 * @param string $_nodePath = null folder name to fetch from IMAP,
2809
	 *			null means all folders
2810
	 * @param boolean $_onlyTopLevel if set to true only top level objects
2811
	 *			will be return and nodePath would be ignored
2812
	 * @param int $_search = 2 search restriction in given mailbox
2813
	 *	0:All folders recursively from the $_nodePath
2814
	 *  1:Only folder of specified $_nodePath
2815
	 *	2:All folders of $_nodePath in the same heirachy level
2816
	 *
2817
	 * @param boolean $_subscribedOnly = false Command to fetch only the subscribed folders
2818
	 * @param boolean $_getCounter = false Command to fetch mailbox counter
2819
	 *
2820
	 * @return array arrays of folders
2821
	 */
2822
	function getFolderArrays ($_nodePath = null, $_onlyTopLevel = false, $_search= 2, $_subscribedOnly = false, $_getCounter = false)
2823
	{
2824
		// delimiter
2825
		$delimiter = $this->getHierarchyDelimiter();
2826
2827
		$folders = $nameSpace =  array();
2828
		$nameSpaceTmp = $this->_getNameSpaces();
2829
		foreach($nameSpaceTmp as $k => $singleNameSpace) {
2830
			$nameSpace[$singleNameSpace['type']]=$singleNameSpace;
2831
		}
2832
		unset($nameSpaceTmp);
2833
2834
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2835
		// Get special use folders
2836
		if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders (); // Set self::$specialUseFolders
2837
		// topLevelQueries generally ignore the $_search param. Except for Config::examineNamespace
2838
		if ($_onlyTopLevel) // top level leaves
2839
		{
2840
			// Get top mailboxes of icServer
2841
			$topFolders = $this->icServer->getMailboxes("", 2, true);
2842
			// Trigger examination of namespace to retrieve
2843
			// folders located in other and shared; needed only for some servers
2844
			if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
2845
			if (self::$mailConfig['examineNamespace'])
2846
			{
2847
				$prefixes=array();
2848
				if (is_array($nameSpace))
2849
				{
2850
					foreach($nameSpace as $k => $singleNameSpace) {
2851
						$type = $singleNameSpace['type'];
2852
2853
						if(is_array($singleNameSpace) && $singleNameSpace['prefix']){
2854
							$prefixes[$type] = $singleNameSpace['prefix'];
2855
							//regard extra care for nameSpacequeries when configured AND respect $_search
2856
							$result = $this->icServer->getMailboxes($singleNameSpace['prefix'], $_search==0?0:2, true);
2857
							if (is_array($result))
2858
							{
2859
								ksort($result);
2860
								$topFolders = array_merge($topFolders,$result);
2861
							}
2862
						}
2863
					}
2864
				}
2865
			}
2866
2867
			$autofolders = array();
2868
2869
			foreach(self::$specialUseFolders as $path => $folder)
0 ignored issues
show
Bug introduced by
The expression self::$specialUseFolders of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2870
			{
2871
				if ($this->folderExists($path))
2872
				{
2873
					$autofolders[$folder] = $folder;
2874
				}
2875
			}
2876
			// Check if the special use folders are there, otherwise try to create them
2877
			if (count($autofolders) < count(self::$autoFolders) && $this->check_create_autofolders ($autofolders))
2878
			{
2879
				return $this->getFolderArrays ($_nodePath, $_onlyTopLevel, $_search, $_subscribedOnly, $_getCounter);
2880
			}
2881
2882
			// now process topFolders for next level
2883
			foreach ($topFolders as &$node)
2884
			{
2885
				$pattern = "/\\".$delimiter."/";
2886
				$reference = preg_replace($pattern, '', $node['MAILBOX']);
2887
				if(!empty($prefixes))
2888
				{
2889
					$reference = '';
2890
					$tmpArray = explode($delimiter,$node['MAILBOX']);
2891
					foreach($tmpArray as $p)
2892
					{
2893
						$reference = empty($reference)?$p:$reference.$delimiter.$p;
2894
					}
2895
				}
2896
				$mainFolder = $subFolders = array();
2897
2898
				if ($_subscribedOnly)
2899
				{
2900
					$mainFolder = $this->icServer->listSubscribedMailboxes($reference, 1, true);
2901
					$subFolders = $this->icServer->listSubscribedMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2902
				}
2903
				else
2904
				{
2905
					$mainFolder = $this->icServer->getMailboxes($reference, 1, true);
2906
					$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2907
				}
2908
2909
				if (is_array($mainFolder['INBOX']))
2910
				{
2911
					// Array container of auto folders
2912
					$aFolders = array();
2913
2914
					// Array container of non auto folders
2915
					$nFolders = array();
2916
2917
					foreach ((array)$subFolders as $path => $folder)
2918
					{
2919
						$folderInfo = self::pathToFolderData($folder['MAILBOX'], $folder['delimiter']);
2920
						if (in_array(trim($folderInfo['name']), $autofolders) || in_array(trim($folderInfo['name']), self::$autoFolders))
2921
						{
2922
							$aFolders [$path] = $folder;
2923
						}
2924
						else
2925
						{
2926
							$nFolders [$path] = $folder;
2927
						}
2928
					}
2929
					if (is_array($aFolders)) uasort ($aFolders, array($this,'sortByAutofolder'));
2930
					//ksort($aFolders);
2931
2932
					// Sort none auto folders base on mailbox name
2933
					uasort($nFolders,array($this,'sortByMailbox'));
2934
2935
					$subFolders = array_merge($aFolders,$nFolders);
2936
				}
2937
				else
2938
				{
2939
					if (is_array($subFolders)) ksort($subFolders);
2940
				}
2941
				$folders = array_merge($folders,(array)$mainFolder, (array)$subFolders);
2942
			}
2943
		}
2944
		elseif ($_nodePath) // single node
1 ignored issue
show
Bug Best Practice introduced by
The expression $_nodePath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2945
		{
2946
			switch ($_search)
2947
			{
2948
				// Including children
2949
				case 0:
2950
				case 2:
2951
					$path = $_nodePath.''.$delimiter;
2952
					break;
2953
				// Node itself
2954
				// shouldn't contain next level delimiter
2955
				case 1:
2956
					$path = $_nodePath;
2957
					break;
2958
			}
2959
			if ($_subscribedOnly)
2960
			{
2961
				$folders = $this->icServer->listSubscribedMailboxes($path, $_search, true);
2962
			}
2963
			else
2964
			{
2965
				$folders = $this->icServer->getMailboxes($path, $_search, true);
2966
			}
2967
2968
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
2969
		}
2970
		elseif(!$_nodePath) // all
1 ignored issue
show
Bug Best Practice introduced by
The expression $_nodePath of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2971
		{
2972
			if ($_subscribedOnly)
2973
			{
2974
				$folders = $this->icServer->listSubscribedMailboxes('', 0, true);
2975
			}
2976
			else
2977
			{
2978
				$folders = $this->icServer->getMailboxes('', 0, true);
2979
			}
2980
		}
2981
		// only sort (autofolders, shared, others ...) when retrieving all folders or toplevelquery
2982
		if ($_onlyTopLevel || !$_nodePath)
1 ignored issue
show
Bug Best Practice introduced by
The expression $_nodePath of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2983
		{
2984
			// SORTING FOLDERS
2985
			//self::$debugTimes=true;
2986
			if (self::$debugTimes) $starttime = microtime (true);
2987
			// Merge of all auto folders and specialusefolders
2988
			$autoFoldersTmp = array_unique((array_merge(self::$autoFolders, array_values(self::$specialUseFolders))));
2989
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
2990
			$tmpFolders = $folders;
2991
			$inboxFolderObject=$inboxSubFolderObjects=$autoFolderObjects=$typeFolderObject=$mySpecialUseFolders=array();
2992
			$googleMailFolderObject=$googleAutoFolderObjects=$googleSubFolderObjects=array();
2993
			$isGoogleMail=false;
2994
			foreach($autoFoldersTmp as $afk=>$aF)
2995
			{
2996
				if (!isset($mySpecialUseFolders[$aF]) && $aF) $mySpecialUseFolders[$aF]=$this->getFolderByType($aF,false);
2997
				//error_log($afk.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
2998
			}
2999
			//error_log(array2string($mySpecialUseFolders));
3000
			foreach ($tmpFolders as $k => $f) {
3001
				$sorted=false;
3002
				if (strtoupper(substr($k,0,5))=='INBOX') {
3003
					if (strtoupper($k)=='INBOX') {
3004
						//error_log(__METHOD__.__LINE__.':'.strtoupper(substr($k,0,5)).':'.$k);
3005
						$inboxFolderObject[$k]=$f;
3006
						unset($folders[$k]);
3007
						$sorted=true;
3008 View Code Duplication
					} else {
3009
						$isAutoFolder=false;
3010
						foreach($autoFoldersTmp as $afk=>$aF)
3011
						{
3012
							//error_log(__METHOD__.__LINE__.$k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3013
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3014
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter || //k may be child of an autofolder
3015
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3016
							{
3017
								//error_log(__METHOD__.__LINE__.$k.'->'.$mySpecialUseFolders[$aF]);
3018
								$isAutoFolder=true;
3019
								$autoFolderObjects[$k]=$f;
3020
								break;
3021
							}
3022
						}
3023
						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...
3024
						unset($folders[$k]);
3025
						$sorted=true;
3026
					}
3027
				} elseif (strtoupper(substr($k,0,13))=='[GOOGLE MAIL]') {
3028
					$isGoogleMail=true;
3029
					if (strtoupper($k)=='[GOOGLE MAIL]') {
3030
						$googleMailFolderObject[$k]=$f;
3031
						unset($folders[$k]);
3032
						$sorted=true;
3033 View Code Duplication
					} else {
3034
						$isAutoFolder=false;
3035
						foreach($autoFoldersTmp as $afk=>$aF)
3036
						{
3037
							//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3038
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3039
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3040
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3041
							{
3042
								//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3043
								$isAutoFolder=true;
3044
								$googleAutoFolderObjects[$k]=$f;
3045
								break;
3046
							}
3047
						}
3048
						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...
3049
						unset($folders[$k]);
3050
						$sorted=true;
3051
					}
3052
				} else {
3053
					$isAutoFolder=false;
3054
					foreach($autoFoldersTmp as $afk=>$aF)
3055
					{
3056
						//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3057
						if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3058
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3059
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3060
						{
3061
							//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3062
							$isAutoFolder=true;
3063
							$autoFolderObjects[$k]=$f;
3064
							unset($folders[$k]);
3065
							$sorted=true;
3066
							break;
3067
						}
3068
					}
3069
				}
3070
3071
				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...
3072
				{
3073
					foreach(array('others','shared') as $type)
3074
					{
3075
						if ($nameSpace[$type]['prefix_present']&&$nameSpace[$type]['prefix'])
3076
						{
3077
							if (substr($k,0,strlen($nameSpace[$type]['prefix']))==$nameSpace[$type]['prefix']||
3078
								substr($k,0,strlen($nameSpace[$type]['prefix'])-strlen($nameSpace[$type]['delimiter']))==substr($nameSpace[$type]['prefix'],0,strlen($nameSpace[$type]['delimiter'])*-1)) {
3079
								//error_log(__METHOD__.__LINE__.':'.substr($k,0,strlen($nameSpace[$type]['prefix'])).':'.$k);
3080
								$typeFolderObject[$type][$k]=$f;
3081
								unset($folders[$k]);
3082
							}
3083
						}
3084
					}
3085
				}
3086
			}
3087
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3088
			// avoid calling sortByAutoFolder as it is not regarding subfolders
3089
			$autoFolderObjectsTmp = $autoFolderObjects;
3090
			unset($autoFolderObjects);
3091
			uasort($autoFolderObjectsTmp, array($this,'sortByMailbox'));
3092
			foreach($autoFoldersTmp as $afk=>$aF)
3093
			{
3094
				foreach($autoFolderObjectsTmp as $k => $f)
3095
				{
3096
					if($aF && ($mySpecialUseFolders[$aF]==$k ||
3097
						substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter ||
3098
						stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false))
3099
					{
3100
						$autoFolderObjects[$k]=$f;
3101
					}
3102
				}
3103
			}
3104
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3105
			if (!$isGoogleMail) {
3106
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3107
			} else {
3108
				// avoid calling sortByAutoFolder as it is not regarding subfolders
3109
				$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
3110
				unset($googleAutoFolderObjects);
3111
				uasort($gAutoFolderObjectsTmp, array($this,'sortByMailbox'));
3112
				foreach($autoFoldersTmp as $afk=>$aF)
3113
				{
3114
					foreach($gAutoFolderObjectsTmp as $k => $f)
3115
					{
3116
						if($aF && ($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter))
3117
						{
3118
							$googleAutoFolderObjects[$k]=$f;
3119
						}
3120
					}
3121
				}
3122
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$folders,(array)$googleMailFolderObject,$googleAutoFolderObjects,$googleSubFolderObjects,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3123
			}
3124
			if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') Sorting:');
3125
			//self::$debugTimes=false;
3126
		}
3127
		// Get counter information and add them to each fetched folders array
3128
		// TODO:  do not fetch counters for user .... as in shared / others
3129
		if ($_getCounter)
3130
		{
3131
			foreach ($folders as &$folder)
3132
			{
3133
				$folder['counter'] = $this->icServer->getMailboxCounters($folder['MAILBOX']);
3134
			}
3135
		}
3136
		return $folders;
3137
	}
3138
3139
3140
	/**
3141
	 * Check if all automatic folders exist and create them if not
3142
	 *
3143
	 * @param array $autofolders_exists existing folders, no need to check their existance again
3144
	 * @return int number of new folders created
3145
	 */
3146
	function check_create_autofolders(array $autofolders_exists=array())
3147
	{
3148
		$num_created = 0;
3149
		foreach(self::$autoFolders as $folder)
3150
		{
3151
			$created = false;
3152
			if (!in_array($folder, $autofolders_exists) && $this->_getSpecialUseFolder($folder, true, $created) &&
3153
				$created && $folder != 'Outbox')
3154
			{
3155
				$num_created++;
3156
			}
3157
		}
3158
		return $num_created;
3159
	}
3160
3161
	/**
3162
	 * search Value In FolderObjects
3163
	 *
3164
	 * Helper function to search for a specific value within the foldertree objects
3165
	 * @param string $needle
3166
	 * @param array $haystack array of folderobjects
3167
	 * @return MIXED false or key
3168
	 */
3169
	static function searchValueInFolderObjects($needle, $haystack)
3170
	{
3171
		$rv = false;
3172
		foreach ($haystack as $k => $v)
3173
		{
3174
			foreach($v as &$sv) {if (trim($sv)==trim($needle)) return $k;}
3175
		}
3176
		return $rv;
3177
	}
3178
3179
	/**
3180
	 * sortByMailbox
3181
	 *
3182
	 * Helper function to sort folders array by mailbox
3183
	 * @param array $a
3184
	 * @param array $b array of folders
3185
	 * @return int expect values (0, 1 or -1)
3186
	 */
3187
	function sortByMailbox($a,$b)
3188
	{
3189
		return strcasecmp($a['MAILBOX'],$b['MAILBOX']);
3190
	}
3191
3192
	/**
3193
	 * Get folder data from path
3194
	 *
3195
	 * @param string $_path a node path
3196
	 * @param string $_hDelimiter hierarchy delimiter
3197
	 * @return array returns an array of data extracted from given node path
3198
	 */
3199
	static function pathToFolderData ($_path, $_hDelimiter)
3200
	{
3201
		if (!strpos($_path, self::DELIMITER)) $_path = self::DELIMITER.$_path;
3202
		list(,$path) = explode(self::DELIMITER, $_path);
3203
		$path_chain = $parts = explode($_hDelimiter, $path);
3204
		$name = array_pop($parts);
3205
		return array (
3206
			'name' => $name,
3207
			'mailbox' => $path,
3208
			'parent' => implode($_hDelimiter, $parts),
3209
			'text' => $name,
3210
			'tooltip' => $name,
3211
			'path' => $path_chain
3212
		);
3213
	}
3214
3215
	/**
3216
	 * sortByAutoFolder
3217
	 *
3218
	 * Helper function to sort folder-objects by auto Folder Position
3219
	 * @param array $_a
3220
	 * @param array $_b
3221
	 * @return int expect values (0, 1 or -1)
3222
	 */
3223
	function sortByAutoFolder($_a, $_b)
3224
	{
3225
		// 0, 1 und -1
3226
		$a = self::pathToFolderData($_a['MAILBOX'], $_a['delimiter']);
3227
		$b = self::pathToFolderData($_b['MAILBOX'], $_b['delimiter']);
3228
		$pos1 = array_search(trim($a['name']),self::$autoFolders);
3229
		$pos2 = array_search(trim($b['name']),self::$autoFolders);
3230
		if ($pos1 == $pos2) return 0;
3231
		return ($pos1 < $pos2) ? -1 : 1;
3232
	}
3233
3234
	/**
3235
	 * sortByDisplayName
3236
	 *
3237
	 * Helper function to sort folder-objects by displayname
3238
	 * @param object $a
3239
	 * @param object $b array of folderobjects
3240
	 * @return int expect values (0, 1 or -1)
3241
	 */
3242
	function sortByDisplayName($a,$b)
3243
	{
3244
		// 0, 1 und -1
3245
		return strcasecmp($a->displayName,$b->displayName);
3246
	}
3247
3248
	/**
3249
	 * sortByAutoFolderPos
3250
	 *
3251
	 * Helper function to sort folder-objects by auto Folder Position
3252
	 * @param object $a
3253
	 * @param object $b array of folderobjects
3254
	 * @return int expect values (0, 1 or -1)
3255
	 */
3256
	function sortByAutoFolderPos($a,$b)
3257
	{
3258
		// 0, 1 und -1
3259
		$pos1 = array_search(trim($a->shortFolderName),self::$autoFolders);
3260
		$pos2 = array_search(trim($b->shortFolderName),self::$autoFolders);
3261
		if ($pos1 == $pos2) return 0;
3262
		return ($pos1 < $pos2) ? -1 : 1;
3263
	}
3264
3265
	/**
3266
	 * getMailBoxCounters
3267
	 *
3268
	 * function to retrieve the counters for a given folder
3269
	 * @param string $folderName
3270
	 * @param boolean $_returnObject return the counters as object rather than an array
3271
	 * @return mixed false or array of counters array(MESSAGES,UNSEEN,RECENT,UIDNEXT,UIDVALIDITY) or object
3272
	 */
3273
	function getMailBoxCounters($folderName,$_returnObject=true)
3274
	{
3275
		try
3276
		{
3277
			$folderStatus = $this->icServer->getMailboxCounters($folderName);
3278
			//error_log(__METHOD__.' ('.__LINE__.') '.$folderName.": FolderStatus:".array2string($folderStatus).function_backtrace());
3279
		}
3280
		catch (\Exception $e)
3281
		{
3282
			if (self::$debug) error_log(__METHOD__." returned FolderStatus for Folder $folderName:".$e->getMessage());
3283
			return false;
3284
		}
3285
		if(is_array($folderStatus)) {
3286
			if ($_returnObject===false) return $folderStatus;
3287
			$status =  new \stdClass;
3288
			$status->messages   = $folderStatus['MESSAGES'];
3289
			$status->unseen     = $folderStatus['UNSEEN'];
3290
			$status->recent     = $folderStatus['RECENT'];
3291
			$status->uidnext        = $folderStatus['UIDNEXT'];
3292
			$status->uidvalidity    = $folderStatus['UIDVALIDITY'];
3293
3294
			return $status;
3295
		}
3296
		return false;
3297
	}
3298
3299
	/**
3300
	 * getMailBoxesRecursive
3301
	 *
3302
	 * function to retrieve mailboxes recursively from given mailbox
3303
	 * @param string $_mailbox
3304
	 * @param string $delimiter
3305
	 * @param string $prefix
3306
	 * @param string $reclevel 0, counter to keep track of the current recursionlevel
3307
	 * @return array of mailboxes
3308
	 */
3309 View Code Duplication
	function getMailBoxesRecursive($_mailbox, $delimiter, $prefix, $reclevel=0)
3310
	{
3311
		#echo __METHOD__." retrieve SubFolders for $_mailbox$delimiter <br>";
3312
		$maxreclevel=25;
3313
		if ($reclevel > $maxreclevel) {
3314
			error_log( __METHOD__." Recursion Level Exeeded ($reclevel) while looking up $_mailbox$delimiter ");
3315
			return array();
3316
		}
3317
		$reclevel++;
3318
		// clean up double delimiters
3319
		$_mailbox = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$_mailbox);
3320
		//get that mailbox in question
3321
		$mbx = $this->icServer->getMailboxes($_mailbox,1,true);
3322
		$mbxkeys = array_keys($mbx);
3323
		#_debug_array($mbx);
3324
//error_log(__METHOD__.' ('.__LINE__.') '.' Delimiter:'.array2string($delimiter));
3325
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
3326
		// Example: Array([INBOX/GaGa] => Array([MAILBOX] => INBOX/GaGa[ATTRIBUTES] => Array([0] => \\unmarked)[delimiter] => /))
3327
		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"]))) {
3328
			// if there are children fetch them
3329
			//echo $mbx[$mbxkeys[0]]['MAILBOX']."<br>";
3330
3331
			$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'].($mbx[$mbxkeys[0]]['MAILBOX'] == $prefix ? '':$delimiter),2,false);
3332
			//$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'],2,false);
3333
			//_debug_array($buff);
3334
			$allMailboxes = array();
3335
			foreach ($buff as $mbxname) {
3336
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbxname));
3337
				$mbxname = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$mbxname['MAILBOX']);
3338
				#echo "About to recur in level $reclevel:".$mbxname."<br>";
3339
				if ( $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'] && $mbxname != $prefix  && $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'].$delimiter)
3340
				{
3341
					$allMailboxes = array_merge($allMailboxes, self::getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel));
3342
				}
3343
			}
3344
			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'];
3345
			return $allMailboxes;
3346
		} else {
3347
			return array($_mailbox);
3348
		}
3349
	}
3350
3351
	/**
3352
	 * _getSpecialUseFolder
3353
	 * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder
3354
	 * @param string $_type the type to fetch (Drafts|Template|Trash|Sent)
3355
	 * @param boolean $_checkexistance trigger check for existance
3356
	 * @param boolean& $created =null on return true: if folder was just created, false if not
3357
	 * @return mixed string or false
3358
	 */
3359
	function _getSpecialUseFolder($_type, $_checkexistance=TRUE, &$created=null)
3360
	{
3361
		static $types = array(
3362
			'Drafts'   => array('profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'),
3363
			'Template' => array('profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'),
3364
			'Trash'    => array('profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'),
3365
			'Sent'     => array('profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'),
3366
			'Junk'     => array('profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'),
3367
			'Outbox'   => array('profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'),
3368
			'Archive'   => array('profileKey'=>'acc_folder_archive','autoFolderName'=>'Archive'),
3369
		);
3370
		if ($_type == 'Templates') $_type = 'Template';	// for some reason self::$autofolders uses 'Templates'!
3371
		$created = false;
3372
		if (!isset($types[$_type]))
3373
		{
3374
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$_type.' not supported for '.__METHOD__);
3375
			return false;
3376
		}
3377
		if (is_null(self::$specialUseFolders) || empty(self::$specialUseFolders)) self::$specialUseFolders = $this->getSpecialUseFolders();
3378
3379
		//highest precedence
3380
		try
3381
		{
3382
			$_folderName = $this->icServer->$types[$_type]['profileKey'];
3383
		}
3384
		catch (\Exception $e)
3385
		{
3386
			// we know that outbox is not supported, but we use this here, as we autocreate expected SpecialUseFolders in this function
3387
			if ($_type != 'Outbox') error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve Folder'.$_folderName." for ".array2string($types[$_type]).":".$e->getMessage());
3388
			$_folderName = false;
3389
		}
3390
		// do not try to autocreate configured Archive-Folder. Return false if configured folder does not exist
3391
		if ($_type == 'Archive') {
3392
			if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3393
				return false;
3394
			} else {
3395
				return $_folderName;
3396
			}
3397
3398
		}
3399
		// does the folder exist??? (is configured/preset, but non-existent)
3400
		if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3401
			try
3402
			{
3403
				$error = null;
3404
				if (($_folderName = $this->createFolder('', $_folderName, $error))) $created = true;
3405 View Code Duplication
				if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3406
			}
3407
			catch(Exception $e)
3408
			{
3409
				error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage().':'.function_backtrace());
3410
				$_folderName = false;
3411
			}
3412
		}
3413
		// not sure yet if false is the correct behavior on none
3414
		if ($_folderName =='none') return 'none' ; //false;
3415
		//no (valid) folder found yet; try specialUseFolders
3416
		if (empty($_folderName) && is_array(self::$specialUseFolders) && ($f = array_search($_type,self::$specialUseFolders))) $_folderName = $f;
3417
		//no specialUseFolder; try some Defaults
3418
		if (empty($_folderName) && isset($types[$_type]))
3419
		{
3420
			$nameSpace = $this->_getNameSpaces();
3421
			$prefix='';
3422
			foreach ($nameSpace as $nSp)
3423
			{
3424
				if ($nSp['type']=='personal')
3425
				{
3426
					//error_log(__METHOD__.__LINE__.array2string($nSp));
3427
					$prefix = $nSp['prefix'];
3428
					break;
3429
				}
3430
			}
3431
			if ($this->folderExists($prefix.$types[$_type]['autoFolderName'],true))
3432
			{
3433
				$_folderName = $prefix.$types[$_type]['autoFolderName'];
3434
			}
3435
			else
3436
			{
3437
				try
3438
				{
3439
					$error = null;
3440
					$this->createFolder('', $prefix.$types[$_type]['autoFolderName'],$error);
3441
					$_folderName = $prefix.$types[$_type]['autoFolderName'];
3442 View Code Duplication
					if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3443
				}
3444
				catch(Exception $e)
3445
				{
3446
					error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage());
3447
					$_folderName = false;
3448
				}
3449
			}
3450
		}
3451
		return $_folderName;
3452
	}
3453
3454
	/**
3455
	 * getFolderByType wrapper for _getSpecialUseFolder Type as param
3456
	 * @param string $type foldertype to look for
3457
	 * @param boolean $_checkexistance trigger check for existance
3458
	 * @return mixed string or false
3459
	 */
3460
	function getFolderByType($type, $_checkexistance=false)
3461
	{
3462
		return $this->_getSpecialUseFolder($type, $_checkexistance);
3463
	}
3464
3465
	/**
3466
	 * getJunkFolder wrapper for _getSpecialUseFolder Type Junk
3467
	 * @param boolean $_checkexistance trigger check for existance
3468
	 * @return mixed string or false
3469
	 */
3470
	function getJunkFolder($_checkexistance=TRUE)
3471
	{
3472
		return $this->_getSpecialUseFolder('Junk', $_checkexistance);
3473
	}
3474
3475
	/**
3476
	 * getDraftFolder wrapper for _getSpecialUseFolder Type Drafts
3477
	 * @param boolean $_checkexistance trigger check for existance
3478
	 * @return mixed string or false
3479
	 */
3480
	function getDraftFolder($_checkexistance=TRUE)
3481
	{
3482
		return $this->_getSpecialUseFolder('Drafts', $_checkexistance);
3483
	}
3484
3485
	/**
3486
	 * getTemplateFolder wrapper for _getSpecialUseFolder Type Template
3487
	 * @param boolean $_checkexistance trigger check for existance
3488
	 * @return mixed string or false
3489
	 */
3490
	function getTemplateFolder($_checkexistance=TRUE)
3491
	{
3492
		return $this->_getSpecialUseFolder('Template', $_checkexistance);
3493
	}
3494
3495
	/**
3496
	 * getTrashFolder wrapper for _getSpecialUseFolder Type Trash
3497
	 * @param boolean $_checkexistance trigger check for existance
3498
	 * @return mixed string or false
3499
	 */
3500
	function getTrashFolder($_checkexistance=TRUE)
3501
	{
3502
		return $this->_getSpecialUseFolder('Trash', $_checkexistance);
3503
	}
3504
3505
	/**
3506
	 * getSentFolder wrapper for _getSpecialUseFolder Type Sent
3507
	 * @param boolean $_checkexistance trigger check for existance
3508
	 * @return mixed string or false
3509
	 */
3510
	function getSentFolder($_checkexistance=TRUE)
3511
	{
3512
		return $this->_getSpecialUseFolder('Sent', $_checkexistance);
3513
	}
3514
3515
	/**
3516
	 * getOutboxFolder wrapper for _getSpecialUseFolder Type Outbox
3517
	 * @param boolean $_checkexistance trigger check for existance
3518
	 * @return mixed string or false
3519
	 */
3520
	function getOutboxFolder($_checkexistance=TRUE)
3521
	{
3522
		return $this->_getSpecialUseFolder('Outbox', $_checkexistance);
3523
	}
3524
3525
	/**
3526
	 * getArchiveFolder wrapper for _getSpecialUseFolder Type Archive
3527
	 * @param boolean $_checkexistance trigger check for existance . We do no autocreation for configured Archive folder
3528
	 * @return mixed string or false
3529
	 */
3530
	function getArchiveFolder($_checkexistance=TRUE)
3531
	{
3532
		return $this->_getSpecialUseFolder('Archive', $_checkexistance);
3533
	}
3534
3535
	/**
3536
	 * isSentFolder is the given folder the sent folder or at least a subfolder of it
3537
	 * @param string $_folderName folder to perform the check on
3538
	 * @param boolean $_checkexistance trigger check for existance
3539
	 * @return boolean
3540
	 */
3541 View Code Duplication
	function isSentFolder($_folderName, $_checkexistance=TRUE)
3542
	{
3543
		$sentFolder = $this->getSentFolder($_checkexistance);
3544
		if(empty($sentFolder)) {
3545
			return false;
3546
		}
3547
		// does the folder exist???
3548
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3549
			return false;
3550
		}
3551
3552
		if(false !== stripos($_folderName, $sentFolder)) {
3553
			return true;
3554
		} else {
3555
			return false;
3556
		}
3557
	}
3558
3559
	/**
3560
	 * checks if the Outbox folder exists and is part of the foldername to be checked
3561
	 * @param string $_folderName folder to perform the check on
3562
	 * @param boolean $_checkexistance trigger check for existance
3563
	 * @return boolean
3564
	 */
3565
	function isOutbox($_folderName, $_checkexistance=TRUE)
3566
	{
3567
		if (stripos($_folderName, 'Outbox')===false) {
3568
			return false;
3569
		}
3570
		// does the folder exist???
3571
		if ($_checkexistance && $GLOBALS['egw_info']['user']['apps']['activesync'] && !$this->folderExists($_folderName)) {
3572
			$outboxFolder = $this->getOutboxFolder($_checkexistance);
3573
			if(false !== stripos($_folderName, $outboxFolder)) {
3574
				return true;
3575
			} else {
3576
				return false;
3577
			}
3578
		}
3579
		return true;
3580
	}
3581
3582
	/**
3583
	 * isDraftFolder is the given folder the sent folder or at least a subfolder of it
3584
	 * @param string $_folderName folder to perform the check on
3585
	 * @param boolean $_checkexistance trigger check for existance
3586
	 * @return boolean
3587
	 */
3588
	function isDraftFolder($_folderName, $_checkexistance=TRUE)
3589
	{
3590
		$draftFolder = $this->getDraftFolder($_checkexistance);
3591
		if(empty($draftFolder)) {
3592
			return false;
3593
		}
3594
		// does the folder exist???
3595
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3596
			return false;
3597
		}
3598
		if (is_a($_folderName,"Horde_Imap_Client_Mailbox")) $_folderName = $_folderName->utf8;
3599
		if(false !== stripos($_folderName, $draftFolder)) {
3600
			return true;
3601
		} else {
3602
			return false;
3603
		}
3604
	}
3605
3606
	/**
3607
	 * isTrashFolder is the given folder the sent folder or at least a subfolder of it
3608
	 * @param string $_folderName folder to perform the check on
3609
	 * @param boolean $_checkexistance trigger check for existance
3610
	 * @return boolean
3611
	 */
3612 View Code Duplication
	function isTrashFolder($_folderName, $_checkexistance=TRUE)
3613
	{
3614
		$trashFolder = $this->getTrashFolder($_checkexistance);
3615
		if(empty($trashFolder)) {
3616
			return false;
3617
		}
3618
		// does the folder exist???
3619
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3620
			return false;
3621
		}
3622
3623
		if(false !== stripos($_folderName, $trashFolder)) {
3624
			return true;
3625
		} else {
3626
			return false;
3627
		}
3628
	}
3629
3630
	/**
3631
	 * isTemplateFolder is the given folder the sent folder or at least a subfolder of it
3632
	 * @param string $_folderName folder to perform the check on
3633
	 * @param boolean $_checkexistance trigger check for existance
3634
	 * @return boolean
3635
	 */
3636 View Code Duplication
	function isTemplateFolder($_folderName, $_checkexistance=TRUE)
3637
	{
3638
		$templateFolder = $this->getTemplateFolder($_checkexistance);
3639
		if(empty($templateFolder)) {
3640
			return false;
3641
		}
3642
		// does the folder exist???
3643
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3644
			return false;
3645
		}
3646
3647
		if(false !== stripos($_folderName, $templateFolder)) {
3648
			return true;
3649
		} else {
3650
			return false;
3651
		}
3652
	}
3653
3654
	/**
3655
	 * folderExists checks for existance of a given folder
3656
	 * @param string $_folder folder to perform the check on
3657
	 * @param boolean $_forceCheck trigger check for existance on icServer
3658
	 * @return mixed string or false
3659
	 */
3660
	function folderExists($_folder, $_forceCheck=false)
3661
	{
3662
		static $folderInfo;
3663
		$forceCheck = $_forceCheck;
3664
		if (empty($_folder))
3665
		{
3666
			// this error is more or less without significance, unless we force the check
3667
			if ($_forceCheck===true) error_log(__METHOD__.' ('.__LINE__.') '.' Called with empty Folder:'.$_folder.function_backtrace());
3668
			return false;
3669
		}
3670
		// when check is not enforced , we assume a folder represented as Horde_Imap_Client_Mailbox as existing folder
3671
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")&&$_forceCheck===false) return true;
3672
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")) $_folder =  $_folder->utf8;
3673
		// reduce traffic within the Instance per User; Expire every 5 hours
3674
		//error_log(__METHOD__.' ('.__LINE__.') '.' Called with Folder:'.$_folder.function_backtrace());
3675
		if (is_null($folderInfo)) $folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*5);
3676
		//error_log(__METHOD__.' ('.__LINE__.') '.'Cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.($forceCheck?'(forcedCheck)':'').':'.array2string($folderInfo));
3677
		if (!empty($folderInfo) && isset($folderInfo[$this->profileID]) && isset($folderInfo[$this->profileID][$_folder]) && $forceCheck===false)
3678
		{
3679
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID);
3680
			return $folderInfo[$this->profileID][$_folder];
3681
		}
3682
		else
3683
		{
3684
			if ($forceCheck === false)
3685
			{
3686
				//error_log(__METHOD__.' ('.__LINE__.') '.' No cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.' FolderExistsInfoCache:'.array2string($folderInfo[$this->profileID]));
3687
				$forceCheck = true; // try to force the check, in case there is no connection, we may need that
3688
			}
3689
		}
3690
3691
		// does the folder exist???
3692
		//error_log(__METHOD__."->Connected?".$this->icServer->_connected.", ".$_folder.", ".($forceCheck?' forceCheck activated':'dont check on server'));
3693
		if ( $forceCheck || empty($folderInfo) || !isset($folderInfo[$this->profileID]) || !isset($folderInfo[$this->profileID][$_folder])) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
3694
			//error_log(__METHOD__."->NotConnected and forceCheck with profile:".$this->profileID);
3695
			//return false;
3696
			//try to connect
3697
		}
3698
		try
3699
		{
3700
			$folderInfo[$this->profileID][$_folder] = $this->icServer->mailboxExist($_folder);
3701
		}
3702
		catch (\Exception $e)
3703
		{
3704
			error_log(__METHOD__.__LINE__.$e->getMessage().($e->details?', '.$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3705
			self::$profileDefunct[$this->profileID]=true;
3706
			$folderInfo[$this->profileID][$_folder] = false;
3707
		}
3708
		//error_log(__METHOD__.' ('.__LINE__.') '.' Folder Exists:'.$folderInfo[$this->profileID][$_folder].function_backtrace());
3709
3710
		if(!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) &&
3711
			$folderInfo[$this->profileID][$_folder] !== true)
3712
		{
3713
			$folderInfo[$this->profileID][$_folder] = false; // set to false, whatever it was (to have a valid returnvalue for the static return)
3714
		}
3715
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*60*5);
3716
		return (!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) ? $folderInfo[$this->profileID][$_folder] : false);
3717
	}
3718
3719
	/**
3720
	 * remove any messages which are marked as deleted or
3721
	 * remove any messages from the trashfolder
3722
	 *
3723
	 * @param string _folderName the foldername
3724
	 * @return nothing
3725
	 */
3726
	function compressFolder($_folderName = false)
3727
	{
3728
		$folderName	= ($_folderName ? $_folderName : $this->sessionData['mailbox']);
3729
		$deleteOptions	= $GLOBALS['egw_info']['user']['preferences']['mail']['deleteOptions'];
3730
		$trashFolder	= $this->getTrashFolder();
3731
3732
		$this->icServer->openMailbox($folderName);
3733
3734
		if(strtolower($folderName) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") {
3735
			$this->deleteMessages('all',$folderName,'remove_immediately');
3736
		} else {
3737
			$this->icServer->expunge($folderName);
3738
		}
3739
	}
3740
3741
	/**
3742
	 * delete a Message
3743
	 *
3744
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
3745
	 * @param string _folder foldername
3746
	 * @param string _forceDeleteMethod - "no", or deleteMethod like 'move_to_trash',"mark_as_deleted","remove_immediately"
3747
	 *
3748
	 * @return bool true, as we do not handle return values yet
3749
	 * @throws Exception
3750
	 */
3751
	function deleteMessages($_messageUID, $_folder=NULL, $_forceDeleteMethod='no')
3752
	{
3753
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.array2string($_folder).', '.$_forceDeleteMethod);
3754
		$oldMailbox = '';
3755
		if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
3756 View Code Duplication
		if (empty($_messageUID))
3757
		{
3758
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3759
			return false;
3760
		}
3761
		elseif ($_messageUID==='all')
3762
		{
3763
			$_messageUID= null;
3764
		}
3765
		else
3766
		{
3767
			$uidsToDelete = new Horde_Imap_Client_Ids();
3768
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3769
			$uidsToDelete->add($_messageUID);
3770
		}
3771
		$deleteOptions = $_forceDeleteMethod; // use forceDeleteMethod if not "no", or unknown method
3772
		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");
3773
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3774
		$trashFolder    = $this->getTrashFolder();
3775
		$draftFolder	= $this->getDraftFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['draftFolder'];
3776
		$templateFolder = $this->getTemplateFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['templateFolder'];
3777
		if((strtolower($_folder) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") ||
3778
		   (strtolower($_folder) == strtolower($draftFolder))) {
3779
			$deleteOptions = "remove_immediately";
3780
		}
3781
		if($this->icServer->getCurrentMailbox() != $_folder) {
3782
			$oldMailbox = $this->icServer->getCurrentMailbox();
3783
			$this->icServer->openMailbox($_folder);
3784
		}
3785
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3786
		$updateCache = false;
3787
		switch($deleteOptions) {
3788
			case "move_to_trash":
3789
				//error_log(__METHOD__.' ('.__LINE__.') ');
3790
				$updateCache = true;
3791
				if(!empty($trashFolder)) {
3792
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.implode(' : ', $_messageUID));
3793
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$trashFolder <= $_folder / ". $this->sessionData['mailbox']);
3794
					// copy messages
3795
					try
3796
					{
3797
						$this->icServer->copy($_folder, $trashFolder, array('ids'=>$uidsToDelete,'move'=>true));
3798
					}
3799
					catch (\Exception $e)
3800
					{
3801
						throw new Exception("Failed to move Messages (".array2string($uidsToDelete).") from Folder $_folder to $trashFolder Error:".$e->getMessage());
3802
					}
3803
				}
3804
				break;
3805
3806
			case "mark_as_deleted":
3807
				//error_log(__METHOD__.' ('.__LINE__.') ');
3808
				// mark messages as deleted
3809
				if (is_null($_messageUID)) $_messageUID='all';
3810
				foreach((array)$_messageUID as $key =>$uid)
3811
				{
3812
					//flag messages, that are flagged for deletion as seen too
3813
					$this->flagMessages('read', $uid, $_folder);
3814
					$flags = $this->getFlags($uid);
3815
					$this->flagMessages('delete', $uid, $_folder);
3816
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
3817
					if (strpos( array2string($flags),'Deleted')!==false) $undelete[] = $uid;
3818
					unset($flags);
3819
				}
3820
				foreach((array)$undelete as $key =>$uid)
3821
				{
3822
					$this->flagMessages('undelete', $uid, $_folder);
3823
				}
3824
				break;
3825
3826
			case "remove_immediately":
3827
				//error_log(__METHOD__.' ('.__LINE__.') ');
3828
				$updateCache = true;
3829
				if (is_null($_messageUID)) $_messageUID='all';
3830
				if (is_object($_messageUID))
3831
				{
3832
					$this->flagMessages('delete', $_messageUID, $_folder);
3833
				}
3834
				else
3835
				{
3836
					foreach((array)$_messageUID as $key =>$uid)
3837
					{
3838
						//flag messages, that are flagged for deletion as seen too
3839
						$this->flagMessages('delete', $uid, $_folder);
3840
					}
3841
				}
3842
				// delete the messages finaly
3843
				$this->icServer->expunge($_folder);
3844
				break;
3845
		}
3846
		if($oldMailbox != '') {
3847
			$this->icServer->openMailbox($oldMailbox);
3848
		}
3849
3850
		return true;
3851
	}
3852
3853
	/**
3854
	 * get flags for a Message
3855
	 *
3856
	 * @param mixed string _messageUID array of id to retrieve the flags for
3857
	 *
3858
	 * @return null/array flags
3859
	 */
3860
	function getFlags ($_messageUID) {
3861
		try
3862
		{
3863
			$uidsToFetch = new Horde_Imap_Client_Ids();
3864
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3865
			$uidsToFetch->add($_messageUID);
3866
			$_folderName = $this->icServer->getCurrentMailbox();
3867
			$fquery = new Horde_Imap_Client_Fetch_Query();
3868
			$fquery->flags();
3869
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
3870
				'ids' => $uidsToFetch,
3871
			));
3872
			if (is_object($headersNew)) {
3873
				foreach($headersNew->ids() as $id) {
3874
					$_headerObject = $headersNew->get($id);
3875
					$flags = $_headerObject->getFlags();
3876
				}
3877
			}
3878
		}
3879
		catch (\Exception $e)
3880
		{
3881
			error_log(__METHOD__.' ('.__LINE__.') '."Failed to fetch flags for ".array2string($_messageUID)." Error:".$e->getMessage());
3882
			return null;
3883
			//throw new Exception("Failed to fetch flags for ".array2string($_messageUID)" Error:".$e->getMessage());
3884
		}
3885
		return $flags;
3886
	}
3887
3888
	/**
3889
	 * get and parse the flags response for the Notifyflag for a Message
3890
	 *
3891
	 * @param string _messageUID array of id to retrieve the flags for
3892
	 * @param array flags - to avoid additional server call
3893
	 *
3894
	 * @return null/boolean
3895
	 */
3896
	function getNotifyFlags ($_messageUID, $flags=null)
3897
	{
3898
		if (self::$debug) error_log(__METHOD__.$_messageUID.' Flags:'.array2string($flags));
3899
		try
3900
		{
3901
			if($flags===null) $flags =  $this->getFlags($_messageUID);
3902
		}
3903
		catch (\Exception $e)
3904
		{
3905
			return null;
3906
		}
3907
3908
		if ( stripos( array2string($flags),'MDNSent')!==false)
3909
			return true;
3910
3911
		if ( stripos( array2string($flags),'MDNnotSent')!==false)
3912
			return false;
3913
3914
		return null;
3915
	}
3916
3917
	/**
3918
	 * flag a Message
3919
	 *
3920
	 * @param string _flag (readable name)
3921
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
3922
	 * @param string _folder foldername
3923
	 *
3924
	 * @todo handle handle icserver->setFlags returnValue
3925
	 *
3926
	 * @return bool true, as we do not handle icserver->setFlags returnValue
3927
	 */
3928
	function flagMessages($_flag, $_messageUID,$_folder=NULL)
3929
	{
3930
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']);
3931
		if (empty($_messageUID))
3932
		{
3933
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3934
			return false;
3935
		}
3936
		$this->icServer->openMailbox(($_folder?$_folder:$this->sessionData['mailbox']));
3937
		$folder = $this->icServer->getCurrentMailbox();
3938
		if (is_array($_messageUID)&& count($_messageUID)>50)
3939
		{
3940
			$count = $this->getMailBoxCounters($folder,true);
3941
			if ($count->messages == count($_messageUID)) $_messageUID='all';
3942
		}
3943
3944
		if ($_messageUID==='all')
3945
		{
3946
			$messageUIDs = array('all');
3947
		}
3948
		else
3949
		{
3950
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3951
			$messageUIDs = array_chunk($_messageUID,50,true);
3952
		}
3953
		try
3954
		{
3955
			foreach($messageUIDs as &$uids)
3956
			{
3957
				if ($uids==='all')
3958
				{
3959
					$uidsToModify=null;
3960
				}
3961
				else
3962
				{
3963
					$uidsToModify = new Horde_Imap_Client_Ids();
3964
					$uidsToModify->add($uids);
3965
				}
3966
				switch($_flag) {
3967 View Code Duplication
					case "delete":
3968
						$ret = $this->icServer->store($folder, array('add'=>array('\\Deleted'), 'ids'=> $uidsToModify));
3969
						break;
3970 View Code Duplication
					case "undelete":
3971
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Deleted'), 'ids'=> $uidsToModify));
3972
						break;
3973 View Code Duplication
					case "flagged":
3974
						$ret = $this->icServer->store($folder, array('add'=>array('\\Flagged'), 'ids'=> $uidsToModify));
3975
						break;
3976
					case "read":
3977 View Code Duplication
					case "seen":
3978
						$ret = $this->icServer->store($folder, array('add'=>array('\\Seen'), 'ids'=> $uidsToModify));
3979
						break;
3980 View Code Duplication
					case "forwarded":
3981
						$ret = $this->icServer->store($folder, array('add'=>array('$Forwarded'), 'ids'=> $uidsToModify));
3982 View Code Duplication
					case "answered":
3983
						$ret = $this->icServer->store($folder, array('add'=>array('\\Answered'), 'ids'=> $uidsToModify));
3984
						break;
3985 View Code Duplication
					case "unflagged":
3986
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Flagged'), 'ids'=> $uidsToModify));
3987
						break;
3988
					case "unread":
3989 View Code Duplication
					case "unseen":
3990
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Seen','\\Answered','$Forwarded'), 'ids'=> $uidsToModify));
3991
						break;
3992 View Code Duplication
					case "mdnsent":
3993
						$ret = $this->icServer->store($folder, array('add'=>array('MDNSent'), 'ids'=> $uidsToModify));
3994
						break;
3995 View Code Duplication
					case "mdnnotsent":
3996
						$ret = $this->icServer->store($folder, array('add'=>array('MDNnotSent'), 'ids'=> $uidsToModify));
3997
						break;
3998
					case "label1":
3999 View Code Duplication
					case "labelone":
4000
						$ret = $this->icServer->store($folder, array('add'=>array('$label1'), 'ids'=> $uidsToModify));
4001
						break;
4002
					case "unlabel1":
4003 View Code Duplication
					case "unlabelone":
4004
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4005
						break;
4006
					case "label2":
4007 View Code Duplication
					case "labeltwo":
4008
						$ret = $this->icServer->store($folder, array('add'=>array('$label2'), 'ids'=> $uidsToModify));
4009
						break;
4010
					case "unlabel2":
4011 View Code Duplication
					case "unlabeltwo":
4012
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4013
						break;
4014
					case "label3":
4015 View Code Duplication
					case "labelthree":
4016
						$ret = $this->icServer->store($folder, array('add'=>array('$label3'), 'ids'=> $uidsToModify));
4017
						break;
4018
					case "unlabel3":
4019 View Code Duplication
					case "unlabelthree":
4020
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4021
						break;
4022
					case "label4":
4023 View Code Duplication
					case "labelfour":
4024
						$ret = $this->icServer->store($folder, array('add'=>array('$label4'), 'ids'=> $uidsToModify));
4025
						break;
4026
					case "unlabel4":
4027 View Code Duplication
					case "unlabelfour":
4028
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4029
						break;
4030
					case "label5":
4031 View Code Duplication
					case "labelfive":
4032
						$ret = $this->icServer->store($folder, array('add'=>array('$label5'), 'ids'=> $uidsToModify));
4033
						break;
4034
					case "unlabel5":
4035 View Code Duplication
					case "unlabelfive":
4036
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4037
						break;
4038
					case "unlabel":
4039
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4040
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4041
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4042
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4043
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4044
						break;
4045
				}
4046
			}
4047
		}
4048
		catch(Exception $e)
4049
		{
4050
			error_log(__METHOD__.__LINE__.' Error, could not flag messages in folder '.$folder.' Reason:'.$e->getMessage());
4051
		}
4052
		if ($folder instanceof Horde_Imap_Client_Mailbox) $_folder = $folder->utf8;
0 ignored issues
show
Bug introduced by
The class EGroupware\Api\Horde_Imap_Client_Mailbox does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
4053
		//error_log(__METHOD__.__LINE__.'#'.$this->icServer->ImapServerId.'#'.array2string($_folder).'#');
4054
		self::$folderStatusCache[$this->icServer->ImapServerId][(!empty($_folder)?$_folder: $this->sessionData['mailbox'])]['uidValidity'] = 0;
4055
4056
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
4057
		return true; // as we do not catch/examine setFlags returnValue
4058
	}
4059
4060
	/**
4061
	 * move Message(s)
4062
	 *
4063
	 * @param string _foldername target folder
4064
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
4065
	 * @param boolean $deleteAfterMove - decides if a mail is moved (true) or copied (false)
4066
	 * @param string $currentFolder
4067
	 * @param boolean $returnUIDs - control wether or not the action called should return the new uids
4068
	 *						caveat: not all servers do support that
4069
	 * @param int $_sourceProfileID - source profile ID, should be handed over, if not $this->icServer->ImapServerId is used
4070
	 * @param int $_targetProfileID - target profile ID, should only be handed over when target server is different from source
4071
	 *
4072
	 * @return mixed/bool true,false or new uid
4073
	 * @throws Exception
4074
	 */
4075
	function moveMessages($_foldername, $_messageUID, $deleteAfterMove=true, $currentFolder = Null, $returnUIDs = false, $_sourceProfileID = Null, $_targetProfileID = Null)
4076
	{
4077
		$source = Mail\Account::read(($_sourceProfileID?$_sourceProfileID:$this->icServer->ImapServerId))->imapServer();
4078
		//$deleteOptions  = $GLOBALS['egw_info']["user"]["preferences"]["mail"]["deleteOptions"];
4079 View Code Duplication
		if (empty($_messageUID))
4080
		{
4081
			if (self::$debug) error_log(__METHOD__." no Message(s): ".implode(',',$_messageUID));
4082
			return false;
4083
		}
4084
		elseif ($_messageUID==='all')
4085
		{
4086
			//error_log(__METHOD__." all Message(s): ".implode(',',$_messageUID));
4087
			$uidsToMove= null;
4088
		}
4089
		else
4090
		{
4091
			//error_log(__METHOD__." Message(s): ".implode(',',$_messageUID));
4092
			$uidsToMove = new Horde_Imap_Client_Ids();
4093
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4094
			$uidsToMove->add($_messageUID);
4095
		}
4096
		$sourceFolder = (!empty($currentFolder)?$currentFolder: $this->sessionData['mailbox']);
4097
		//error_log(__METHOD__.__LINE__."$_targetProfileID !== ".array2string($source->ImapServerId));
4098
		if (!is_null($_targetProfileID) && $_targetProfileID !== $source->ImapServerId)
4099
		{
4100
			$sourceFolder = $source->getMailbox($sourceFolder);
4101
			$source->openMailbox($sourceFolder);
4102
			$uidsToFetch = new Horde_Imap_Client_Ids();
4103
			$uidsToFetch->add($_messageUID);
4104
			$fquery = new Horde_Imap_Client_Fetch_Query();
4105
			$fquery->flags();
4106
			$fquery->headerText(array('peek'=>true));
4107
			$fquery->fullText(array('peek'=>true));
4108
			$fquery->imapDate();
4109
			$headersNew = $source->fetch($sourceFolder, $fquery, array(
4110
				'ids' => $uidsToFetch,
4111
			));
4112
4113
			//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' mailheaders:'.array2string($headersNew));
4114
4115
			if (is_object($headersNew)) {
4116
				$c=0;
4117
				$retUid = new Horde_Imap_Client_Ids();
4118
				// we copy chunks of 5 to avoid too much memory and/or server stress
4119
				// some servers seem not to allow/support the appendig of multiple messages. so we are down to one
4120
				foreach($headersNew as &$_headerObject) {
4121
					$c++;
4122
					$flags = $_headerObject->getFlags(); //unseen status seems to be lost when retrieving the full message
4123
					$date = $_headerObject->getImapDate();
4124
					$currentDate =  new Horde_Imap_Client_DateTime();
4125
					// if the internal Date of the message equals the current date; try using the header date
4126
					if ($date==$currentDate)
4127
					{
4128
						$headerForPrio = array_change_key_case($_headerObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray(), CASE_UPPER);
4129
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#'.$headerForPrio['DATE']);
4130
						$date = new Horde_Imap_Client_DateTime($headerForPrio['DATE']);
4131
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#');
4132
					}
4133
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject)));
4134
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
4135
					$body = $_headerObject->getFullMsg();
4136
					$dataNflags[] = array('data'=>$body, 'flags'=>$flags, 'internaldate'=>$date);
4137
					if ($c==1)
4138
					{
4139
						$target = Mail\Account::read($_targetProfileID)->imapServer();
4140
						//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($_foldername));
4141
						$foldername = $target->getMailbox($_foldername);
4142
						// make sure the target folder is open and ready
4143
						$target->openMailbox($foldername);
4144
						$ret = $target->append($foldername,$dataNflags);
4145
						$retUid->add($ret);
4146
						unset($dataNflags);
4147
						// sleep 500 miliseconds; AS some sERVERs seem not to be capable of the load this is
4148
						// inflicting in them. they "reply" with an unspecific IMAP Error
4149
						time_nanosleep(0,500000);
4150
						$c=0;
4151
					}
4152
				}
4153
				if (isset($dataNflags))
4154
				{
4155
					$target = Mail\Account::read($_targetProfileID)->imapServer();
4156
					//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($foldername));
4157
					$foldername = $target->getMailbox($_foldername);
4158
					// make sure the target folder is open and ready
4159
					$target->openMailbox($foldername);
4160
					$ret = $target->append($foldername,$dataNflags);
4161
					$retUid->add($ret);
4162
					unset($dataNflags);
4163
				}
4164
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4165
				// make sure we are back to source
4166
				$source->openMailbox($sourceFolder);
4167
				if ($deleteAfterMove)
4168
				{
4169
					$remember = $this->icServer;
4170
					$this->icServer = $source;
4171
					$this->deleteMessages($_messageUID, $sourceFolder, $_forceDeleteMethod='remove_immediately');
4172
					$this->icServer = $remember;
4173
				}
4174
			}
4175
		}
4176
		else
4177
		{
4178
			try
4179
			{
4180
				$retUid = $source->copy($sourceFolder, $_foldername, array('ids'=>$uidsToMove,'move'=>$deleteAfterMove));
4181
			}
4182
			catch (exception $e)
4183
			{
4184
				error_log(__METHOD__.' ('.__LINE__.') '."Copying to Folder $_foldername failed! Error:".$e->getMessage());
4185
				throw new Exception("Copying to Folder $_foldername failed! Error:".$e->getMessage());
4186
			}
4187
		}
4188
4189
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4190
		return ($returnUIDs ? $retUid : true);
4191
	}
4192
4193
	/**
4194
	 * Parse dates, also handle wrong or unrecognized timezones, falling back to current time
4195
	 *
4196
	 * @param string $_date to be parsed/formatted
4197
	 * @param string $format ='' if none is passed, use user prefs
4198
	 * @return string returns the date as it is parseable by strtotime, or current timestamp if everything fails
4199
	 */
4200
	static function _strtotime($_date='', $format='', $convert2usertime=false)
4201
	{
4202
		try {
4203
			$date = new DateTime($_date);	// parse date & time including timezone (throws exception, if not parsable)
4204
			if ($convert2usertime) $date->setUser();	// convert to user-time
4205
			$date2return = $date->format($format);
4206
		}
4207
		catch(\Exception $e)
4208
		{
4209
			unset($e);	// not used
4210
4211
			// remove last space-separated part and retry
4212
			$parts = explode(' ',$_date);
4213
			if (count($parts) > 1)
4214
			{
4215
				array_pop($parts);
4216
				$date2return = self::_strtotime(implode(' ', $parts), $format, $convert2usertime);
4217
			}
4218
			else	// not last part, use current time
4219
			{
4220
				$date2return = DateTime::to('now', $format);
4221
			}
4222
		}
4223
		return $date2return;
4224
	}
4225
4226
	/**
4227
	 * htmlentities
4228
	 * helperfunction to cope with wrong encoding in strings
4229
	 * @param string $_string  input to be converted
4230
	 * @param mixed $_charset false or string -> Target charset, if false Mail displayCharset will be used
4231
	 * @return string
4232
	 */
4233
	static function htmlentities($_string, $_charset=false)
4234
	{
4235
		//setting the charset (if not given)
4236
		if ($_charset===false) $_charset = self::$displayCharset;
4237
		$string = @htmlentities($_string, ENT_QUOTES, $_charset, false);
4238
		if (empty($string) && !empty($_string)) $string = @htmlentities(Translation::convert($_string,Translation::detect_encoding($_string),$_charset),ENT_QUOTES | ENT_IGNORE,$_charset, false);
4239
		return $string;
4240
	}
4241
4242
	/**
4243
	 * clean a message from elements regarded as potentially harmful
4244
	 * param string/reference $_html is the text to be processed
4245
	 * return nothing
4246
	 */
4247
	static function getCleanHTML(&$_html)
4248
	{
4249
		// remove CRLF and TAB as it is of no use in HTML.
4250
		// but they matter in <pre>, so we rather don't
4251
		//$_html = str_replace("\r\n",' ',$_html);
4252
		//$_html = str_replace("\t",' ',$_html);
4253
		//error_log(__METHOD__.__LINE__.':'.$_html);
4254
		//repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on
4255
		$_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>'),
4256
							 array('&amp;',    '<BR>',           '<BR>',             '<BR>',             '</font></td>','<td>',    '',         '',           '',  ''),$_html);
4257
		//$_html = str_replace(array('&amp;amp;'),array('&amp;'),$_html);
4258
		if (stripos($_html,'style')!==false) Mail\Html::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags
4259
		if (stripos($_html,'head')!==false) Mail\Html::replaceTagsCompletley($_html,'head'); // Strip out stuff in head
4260
		//if (stripos($_html,'![if')!==false && stripos($_html,'<![endif]>')!==false) Mail\Html::replaceTagsCompletley($_html,'!\[if','<!\[endif\]>',false); // Strip out stuff in ifs
4261
		//if (stripos($_html,'!--[if')!==false && stripos($_html,'<![endif]-->')!==false) Mail\Html::replaceTagsCompletley($_html,'!--\[if','<!\[endif\]-->',false); // Strip out stuff in ifs
4262
		//error_log(__METHOD__.' ('.__LINE__.') '.$_html);
4263
4264
		if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html);
4265
		// Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards
4266
		if (stripos($_html,'!doctype')!==false) Mail\Html::replaceTagsCompletley($_html,'!doctype');
4267
		if (stripos($_html,'?xml:namespace')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml:namespace','/>',false);
4268
		if (stripos($_html,'?xml version')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml version','\?>',false);
4269
		if (strpos($_html,'!CURSOR')!==false) Mail\Html::replaceTagsCompletley($_html,'!CURSOR');
4270
		// htmLawed filter only the 'body'
4271
		//preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $_html, $matches);
4272
		//if ($matches[2])
4273
		//{
4274
		//	$hasOther = true;
4275
		//	$_html = $matches[2];
4276
		//}
4277
		// purify got switched to htmLawed
4278
		// some testcode to test purifying / htmlawed
4279
		//$_html = "<BLOCKQUOTE>hi <div> there </div> kram <br> </blockquote>".$_html;
4280
		$_html = Html\HtmLawed::purify($_html,self::$htmLawed_config,array(),true);
4281
		//if ($hasOther) $_html = $matches[1]. $_html. $matches[3];
4282
		// clean out comments , should not be needed as purify should do the job.
4283
		$search = array(
4284
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
4285
			'@<!--[\s\S]*?[ \t\n\r]*-->@',         // Strip multi-line comments including CDATA
4286
		);
4287
		$_html = preg_replace($search,"",$_html);
4288
		// remove non printable chars
4289
		$_html = preg_replace('/([\000-\011])/','',$_html);
4290
		//error_log(__METHOD__.':'.__LINE__.':'.$_html);
4291
	}
4292
4293
	/**
4294
	 * Header and Bodystructure stuff
4295
	 */
4296
4297
	/**
4298
	 * getMimePartCharset - fetches the charset mimepart if it exists
4299
	 * @param $_mimePartObject structure object
4300
	 * @return mixed mimepart or false if no CHARSET is found, the missing charset has to be handled somewhere else,
4301
	 *		as we cannot safely assume any charset as we did earlier
4302
	 */
4303
	function getMimePartCharset($_mimePartObject)
4304
	{
4305
		//$charSet = 'iso-8859-1';//self::$displayCharset; //'iso-8859-1'; // self::displayCharset seems to be asmarter fallback than iso-8859-1
4306
		$CharsetFound=false;
4307
		//echo "#".$_mimePartObject->encoding.'#<br>';
4308
		if(is_array($_mimePartObject->parameters)) {
4309
			if(isset($_mimePartObject->parameters['CHARSET'])) {
4310
				$charSet = $_mimePartObject->parameters['CHARSET'];
4311
				$CharsetFound=true;
4312
			}
4313
		}
4314
		// this one is dirty, but until I find something that does the trick of detecting the encoding, ....
4315
		//if ($CharsetFound == false && $_mimePartObject->encoding == "QUOTED-PRINTABLE") $charSet = 'iso-8859-1'; //assume quoted-printable to be ISO
4316
		//if ($CharsetFound == false && $_mimePartObject->encoding == "BASE64") $charSet = 'utf-8'; // assume BASE64 to be UTF8
4317
		return ($CharsetFound ? $charSet : $CharsetFound);
4318
	}
4319
4320
	/**
4321
	 * decodeMimePart - fetches the charset mimepart if it exists
4322
	 * @param string $_mimeMessage - the message to be decoded
4323
	 * @param string $_encoding - the encoding used BASE64 and QUOTED-PRINTABLE is supported
4324
	 * @param string $_charset - not used
4325
	 * @return string decoded mimePart
4326
	 */
4327
	function decodeMimePart($_mimeMessage, $_encoding, $_charset = '')
4328
	{
4329
		// decode the part
4330
		if (self::$debug) error_log(__METHOD__."() with $_encoding and $_charset:".print_r($_mimeMessage,true));
4331
		switch (strtoupper($_encoding))
4332
		{
4333
			case 'BASE64':
4334
				// use imap_base64 to decode, not any longer, as it is strict, and fails if it encounters invalid chars
4335
				return base64_decode($_mimeMessage);
4336
4337
			case 'QUOTED-PRINTABLE':
4338
				// use imap_qprint to decode
4339
				return quoted_printable_decode($_mimeMessage);
4340
4341
			case 'WEDONTKNOWTHEENCODING':
4342
				// try base64
4343
				$r = base64_decode($_mimeMessage);
4344
				if (json_encode($r))
4345
				{
4346
					return $r;
4347
				}
4348
				//we do not know the encoding, so we do not decode
4349
			default:
4350
				// it is either not encoded or we don't know about it
4351
				return $_mimeMessage;
4352
		}
4353
	}
4354
4355
	/**
4356
	 * get part of the message, if its stucture is indicating its of multipart alternative style
4357
	 * a wrapper for multipartmixed
4358
	 * @param string/int $_uid the messageuid,
4359
	 * @param Horde_Mime_Part $_structure structure for parsing
4360
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4361
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4362
	 * @return array containing the desired part
4363
	 */
4364
	function getMultipartAlternative($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false)
4365
	{
4366
		// a multipart/alternative has exactly 2 parts (text and html  OR  text and something else)
4367
		// sometimes there are 3 parts, when there is an ics/ical attached/included-> we want to show that
4368
		// as attachment AND as abstracted ical information (we use our notification style here).
4369
		$partText = $partCalendar = $partHTML = null;
4370
		if (self::$debug) _debug_array(array("METHOD"=>__METHOD__,"LINE"=>__LINE__,"STRUCTURE"=>$_structure));
4371
		//error_log(__METHOD__.' ('.__LINE__.') ');
4372
		$ignore_first_part = true;
4373
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4374
		{
4375
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type"." ignoreFirstPart:".$ignore_first_part);
4376
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4377
4378
			if ($ignore_first_part)
4379
			{
4380
				$ignore_first_part = false;
4381
				continue;	// ignore multipart/alternative itself
4382
			}
4383
4384
			$mimePart = $_structure->getPart($mime_id);
4385
4386
			switch($mimePart->getPrimaryType())
4387
			{
4388
				case 'text':
4389
					switch($mimePart->getSubType())
4390
					{
4391
						case 'plain':
4392
							if ($mimePart->getBytes() > 0) $partText = $mimePart;
4393
							break;
4394
4395
						case 'html':
4396
							if ($mimePart->getBytes() > 0)  $partHTML = $mimePart;
4397
							break;
4398
					}
4399
					break;
4400
4401
				case 'multipart':
4402
					switch($mimePart->getSubType())
4403
					{
4404
						case 'related':
4405
						case 'mixed':
4406
							if (count($mimePart->getParts()) > 1)
4407
							{
4408
								// in a multipart alternative we treat the multipart/related as html part
4409
								if (self::$debug) error_log(__METHOD__." process MULTIPART/".$mimePart->getSubType()." with array as subparts");
4410
								$partHTML = $mimePart;
4411
								break 3; // GET OUT OF LOOP, will be processed according to type
4412
							}
4413
							break;
4414
						case 'alternative':
4415
							if (count($mimePart->getParts()) > 1)
4416
							{
4417
								//cascading multipartAlternative structure, assuming only the first one is to be used
4418
								return $this->getMultipartAlternative($_uid, $mimePart, $_htmlMode, $_preserveSeen);
4419
							}
4420
					}
4421
			}
4422
		}
4423
4424
		switch($_htmlMode)
4425
		{
4426
			case 'html_only':
4427
			case 'always_display':
4428
				if ($partHTML)
4429
				{
4430
					switch($partHTML->getSubType())
4431
					{
4432
						case 'related':
4433
							return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4434
4435
						case 'mixed':
4436
							return $this->getMultipartMixed($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4437
4438
						default:
4439
							return $this->getTextPart($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4440
					}
4441
				}
4442
				elseif ($partText && $_htmlMode=='always_display')
4443
				{
4444
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4445
				}
4446
				break;
4447
4448
			case 'only_if_no_text':
4449
				if ($partText)
4450
				{
4451
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4452
				}
4453
				if ($partHTML)
4454
				{
4455
					if ($partHTML->getPrimaryType())
4456
					{
4457
						return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4458
					}
4459
					return $this->getTextPart($_uid, $partHTML, 'always_display', $_preserveSeen);
4460
				}
4461
				break;
4462
4463
			default:
4464
				if ($partText)
4465
				{
4466
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4467
				}
4468
				$bodyPart = array(
4469
					'body'		=> lang("no plain text part found"),
4470
					'mimeType'	=> 'text/plain',
4471
					'charSet'	=> self::$displayCharset,
4472
				);
4473
				break;
4474
		}
4475
		return $bodyPart;
4476
	}
4477
4478
	/**
4479
	 * Get part of the message, if its stucture is indicating its of multipart mixed style
4480
	 *
4481
	 * @param int $_uid the messageuid,
4482
	 * @param Horde_Mime_Part $_structure = '' if given use structure for parsing
4483
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4484
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4485
	 * @param array	&$skipParts - passed by reference to have control/knowledge which parts are already fetched
4486
	 * @return array containing the desired part
4487
	 */
4488
	function getMultipartMixed($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$skipParts=array())
4489
	{
4490
		if (self::$debug) echo __METHOD__."$_uid, $_htmlMode<br>";
4491
		$bodyPart = array();
4492
		if (self::$debug) _debug_array($_structure);
4493
4494
		$ignore_first_part = true;
4495
		//$skipParts = array();
4496
		//error_log(__METHOD__.__LINE__.array2string($_structure->contentTypeMap()));
4497
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4498
		{
4499
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type");
4500
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4501
			if ($ignore_first_part)
4502
			{
4503
				$ignore_first_part = false;
4504
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED FirstPart $mime_id: $mime_type");
4505
				continue;	// ignore multipart/mixed itself
4506
			}
4507
			if (array_key_exists($mime_id,$skipParts))
4508
			{
4509
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED $mime_id: $mime_type");
4510
				continue;
4511
			}
4512
4513
			$part = $_structure->getPart($mime_id);
4514
4515
			switch($part->getPrimaryType())
4516
			{
4517
				case 'multipart':
4518
					if ($part->getDisposition() == 'attachment') continue;
4519
					switch($part->getSubType())
4520
					{
4521
						case 'alternative':
4522
							return array($this->getMultipartAlternative($_uid, $part, $_htmlMode, $_preserveSeen));
4523
4524
						case 'mixed':
4525
						case 'signed':
4526
							$bodyPart = array_merge($bodyPart, $this->getMultipartMixed($_uid, $part, $_htmlMode, $_preserveSeen, $skipParts));
4527
							break;
4528
4529
						case 'related':
4530
							$bodyPart = array_merge($bodyPart, $this->getMultipartRelated($_uid, $part, $_htmlMode, $_preserveSeen));
4531
							break;
4532
					}
4533
					break;
4534
				case 'application':
4535
					switch($part->getSubType())
4536
					{
4537
						case 'pgp-encrypted':
4538
							if (($part = $_structure->getPart($mime_id+1)) &&
4539
								$part->getType() == 'application/octet-stream')
4540
							{
4541
								$this->fetchPartContents($_uid, $part);
4542
								$skipParts[$mime_id]=$mime_type;
4543
								$skipParts[$mime_id+1]=$part->getType();
4544
								$bodyPart[] = array(
4545
									'body'		=> $part->getContents(array(
4546
										'stream' => false,
4547
									)),
4548
									'mimeType'  => 'text/plain',
4549
									'charSet'	=> $_structure->getCharset(),
4550
								);
4551
							}
4552
							break;
4553
					}
4554
					break;
4555
4556
				case 'text':
4557
					switch($part->getSubType())
4558
					{
4559
						case 'plain':
4560
						case 'html':
4561
						case 'calendar': // inline ics/ical files
4562
							if($part->getDisposition() != 'attachment')
4563
							{
4564
								$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4565
								$skipParts[$mime_id]=$mime_type;
4566
							}
4567
							//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$part->type."/".$part->subType.' -> BodyPart:'.array2string($bodyPart[count($bodyPart)-1]));
4568
							break;
4569
					}
4570
					break;
4571
4572
				case 'message':
4573
					//skip attachments
4574
					if($part->getSubType() == 'delivery-status' && $part->getDisposition() != 'attachment')
4575
					{
4576
						$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4577
						$skipParts[$mime_id]=$mime_type;
4578
					}
4579
					// do not descend into attached Messages
4580
					if($part->getSubType() == 'rfc822' || $part->getDisposition() == 'attachment')
4581
					{
4582
						$skipParts[$mime_id.'.0'] = $mime_type;
4583
						foreach($part->contentTypeMap() as $sub_id => $sub_type){ $skipParts[$sub_id] = $sub_type;}
4584
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$_uid.' Part:'.$mime_id.':'.array2string($skipParts));
4585
						//break 2;
4586
					}
4587
					break;
4588
4589
				default:
4590
					// do nothing
4591
					// the part is a attachment
4592
			}
4593
		}
4594
		return $bodyPart;
4595
	}
4596
4597
	/**
4598
	 * getMultipartRelated
4599
	 * get part of the message, if its stucture is indicating its of multipart related style
4600
	 * a wrapper for multipartmixed
4601
	 * @param string/int $_uid the messageuid,
4602
	 * @param Horde_Mime_Part $_structure if given use structure for parsing
4603
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4604
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4605
	 * @return array containing the desired part
4606
	 */
4607
	function getMultipartRelated($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false)
4608
	{
4609
		return $this->getMultipartMixed($_uid, $_structure, $_htmlMode, $_preserveSeen);
4610
	}
4611
4612
	/**
4613
	 * Fetch a body part
4614
	 *
4615
	 * @param int $_uid
4616
	 * @param string $_partID = null
4617
	 * @param string $_folder = null
4618
	 * @param boolean $_preserveSeen = false
4619
	 * @param boolean $_stream = false true return a stream, false return string
4620
	 * @param string &$_encoding = null on return: transfer encoding of returned part
4621
	 * @param boolean $_tryDecodingServerside = true; wether to try to fetch Data with BINARY instead of BODY
4622
	 * @return string|resource
4623
	 */
4624
	function getBodyPart($_uid, $_partID=null, $_folder=null, $_preserveSeen=false, $_stream=false, &$_encoding=null, $_tryDecodingServerside=true)
4625
	{
4626
		if (self::$debug) error_log( __METHOD__.__LINE__."(".array2string($_uid).", $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, $_tryDecodingServerside)");
4627
4628 View Code Duplication
		if (empty($_folder))
4629
		{
4630
			$_folder = (isset($this->sessionData['mailbox'])&&$this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
4631
		}
4632
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_folder).'/'.$this->icServer->getCurrentMailbox().'/'. $this->sessionData['mailbox']);
4633
		// querying contents of body part
4634
		$uidsToFetch = new Horde_Imap_Client_Ids();
4635
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
4636
		$uidsToFetch->add($_uid);
4637
4638
		$fquery = new Horde_Imap_Client_Fetch_Query();
4639
		$fetchParams = array(
4640
			'peek' => $_preserveSeen,
4641
			'decode' => true,	// try decode on server, does NOT neccessary work
4642
		);
4643
		if ($_tryDecodingServerside===false)// || ($_tryDecodingServerside&&$this->isDraftFolder($_folder)))
4644
		{
4645
			$_tryDecodingServerside=false;
4646
			$fetchParams = array(
4647
				'peek' => $_preserveSeen,
4648
			);
4649
		}
4650
		$fquery->bodyPart($_partID, $fetchParams);
4651
4652
		$part = $this->icServer->fetch($_folder, $fquery, array(
4653
			'ids' => $uidsToFetch,
4654
		))->first();
4655
		$partToReturn = null;
4656
		if ($part)
4657
		{
4658
			$_encoding = $part->getBodyPartDecode($_partID);
4659
			//error_log(__METHOD__.__LINE__.':'.$_encoding.'#');
4660
			$partToReturn = $part->getBodyPart($_partID, $_stream);
4661
			//error_log(__METHOD__.__LINE__.':'.$partToReturn.'#');
4662
		}
4663
		// if we get an empty result, server may have trouble fetching data with UID FETCH $_uid (BINARY.PEEK[$_partID])
4664
		// thus we trigger a second go with UID FETCH $_uid (BODY.PEEK[$_partID])
4665
		if (empty($partToReturn)&&$_tryDecodingServerside===true)
4666
		{
4667
			error_log(__METHOD__.__LINE__.' failed to fetch bodyPart in  BINARY. Try BODY');
4668
			$partToReturn = $this->getBodyPart($_uid, $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, false);
4669
		}
4670
		return ($partToReturn?$partToReturn:null);
4671
	}
4672
4673
	/**
4674
	 * Get Body from message
4675
	 *
4676
	 * @param int $_uid the messageuid
4677
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4678
	 * @param string $_htmlMode how to display a message: 'html_only', 'always_display', 'only_if_no_text' or ''
4679
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4680
	 * @param boolean $_stream = false true return a stream, false return string
4681
	 * @return array containing the desired text part, mimeType and charset
4682
	 */
4683
	function getTextPart($_uid, Horde_Mime_Part $_structure, $_htmlMode='', $_preserveSeen=false, $_stream=false)
4684
	{
4685
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_uid.':'.array2string($_structure).' '.function_backtrace());
4686
		$bodyPart = array();
4687
		if (self::$debug) _debug_array(array($_structure,function_backtrace()));
4688
4689
		if($_structure->getSubType() == 'html' && !in_array($_htmlMode, array('html_only', 'always_display', 'only_if_no_text')))
4690
		{
4691
			$bodyPart = array(
4692
				'error'		=> 1,
4693
				'body'		=> lang("displaying html messages is disabled"),
4694
				'mimeType'	=> 'text/html',
4695
				'charSet'	=> self::$displayCharset,
4696
			);
4697
		}
4698
		elseif ($_structure->getSubType() == 'plain' && $_htmlMode == 'html_only')
4699
		{
4700
			$bodyPart = array(
4701
				'error'		=> 1,
4702
				'body'      => lang("displaying plain messages is disabled"),
4703
				'mimeType'  => 'text/plain', // make sure we do not return mimeType text/html
4704
				'charSet'   => self::$displayCharset,
4705
			);
4706
		}
4707
		else
4708
		{
4709
			// some Servers append PropertyFile___ ; strip that here for display
4710
			// RB: not sure what this is: preg_replace('/PropertyFile___$/','',$this->decodeMimePart($mimePartBody, $_structure->encoding, $this->getMimePartCharset($_structure))),
4711
			$this->fetchPartContents($_uid, $_structure, $_stream, $_preserveSeen);
4712
4713
			$bodyPart = array(
4714
				'body'		=> $_structure->getContents(array(
4715
					'stream' => $_stream,
4716
				)),
4717
				'mimeType'  => $_structure->getType() == 'text/html' ? 'text/html' : 'text/plain',
4718
				'charSet'	=> $_structure->getCharset(),
4719
			);
4720
		}
4721
		return $bodyPart;
4722
	}
4723
4724
	/**
4725
	 * Get Body of message
4726
	 *
4727
	 * @param int $_uid the messageuid,
4728
	 * @param string $_htmlOptions how to display a message, html, plain text, ...
4729
	 * @param string $_partID = null the partID, may be omitted
4730
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4731
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4732
	 * @param string $_folder folder to work on
4733
	 * @return array containing the message body, mimeType and charset
4734
	 */
4735
	function getMessageBody($_uid, $_htmlOptions='', $_partID=null, Horde_Mime_Part $_structure=null, $_preserveSeen = false, $_folder = '')
4736
	{
4737
		if (self::$debug) echo __METHOD__."$_uid, $_htmlOptions, $_partID<br>";
4738
		if($_htmlOptions != '') {
4739
			$this->htmlOptions = $_htmlOptions;
4740
		}
4741
		if (empty($_folder))
4742
		{
4743
			$_folder = $this->sessionData['mailbox'];
4744
		}
4745
		if (empty($this->sessionData['mailbox']) && !empty($_folder))
4746
		{
4747
			$this->sessionData['mailbox'] = $_folder;
4748
		}
4749
4750
		if (!isset($_structure))
4751
		{
4752
			$_structure = $this->getStructure($_uid, $_partID, $_folder, $_preserveSeen);
4753
		}
4754
		if (!is_object($_structure))
4755
		{
4756
			return array(
4757
				array(
4758
					'error'		=> 1,
4759
					'body'		=> 'Error: Could not fetch structure on mail:'.$_uid." as $_htmlOptions". 'for Mailprofile'.$this->icServer->ImapServerId.' User:'.$GLOBALS['egw_info']['user']['account_lid'],
4760
					'mimeType'	=> 'text/plain',
4761
					'charSet'	=> self::$displayCharset,
4762
				)
4763
			);
4764
		}
4765
		if (!empty($_partID))
4766
		{
4767
			$_structure->contentTypeMap();
4768
			$_structure = $_structure->getPart($_partID);
4769
			//_debug_array($_structure->getMimeId()); exit;
4770
		}
4771
4772
		switch($_structure->getPrimaryType())
4773
		{
4774
			case 'application':
4775
				return array(
4776
					array(
4777
						'body'		=> '',
4778
						'mimeType'	=> 'text/plain',
4779
						'charSet'	=> 'iso-8859-1',
4780
					)
4781
				);
4782
4783
			case 'multipart':
4784
				switch($_structure->getSubType())
4785
				{
4786
					case 'alternative':
4787
						$bodyParts = array($this->getMultipartAlternative($_uid, $_structure, $this->htmlOptions, $_preserveSeen));
0 ignored issues
show
Bug introduced by
It seems like $this->htmlOptions can also be of type array; however, EGroupware\Api\Mail::getMultipartAlternative() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4788
						break;
4789
4790
					case 'nil': // multipart with no Alternative
4791
					case 'mixed':
4792
					case 'report':
4793
					case 'signed':
4794
					case 'encrypted':
4795
						$bodyParts = $this->getMultipartMixed($_uid, $_structure, $this->htmlOptions, $_preserveSeen);
0 ignored issues
show
Bug introduced by
It seems like $this->htmlOptions can also be of type array; however, EGroupware\Api\Mail::getMultipartMixed() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4796
						break;
4797
4798
					case 'related':
4799
						$bodyParts = $this->getMultipartRelated($_uid, $_structure, $this->htmlOptions, $_preserveSeen);
0 ignored issues
show
Bug introduced by
It seems like $this->htmlOptions can also be of type array; however, EGroupware\Api\Mail::getMultipartRelated() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4800
						break;
4801
				}
4802
				return self::normalizeBodyParts($bodyParts);
4803
4804
			case 'video':
4805
			case 'audio': // some servers send audiofiles and imagesfiles directly, without any stuff surround it
4806
			case 'image': // they are displayed as Attachment NOT INLINE
4807
				return array(
4808
					array(
4809
						'body'      => '',
4810
						'mimeType'  => $_structure->getSubType(),
4811
					),
4812
				);
4813
4814
			case 'text':
4815
				$bodyPart = array();
4816
				if ($_structure->getDisposition() != 'attachment')
4817
				{
4818
					switch($_structure->getSubType())
4819
					{
4820
						case 'calendar':
4821
							// this is handeled in getTextPart
4822
						case 'html':
4823
						case 'plain':
4824
						default:
4825
							$bodyPart = array($this->getTextPart($_uid, $_structure, $this->htmlOptions, $_preserveSeen));
0 ignored issues
show
Bug introduced by
It seems like $this->htmlOptions can also be of type array; however, EGroupware\Api\Mail::getTextPart() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4826
					}
4827
				} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
4828
					// what if the structure->disposition is attachment ,...
4829
				}
4830
				return self::normalizeBodyParts($bodyPart);
4831
4832
			case 'attachment':
4833
			case 'message':
4834
				switch($_structure->getSubType())
4835
				{
4836
					case 'rfc822':
4837
						$newStructure = $_structure->getParts();
4838
						if (self::$debug) {echo __METHOD__." Message -> RFC -> NewStructure:"; _debug_array($newStructure[0]);}
4839
						return self::normalizeBodyParts($this->getMessageBody($_uid, $_htmlOptions, $newStructure[0]->getMimeId(), $newStructure[0], $_preserveSeen, $_folder));
4840
				}
4841
				break;
4842
4843
			default:
4844
				if (self::$debug) _debug_array($_structure);
4845
				return array(
4846
					array(
4847
						'body'		=> lang('The mimeparser can not parse this message.').$_structure->getType(),
4848
						'mimeType'	=> 'text/plain',
4849
						'charSet'	=> self::$displayCharset,
4850
					)
4851
				);
4852
		}
4853
	}
4854
4855
	/**
4856
	 * normalizeBodyParts - function to gather and normalize all body Information
4857
	 * as we may recieve a bodyParts structure from within getMessageBody nested deeper than expected
4858
	 * so this is used to normalize the output, so we are able to rely on our expectation
4859
	 * @param _bodyParts - Body Array
4860
	 * @return array - a normalized Bodyarray
4861
	 */
4862
	static function normalizeBodyParts($_bodyParts)
4863
	{
4864
		if (is_array($_bodyParts))
4865
		{
4866
			foreach($_bodyParts as $singleBodyPart)
4867
			{
4868
				if (!isset($singleBodyPart['body'])) {
4869
					$buff = self::normalizeBodyParts($singleBodyPart);
4870
					foreach ((array)$buff as $val) { $body2return[] = $val;}
4871
					continue;
4872
				}
4873
				$body2return[] = $singleBodyPart;
4874
			}
4875
		}
4876
		else
4877
		{
4878
			$body2return = $_bodyParts;
4879
		}
4880
		return $body2return;
4881
	}
4882
4883
	/**
4884
	 * getdisplayableBody - creates the bodypart of the email as textual representation
4885
	 * @param object $mailClass the mailClass object to be used
4886
	 * @param array $bodyParts  with the bodyparts
4887
	 * @param boolean $preserveHTML  switch to preserve HTML
4888
	 * @param boolean $useTidy  switch to use tidy
4889
	 * @return string a preformatted string with the mails converted to text
4890
	 */
4891
	static function &getdisplayableBody(&$mailClass, $bodyParts, $preserveHTML = false,  $useTidy = true)
4892
	{
4893
		$message='';
4894
		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...
4895
		{
4896
			if (!isset($bodyParts[$i]['body'])) {
4897
				$bodyParts[$i]['body'] = self::getdisplayableBody($mailClass, $bodyParts[$i], $preserveHTML, $useTidy);
4898
				$message .= empty($bodyParts[$i]['body'])?'':$bodyParts[$i]['body'];
4899
				continue;
4900
			}
4901
			if (isset($bodyParts[$i]['error'])) continue;
4902
			if (empty($bodyParts[$i]['body'])) continue;
4903
			// some characterreplacements, as they fail to translate
4904
			$sar = array(
4905
				'@(\x84|\x93|\x94)@',
4906
				'@(\x96|\x97|\x1a)@',
4907
				'@(\x82|\x91|\x92)@',
4908
				'@(\x85)@',
4909
				'@(\x86)@',
4910
				'@(\x99)@',
4911
				'@(\xae)@',
4912
			);
4913
			$rar = array(
4914
				'"',
4915
				'-',
4916
				'\'',
4917
				'...',
4918
				'&',
4919
				'(TM)',
4920
				'(R)',
4921
			);
4922
4923
			if(($bodyParts[$i]['mimeType'] == 'text/html' || $bodyParts[$i]['mimeType'] == 'text/plain') &&
4924
				strtoupper($bodyParts[$i]['charSet']) != 'UTF-8')
4925
			{
4926
				$bodyParts[$i]['body'] = preg_replace($sar,$rar,$bodyParts[$i]['body']);
4927
			}
4928
4929 View Code Duplication
			if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Translation::detect_encoding($bodyParts[$i]['body']);
4930
			// add line breaks to $bodyParts
4931
			//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
4932
			$newBody  = Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
4933
			//error_log(__METHOD__.' ('.__LINE__.') '.' MimeType:'.$bodyParts[$i]['mimeType'].'->'.$newBody);
4934
			$mailClass->activeMimeType = 'text/plain';
4935
			if ($bodyParts[$i]['mimeType'] == 'text/html') {
4936
				$mailClass->activeMimeType = $bodyParts[$i]['mimeType'];
4937
				if (!$preserveHTML)
4938
				{
4939
					$alreadyHtmlLawed=false;
4940
					// as Translation::convert reduces \r\n to \n and purifier eats \n -> peplace it with a single space
4941
					$newBody = str_replace("\n"," ",$newBody);
4942
					// convert HTML to text, as we dont want HTML in infologs
4943 View Code Duplication
					if ($useTidy && extension_loaded('tidy'))
4944
					{
4945
						$tidy = new tidy();
4946
						$cleaned = $tidy->repairString($newBody, self::$tidy_config,'utf8');
4947
						// Found errors. Strip it all so there's some output
4948
						if($tidy->getStatus() == 2)
4949
						{
4950
							error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$tidy->errorBuffer);
0 ignored issues
show
Bug introduced by
The property errorBuffer does not seem to exist in tidy.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
4951
						}
4952
						else
4953
						{
4954
							$newBody = $cleaned;
4955
						}
4956
						if (!$preserveHTML)
4957
						{
4958
							// filter only the 'body', as we only want that part, if we throw away the html
4959
							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.
Loading history...
4960
							if ($matches[2])
4961
							{
4962
								$hasOther = true;
4963
								$newBody = $matches[2];
4964
							}
4965
						}
4966
					}
4967
					else
4968
					{
4969
						// htmLawed filter only the 'body'
4970
						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.
Loading history...
4971
						if ($matches[2])
4972
						{
4973
							$hasOther = true;
4974
							$newBody = $matches[2];
4975
						}
4976
						$htmLawed = new Html\HtmLawed();
4977
						// the next line should not be needed, but produces better results on HTML 2 Text conversion,
4978
						// as we switched off HTMLaweds tidy functionality
4979
						$newBody = str_replace(array('&amp;amp;','<DIV><BR></DIV>',"<DIV>&nbsp;</DIV>",'<div>&nbsp;</div>'),array('&amp;','<BR>','<BR>','<BR>'),$newBody);
4980
						$newBody = $htmLawed->run($newBody,self::$htmLawed_config);
4981
						if ($hasOther && $preserveHTML) $newBody = $matches[1]. $newBody. $matches[3];
4982
						$alreadyHtmlLawed=true;
4983
					}
4984
					//error_log(__METHOD__.' ('.__LINE__.') '.' after purify:'.$newBody);
4985
					if ($preserveHTML==false) $newBody = Mail\Html::convertHTMLToText($newBody,self::$displayCharset,true,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...
4986
					//error_log(__METHOD__.' ('.__LINE__.') '.' after convertHTMLToText:'.$newBody);
4987
					if ($preserveHTML==false) $newBody = nl2br($newBody); // we need this, as htmLawed removes \r\n
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...
4988
					/*if (!$alreadyHtmlLawed) */ $mailClass->getCleanHTML($newBody); // remove stuff we regard as unwanted
4989
					if ($preserveHTML==false) $newBody = str_replace("<br />","\r\n",$newBody);
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...
4990
					//error_log(__METHOD__.' ('.__LINE__.') '.' after getClean:'.$newBody);
4991
				}
4992
				$message .= $newBody;
4993
				continue;
4994
			}
4995
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after specialchars):'.$newBody);
4996
			//use Mail\Html::convertHTMLToText instead of strip_tags, (even message is plain text) as strip_tags eats away too much
4997
			//$newBody = strip_tags($newBody); //we need to fix broken tags (or just stuff like "<800 USD/p" )
4998
			$newBody = Mail\Html::convertHTMLToText($newBody,self::$displayCharset,false,false);
0 ignored issues
show
Bug introduced by
It seems like $newBody can also be of type array; however, EGroupware\Api\Mail\Html::convertHTMLToText() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
4999
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after strip tags):'.$newBody);
5000
			$newBody = htmlspecialchars_decode($newBody,ENT_QUOTES);
5001
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body (after hmlspc_decode):'.$newBody);
5002
			$message .= $newBody;
5003
			//continue;
5004
		}
5005
		return $message;
5006
	}
5007
5008
	static function wordwrap($str, $cols, $cut, $dontbreaklinesstartingwith=false)
5009
	{
5010
		$lines = explode("\n", $str);
5011
		$newStr = '';
5012
		foreach($lines as $line)
5013
		{
5014
			// replace tabs by 8 space chars, or any tab only counts one char
5015
			//$line = str_replace("\t","        ",$line);
5016
			//$newStr .= wordwrap($line, $cols, $cut);
5017
			$allowedLength = $cols-strlen($cut);
5018
			//dont try to break lines with links, chance is we mess up the text is way too big
5019
			if (strlen($line) > $allowedLength && stripos($line,'href=')===false &&
5020
				($dontbreaklinesstartingwith==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...
5021
				 ($dontbreaklinesstartingwith &&
5022
				  strlen($dontbreaklinesstartingwith)>=1 &&
5023
				  substr($line,0,strlen($dontbreaklinesstartingwith)) != $dontbreaklinesstartingwith
5024
				 )
5025
				)
5026
			   )
5027
			{
5028
				$s=explode(" ", $line);
5029
				$line = "";
5030
				$linecnt = 0;
5031
				foreach ($s as &$v) {
5032
					$cnt = strlen($v);
5033
					// only break long words within the wordboundaries,
5034
					// but it may destroy links, so we check for href and dont do it if we find one
5035
					// we check for any html within the word, because we do not want to break html by accident
5036
					if($cnt > $allowedLength && stripos($v,'href=')===false && stripos($v,'onclick=')===false && $cnt == strlen(html_entity_decode($v)))
5037
					{
5038
						$v=wordwrap($v, $allowedLength, $cut, true);
5039
					}
5040
					// the rest should be broken at the start of the new word that exceeds the limit
5041
					if ($linecnt+$cnt > $allowedLength) {
5042
						$v=$cut.$v;
5043
						#$linecnt = 0;
5044
						$linecnt =strlen($v)-strlen($cut);
5045
					} else {
5046
						$linecnt += $cnt;
5047
					}
5048
					if (strlen($v)) $line .= (strlen($line) ? " " : "").$v;
5049
				}
5050
			}
5051
			$newStr .= $line . "\n";
5052
		}
5053
		return $newStr;
5054
	}
5055
5056
	/**
5057
	 * getMessageEnvelope
5058
	 * get parsed headers from message
5059
	 * @param string/int $_uid the messageuid,
5060
	 * @param string/int $_partID = '' , the partID, may be omitted
5061
	 * @param boolean $decode flag to do the decoding on the fly
5062
	 * @param string $_folder folder to work on
5063
	 * @param boolean $_useHeaderInsteadOfEnvelope - force getMessageHeader method to be used for fetching Envelope Information
5064
	 * @return array the message header
5065
	 */
5066
	function getMessageEnvelope($_uid, $_partID = '',$decode=false, $_folder='', $_useHeaderInsteadOfEnvelope=false)
5067
	{
5068
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder".function_backtrace());
5069 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5070
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder");
5071
		if((empty($_partID)||$_partID=='null')&&$_useHeaderInsteadOfEnvelope===false) {
5072
			$uidsToFetch = new Horde_Imap_Client_Ids();
5073
			if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5074
			$uidsToFetch->add($_uid);
5075
5076
			$fquery = new Horde_Imap_Client_Fetch_Query();
5077
			$envFields = new Horde_Mime_Headers();
5078
			$fquery->envelope();
5079
			$fquery->size();
5080
			$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5081
				'ids' => $uidsToFetch,
5082
			));
5083
			if (is_object($headersNew)) {
5084
				foreach($headersNew as &$_headerObject) {
5085
					$env = $_headerObject->getEnvelope();
5086
					//_debug_array($envFields->singleFields());
5087
					$singleFields = $envFields->singleFields();
5088
					foreach ($singleFields as &$v)
5089
					{
5090
						switch ($v)
5091
						{
5092
							case 'to':
5093
							case 'reply-to':
5094
							case 'from':
5095
							case 'cc':
5096
							case 'bcc':
5097
							case 'sender':
5098
								//error_log(__METHOD__.' ('.__LINE__.') '.$v.'->'.array2string($env->$v->addresses));
5099
								$envelope[$v]=$env->$v->addresses;
5100
								$address = array();
5101
								if (!is_array($envelope[$v])) break;
5102
								foreach ($envelope[$v] as $k => $ad)
5103
								{
5104
									if (stripos($ad,'@')===false)
5105
									{
5106
										$remember=$k;
5107
									}
5108
									else
5109
									{
5110
										$address[] = (!is_null($remember)?$envelope[$v][$remember].' ':'').$ad;
5111
										$remember=null;
5112
									}
5113
								}
5114
								$envelope[$v] = $address;
5115
								break;
5116
							case 'date':
5117
								$envelope[$v]=DateTime::to($env->$v);
5118
								break;
5119
							default:
5120
								$envelope[$v]=$env->$v;
5121
						}
5122
					}
5123
					$envelope['size']=$_headerObject->getSize();
5124
				}
5125
			}
5126
			$envelope = array_change_key_case($envelope,CASE_UPPER);
5127
			//if ($decode) _debug_array($envelope);
5128
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($envelope));
5129 View Code Duplication
			if ($decode)
5130
			{
5131
				foreach ($envelope as $key => $rvV)
5132
				{
5133
					//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5134
					$envelope[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5135
				}
5136
			}
5137
			return $envelope;
5138
		} else {
5139
5140
			$headers = $this->getMessageHeader($_uid, $_partID, true,true,$_folder);
5141
5142
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($headers));
5143
			//_debug_array($headers);
5144
			$newData = array(
5145
				'DATE'		=> $headers['DATE'],
5146
				'SUBJECT'	=> ($decode ? self::decode_header($headers['SUBJECT']):$headers['SUBJECT']),
5147
				'MESSAGE_ID'	=> $headers['MESSAGE-ID']
5148
			);
5149
			if (isset($headers['IN-REPLY-TO'])) $newData['IN-REPLY-TO'] = $headers['IN-REPLY-TO'];
5150
			if (isset($headers['REFERENCES'])) $newData['REFERENCES'] = $headers['REFERENCES'];
5151
			if (isset($headers['THREAD-TOPIC'])) $newData['THREAD-TOPIC'] = $headers['THREAD-TOPIC'];
5152
			if (isset($headers['THREAD-INDEX'])) $newData['THREAD-INDEX'] = $headers['THREAD-INDEX'];
5153
			if (isset($headers['LIST-ID'])) $newData['LIST-ID'] = $headers['LIST-ID'];
5154
			if (isset($headers['SIZE'])) $newData['SIZE'] = $headers['SIZE'];
5155
			//_debug_array($newData);
5156
			$recepientList = array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO');
5157
			foreach($recepientList as $recepientType) {
5158
				if(isset($headers[$recepientType])) {
5159
					if ($decode) $headers[$recepientType] =  self::decode_header($headers[$recepientType],true);
5160
					//error_log(__METHOD__.__LINE__." ".$recepientType."->".array2string($headers[$recepientType]));
5161
					foreach(self::parseAddressList($headers[$recepientType]) as $singleAddress) {
5162
						$addressData = array(
5163
							'PERSONAL_NAME'		=> $singleAddress->personal ? $singleAddress->personal : 'NIL',
5164
							'AT_DOMAIN_LIST'	=> $singleAddress->adl ? $singleAddress->adl : 'NIL',
5165
							'MAILBOX_NAME'		=> $singleAddress->mailbox ? $singleAddress->mailbox : 'NIL',
5166
							'HOST_NAME'		=> $singleAddress->host ? $singleAddress->host : 'NIL',
5167
							'EMAIL'			=> $singleAddress->host ? $singleAddress->mailbox.'@'.$singleAddress->host : $singleAddress->mailbox,
5168
						);
5169
						if($addressData['PERSONAL_NAME'] != 'NIL') {
5170
							$addressData['RFC822_EMAIL'] = imap_rfc822_write_address($singleAddress->mailbox, $singleAddress->host, $singleAddress->personal);
5171
						} else {
5172
							$addressData['RFC822_EMAIL'] = 'NIL';
5173
						}
5174
						$newData[$recepientType][] = ($addressData['RFC822_EMAIL']!='NIL'?$addressData['RFC822_EMAIL']:$addressData['EMAIL']);//$addressData;
5175
					}
5176
				} else {
5177
					if($recepientType == 'SENDER' || $recepientType == 'REPLY-TO') {
5178
						$newData[$recepientType] = $newData['FROM'];
5179
					} else {
5180
						$newData[$recepientType] = array();
5181
					}
5182
				}
5183
			}
5184
			//if ($decode) _debug_array($newData);
5185
			return $newData;
5186
		}
5187
	}
5188
5189
	/**
5190
	 * Get parsed headers from message
5191
	 *
5192
	 * @param string/int $_uid the messageuid,
5193
	 * @param string/int $_partID ='' , the partID, may be omitted
5194
	 * @param boolean|string $decode flag to do the decoding on the fly or "object"
5195
	 * @param boolean $preserveUnSeen flag to preserve the seen flag where applicable
5196
	 * @param string $_folder folder to work on
5197
	 * @return array|Horde_Mime_Headers message header as array or object
5198
	 */
5199
	function getMessageHeader($_uid, $_partID = '',$decode=false, $preserveUnSeen=false, $_folder='')
5200
	{
5201
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$_uid.', '.$_partID.', '.$decode.', '.$preserveUnSeen.', '.$_folder);
5202 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5203
		$uidsToFetch = new Horde_Imap_Client_Ids();
5204
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5205
		$uidsToFetch->add($_uid);
5206
5207
		$fquery = new Horde_Imap_Client_Fetch_Query();
5208
		if ($_partID != '')
5209
		{
5210
			$fquery->headerText(array('id'=>$_partID,'peek'=>$preserveUnSeen));
5211
			$fquery->structure();
5212
		}
5213
		else
5214
		{
5215
			$fquery->headerText(array('peek'=>$preserveUnSeen));
5216
		}
5217
		$fquery->size();
5218
5219
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5220
			'ids' => $uidsToFetch,
5221
		));
5222
		if (is_object($headersNew)) {
5223
			foreach($headersNew as $_fetchObject)
5224
			{
5225
				$headers = $_fetchObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5226
				if ($_partID != '')
5227
				{
5228
					$mailStructureObject = $_fetchObject->getStructure();
5229
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5230
					{
5231
						if ($mime_id==$_partID)
5232
						{
5233
							//error_log(__METHOD__.' ('.__LINE__.') '."$mime_id == $_partID".array2string($_headerObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray()));
5234
							$headers = $_fetchObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5235
							break;
5236
						}
5237
					}
5238
				}
5239
				$size = $_fetchObject->getSize();
5240
				//error_log(__METHOD__.__LINE__.'#'.$size);
5241
			}
5242
			if ($decode === 'object')
5243
			{
5244
				if (is_object($headers)) $headers->setUserAgent('EGroupware API '.$GLOBALS['egw_info']['server']['versions']['phpgwapi']);
5245
				return $headers;
5246
			}
5247
			$retValue = is_object($headers) ? $headers->toArray():array();
5248
			if ($size) $retValue['size'] = $size;
5249
		}
5250
		$retValue = array_change_key_case($retValue,CASE_UPPER);
5251
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue));
5252
		// if SUBJECT is an array, use thelast one, as we assume something with the unfolding for the subject did not work
5253
		if (is_array($retValue['SUBJECT']))
5254
		{
5255
			$retValue['SUBJECT'] = $retValue['SUBJECT'][count($retValue['SUBJECT'])-1];
5256
		}
5257
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($decode ? self::decode_header($retValue,true):$retValue));
5258 View Code Duplication
		if ($decode)
5259
		{
5260
			foreach ($retValue as $key => $rvV)
5261
			{
5262
				//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5263
				$retValue[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5264
			}
5265
		}
5266
		return $retValue;
5267
	}
5268
5269
	/**
5270
	 * getMessageRawHeader
5271
	 * get messages raw header data
5272
	 * @param string/int $_uid the messageuid,
5273
	 * @param string/int $_partID = '' , the partID, may be omitted
5274
	 * @param string $_folder folder to work on
5275
	 * @return string the message header
5276
	 */
5277
	function getMessageRawHeader($_uid, $_partID = '', $_folder = '')
5278
	{
5279
		static $rawHeaders;
5280 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5281
		//error_log(__METHOD__.' ('.__LINE__.') '." Try Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5282
5283 View Code Duplication
		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);
5284 View Code Duplication
		if (isset($rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5285
		{
5286
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5287
			return $rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5288
		}
5289
		$uidsToFetch = new Horde_Imap_Client_Ids();
5290
		$uid = $_uid;
5291
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5292
		$uidsToFetch->add($uid);
5293
5294
		$fquery = new Horde_Imap_Client_Fetch_Query();
5295
		if ($_partID != '')
5296
		{
5297
			$fquery->headerText(array('id'=>$_partID,'peek'=>true));
5298
			$fquery->structure();
5299
		}
5300
		else
5301
		{
5302
			$fquery->headerText(array('peek'=>true));
5303
		}
5304
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5305
			'ids' => $uidsToFetch,
5306
		));
5307
		if (is_object($headersNew)) {
5308 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5309
				$retValue = $_headerObject->getHeaderText();
5310
				if ($_partID != '')
5311
				{
5312
					$mailStructureObject = $_headerObject->getStructure();
5313
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5314
					{
5315
						if ($mime_id==$_partID)
5316
						{
5317
							$retValue = $_headerObject->getHeaderText($mime_id);
5318
						}
5319
					}
5320
				}
5321
			}
5322
		}
5323
		$rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]=$retValue;
5324
		Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($GLOBALS['egw_info']['user']['account_id']),$rawHeaders,60*60*1);
5325
		return $retValue;
5326
	}
5327
5328
	/**
5329
	 * getStyles - extracts the styles from the given bodyparts
5330
	 * @param array $_bodyParts  with the bodyparts
5331
	 * @return string a preformatted string with the mails converted to text
5332
	 */
5333
	static function &getStyles($_bodyParts)
5334
	{
5335
		$style = '';
5336
		if (empty($_bodyParts)) return "";
5337
		foreach((array)$_bodyParts as $singleBodyPart) {
5338
			if (!isset($singleBodyPart['body'])) {
5339
				$singleBodyPart['body'] = self::getStyles($singleBodyPart);
5340
				$style .= $singleBodyPart['body'];
5341
				continue;
5342
			}
5343
5344
			if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = Translation::detect_encoding($singleBodyPart['body']);
5345
			$singleBodyPart['body'] = Translation::convert(
5346
				$singleBodyPart['body'],
5347
				strtolower($singleBodyPart['charSet'])
5348
			);
5349
			$ct = 0;
5350
			$newStyle=array();
5351
			if (stripos($singleBodyPart['body'],'<style')!==false)  $ct = preg_match_all('#<style(?:\s.*)?>(.+)</style>#isU', $singleBodyPart['body'], $newStyle);
5352
			if ($ct>0)
5353
			{
5354
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$ct.'#'.array2string($newStyle));
5355
				$style2buffer = implode('',$newStyle[0]);
5356
			}
5357
			if ($style2buffer && strtoupper(self::$displayCharset) == 'UTF-8')
5358
			{
5359
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($style2buffer));
5360
				$test = json_encode($style2buffer);
5361
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$test.'# ->'.strlen($style2buffer).' Error:'.json_last_error());
5362
				//if (json_last_error() != JSON_ERROR_NONE && strlen($style2buffer)>0)
5363
				if ($test=="null" && strlen($style2buffer)>0)
5364
				{
5365
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
5366
					error_log(__METHOD__.' ('.__LINE__.') '.' Found Invalid sequence for utf-8 in CSS:'.$style2buffer.' Charset Reported:'.$singleBodyPart['charSet'].' Carset Detected:'.Translation::detect_encoding($style2buffer));
5367
					$style2buffer = utf8_encode($style2buffer);
5368
				}
5369
			}
5370
			$style .= $style2buffer;
5371
		}
5372
		// clean out comments and stuff
5373
		$search = array(
5374
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
5375
//			'@<!--[\s\S]*?[ \t\n\r]*-->@',   // Strip multi-line comments including CDATA
5376
//			'@<!--[\s\S]*?[ \t\n\r]*--@',    // Strip broken multi-line comments including CDATA
5377
		);
5378
		$style = preg_replace($search,"",$style);
5379
5380
		// CSS Security
5381
		// http://code.google.com/p/browsersec/wiki/Part1#Cascading_stylesheets
5382
		$css = preg_replace('/(javascript|expression|-moz-binding)/i','',$style);
5383
		if (stripos($css,'script')!==false) Mail\Html::replaceTagsCompletley($css,'script'); // Strip out script that may be included
5384
		// 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
5385
		// as the comments as <!-- styledefinition --> in stylesheet are outdated, and ck-editor does not understand it, we remove it
5386
		$css = str_replace(array(':','<!--','-->'),array(': ','',''),$css);
5387
		//error_log(__METHOD__.' ('.__LINE__.') '.$css);
5388
		// TODO: we may have to strip urls and maybe comments and ifs
5389
		return $css;
5390
	}
5391
5392
	/**
5393
	 * getMessageRawBody
5394
	 * get the message raw body
5395
	 * @param string/int $_uid the messageuid,
5396
	 * @param string/int $_partID = '' , the partID, may be omitted
5397
	 * @param string $_folder folder to work on
5398
	 * @return string the message body
5399
	 */
5400
	function getMessageRawBody($_uid, $_partID = '', $_folder='')
5401
	{
5402
		//TODO: caching einbauen static!
5403
		static $rawBody;
5404
		if (is_null($rawBody)) $rawBody = array();
5405 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5406 View Code Duplication
		if (isset($rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5407
		{
5408
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder");
5409
			return $rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5410
		}
5411
5412
		$uidsToFetch = new Horde_Imap_Client_Ids();
5413
		$uid = $_uid;
5414
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5415
		$uidsToFetch->add($uid);
5416
5417
		$fquery = new Horde_Imap_Client_Fetch_Query();
5418
		$fquery->fullText(array('peek'=>true));
5419
		if ($_partID != '')
5420
		{
5421
			$fquery->structure();
5422
			$fquery->bodyPart($_partID,array('peek'=>true));
5423
		}
5424
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5425
			'ids' => $uidsToFetch,
5426
		));
5427
		if (is_object($headersNew)) {
5428 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5429
				$body = $_headerObject->getFullMsg();
5430
				if ($_partID != '')
5431
				{
5432
					$mailStructureObject = $_headerObject->getStructure();
5433
					//_debug_array($mailStructureObject->contentTypeMap());
5434
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5435
					{
5436
						if ($mime_id==$_partID)
5437
						{
5438
							$body = $_headerObject->getBodyPart($mime_id);
5439
						}
5440
					}
5441
				}
5442
			}
5443
		}
5444
		//error_log(__METHOD__.' ('.__LINE__.') '."[$this->icServer->ImapServerId][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]");
5445
		$rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)] = $body;
5446
		return $body;
5447
	}
5448
5449
	/**
5450
	 * Get structure of a mail or part of a mail
5451
	 *
5452
	 * @param int $_uid
5453
	 * @param string $_partID = null
5454
	 * @param string $_folder = null
5455
	 * @param boolean $_preserveSeen = false flag to preserve the seenflag by using body.peek
5456
	 * @param Horde_Imap_Client_Fetch_Query $fquery=null default query just structure
5457
	 * @return Horde_Mime_Part
5458
	 */
5459
	function getStructure($_uid, $_partID=null, $_folder=null, $_preserveSeen=false)
5460
	{
5461
		if (self::$debug) error_log( __METHOD__.' ('.__LINE__.') '.":$_uid, $_partID");
5462
5463 View Code Duplication
		if (empty($_folder))
5464
		{
5465
			$_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5466
		}
5467
		$uidsToFetch = new Horde_Imap_Client_Ids();
5468
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5469
		$uidsToFetch->add($_uid);
5470
		try
5471
		{
5472
			$_fquery = new Horde_Imap_Client_Fetch_Query();
5473
	// not sure why Klaus add these, seem not necessary
5474
	//		$fquery->envelope();
5475
	//		$fquery->size();
5476
			$_fquery->structure();
5477
			if ($_partID) $_fquery->bodyPart($_partID, array('peek' => $_preserveSeen));
1 ignored issue
show
Bug Best Practice introduced by
The expression $_partID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
5478
5479
			$mail = $this->icServer->fetch($_folder, $_fquery, array(
5480
				'ids' => $uidsToFetch,
5481
			))->first();
5482
5483
			return is_object($mail)?$mail->getStructure():null;
5484
		}
5485
		catch (\Exception $e)
5486
		{
5487
			error_log(__METHOD__.' ('.__LINE__.') '.' Could not fetch structure on mail:'.$_uid.' Serverprofile->'.$this->icServer->ImapServerId.' Message:'.$e->getMessage().' Stack:'.function_backtrace());
5488
			return null;
5489
		}
5490
	}
5491
5492
	/**
5493
	 * Parse the structure for attachments
5494
	 *
5495
	 * Returns not the attachments itself, but an array of information about the attachment
5496
	 *
5497
	 * @param int $_uid the messageuid,
5498
	 * @param string $_partID = null , the partID, may be omitted
5499
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
5500
	 * @param boolean $fetchEmbeddedImages = true,
5501
	 * @param boolean $fetchTextCalendar = false,
5502
	 * @param boolean $resolveTNEF = true
5503
	 * @param string $_folder folder to work on
5504
	 * @return array  an array of information about the attachment: array of array(name, size, mimeType, partID, encoding)
5505
	 */
5506
	function getMessageAttachments($_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folder='')
5507
	{
5508
		if (self::$debug) error_log( __METHOD__.":$_uid, $_partID");
5509 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5510
		$attachments = array();
5511
		if (!isset($_structure))
5512
		{
5513
			$_structure = $this->getStructure($_uid, $_partID,$_folder,true);
5514
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.print_r($_structure->contentTypeMap(),true));
5515
		}
5516
		if (!$_structure || !$_structure->contentTypeMap()) return array();
5517
		if (!empty($_partID)) $_structure = $_structure->getPart($_partID);
5518
		$skipParts = array();
5519
		$tnefParts = array();
5520
		$skip = 0;
5521
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
5522
		{
5523
			// skip multipart/encrypted incl. its two sub-parts, as we show 2. sub-part as body to be decrypted client-side
5524
			if ($mime_type == 'multipart/encrypted')
5525
			{
5526
				$skip = 2;
5527
				continue;
5528
			}
5529
			elseif($skip)
5530
			{
5531
				$skip--;
5532
				continue;
5533
			}
5534
			$part = $_structure->getPart($mime_id);
5535
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getMimeId()));
5536
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.$part->getPrimaryType().'/'.$part->getSubType().'->'.$part->getDisposition());
5537
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllDispositionParameters()));
5538
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllContentTypeParameters()));
5539
			$partDisposition = $part->getDisposition();
5540
			$partPrimaryType = $part->getPrimaryType();
5541
			// we only want to retrieve the attachments of the current mail, not those of possible
5542
			// attached mails
5543 View Code Duplication
			if ($mime_type=='message/rfc822' && $_partID!=$mime_id)
5544
			{
5545
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
5546
				foreach($part->contentTypeMap() as $sub_id => $sub_type) {if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
5547
			}
5548
			if (empty($partDisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
5549
			{
5550
				// the absence of an partDisposition does not necessarily indicate there is no attachment. it may be an
5551
				// attachment with no link to show the attachment inline.
5552
				// Considering this: we "list" everything that matches the above criteria
5553
				// as attachment in order to not loose/miss information on our data
5554
				$partDisposition='attachment';
5555
			}
5556
			//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($skipParts));
5557
			if (array_key_exists($mime_id,$skipParts)) continue;
5558
5559
			if ($partDisposition == 'attachment' ||
5560
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image' && $part->getContentId()=='') ||
5561
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType != 'image' && $partPrimaryType != 'text' && $partPrimaryType != 'multipart') ||
5562
				($mime_type=='image/tiff') || //always fetch. even if $fetchEmbeddedImages is false. as we cannot display tiffs
5563
				($fetchEmbeddedImages && ($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image') ||
5564
				($fetchTextCalendar && $partPrimaryType == 'text' && $part->getSubType() == 'calendar'))
5565
			{
5566
				// if type is message/rfc822 and _partID is given, and MimeID equals partID
5567
				// we attempt to fetch "ourselves"
5568
				if ($_partID==$part->getMimeId() && $part->getPrimaryType()=='message') continue;
5569
				$attachment = $part->getAllDispositionParameters();
5570
				$attachment['disposition'] = $part->getDisposition();
5571
				$attachment['mimeType'] = $mime_type;
5572
				$attachment['uid'] = $_uid;
5573
				$attachment['partID'] = $mime_id;
5574 View Code Duplication
				if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5575
				if ($fetchTextCalendar)
5576
				{
5577
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part->getAllContentTypeParameters()));
5578
					$method = $part->getContentTypeParameter('method');
5579
					if ($method) $attachment['method'] = $method;
5580
					if (!isset($attachment['name'])) $attachment['name'] = 'event.ics';
5581
				}
5582
				$attachment['size'] = $part->getBytes();
5583
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5584 View Code Duplication
				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);
5585
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($attachment));
5586
				//typical winmail.dat attachment is
5587
				//Array([size] => 1462762[filename] => winmail.dat[mimeType] => application/ms-tnef[uid] => 100[partID] => 2[name] => winmail.dat)
5588
				if ($resolveTNEF && ($attachment['mimeType']=='application/ms-tnef' || !strcasecmp($attachment['name'],'winmail.dat')))
5589
				{
5590
					$tnefParts[] = $attachment;
5591
				}
5592
				else
5593
				{
5594
					$attachments[] = $attachment;
5595
				}
5596
			}
5597
		}
5598
		if ($resolveTNEF && !empty($tnefParts))
5599
		{
5600
			//error_log(__METHOD__.__LINE__.array2string($tnefParts));
5601
			foreach ($tnefParts as $k => $tnp)
5602
			{
5603
				$tnefResolved=false;
5604
				$tnef_data = $this->getAttachment($tnp['uid'],$tnp['partID'],$k,false);
5605
				$myTnef = $this->tnef_decoder($tnef_data['attachment']);
5606
				//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5607
				// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5608
				// we need to build Something that meets the needs
5609
				if ($myTnef)
5610
				{
5611
					foreach($myTnef->getParts() as $mime_id => $part)
5612
					{
5613
						$tnefResolved=true;
5614
						$attachment = $part->getAllDispositionParameters();
5615
						$attachment['disposition'] = $part->getDisposition();
5616
						$attachment['mimeType'] = $part->getType();
5617
						$attachment['uid'] = $tnp['uid'];
5618
						$attachment['partID'] = $tnp['partID'];
5619
						$attachment['is_winmail'] = $tnp['uid'].'@'.$tnp['partID'].'@'.$mime_id;
5620 View Code Duplication
						if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5621
						$attachment['size'] = $part->getBytes();
5622
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5623 View Code Duplication
						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']);
5624
						$attachments[] = $attachment;
5625
					}
5626
				}
5627
				if ($tnefResolved===false) $attachments[]=$tnp;
5628
			}
5629
		}
5630
		//error_log(__METHOD__.__LINE__.array2string($attachments));
5631
		return $attachments;
5632
	}
5633
5634
	/**
5635
	 * Decode TNEF type attachment into Multipart/mixed attachment
5636
	 *
5637
	 * @param MIME object $data Mime part object
5638
	 *
5639
	 * @return boolean|Horde_Mime_part Multipart/Mixed part decoded attachments |
5640
	 *	return false if there's no attachments or failure
5641
	 */
5642
	public function tnef_decoder( $data )
5643
	{
5644
		foreach(array('Horde_Compress', 'Horde_Icalendar', 'Horde_Mapi') as $class)
5645
		{
5646
			if (!class_exists($class))
5647
			{
5648
				error_log(__METHOD__."() missing required PEAR package $class --> aborting");
5649
				return false;
5650
			}
5651
		}
5652
		$parts_obj = new Horde_Mime_part;
5653
		$parts_obj->setType('multipart/mixed');
5654
5655
		$tnef_object = Horde_Compress::factory('tnef');
5656
		try
5657
		{
5658
			$tnef_data = $tnef_object->decompress($data);
5659
		}
5660
		catch (Horde_Exception $ex)
0 ignored issues
show
Bug introduced by
The class EGroupware\Api\Horde_Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
5661
		{
5662
			error_log(__METHOD__."() ".$ex->getMessage().' --> aborting');
5663
			_egw_log_exception($ex);
5664
			return false;
5665
		}
5666
		if (is_array($tnef_data))
5667
		{
5668
			foreach ($tnef_data as &$data)
5669
			{
5670
				$tmp_part = new Horde_Mime_part;
5671
5672
				$tmp_part->setName($data['name']);
5673
				$tmp_part->setContents($data['stream']);
5674
				$tmp_part->setDescription($data['name']);
5675
5676
				$type = $data['type'] . '/' . $data['subtype'];
5677
				if (in_array($type, array('application/octet-stream', 'application/base64')))
5678
				{
5679
					$type = Horde_Mime_Magic::filenameToMIME($data['name']);
5680
				}
5681
				$tmp_part->setType($type);
5682
				//error_log(__METHOD__.__LINE__.array2string($tmp_part));
5683
				$parts_obj->addPart($tmp_part);
5684
			}
5685
			$parts_obj->buildMimeIds();
5686
			return $parts_obj;
5687
		}
5688
		return false;
5689
	}
5690
5691
	/**
5692
	 * Get attachment data as string, to be used with Link::(get|set)_data()
5693
	 *
5694
	 * @param int $acc_id
5695
	 * @param string $_mailbox
5696
	 * @param int $_uid
5697
	 * @param string $_partID
5698
	 * @param int $_winmail_nr
5699
	 * @return resource stream with attachment content
5700
	 */
5701
	public static function getAttachmentAccount($acc_id, $_mailbox, $_uid, $_partID, $_winmail_nr)
5702
	{
5703
		$bo = self::getInstance(false, $acc_id);
5704
5705
		$attachment = $bo->getAttachment($_uid, $_partID, $_winmail_nr, false, true, $_mailbox);
5706
5707
		return $attachment['attachment'];
5708
	}
5709
5710
	/**
5711
	 * Retrieve tnef attachments
5712
	 *
5713
	 * @param int $_uid the uid of the message
5714
	 * @param string $_partID the id of the part, which holds the attachment
5715
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5716
	 *
5717
	 * @return array returns an array of all resolved embeded attachments from winmail.dat
5718
	 */
5719
	function getTnefAttachments ($_uid, $_partID, $_stream=false)
5720
	{
5721
		$tnef_data = $this->getAttachment($_uid, $_partID,0,false);
5722
		$tnef_parts = $this->tnef_decoder($tnef_data['attachment']);
5723
		$attachments = array();
5724
		if ($tnef_parts)
5725
		{
5726
			foreach($tnef_parts->getParts() as $mime_id => $part)
5727
			{
5728
5729
				$attachment = $part->getAllDispositionParameters();
5730
				$attachment['mimeType'] = $part->getType();
5731 View Code Duplication
				if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5732
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5733 View Code Duplication
				if (empty($attachment['filename']))
5734
				{
5735
					$attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?
5736
						$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
5737
				}
5738
5739
				$attachment['attachment'] = $part->getContents(array('stream'=>$_stream));
5740
5741
				$attachments[$_uid.'@'.$_partID.'@'.$mime_id] = $attachment;
5742
			}
5743
		}
5744
		if (!is_array($attachments)) return false;
5745
		return $attachments;
5746
	}
5747
5748
	/**
5749
	 * Retrieve a attachment
5750
	 *
5751
	 * @param int $_uid the uid of the message
5752
	 * @param string $_partID the id of the part, which holds the attachment
5753
	 * @param int $_winmail_nr = 0 winmail.dat attachment nr.
5754
	 * @param boolean $_returnPart =true flag to indicate if the attachment is to be returned as horde mime part object
5755
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5756
	 * @param string $_folder =null folder to use if not current folder
5757
	 *
5758
	 * @return array
5759
	 */
5760
	function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false, $_folder=null)
5761
	{
5762
		//error_log(__METHOD__.__LINE__."Uid:$_uid, PartId:$_partID, WinMailNr:$_winmail_nr, ReturnPart:$_returnPart, Stream:$_stream, Folder:$_folder".function_backtrace());
5763 View Code Duplication
		if (!isset($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5764
5765
		$uidsToFetch = new Horde_Imap_Client_Ids();
5766
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5767
		$uidsToFetch->add($_uid);
5768
5769
		$fquery = new Horde_Imap_Client_Fetch_Query();
5770
		$fquery->structure();
5771
		$fquery->bodyPart($_partID, array('peek'=>true));
5772
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5773
			'ids' => $uidsToFetch,
5774
		));
5775
		if (is_object($headersNew)) {
5776
			foreach($headersNew as $id=>$_headerObject) {
5777
				$body = $_headerObject->getFullMsg();
5778
				if ($_partID != '')
5779
				{
5780
					$mailStructureObject = $_headerObject->getStructure();
5781
					$mailStructureObject->contentTypeMap();
5782
					$part = $mailStructureObject->getPart($_partID);
5783
					$partDisposition = ($part?$part->getDisposition():'failed');
5784
					if ($partDisposition=='failed')
5785
					{
5786
						error_log(__METHOD__.'('.__LINE__.'):'.array2string($_uid).','.$_partID.' ID:'.$id.' HObject:'.array2string($_headerObject).' StructureObject:'.array2string($mailStructureObject->contentTypeMap()).'->'.function_backtrace());
5787
					}
5788
					// if $partDisposition is empty, we assume attachment, and hope that the function
5789
					// itself is only triggered to fetch attachments
5790
					if (empty($partDisposition)) $partDisposition='attachment';
5791
					if ($part && ($partDisposition=='attachment' || $partDisposition=='inline' || ($part->getPrimaryType() == 'text' && $part->getSubType() == 'calendar')))
5792
					{
5793
						//$headerObject=$part->getAllDispositionParameters();//not used anywhere around here
5794
						$structure_mime = $part->getType();
5795
						$filename = $part->getName();
5796
						$charset = $part->getContentTypeParameter('charset');
5797
						//$structure_bytes = $part->getBytes(); $structure_partID=$part->getMimeId(); error_log(__METHOD__.__LINE__." fetchPartContents(".array2string($_uid).", $structure_partID, $_stream, $_preserveSeen,$structure_mime)" );
5798
						$this->fetchPartContents($_uid, $part, $_stream, $_preserveSeen=true,$structure_mime);
5799
						if ($_returnPart) return $part;
5800
					}
5801
				}
5802
			}
5803
		}
5804
		$ext = MimeMagic::mime2ext($structure_mime);
5805
		if ($ext && stripos($filename,'.')===false && stripos($filename,$ext)===false) $filename = trim($filename).'.'.$ext;
5806
		if (!$part)
5807
		{
5808
			throw new Exception\WrongParameter("Error: Could not fetch attachment for Uid=".array2string($_uid).", PartId=$_partID, WinMailNr=$_winmail_nr, folder=$_folder");
5809
		}
5810
		$attachmentData = array(
5811
			'type'		=> $structure_mime,
5812
			'charset' => $charset,
5813
			'filename'	=> $filename,
5814
			'attachment'	=> $part->getContents(array(
5815
				// tnef_decode needs strings not a stream
5816
				'stream' => $_stream && !($filename == 'winmail.dat' && $_winmail_nr)
5817
			)),
5818
		);
5819
5820
		// try guessing the mimetype, if we get the application/octet-stream
5821
		if (strtolower($attachmentData['type']) == 'application/octet-stream') $attachmentData['type'] = MimeMagic::filename2mime($attachmentData['filename']);
5822
		# if the attachment holds a winmail number and is a winmail.dat then we have to handle that.
5823
		if ( $filename == 'winmail.dat' && $_winmail_nr)
5824
		{
5825
			//by now _uid is of type array
5826
			$tnefResolved=false;
5827
			$wantedPart=$_uid[0].'@'.$_partID;
5828
			$myTnef = $this->tnef_decoder($attachmentData['attachment']);
5829
			//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5830
			// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5831
			// we need to build Something that meets the needs
5832
			if ($myTnef)
5833
			{
5834
				foreach($myTnef->getParts() as $mime_id => $part)
5835
				{
5836
					$tnefResolved=true;
5837
					$attachment = $part->getAllDispositionParameters();
5838
					$attachment['mimeType'] = $part->getType();
5839
					//error_log(__METHOD__.__LINE__.'#'.$mime_id.'#'.$filename.'#'.array2string($attachment));
5840
					//error_log(__METHOD__.__LINE__." $_winmail_nr == $wantedPart@$mime_id");
5841
					if ($_winmail_nr == $wantedPart.'@'.$mime_id)
5842
					{
5843
						//error_log(__METHOD__.__LINE__.'#'.$structure_mime.'#'.$filename.'#'.array2string($attachment));
5844 View Code Duplication
						if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5845
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5846 View Code Duplication
						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']);
5847
						$wmattach = $attachment;
5848
						$wmattach['attachment'] = $part->getContents(array('stream'=>$_stream));
5849
5850
					}
5851
				}
5852
			}
5853
			if ($tnefResolved)
5854
			{
5855
				$ext = MimeMagic::mime2ext($wmattach['mimeType']);
5856
				if ($ext && stripos($wmattach['filename'],'.')===false && stripos($wmattach['filename'],$ext)===false) $wmattach['filename'] = trim($wmattach['filename']).'.'.$ext;
5857
				$attachmentData = array(
5858
					'type'       => $wmattach['mimeType'],
5859
					'filename'   => $wmattach['filename'],
5860
					'attachment' => $wmattach['attachment'],
5861
				);
5862
			}
5863
		}
5864
		return $attachmentData;
5865
	}
5866
5867
	/**
5868
	 * Fetch a specific attachment from a message by it's cid
5869
	 *
5870
	 * this function is based on a on "Building A PHP-Based Mail Client"
5871
	 * http://www.devshed.com
5872
	 *
5873
	 * @param string|int $_uid
5874
	 * @param string $_cid
5875
	 * @param string $_part
5876
	 * @param boolean $_stream = null null do NOT fetch content, use fetchPartContents later
5877
	 *	true:
5878
	 * @return Horde_Mime_Part
5879
	 */
5880
	function getAttachmentByCID($_uid, $_cid, $_part, $_stream=null)
5881
	{
5882
		// some static variables to avoid fetching the same mail multiple times
5883
		static $uid=null, $part=null, $structure=null;
5884
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid, $_cid, $_part");
5885
5886
		if(empty($_cid)) return false;
5887
5888
		if ($_uid != $uid || $_part != $part)
5889
		{
5890
			$structure = $this->getStructure($uid=$_uid, $part=$_part);
5891
		}
5892
		/** @var Horde_Mime_Part */
5893
		$attachment = null;
5894
		foreach($structure->contentTypeMap() as $mime_id => $mime_type)
5895
		{
5896
			$part = $structure->getPart($mime_id);
5897
5898
			if ($part->getPrimaryType() == 'image' &&
5899
				(($cid = $part->getContentId()) &&
5900
				// RB: seem a bit fague to search for inclusion in both ways
5901
				(strpos($cid, $_cid) !== false || strpos($_cid, $cid) !== false)) ||
5902
				(($name = $part->getName()) &&
5903
				(strpos($name, $_cid) !== false || strpos($_cid, $name) !== false)))
5904
			{
5905
				// if we have a direct match, dont search any further
5906
				if ($cid == $_cid)
5907
				{
5908
					$attachment = $part;
5909
				}
5910
				// everything else we only consider after we checked all
5911
				if (!isset($attachment)) $attachment = $part;
5912
				// do we want content fetched, can be done later, if not needed
5913
				if (isset($_stream))
5914
				{
5915
					$this->fetchPartContents($_uid, $attachment, $_stream);
5916
				}
5917
				if (isset($attachment)) break;
5918
			}
5919
		}
5920
		// set name as filename, if not set
5921
		if ($attachment && !$attachment->getDispositionParameter('filename'))
5922
		{
5923
			$attachment->setDispositionParameter('filename', $attachment->getName());
5924
		}
5925
		// guess type, if not set
5926
		if ($attachment && $attachment->getType() == 'application/octet-stream')
5927
		{
5928
			$attachment->setType(MimeMagic::filename2mime($attachment->getDispositionParameter('filename')));
5929
		}
5930
		//error_log(__METHOD__."($_uid, '$_cid', '$_part') returning ".array2string($attachment));
5931
		return $attachment;
5932
	}
5933
5934
	/**
5935
	 * Fetch and add contents to a part
5936
	 *
5937
	 * To get contents you use $part->getContents();
5938
	 *
5939
	 * @param int $_uid
5940
	 * @param Horde_Mime_Part $part
5941
	 * @param boolean $_stream = false true return a stream, false a string
5942
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
5943
	 * @param string  $_mimetype to decide wether to try to fetch part as binary or not
5944
	 * @return Horde_Mime_Part
5945
	 */
5946
	public function fetchPartContents($_uid, Horde_Mime_Part $part=null, $_stream=false, $_preserveSeen=false, $_mimetype=null)
5947
	{
5948
		if (is_null($part)) return null;//new Horde_Mime_Part;
5949
		$encoding = null;
5950
		$fetchAsBinary = true;
5951
		if ($_mimetype && strtolower($_mimetype)=='message/rfc822') $fetchAsBinary = false;
1 ignored issue
show
Bug Best Practice introduced by
The expression $_mimetype of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
5952
		// we need to set content on structure to decode transfer encoding
5953
		$part->setContents(
5954
			$this->getBodyPart($_uid, $part->getMimeId(), null, $_preserveSeen, $_stream, $encoding, $fetchAsBinary),
5955
			array('encoding' => (!$fetchAsBinary&&!$encoding?'8bit':$encoding)));
5956
5957
		return $part;
5958
	}
5959
5960
	/**
5961
	 * save a message in folder
5962
	 *	throws exception on failure
5963
	 * @todo set flags again
5964
	 *
5965
	 * @param string _folderName the foldername
5966
	 * @param string|resource _header header part of message or resource with hole message
5967
	 * @param string _body body part of message, only used if _header is NO resource
5968
	 * @param string _flags = '\\Recent'the imap flags to set for the saved message
5969
	 *
5970
	 * @return the id of the message appended or exception
5971
	 * @throws Exception\WrongUserinput
5972
	 */
5973
	function appendMessage($_folderName, $_header, $_body, $_flags='\\Recent')
5974
	{
5975
		if (!is_resource($_header))
5976
		{
5977
			if (stripos($_header,'message-id:')===false)
5978
			{
5979
				$_header = 'Message-ID: <'.self::getRandomString().'@localhost>'."\n".$_header;
5980
			}
5981
			//error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_header, $_body, $_flags");
5982
			$_header = ltrim(str_replace("\n","\r\n",$_header));
5983
			$_header .= str_replace("\n","\r\n",$_body);
5984
		}
5985
		// the recent flag is the default enforced here ; as we assume the _flags is always set,
5986
		// we default it to hordes default (Recent) (, other wise we should not pass the parameter
5987
		// for flags at all)
5988
		if (empty($_flags)) $_flags = '\\Recent';
5989
		//if (!is_array($_flags) && stripos($_flags,',')!==false) $_flags=explode(',',$_flags);
5990
		//if (!is_array($_flags)) $_flags = (array) $_flags;
5991
		try
5992
		{
5993
			$dataNflags = array();
5994
			// both methods below are valid for appending a message to a mailbox.
5995
			// the commented version fails in retrieving the uid of the created message if the server
5996
			// is not returning the uid upon creation, as the method in append for detecting the uid
5997
			// expects data to be a string. this string is parsed for message-id, and the mailbox
5998
			// searched for the message-id then returning the uid found
5999
			//$dataNflags[] = array('data'=>array(array('t'=>'text','v'=>"$header"."$body")), 'flags'=>array($_flags));
6000
			$dataNflags[] = array('data' => $_header, 'flags'=>array($_flags));
6001
			$messageid = $this->icServer->append($_folderName,$dataNflags);
6002
		}
6003
		catch (\Exception $e)
6004
		{
6005
			if (self::$debug) error_log("Could not append Message: ".$e->getMessage());
6006
			throw new Exception\WrongUserinput(lang("Could not append Message:").' '.$e->getMessage().': '.$e->details);
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
6007
			//return false;
6008
		}
6009
		//error_log(__METHOD__.' ('.__LINE__.') '.' appended UID:'.$messageid);
6010
		//$messageid = true; // for debug reasons only
6011
		if ($messageid === true || empty($messageid)) // try to figure out the message uid
6012
		{
6013
			$list = $this->getHeaders($_folderName, $_startMessage=1, 1, 'INTERNALDATE', true, array(),null, false);
6014
			if ($list)
6015
			{
6016 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' MessageUid:'.$messageid.' but found:'.array2string($list));
6017
				$messageid = $list['header'][0]['uid'];
6018
			}
6019
		}
6020
		return $messageid;
6021
	}
6022
6023
	/**
6024
	 * Get a random string of 32 chars
6025
	 *
6026
	 * @return string
6027
	 */
6028
	static function getRandomString()
6029
	{
6030
		return Auth::randomstring(32);
6031
	}
6032
6033
	/**
6034
	 * functions to allow access to mails through other apps to fetch content
6035
	 * used in infolog, tracker
6036
	 */
6037
6038
	/**
6039
	 * get_mailcontent - fetches the actual mailcontent, and returns it as well defined array
6040
	 * @param object mailClass the mailClassobject to be used
6041
	 * @param uid the uid of the email to be processed
6042
	 * @param partid the partid of the email
6043
	 * @param mailbox the mailbox, that holds the message
6044
	 * @param preserveHTML flag to pass through to getdisplayableBody
6045
	 * @param addHeaderSection flag to be able to supress headersection
6046
	 * @param includeAttachments flag to be able to supress possible attachments
6047
	 * @return array/bool with 'mailaddress'=>$mailaddress,
6048
	 *				'subject'=>$subject,
6049
	 *				'message'=>$message,
6050
	 *				'attachments'=>$attachments,
6051
	 *				'headers'=>$headers,; boolean false on failure
6052
	 */
6053
	static function get_mailcontent(&$mailClass,$uid,$partid='',$mailbox='', $preserveHTML = false, $addHeaderSection=true, $includeAttachments=true)
6054
	{
6055
			//echo __METHOD__." called for $uid,$partid <br>";
6056
			$headers = $mailClass->getMessageHeader($uid,$partid,true,false,$mailbox);
6057
			if (empty($headers)) return false;
6058
			// dont force retrieval of the textpart, let mailClass preferences decide
6059
			$bodyParts = $mailClass->getMessageBody($uid,($preserveHTML?'always_display':'only_if_no_text'),$partid,null,false,$mailbox);
6060
			// if we do not want HTML but there is no TextRepresentation with the message itself, try converting
6061
			if ( !$preserveHTML && $bodyParts[0]['mimeType']=='text/html')
6062
			{
6063
				foreach($bodyParts as $i => $part)
6064
				{
6065
					if ($bodyParts[$i]['mimeType']=='text/html')
6066
					{
6067
						$bodyParts[$i]['body'] = Mail\Html::convertHTMLToText($bodyParts[$i]['body'],$bodyParts[$i]['charSet'],true,$stripalltags=true);
6068
						$bodyParts[$i]['mimeType']='text/plain';
6069
					}
6070
				}
6071
			}
6072
			//error_log(array2string($bodyParts));
6073
			$attachments = $includeAttachments?$mailClass->getMessageAttachments($uid,$partid,null,true,false,true,$mailbox):array();
6074
6075
			if ($mailClass->isSentFolder($mailbox)) $mailaddress = $headers['TO'];
6076
			elseif (isset($headers['FROM'])) $mailaddress = $headers['FROM'];
6077
			elseif (isset($headers['SENDER'])) $mailaddress = $headers['SENDER'];
6078
			if (isset($headers['CC'])) $mailaddress .= ','.$headers['CC'];
6079
			//_debug_array(array($headers,$mailaddress));
6080
			$subject = $headers['SUBJECT'];
6081
6082
			$message = self::getdisplayableBody($mailClass, $bodyParts, $preserveHTML);
6083
			if ($preserveHTML && $mailClass->activeMimeType == 'text/plain') $message = '<pre>'.$message.'</pre>';
6084
			$headdata = ($addHeaderSection ? self::createHeaderInfoSection($headers, '',$preserveHTML) : '');
6085
			$message = $headdata.$message;
6086
			//echo __METHOD__.'<br>';
6087
			//_debug_array($attachments);
6088
			if (is_array($attachments))
6089
			{
6090
				// For dealing with multiple files of the same name
6091
				$dupe_count = $file_list = array();
6092
6093
				foreach ($attachments as $num => $attachment)
6094
				{
6095
					if ($attachment['mimeType'] == 'MESSAGE/RFC822')
6096
					{
6097
						//_debug_array($mailClass->getMessageHeader($uid, $attachment['partID']));
6098
						//_debug_array($mailClass->getMessageBody($uid,'', $attachment['partID']));
6099
						//_debug_array($mailClass->getMessageAttachments($uid, $attachment['partID']));
6100
						$mailcontent = self::get_mailcontent($mailClass,$uid,$attachment['partID'],$mailbox);
6101
						$headdata ='';
6102
						if ($mailcontent['headers'])
6103
						{
6104
							$headdata = self::createHeaderInfoSection($mailcontent['headers'],'',$preserveHTML);
6105
						}
6106
						if ($mailcontent['message'])
6107
						{
6108
							$tempname =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6109
							$attachedMessages[] = array(
6110
								'type' => 'TEXT/PLAIN',
6111
								'name' => $mailcontent['subject'].'.txt',
6112
								'tmp_name' => $tempname,
6113
							);
6114
							$tmpfile = fopen($tempname,'w');
6115
							fwrite($tmpfile,$headdata.$mailcontent['message']);
6116
							fclose($tmpfile);
6117
						}
6118
						foreach($mailcontent['attachments'] as &$tmpval)
6119
						{
6120
							$attachedMessages[] = $tmpval;
6121
						}
6122
						unset($attachments[$num]);
6123
					}
6124
					else
6125
					{
6126
						$attachments[$num] = array_merge($attachments[$num],$mailClass->getAttachment($uid, $attachment['partID'],0,false,false));
6127
6128
						if (empty($attachments[$num]['attachment'])&&$attachments[$num]['cid'])
6129
						{
6130
							$c = $mailClass->getAttachmentByCID($uid, $attachment['cid'], $attachment['partID'],true);
6131
							$attachments[$num]['attachment'] = $c->getContents();
6132
						}
6133
						// no attempt to convert, if we dont know about the charset
6134
						if (isset($attachments[$num]['charset'])&&!empty($attachments[$num]['charset'])) {
6135
							// we do not try guessing the charset, if it is not set
6136
							//if ($attachments[$num]['charset']===false) $attachments[$num]['charset'] = Translation::detect_encoding($attachments[$num]['attachment']);
6137
							Translation::convert($attachments[$num]['attachment'],$attachments[$num]['charset']);
6138
						}
6139
						if(in_array($attachments[$num]['name'], $file_list))
6140
						{
6141
							$dupe_count[$attachments[$num]['name']]++;
6142
							$attachments[$num]['name'] = pathinfo($attachments[$num]['name'], PATHINFO_FILENAME) .
6143
								' ('.($dupe_count[$attachments[$num]['name']] + 1).')' . '.' .
6144
								pathinfo($attachments[$num]['name'], PATHINFO_EXTENSION);
6145
						}
6146
						$attachments[$num]['type'] = $attachments[$num]['mimeType'];
6147
						$attachments[$num]['tmp_name'] = tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6148
						$tmpfile = fopen($attachments[$num]['tmp_name'],'w');
6149
						fwrite($tmpfile,$attachments[$num]['attachment']);
6150
						fclose($tmpfile);
6151
						$file_list[] = $attachments[$num]['name'];
6152
						unset($attachments[$num]['attachment']);
6153
					}
6154
				}
6155
				if (is_array($attachedMessages)) $attachments = array_merge($attachments,$attachedMessages);
6156
			}
6157
			return array(
6158
					'mailaddress'=>$mailaddress,
6159
					'subject'=>$subject,
6160
					'message'=>$message,
6161
					'attachments'=>$attachments,
6162
					'headers'=>$headers,
6163
					);
6164
	}
6165
6166
	/**
6167
	 * getStandardIdentityForProfile
6168
	 * get either the first identity out of the given identities or the one matching the profile_id
6169
	 * @param object/array $_identities identity iterator object or array with identities from Mail\Account
6170
	 * @param integer $_profile_id the acc_id/profileID the identity with the matching key is the standard one
6171
	 * @return array the identity
6172
	 */
6173
	static function getStandardIdentityForProfile($_identities, $_profile_id)
6174
	{
6175
		$c = 0;
6176
		// use the standardIdentity
6177
		foreach($_identities as $key => $acc) {
6178
			if ($c==0) $identity = $acc;
6179
			//error_log(__METHOD__.__LINE__." $key == $_profile_id ");
6180
			if ($key==$_profile_id) $identity = $acc;
6181
			$c++;
6182
		}
6183
		return $identity;
6184
	}
6185
	/**
6186
	 * createHeaderInfoSection - creates a textual headersection from headerobject
6187
	 * @param array header headerarray may contain SUBJECT,FROM,SENDER,TO,CC,BCC,DATE,PRIORITY,IMPORTANCE
6188
	 * @param string headline Text tom use for headline, if SUPPRESS, supress headline and footerline
6189
	 * @param bool createHTML do it with HTML breaks
6190
	 * @return string a preformatted string with the information of the header worked into it
6191
	 */
6192
	static function createHeaderInfoSection($header,$headline='', $createHTML = false)
6193
	{
6194
		$headdata = null;
6195
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($header).function_backtrace());
6196
		if ($header['SUBJECT']) $headdata = lang('subject').': '.$header['SUBJECT'].($createHTML?"<br />":"\n");
6197 View Code Duplication
		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 Mail::convertAddressArrayToString() has too many arguments starting with $createHTML.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
6198 View Code Duplication
		if ($header['SENDER']) $headdata .= lang('sender').': '.self::convertAddressArrayToString($header['SENDER'], $createHTML).($createHTML?"<br />":"\n");
0 ignored issues
show
Unused Code introduced by
The call to Mail::convertAddressArrayToString() has too many arguments starting with $createHTML.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
6199 View Code Duplication
		if ($header['TO']) $headdata .= lang('to').': '.self::convertAddressArrayToString($header['TO'], $createHTML).($createHTML?"<br />":"\n");
0 ignored issues
show
Unused Code introduced by
The call to Mail::convertAddressArrayToString() has too many arguments starting with $createHTML.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
6200 View Code Duplication
		if ($header['CC']) $headdata .= lang('cc').': '.self::convertAddressArrayToString($header['CC'], $createHTML).($createHTML?"<br />":"\n");
0 ignored issues
show
Unused Code introduced by
The call to Mail::convertAddressArrayToString() has too many arguments starting with $createHTML.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
6201 View Code Duplication
		if ($header['BCC']) $headdata .= lang('bcc').': '.self::convertAddressArrayToString($header['BCC'], $createHTML).($createHTML?"<br />":"\n");
0 ignored issues
show
Unused Code introduced by
The call to Mail::convertAddressArrayToString() has too many arguments starting with $createHTML.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
6202 View Code Duplication
		if ($header['DATE']) $headdata .= lang('date').': '.$header['DATE'].($createHTML?"<br />":"\n");
6203 View Code Duplication
		if ($header['PRIORITY'] && $header['PRIORITY'] != 'normal') $headdata .= lang('priority').': '.$header['PRIORITY'].($createHTML?"<br />":"\n");
6204 View Code Duplication
		if ($header['IMPORTANCE'] && $header['IMPORTANCE'] !='normal') $headdata .= lang('importance').': '.$header['IMPORTANCE'].($createHTML?"<br />":"\n");
6205
		//if ($mailcontent['headers']['ORGANIZATION']) $headdata .= lang('organization').': '.$mailcontent['headers']['ORGANIZATION']."\
6206
		if (!empty($headdata))
6207
		{
6208 View Code Duplication
			if (!empty($headline) && $headline != 'SUPPRESS') $headdata = "---------------------------- $headline ----------------------------".($createHTML?"<br />":"\n").$headdata;
6209 View Code Duplication
			if (empty($headline)) $headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'').$headdata;
6210
			$headdata .= ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6211
		}
6212
		else
6213
		{
6214
			$headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6215
		}
6216
		return $headdata;
6217
	}
6218
6219
	/**
6220
	 * adaptSubjectForImport - strips subject from unwanted Characters, and does some normalization
6221
	 * to meet expectations
6222
	 * @param string $subject string to process
6223
	 * @return string
6224
	 */
6225
	static function adaptSubjectForImport($subject)
6226
	{
6227
		$subject = str_replace('$$','__',($subject?$subject:lang('(no subject)')));
6228
		$subject = str_ireplace(array('[FWD]','[',']','{','}','<','>'),array('Fwd:',' ',' ',' ',' ',' ',' '),trim($subject));
6229
		return $subject;
6230
	}
6231
6232
	/**
6233
	 * convertAddressArrayToString - converts an mail envelope Address Array To String
6234
	 * @param array $rfcAddressArray  an addressarray as provided by mail retieved via egw_pear....
6235
	 * @return string a comma separated string with the mailaddress(es) converted to text
6236
	 */
6237
	static function convertAddressArrayToString($rfcAddressArray)
6238
	{
6239
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($rfcAddressArray));
6240
		$returnAddr ='';
6241
		if (is_array($rfcAddressArray))
6242
		{
6243
			foreach((array)$rfcAddressArray as $addressData) {
6244
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressData));
6245
				if($addressData['MAILBOX_NAME'] == 'NIL') {
6246
					continue;
6247
				}
6248
				if(strtolower($addressData['MAILBOX_NAME']) == 'undisclosed-recipients') {
6249
					continue;
6250
				}
6251
				if ($addressData['RFC822_EMAIL'])
6252
				{
6253
					$addressObjectA = self::parseAddressList($addressData['RFC822_EMAIL']);
6254
				}
6255
				else
6256
				{
6257
					$emailaddress = ($addressData['PERSONAL_NAME']?$addressData['PERSONAL_NAME'].' <'.$addressData['EMAIL'].'>':$addressData['EMAIL']);
6258
					$addressObjectA = self::parseAddressList($emailaddress);
6259
				}
6260
				$addressObject = $addressObjectA[0];
6261
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressObject));
6262
				if (!$addressObject->valid) continue;
6263
				//$mb =(string)$addressObject->mailbox;
6264
				//$h = (string)$addressObject->host;
6265
				//$p = (string)$addressObject->personal;
6266
				$returnAddr .= (strlen($returnAddr)>0?',':'');
6267
				//error_log(__METHOD__.' ('.__LINE__.') '.$p.' <'.$mb.'@'.$h.'>');
6268
				$buff = imap_rfc822_write_address($addressObject->mailbox, Horde_Idna::decode($addressObject->host), $addressObject->personal);
6269
				$buff = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$buff);
6270
				//error_log(__METHOD__.' ('.__LINE__.') '.' Address: '.$returnAddr);
6271
				$returnAddr .= $buff;
6272
			}
6273
		}
6274
		else
6275
		{
6276
			// do not mess with strings, return them untouched /* ToDo: validate string as Address */
6277
			$rfcAddressArray = self::decode_header($rfcAddressArray,true);
6278
			$rfcAddressArray = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$rfcAddressArray);
6279
			if (is_string($rfcAddressArray)) return $rfcAddressArray;
6280
		}
6281
		return $returnAddr;
6282
	}
6283
6284
	/**
6285
	 * Merges a given content with contact data
6286
	 *
6287
	 * @param string $content
6288
	 * @param array $ids array with contact id(s)
6289
	 * @param string &$err error-message on error
6290
	 * @return string/boolean merged content or false on error
6291
	 */
6292
	static function merge($content,$ids,$mimetype='')
6293
	{
6294
		$mergeobj = new Contacts\Merge();
6295
6296
		if (empty($mimetype)) $mimetype = (strlen(strip_tags($content)) == strlen($content) ?'text/plain':'text/html');
6297
		$rv = $mergeobj->merge_string($content,$ids,$err='',$mimetype, array(), self::$displayCharset);
0 ignored issues
show
Bug introduced by
$err = '' cannot be passed to merge_string() as the parameter $err expects a reference.
Loading history...
6298
		if (empty($rv) && !empty($content) && !empty($err)) $rv = $content;
6299
		if (!empty($err) && !empty($content) && !empty($ids)) error_log(__METHOD__.' ('.__LINE__.') '.' Merge failed for Ids:'.array2string($ids).' ContentType:'.$mimetype.' Content:'.$content.' Reason:'.array2string($err));
6300
		return $rv;
6301
	}
6302
6303
	/**
6304
	 * Returns a string showing the size of the message/attachment
6305
	 *
6306
	 * @param integer $bytes
6307
	 * @return string formatted string
6308
	 */
6309
	static function show_readable_size($bytes)
6310
	{
6311
		$bytes /= 1024;
6312
		$type = 'k';
6313
6314
		if ($bytes / 1024 > 1)
6315
		{
6316
			$bytes /= 1024;
6317
			$type = 'M';
6318
6319
			if ($bytes / 1024 > 1)
6320
			{
6321
				$bytes *= 10;
6322
				settype($bytes, 'integer');
6323
				$bytes /= 10;
6324
				$bytes /= 1024;
6325
				$type = 'G';
6326
			}
6327
6328
		}
6329
6330
		if ($bytes < 10)
6331
		{
6332
			$bytes *= 10;
6333
			settype($bytes, 'integer');
6334
			$bytes /= 10;
6335
		}
6336
		else
6337
			settype($bytes, 'integer');
6338
6339
		return $bytes . ' ' . $type ;
6340
	}
6341
6342
	static function detect_qp(&$sting) {
6343
		$needle = '/(=[0-9][A-F])|(=[A-F][0-9])|(=[A-F][A-F])|(=[0-9][0-9])/';
6344
		return preg_match("$needle",$string);
0 ignored issues
show
Bug introduced by
The variable $string does not exist. Did you mean $sting?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
6345
	}
6346
6347
	/**
6348
	 * logRunTimes
6349
	 *	logs to the error log all parameters given; output only if self::$debugTimes is true
6350
	 *
6351
	 * @param int $_starttime starttime of the action measured based on microtime(true)
6352
	 * @param int $_endtime endtime of the action measured, if not given microtime(true) is used
6353
	 * @param string $_message message to output details or params, whatever seems neccesary
6354
	 * @param string $_methodNline - Information where the log was taken
6355
	 * @return void
6356
	 */
6357
	static function logRunTimes($_starttime,$_endtime=null,$_message='',$_methodNline='')
6358
	{
6359
		if (is_null($_endtime)) $_endtime = microtime(true);
6360
		$usagetime = microtime(true) - $_starttime;
6361
		if (self::$debugTimes) error_log($_methodNline.' took:'.number_format($usagetime,5).'(s) '.($_message?'Details:'.$_message:''));
6362
	}
6363
6364
	/**
6365
	 * check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6366
	 *
6367
	 * @param array $_formData passed by reference Array with information of name, type, file and size, mimetype may be adapted
6368
	 * @param string $IDtoAddToFileName id to enrich the returned tmpfilename
6369
	 * @param string $reqMimeType /(default message/rfc822, if set to false, mimetype check will not be performed
6370
	 * @return mixed $fullPathtoFile or exception
6371
	 *
6372
	 * @throws Exception\WrongUserinput
6373
	 */
6374
	static function checkFileBasics(&$_formData, $IDtoAddToFileName='', $reqMimeType='message/rfc822')
6375
	{
6376
		if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'egw-data') return $_formData['file'];
6377
6378
		//error_log(__METHOD__.__FILE__.array2string($_formData).' Id:'.$IDtoAddToFileName.' ReqMimeType:'.$reqMimeType);
6379
		$importfailed = $tmpFileName = false;
6380
		// ignore empty files, but allow to share vfs directories (which can have 0 size)
6381
		if ($_formData['size'] == 0 && parse_url($_formData['file'], PHP_URL_SCHEME) != 'vfs' && is_dir($_formData['file']))
6382
		{
6383
			$importfailed = true;
6384
			$alert_msg .= lang("Empty file %1 ignored.", $_formData['name']);
6385
		}
6386
		elseif (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs' || is_uploaded_file($_formData['file']) ||
6387
			realpath(dirname($_formData['file'])) == realpath($GLOBALS['egw_info']['server']['temp_dir']))
6388
		{
6389
			// ensure existance of eGW temp dir
6390
			// note: this is different from apache temp dir,
6391
			// and different from any other temp file location set in php.ini
6392
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6393
			{
6394
				@mkdir($GLOBALS['egw_info']['server']['temp_dir'],0700);
6395
			}
6396
6397
			// if we were NOT able to create this temp directory, then make an ERROR report
6398
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6399
			{
6400
				$alert_msg .= 'Error:'.'<br>'
6401
					.'Server is unable to access EGroupware tmp directory'.'<br>'
6402
					.$GLOBALS['egw_info']['server']['temp_dir'].'<br>'
6403
					.'Please check your configuration'.'<br>'
6404
					.'<br>';
6405
			}
6406
6407
			// sometimes PHP is very clue-less about MIME types, and gives NO file_type
6408
			// rfc default for unknown MIME type is:
6409
			if ($reqMimeType == 'message/rfc822')
6410
			{
6411
				$mime_type_default = 'message/rfc';
6412
			}
6413
			else
6414
			{
6415
				$mime_type_default = $reqMimeType;
6416
			}
6417
			// check the mimetype by extension. as browsers seem to report crap
6418
			// maybe its application/octet-stream -> this may mean that we could not determine the type
6419
			// so we check for the suffix too
6420
			// trust vfs mime-types, trust the mimetype if it contains a method
6421
			if ((substr($_formData['file'],0,6) !== 'vfs://' || $_formData['type'] == 'application/octet-stream') && stripos($_formData['type'],'method=')===false)
6422
			{
6423
				$buff = explode('.',$_formData['name']);
6424
				$suffix = '';
6425
				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
6426
				if (!empty($suffix)) $sfxMimeType = MimeMagic::ext2mime($suffix);
6427
				if (!empty($suffix) && !empty($sfxMimeType) &&
6428
					(strlen(trim($_formData['type']))==0 || (strtolower(trim($_formData['type'])) != $sfxMimeType)))
6429
				{
6430
					error_log(__METHOD__.' ('.__LINE__.') '.' Data:'.array2string($_formData));
6431
					error_log(__METHOD__.' ('.__LINE__.') '.' Form reported Mimetype:'.$_formData['type'].' but seems to be:'.$sfxMimeType);
6432
					$_formData['type'] = $sfxMimeType;
6433
				}
6434
			}
6435
			if (trim($_formData['type']) == '')
6436
			{
6437
				$_formData['type'] = 'application/octet-stream';
6438
			}
6439
			// if reqMimeType is set to false do not test for that
6440
			if ($reqMimeType)
6441
			{
6442
				// so if PHP did not pass any file_type info, then substitute the rfc default value
6443
				if (substr(strtolower(trim($_formData['type'])),0,strlen($mime_type_default)) != $mime_type_default)
6444
				{
6445
					if (!(strtolower(trim($_formData['type'])) == "application/octet-stream" && $sfxMimeType == $reqMimeType))
6446
					{
6447
						//error_log("Message rejected, no message/rfc. Is:".$_formData['type']);
6448
						$importfailed = true;
6449
						$alert_msg .= lang("File rejected, no %2. Is:%1",$_formData['type'],$reqMimeType);
6450
					}
6451
					if ((strtolower(trim($_formData['type'])) != $reqMimeType && $sfxMimeType == $reqMimeType))
6452
					{
6453
						$_formData['type'] = MimeMagic::ext2mime($suffix);
6454
					}
6455
				}
6456
			}
6457
			// as FreeBSD seems to have problems with the generated temp names we append some more random stuff
6458
			$randomString = chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90));
6459
			$tmpFileName = $GLOBALS['egw_info']['user']['account_id'].
6460
				trim($IDtoAddToFileName).basename($_formData['file']).'_'.$randomString;
6461
6462
			if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs')
6463
			{
6464
				$tmpFileName = $_formData['file'];	// no need to store it somewhere
6465
			}
6466 View Code Duplication
			elseif (is_uploaded_file($_formData['file']))
6467
			{
6468
				move_uploaded_file($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);	// requirement for safe_mode!
6469
			}
6470
			else
6471
			{
6472
				rename($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);
6473
			}
6474
		} else {
6475
			//error_log("Import of message ".$_formData['file']." failes to meet basic restrictions");
6476
			$importfailed = true;
6477
			$alert_msg .= lang("Processing of file %1 failed. Failed to meet basic restrictions.",$_formData['name']);
6478
		}
6479
		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...
6480
		{
6481
			throw new Exception\WrongUserinput($alert_msg);
6482
		}
6483
		else
6484
		{
6485
			if (parse_url($tmpFileName,PHP_URL_SCHEME) == 'vfs')
6486
			{
6487
				Vfs::load_wrapper('vfs');
6488
			}
6489
			return $tmpFileName;
6490
		}
6491
	}
6492
6493
	/**
6494
	 * Parses a html text for images, and adds them as inline attachment
6495
	 *
6496
	 * Images can be data-urls, own VFS webdav.php urls or absolute path.
6497
	 *
6498
	 * @param Mailer $_mailObject instance of the Mailer Object to be used
6499
	 * @param string $_html2parse the html to parse and to be altered, if conditions meet
6500
	 * @param $mail_bo mail bo object
6501
	 * @return void
6502
	 */
6503
	static function processURL2InlineImages(Mailer $_mailObject, &$_html2parse, $mail_bo)
6504
	{
6505
		//error_log(__METHOD__."()");
6506
		$imageC = 0;
6507
		$images = null;
6508
		if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
6509
		{
6510
			foreach($images[2] as $i => $url)
6511
			{
6512
				//$isData = false;
6513
				$basedir = $data = '';
6514
				$needTempFile = true;
6515
6516
				// do not change urls for absolute images (thanks to corvuscorax)
6517
				if (substr($url, 0, 5) !== 'data:')
6518
				{
6519
					$filename = basename($url);
6520
					if (($directory = dirname($url)) == '.') $directory = '';
6521
					$ext = pathinfo($filename, PATHINFO_EXTENSION);
6522
					$mimeType  = MimeMagic::ext2mime($ext);
6523
					if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; }
6524
					$myUrl = $directory.$filename;
6525
					if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
6526
					{
6527
						$basedir = ($_SERVER['HTTPS']?'https://':'http://'.$_SERVER['HTTP_HOST']);
6528
					}
6529
					// use vfs instead of url containing webdav.php
6530
					// ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign
6531
					// webdav.php urls as vfs
6532
					if (strpos($myUrl,'/webdav.php') !== false) // we have a webdav link, so we build a vfs/sqlfs link of it.
6533
					{
6534
						Vfs::load_wrapper('vfs');
6535
						list(,$myUrl) = explode('/webdav.php',$myUrl,2);
6536
						$basedir = 'vfs://default';
6537
						$needTempFile = false;
6538
					}
6539
6540
					// If it is an inline image url, we need to fetch the actuall attachment
6541
					// content and later on to be able to store its content as temp file
6542
					if (strpos($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage') !== false)
6543
					{
6544
						$URI_params = array();
6545
						// Strips the url and store it into a temp for further procss
6546
						$tmp_url = html_entity_decode($myUrl);
6547
6548
						parse_str(parse_url($tmp_url, PHP_URL_QUERY),$URI_params);
6549
						if ($URI_params['mailbox'] && $URI_params['uid'] && $URI_params['cid'])
6550
						{
6551
							$mail_bo->reopen(base64_decode($URI_params['mailbox']));
6552
							$attachment = $mail_bo->getAttachmentByCID($URI_params['uid'], base64_decode($URI_params['cid']),base64_decode($URI_params['partID']),true);
6553
							$mail_bo->closeConnection();
6554
							if ($attachment)
6555
							{
6556
								$data = $attachment->getContents();
6557
								$mimeType = $attachment->getType();
6558
								$filename = $attachment->getDispositionParameter('filename');
6559
							}
6560
						}
6561
					}
6562
6563
					if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; }
6564
					if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl));
6565
				}
6566
				if (substr($url,0,strlen('data:'))=='data:')
6567
				{
6568
					//error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i]));
6569
					// we only support base64 encoded data
6570
					$tmp = substr($url,strlen('data:'));
6571
					list($mimeType,$data_base64) = explode(';base64,',$tmp);
6572
					$data = base64_decode($data_base64);
6573
					// FF currently does NOT add any mime-type
6574
					if (strtolower(substr($mimeType, 0, 6)) != 'image/')
6575
					{
6576
						$mimeType = MimeMagic::analyze_data($data);
6577
					}
6578
					list($what,$exactly) = explode('/',$mimeType);
6579
					$needTempFile = true;
6580
					$filename = ($what?$what:'data').$imageC++.'.'.$exactly;
6581
				}
6582
				if ($data || $needTempFile === false)
6583
				{
6584
					if ($needTempFile)
6585
					{
6586
						$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6587
						$tmpfile = fopen($attachment_file,'w');
6588
						fwrite($tmpfile,$data);
6589
						fclose($tmpfile);
6590
					}
6591
					else
6592
					{
6593
						$attachment_file = $basedir.urldecode($myUrl);
6594
					}
6595
					// we use $attachment_file as base for cid instead of filename, as it may be image.png
6596
					// (or similar) in all cases (when cut&paste). This may lead to more attached files, in case
6597
					// we use the same image multiple times, but, if we do this, we should try to detect that
6598
					// on upload. filename itself is not sufficient to determine the sameness of images
6599
					$cid = 'cid:' . md5($attachment_file);
6600
					if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null)
6601
					{
6602
						//$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse);
6603
						$_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse);
6604
					}
6605
				}
6606
			}
6607
		}
6608
	}
6609
6610
	/**
6611
	 * importMessageToMergeAndSend
6612
	 *
6613
	 * @param Storage\Merge Storage\Merge bo_merge object
6614
	 * @param string $document the full filename
6615
	 * @param array $SendAndMergeTocontacts array of contact ids
6616
	 * @param string& $_folder (passed by reference) will set the folder used. must be set with a folder, but will hold modifications if
6617
	 *					folder is modified
6618
	 * @param string& $importID ID for the imported message, used by attachments to identify them unambiguously
6619
	 * @return mixed array of messages with success and failed messages or exception
6620
	 */
6621
	function importMessageToMergeAndSend(Storage\Merge $bo_merge, $document, $SendAndMergeTocontacts, &$_folder, &$importID='')
6622
	{
6623
		$importfailed = false;
6624
		$processStats = array('success'=>array(),'failed'=>array());
6625
		if (empty($SendAndMergeTocontacts))
6626
		{
6627
			$importfailed = true;
6628
			$alert_msg .= lang("Import of message %1 failed. No Contacts to merge and send to specified.", '');
6629
		}
6630
6631
		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6632
		/* as the file is provided by Storage\Merge, we do not check
6633
		try
6634
		{
6635
			$tmpFileName = Mail::checkFileBasics($_formData,$importID);
6636
		}
6637
		catch (\Exception\WrongUserinput $e)
6638
		{
6639
			$importfailed = true;
6640
			$alert_msg .= $e->getMessage();
6641
		}
6642
		*/
6643
		$tmpFileName = $document;
6644
		// -----------------------------------------------------------------------
6645
		if ($importfailed === false)
6646
		{
6647
			$mailObject = new Mailer($this->profileID);
6648
			try
6649
			{
6650
				$this->parseFileIntoMailObject($mailObject, $tmpFileName);
6651
			}
6652
			catch (Exception\AssertionFailed $e)
6653
			{
6654
				$importfailed = true;
6655
				$alert_msg .= $e->getMessage();
6656
			}
6657
6658
			//_debug_array($Body);
6659
			$this->openConnection();
6660
			if (empty($_folder))
6661
			{
6662
				$_folder = $this->getSentFolder();
6663
			}
6664
			$delimiter = $this->getHierarchyDelimiter();
6665
			if($_folder=='INBOX'.$delimiter) $_folder='INBOX';
6666
			if ($importfailed === false)
6667
			{
6668
				$Subject = $mailObject->getHeader('Subject');
6669
				//error_log(__METHOD__.' ('.__LINE__.') '.' Subject:'.$Subject);
6670
				$Body = ($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null;
6671
				//error_log(__METHOD__.' ('.__LINE__.') '.' Body:'.$Body);
6672
				//error_log(__METHOD__.' ('.__LINE__.') '.' BodyContentType:'.$mailObject->BodyContentType);
6673
				$AltBody = ($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null;
6674
				//error_log(__METHOD__.' ('.__LINE__.') '.' AltBody:'.$AltBody);
6675
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject->GetReplyTo()));
6676
6677
				// Fetch ReplyTo - Address if existing to check if we are to replace it
6678
				$replyTo = $mailObject->getReplyTo();
6679
				if (isset($replyTo['[email protected]']))
6680
				{
6681
					$mailObject->clearReplyTos();
6682
					$activeMailProfiles = $this->mail->getAccountIdentities($this->profileID);
6683
					$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6684
6685
					$mailObject->addReplyTo(Horde_Idna::encode($activeMailProfile['ident_email']),Mail::generateIdentityString($activeMailProfile,false));
6686
				}
6687
				foreach ($SendAndMergeTocontacts as $k => $val)
6688
				{
6689
					$errorInfo = $email = '';
6690
					$sendOK = $openComposeWindow = $openAsDraft = null;
6691
					//error_log(__METHOD__.' ('.__LINE__.') '.' Id To Merge:'.$val);
6692
					if (/*$GLOBALS['egw_info']['flags']['currentapp'] == 'addressbook' &&*/
6693
						count($SendAndMergeTocontacts) > 1 && $val &&
6694
						(is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val))) // do the merge
6695
					{
6696
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6697
6698
						// Parse destinations for placeholders
6699
						foreach(Mailer::$type2header as $type => $h)
6700
						{
6701
							//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));
6702
							$merged = $bo_merge->merge_string($mailObject->getHeader(Mailer::$type2header[$type]),$val,$e,'text/plain',array(),self::$displayCharset);
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader(\...r::$type2header[$type]) targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Storage\Merge::merge_string() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
6703
							$mailObject->addAddress($merged,'',$type);
6704
							if($type == 'to')
6705
							{
6706
								$email = $merged;
6707
							}
6708
						}
6709
6710
						// No addresses from placeholders?  Treat it as just a contact ID
6711
						if (!$email)
6712
						{
6713
							$contact = $bo_merge->contacts->read($val);
6714
							//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
6715
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6716
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6717
							if($email)
6718
							{
6719
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6720
							}
6721
						}
6722
6723
						$activeMailProfiles = $this->getAccountIdentities($this->profileID);
6724
						$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6725
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($activeMailProfile));
6726
						$mailObject->setFrom($activeMailProfile['ident_email'],
6727
							self::generateIdentityString($activeMailProfile,false));
6728
6729
						$mailObject->removeHeader('Message-ID');
6730
						$mailObject->removeHeader('Date');
6731
						$mailObject->clearCustomHeaders();
6732
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6733
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6734 View Code Duplication
						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));
6735
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6736 View Code Duplication
						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));
6737
6738
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6739
						// set a higher timeout for big messages
6740
						@set_time_limit(120);
6741
						$sendOK = true;
6742
						try {
6743
							$mailObject->send();
6744
						}
6745
						catch(Exception $e) {
6746
							$sendOK = false;
6747
							$errorInfo = $e->getMessage();
6748
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($errorInfo));
6749
						}
6750
					}
6751
					elseif (!$k)	// 1. entry, further entries will fail for apps other then addressbook
6752
					{
6753
						$openAsDraft = true;
6754
						$mailObject->removeHeader('Message-ID');
6755
						$mailObject->removeHeader('Date');
6756
						$mailObject->clearCustomHeaders();
6757
6758
						// Parse destinations for placeholders
6759
						foreach(Mailer::$type2header as $type => $h)
6760
						{
6761
							$merged = $bo_merge->merge_string($mailObject->getHeader(Mailer::$type2header[$type]),$val,$e,'text/plain',array(),self::$displayCharset);
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader(\...r::$type2header[$type]) targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Storage\Merge::merge_string() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
6762
							//error_log($type . ': ' . $mailObject->getHeader(Mailer::$type2header[$type]) . ' -> ' .$merged);
6763
							$mailObject->addAddress(trim($merged,'"'),'',$type);
6764
						}
6765
6766
						// No addresses from placeholders?  Treat it as just a contact ID
6767
						if (count($mailObject->getAddresses('to',true)) == 0 &&
6768
							is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val)) // do the merge
6769
						{
6770
							$contact = $bo_merge->contacts->read($val);
6771
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($contact));
6772
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6773
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6774
							if($email)
6775
							{
6776
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6777
							}
6778
						}
6779
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6780
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6781 View Code Duplication
						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));
6782
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6783 View Code Duplication
						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));
6784
						$_folder = $this->getDraftFolder();
6785
					}
6786
					if ($sendOK || $openAsDraft)
6787
					{
6788
						if ($this->folderExists($_folder,true))
6789
						{
6790
						    if($this->isSentFolder($_folder))
6791
							{
6792
						        $flags = '\\Seen';
6793
						    } elseif($this->isDraftFolder($_folder)) {
6794
						        $flags = '\\Draft';
6795
						    } else {
6796
						        $flags = '';
6797
						    }
6798
							$savefailed = false;
6799
							try
6800
							{
6801
								$messageUid =$this->appendMessage($_folder,
6802
									$mailObject->getRaw(),
6803
									null,
6804
									$flags);
6805
							}
6806
							catch (\Exception\WrongUserinput $e)
0 ignored issues
show
Bug introduced by
The class Exception\WrongUserinput does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
6807
							{
6808
								$savefailed = true;
6809
								$alert_msg .= lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",$Subject,$_folder,$e->getMessage());
6810
							}
6811
							// no send, save successful, and message_uid present
6812
							if ($savefailed===false && $messageUid && is_null($sendOK))
6813
							{
6814
								$importID = $messageUid;
6815
								$openComposeWindow = true;
6816
							}
6817
						}
6818
						else
6819
						{
6820
							$savefailed = true;
6821
							$alert_msg .= lang("Saving of message %1 failed. Destination Folder %2 does not exist.",$Subject,$_folder);
6822
						}
6823
						if ($sendOK)
6824
						{
6825
							$processStats['success'][$val] = 'Send succeeded to '.$nfn.'<'.$email.'>'.($savefailed?' but failed to store to Folder:'.$_folder:'');
6826
						}
6827
						else
6828
						{
6829
							if (!$openComposeWindow) $processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
1 ignored issue
show
Bug Best Practice introduced by
The expression $openComposeWindow of type boolean|null 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...
6830
						}
6831
					}
6832
					if (!is_null($sendOK) && $sendOK===false && is_null($openComposeWindow))
6833
					{
6834
						$processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
6835
					}
6836
				}
6837
			}
6838
			unset($mailObject);
6839
		}
6840
		// set the url to open when refreshing
6841
		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...
6842
		{
6843
			throw new Exception\WrongUserinput($alert_msg);
6844
		}
6845
		else
6846
		{
6847
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($processStats));
6848
			return $processStats;
6849
		}
6850
	}
6851
6852
	/**
6853
	 * functions to allow the parsing of message/rfc files
6854
	 * used in felamimail to import mails, or parsev a message from file enrich it with addressdata (merge) and send it right away.
6855
	 */
6856
6857
	/**
6858
	 * Parses a message/rfc mail from file to the mailobject
6859
	 *
6860
	 * @param object $mailer instance of the SMTP Mailer Object
6861
	 * @param string $tmpFileName string that points/leads to the file to be imported
6862
	 * @throws Exception\NotFound if $fle is not found
6863
	 */
6864
	function parseFileIntoMailObject(Mailer $mailer, $tmpFileName)
6865
	{
6866
		switch (parse_url($tmpFileName, PHP_URL_SCHEME))
6867
		{
6868
			case 'vfs':
6869
				break;
6870
			case 'egw-data':
6871
				$message = Link::get_data(parse_url($tmpFileName, PHP_URL_HOST), true);
0 ignored issues
show
Security Bug introduced by
It seems like parse_url($tmpFileName, PHP_URL_HOST) targeting parse_url() can also be of type false; however, EGroupware\Api\Link::get_data() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
6872
				break;
6873
			default:
6874
				$tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($tmpFileName);
6875
				break;
6876
		}
6877
		if (!isset($message)) $message = fopen($tmpFileName, 'r');
6878
6879
		if (!$message)
6880
		{
6881
			throw new Exception\NotFound("File '$tmpFileName' not found!");
6882
		}
6883
		$this->parseRawMessageIntoMailObject($mailer, $message);
6884
6885
		fclose($message);
6886
	}
6887
6888
	/**
6889
	 * Parses a message/rfc mail from file to the mailobject
6890
	 *
6891
	 * @param Mailer $mailer instance of SMTP Mailer object
6892
	 * @param string|ressource|Horde_Mime_Part $message string or resource containing the RawMessage / object Mail_mimeDecoded message (part))
6893
	 * @throws Exception\WrongParameter when the required Horde_Mail_Part not found
6894
	 */
6895
	function parseRawMessageIntoMailObject(Mailer $mailer, $message)
6896
	{
6897
		if (is_string($message) || is_resource($message))
6898
		{
6899
			$structure = Horde_Mime_Part::parseMessage($message);
6900
			$mailer->setBasePart($structure);
6901
			//error_log(__METHOD__.__LINE__.':'.array2string($structure));
6902
6903
			// unfortunately parseMessage does NOT return parsed headers (we assume header is shorter then 8k)
6904
			$start = is_string($message) ? substr($message, 0, 8192) :
6905
				(fseek($message, 0, SEEK_SET) == -1 ? '' : fread($message, 8192));
6906
6907
			$length = strpos($start, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
6908
			if ($length===false) $length = strlen($start);
6909
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$length));
6910
6911
			foreach($headers->toArray(array('nowrap' => true)) as $header => $value)
6912
			{
6913
				foreach((array)$value as $n => $val)
6914
				{
6915
					$overwrite = !$n;
6916
					switch($header)
6917
					{
6918
						case 'Content-Transfer-Encoding':
6919
							//as we parse the message and this sets the part with a Content-Transfer-Encoding, we
6920
							//should not overwrite it with the header-values of the source-message as the encoding
6921
							//may be altered when retrieving the message e.g. from server
6922
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val.'<->'.$mailer->getHeader('Content-Transfer-Encoding'));
6923
							break;
6924
						default:
6925
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
6926
							$mailer->addHeader($header, $val, $overwrite);
6927
					}
6928
				}
6929
			}
6930
		}
6931
		elseif (is_a($message, 'Horde_Mime_Part'))
6932
		{
6933
			$mailer->setBasePart($message);
6934
		}
6935
		else
6936
		{
6937
			if (($type = gettype($message)) == 'object') $type = get_class ($message);
6938
			throw new Exception\WrongParameter('Wrong parameter type for message: '.$type);
6939
		}
6940
	}
6941
6942
	/**
6943
	 * Parse an address-list
6944
	 *
6945
	 * Replaces imap_rfc822_parse_adrlist, which fails for utf-8, if not our replacement in common_functions is used!
6946
	 *
6947
	 * @param string $addresses
6948
	 * @param string $default_domain
6949
	 * @return Horde_Mail_Rfc822_List iteratable Horde_Mail_Rfc822_Address objects with attributes mailbox, host, personal and valid
6950
	 */
6951
	public static function parseAddressList($addresses, $default_domain=null)
6952
	{
6953
		$rfc822 = new Horde_Mail_Rfc822();
6954
		$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
6955
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count.function_backtrace());
6956
		if ((empty($ret) || $ret->count()==0)&& is_string($addresses) && strlen($addresses)>0)
6957
		{
6958
			$matches = array();
6959
			preg_match_all("/[\w\.,-.,_.,0-9.]+@[\w\.,-.,_.,0-9.]+/",$addresses,$matches);
6960
			//error_log(__METHOD__.__LINE__.array2string($matches));
6961
			foreach ($matches[0] as &$match) {$match = trim($match,', ');}
6962
			$addresses = implode(',',$matches[0]);
6963
			//error_log(__METHOD__.__LINE__.array2string($addresses));
6964
			$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
6965
			//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count);
6966
		}
6967
		$previousFailed=false;
6968
		$ret2 = new Horde_Mail_Rfc822_List();
6969
		// handle known problems on emailaddresses
6970
		foreach($ret as $i => $adr)
6971
		{
6972
			//mailaddresses enclosed in single quotes like '[email protected]' show up as 'me as mailbox and you.com' as host
6973
			if ($adr->mailbox && stripos($adr->mailbox,"'")== 0 &&
6974
					$adr->host && stripos($adr->host,"'")== (strlen($adr->host) -1))
6975
			{
6976
				$adr->mailbox = str_replace("'","",$adr->mailbox);
6977
				$adr->host = str_replace("'","",$adr->host);
6978
			}
6979
			// no mailbox or host part as 'Xr\xc3\xa4hlyz, User <[email protected]>' is parsed as 2 addresses separated by ','
6980
			//#'Xr\xc3\xa4hlyz, User <[email protected]>'
6981
			//#Horde_Mail_Rfc822_List Object([_data:protected] => Array(
6982
			//[0] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => Xr\xc3\xa4hlyz[_host:protected] => [_personal:protected] => )
6983
			//[1] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => mailboxpart1.mailboxpart2[_host:protected] => youthost.com[_personal:protected] => User))[_filter:protected] => Array()[_ptr:protected] => )#2#,
6984
			if (strlen($adr->mailbox)==0||strlen($adr->host)==0)
6985
			{
6986
				$remember = ($adr->mailbox?$adr->mailbox:($adr->host?$adr->host:''));
6987
				$previousFailed=true;
6988
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
6989
			}
6990
			else
6991
			{
6992
				if ($previousFailed && $remember) $adr->personal = $remember. ' ' . $adr->personal;
6993
				$remember = '';
6994
				$previousFailed=false;
6995
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
6996
				$ret2->add($adr);
6997
			}
6998
		}
6999
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret2).'#'.$ret2->count().'#'.$ret2->count);
7000
		return $ret2;
7001
	}
7002
7003
	/**
7004
	 * Send a read notification
7005
	 *
7006
	 * @param string $uid
7007
	 * @param string $_folder
7008
	 * @return boolean
7009
	 */
7010
	function sendMDN($uid,$_folder)
7011
	{
7012
		$acc = Mail\Account::read($this->profileID);
7013
		$identity = Mail\Account::read_identity($acc['ident_id'], true, null, $acc);
7014
		if (self::$debug) error_log(__METHOD__.__LINE__.array2string($identity));
7015
		$headers = $this->getMessageHeader($uid, '', 'object', true, $_folder);
7016
7017
		$mdn = new Horde_Mime_Mdn($headers);
7018
		$mdn->generate(true, true, 'displayed', php_uname('n'), $acc->smtpTransport(), array(
7019
			'charset' => 'utf-8',
7020
			'from_addr' => self::generateIdentityString($identity),
7021
		));
7022
7023
		return true;
7024
	}
7025
7026
	/**
7027
	 * Hook stuff
7028
	 */
7029
7030
	/**
7031
	 * hook to add account
7032
	 *
7033
	 * this function is a wrapper function for emailadmin
7034
	 *
7035
	 * @param _hookValues contains the hook values as array
7036
	 * @return nothing
7037
	 */
7038
	function addAccount($_hookValues)
7039
	{
7040
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7041
7042
	}
7043
7044
	/**
7045
	 * hook to delete account
7046
	 *
7047
	 * this function is a wrapper function for emailadmin
7048
	 *
7049
	 * @param _hookValues contains the hook values as array
7050
	 * @return nothing
7051
	 */
7052
	function deleteAccount($_hookValues)
7053
	{
7054
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7055
7056
	}
7057
7058
	/**
7059
	 * hook to update account
7060
	 *
7061
	 * this function is a wrapper function for emailadmin
7062
	 *
7063
	 * @param _hookValues contains the hook values as array
7064
	 * @return nothing
7065
	 */
7066
	function updateAccount($_hookValues)
7067
	{
7068
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7069
7070
	}
7071
}
7072