Completed
Push — 16.1 ( bd8dd1...6aed0b )
by Hadi
38:41 queued 19:17
created

Mail::_checkAndfixLongHeaderFields()   D

Complexity

Conditions 10
Paths 12

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 12
nop 1
dl 0
loc 38
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
		// we switch the balance off because of some broken html mails contents get removed like (td in table), and let browser deal with it
135
		'balance'=>0,//turn off tag-balancing (config['balance']=>0). That will not introduce any security risk; only standards-compliant tag nesting check/filtering will be turned off (basic tag-balance will remain; i.e., there won't be any unclosed tag, etc., after filtering)
136
		'direct_list_nest' => 1,
137
		'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
138
		// tidy eats away even some wanted whitespace, so we switch it off;
139
		// we used it for its compacting and beautifying capabilities, which resulted in better html for further processing
140
		'tidy'=>0,
141
		'elements' => "* -script",
142
		'deny_attribute' => 'on*',
143
		'schemes'=>'href: file, ftp, http, https, mailto, phone; src: cid, data, file, ftp, http, https; *:file, http, https, cid, src',
144
		'hook_tag' =>"hl_email_tag_transform",
145
	);
146
147
	/**
148
	 * static used define abbrevations for common access rights
149
	 *
150
	 * @array
151
	 */
152
	static $aclShortCuts = array('' => array('label'=>'none','title'=>'The user has no rights whatsoever.'),
153
		'lrs'		=> array('label'=>'readable','title'=>'Allows a user to read the contents of the mailbox.'),
154
		'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.'),
155
		'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.'),
156
		'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.'),
157
		'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.'),
158
		'custom'	=> array('label'=>'custom','title'=>'User defined combination of rights for the ACL'),
159
	);
160
161
	/**
162
	 * Folders that get automatic created AND get translated to the users language
163
	 * their creation is also controlled by users mailpreferences. if set to none / dont use folder
164
	 * the folder will not be automatically created. This is controlled in Mail->getFolderObjects
165
	 * so changing names here, must include a change of keywords there as well. Since these
166
	 * foldernames are subject to translation, keep that in mind too, if you change names here.
167
	 * lang('Drafts'), lang('Templates'), lang('Sent'), lang('Trash'), lang('Junk'), lang('Outbox')
168
	 * ActiveSync:
169
	 *  Outbox is needed by Nokia Clients to be able to send Mails
170
	 * @var array
171
	 */
172
	static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox');
173
174
	/**
175
	 * Array to cache the specialUseFolders, if existing
176
	 * @var array
177
	 */
178
	static $specialUseFolders;
179
180
	/**
181
	 * Hold instances by profileID for getInstance() singleton
182
	 *
183
	 * @var array
184
	 */
185
	private static $instances = array();
186
	private static $profileDefunct = array();
187
188
	/**
189
	 * Singleton for Mail
190
	 *
191
	 * @param boolean $_restoreSession = true
192
	 * @param int $_profileID = 0
193
	 * @param boolean $_validate = true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile
194
	 *                                  not matching the input profileID, if we can not find a profile matching the given ID
195
	 * @param mixed boolean/object $_icServerObject - if object, return instance with object set as icServer
196
	 *												  immediately, if boolean === true use oldImapServer in constructor
197
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
198
	 * @return Mail
199
	 */
200
	public static function getInstance($_restoreSession=true, &$_profileID=0, $_validate=true, $_oldImapServerObject=false, $_reuseCache=null)
201
	{
202
		//$_restoreSession=false;
203
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
204
		//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());
205
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_oldImapServerObject));
206
		self::$profileDefunct = Cache::getCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),5*1);
207
		if (isset(self::$profileDefunct[$_profileID]) && strlen(self::$profileDefunct[$_profileID]))
208
		{
209
			throw new Exception(__METHOD__." failed to instanciate Mail for Profile #$_profileID Reason:".self::$profileDefunct[$_profileID]);
210
		}
211
		if ($_oldImapServerObject instanceof Mail\Imap)
212
		{
213
			if (!is_object(self::$instances[$_profileID]))
214
			{
215
				self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
216
			}
217
			self::$instances[$_profileID]->icServer = $_oldImapServerObject;
218
			self::$instances[$_profileID]->accountid= $_oldImapServerObject->ImapServerId;
219
			self::$instances[$_profileID]->profileID= $_oldImapServerObject->ImapServerId;
220
			self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
221
			self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
222
			return self::$instances[$_profileID];
223
		}
224
		if ($_profileID == 0)
225
		{
226 View Code Duplication
			if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
227
			{
228
				$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
229
			}
230
			else
231
			{
232
				$profileID = Mail\Account::get_default_acc_id();
233
			}
234
			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...
235
			$_profileID=$profileID;
236 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace());
237
		}
238
		// no validation or restoreSession for old ImapServer Object, just fetch it and return it
239
		if ($_oldImapServerObject===true)
240
		{
241
			return new Mail('utf-8',false,$_profileID,true,$_reuseCache);
242
		}
243
		if ($_profileID != 0 && $_validate)
244
		{
245
			$profileID = self::validateProfileID($_profileID);
246
			if ($profileID != $_profileID)
247
			{
248
				if (self::$debug)
249
				{
250
					error_log(__METHOD__.' ('.__LINE__.') '.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.');
251
					error_log(__METHOD__.' ('.__LINE__.') '.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);
252
				}
253
				$_profileID = $profileID;
254
				//$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
255
				// save prefs
256
				//$GLOBALS['egw']->preferences->save_repository(true);
257
			}
258
			//Cache::setSession('mail','activeProfileID',$_profileID);
259
		}
260
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
261
		if ($_profileID && (!isset(self::$instances[$_profileID]) || $_restoreSession===false))
262
		{
263
			self::$instances[$_profileID] = new Mail('utf-8',$_restoreSession,$_profileID,false,$_reuseCache);
264
		}
265
		else
266
		{
267
			//refresh objects
268
			try
269
			{
270
				self::$instances[$_profileID]->icServer = Mail\Account::read($_profileID)->imapServer();
271
				self::$instances[$_profileID]->ogServer = Mail\Account::read($_profileID)->smtpServer();
272
				// TODO: merge mailprefs into userprefs, for easy treatment
273
				self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
274
				self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
275
			} catch (\Exception $e)
276
			{
277
				$newprofileID = Mail\Account::get_default_acc_id();
278
				// try loading the default profile for the user
279
				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());
280
				if ($newprofileID)
281
				{
282
					self::$instances[$newprofileID] = new Mail('utf-8',false,$newprofileID,false,$_reuseCache);
283
					$_profileID = $newprofileID;
284
				}
285
				else
286
				{
287
					throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_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...
288
				}
289
			}
290
			self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );
291
		}
292
		self::$instances[$_profileID]->profileID = $_profileID;
293
		if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new Horde_Idna;
294
		//if ($_profileID==0); error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID);
295
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
296
		return self::$instances[$_profileID];
297
	}
298
299
	/**
300
	 * This method tries to fix alias address lacking domain part
301
	 * by trying to add domain part extracted from given reference address
302
	 *
303
	 * @param string $refrence email address to be used for domain extraction
304
	 * @param string $address alias address
305
	 *
306
	 * @return string returns alias address with appended default domain
307
	 */
308
	public static function fixInvalidAliasAddress($refrence, $address)
309
	{
310
		$parts = explode('@', $refrence);
311
		if (!strpos($address,'@') && !empty($parts[1])) $address .= '@'.$parts[1];
312
		return $address;
313
	}
314
315
	/**
316
	 * store given ProfileID to Session and pref
317
	 *
318
	 * @param int $_profileID = 0
319
	 * @param boolean $_testConnection = 0
320
	 * @return mixed $_profileID or false on failed ConnectionTest
321
	 */
322
	public static function storeActiveProfileIDToPref($_icServerObject, $_profileID=0, $_testConnection=true)
323
	{
324 View Code Duplication
		if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
325
		{
326
			$oldProfileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
327
		}
328
		if ($_testConnection)
329
		{
330
			try
331
			{
332
				$_icServerObject->getCurrentMailbox();
333
			}
334
			catch (\Exception $e)
335
			{
336
				if ($_profileID != Mail\Account::get_default_acc_id()) $_profileID = Mail\Account::get_default_acc_id();
337
				error_log(__METHOD__.__LINE__.' '.$e->getMessage());
338
				return false;
339
			}
340
		}
341
		if ($oldProfileID != $_profileID)
342
		{
343
			if ($oldProfileID && $_profileID==0) $_profileID = $oldProfileID;
344
			$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
345
			// save prefs
346
			$GLOBALS['egw']->preferences->save_repository(true);
347
			$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $_profileID;
348
			Cache::setSession('mail','activeProfileID',$_profileID);
349
		}
350
		return $_profileID;
351
	}
352
353
	/**
354
	 * Validate given account acc_id to make sure account is valid for current user
355
	 *
356
	 * Validation checks:
357
	 * - non-empty imap-host
358
	 * - non-empty imap-username
359
	 *
360
	 * @param int $_acc_id = 0
361
	 * @return int validated acc_id -> either acc_id given, or first valid one
362
	 */
363
	public static function validateProfileID($_acc_id=0)
364
	{
365
		if ($_acc_id)
366
		{
367
			try {
368
				$account = Mail\Account::read($_acc_id);
369
				if ($account->is_imap())
370
				{
371
					return $_acc_id;
372
				}
373
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT valid, no imap-host!");
374
			}
375
			catch (\Exception $e) {
376
				unset($e);
377
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT found!");
378
			}
379
		}
380
		// no account specified or specified account not found or not valid
381
		// --> search existing account for first valid one and return that
382
		foreach(Mail\Account::search($only_current_user=true, 'acc_imap_host') as $acc_id => $imap_host)
383
		{
384
			if (!empty($imap_host) && ($account = Mail\Account::read($acc_id)) && $account->is_imap())
385
			{
386
				if (self::$debug && $_acc_id) error_log(__METHOD__."($_acc_id) using $acc_id instead");
387
				return $acc_id;
388
			}
389
		}
390
		if (self::$debug) error_log(__METHOD__."($_acc_id) NO valid account found!");
391
		return 0;
392
	}
393
394
395
	/**
396
	 * Private constructor, use Mail::getInstance() instead
397
	 *
398
	 * @param string $_displayCharset = 'utf-8'
399
	 * @param boolean $_restoreSession = true
400
	 * @param int $_profileID = 0 if not nummeric, we assume we only want an empty class object
401
	 * @param boolean $_oldImapServerObject = false
402
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
403
	 */
404
	private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0, $_oldImapServerObject=false, $_reuseCache=null)
405
	{
406
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
407
		if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset;
408
		// not nummeric, we assume we only want an empty class object
409
		if (!is_numeric($_profileID)) return true;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
410
		if ($_restoreSession)
411
		{
412
			//error_log(__METHOD__." Session restore ".function_backtrace());
413
			$this->restoreSessionData();
414
			$lv_mailbox = $this->sessionData['mailbox'];
415
			$firstMessage = $this->sessionData['previewMessage'];
416
		}
417
		else
418
		{
419
			$this->restoreSessionData();
420
			$lv_mailbox = $this->sessionData['mailbox'];
421
			$firstMessage = $this->sessionData['previewMessage'];
422
			$this->sessionData = array();
423
		}
424
		if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
425
		try
426
		{
427
			$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...
428
			$this->accountid	= $GLOBALS['egw_info']['user']['account_id'];
429
430
			//error_log(__METHOD__.' ('.__LINE__.') '." ProfileID ".$this->profileID.' called from:'.function_backtrace());
431
			$acc = Mail\Account::read($this->profileID);
432
		}
433
		catch (\Exception $e)
434
		{
435
			throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage());
436
		}
437
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($acc->imapServer()));
438
		$this->icServer = ($_oldImapServerObject?$acc->oldImapServer():$acc->imapServer());
439
		$this->ogServer = $acc->smtpServer();
440
		// TODO: merge mailprefs into userprefs, for easy treatment
441
		$this->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
442
		$this->htmlOptions  = $this->mailPreferences['htmlOptions'];
443
		if (isset($this->icServer->ImapServerId) && !empty($this->icServer->ImapServerId))
444
		{
445
			$_profileID = $this->profileID = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->icServer->ImapServerId;
446
		}
447
448
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
449
	}
450
451
	/**
452
	 * forceEAProfileLoad
453
	 * used to force the load of a specific emailadmin profile; we assume administrative use only (as of now)
454
	 * @param int $_profile_id
455
	 * @return object instance of Mail (by reference)
456
	 */
457
	public static function &forceEAProfileLoad($_profile_id)
458
	{
459
		self::unsetCachedObjects($_profile_id);
460
		$mail = self::getInstance(false, $_profile_id,false);
461
		//_debug_array( $_profile_id);
462
		$mail->icServer = Mail\Account::read($_profile_id)->imapServer();
463
		$mail->ogServer = Mail\Account::read($_profile_id)->smtpServer();
464
		return $mail;
465
	}
466
467
	/**
468
	 * trigger the force of the reload of the SessionData by resetting the session to an empty array
469
	 * @param int $_profile_id
470
	 * @param boolean $_resetFolderObjects
471
	 */
472
	public static function forcePrefReload($_profile_id=null,$_resetFolderObjects=true)
473
	{
474
		// unset the mail_preferences session object, to force the reload/rebuild
475
		Cache::setSession('mail','mail_preferences',serialize(array()));
476
		Cache::setSession('emailadmin','session_data',serialize(array()));
477
		if ($_resetFolderObjects) self::resetFolderObjectCache($_profile_id);
478
	}
479
480
	/**
481
	 * restore the SessionData
482
	 */
483
	function restoreSessionData()
484
	{
485
		$this->sessionData = array();//Cache::getCache(Cache::SESSION,'mail','session_data',$callback=null,$callback_params=array(),$expiration=60*60*1);
486
		self::$activeFolderCache = Cache::getCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
487 View Code Duplication
		if (!empty(self::$activeFolderCache[$this->profileID])) $this->sessionData['mailbox'] = self::$activeFolderCache[$this->profileID];
488
	}
489
490
	/**
491
	 * saveSessionData saves session data
492
	 */
493
	function saveSessionData()
494
	{
495
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($this->sessionData)));
496 View Code Duplication
		if (!empty($this->sessionData['mailbox'])) self::$activeFolderCache[$this->profileID]=$this->sessionData['mailbox'];
497
		if (isset(self::$activeFolderCache) && is_array(self::$activeFolderCache))
498
		{
499
			Cache::setCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),self::$activeFolderCache, 60*60*10);
500
		}
501
		// no need to block session any longer
502
		$GLOBALS['egw']->session->commit_session();
503
	}
504
505
	/**
506
	 * unset certain CachedObjects for the given profile id, unsets the profile for default ID=0 as well
507
	 *
508
	 * 1) icServerIMAP_connectionError
509
	 * 2) icServerSIEVE_connectionError
510
	 * 3) INSTANCE OF MAIL_BO
511
	 * 4) HierarchyDelimiter
512
	 * 5) VacationNotice
513
	 *
514
	 * @param int $_profileID = null default profile of user as returned by getUserDefaultProfileID
515
	 * @return void
516
	 */
517
	static function unsetCachedObjects($_profileID=null)
518
	{
519
		if (is_null($_profileID)) $_profileID = Mail\Account::get_default_acc_id();
520
		if (is_array($_profileID) && $_profileID['account_id']) $account_id = $_profileID['account_id'];
521
		//error_log(__METHOD__.__LINE__.' called with ProfileID:'.array2string($_profileID).' from '.function_backtrace());
522
		if (!is_array($_profileID) && (is_numeric($_profileID) || !(stripos($_profileID,'tracker_')===false)))
523
		{
524
			self::resetConnectionErrorCache($_profileID);
525
			$rawHeadersCache = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
526 View Code Duplication
			if (isset($rawHeadersCache[$_profileID]))
527
			{
528
				unset($rawHeadersCache[$_profileID]);
529
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$rawHeadersCache, $expiration=60*60*1);
530
			}
531
			$HierarchyDelimiterCache = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*5);
532 View Code Duplication
			if (isset($HierarchyDelimiterCache[$_profileID]))
533
			{
534
				unset($HierarchyDelimiterCache[$_profileID]);
535
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$HierarchyDelimiterCache, $expiration=60*60*24*5);
536
			}
537
			//reset folderObject cache, to trigger reload
538
			self::resetFolderObjectCache($_profileID);
539
			//reset counter of deleted messages per folder
540
			$eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
541 View Code Duplication
			if (isset($eMailListContainsDeletedMessages[$_profileID]))
542
			{
543
				unset($eMailListContainsDeletedMessages[$_profileID]);
544
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$eMailListContainsDeletedMessages, $expiration=60*60*1);
545
			}
546
			$vacationCached = Cache::getCache(Cache::INSTANCE, 'email', 'vacationNotice'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*1);
547 View Code Duplication
			if (isset($vacationCached[$_profileID]))
548
			{
549
				unset($vacationCached[$_profileID]);
550
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),$vacationCached, $expiration=60*60*24*1);
551
			}
552
553
			if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
554
		}
555
		if (is_array($_profileID) && $_profileID['location'] == 'clear_cache')
556
		{
557
			// called via hook
558
			foreach($GLOBALS['egw']->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
559
			{
560
				//error_log(__METHOD__.__LINE__.array2string($account));
561
				$account_id = $account['account_id'];
562
				$_profileID = null;
563
				self::resetConnectionErrorCache($_profileID,$account_id);
564
				self::resetFolderObjectCache($_profileID,$account_id);
565
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),array(), 60*60*1);
566
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),array(), 60*60*24*5);
567
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),array(), 60*60*1);
568
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),array(), 60*60*24*1);
569
			}
570
		}
571
	}
572
573
	/**
574
	 * resets the various cache objects where connection error Objects may be cached
575
	 *
576
	 * @param int $_ImapServerId the profileID to look for
577
	 * @param int $account_id the egw account to look for
578
	 */
579
	static function resetConnectionErrorCache($_ImapServerId=null,$account_id=null)
580
	{
581
		//error_log(__METHOD__.' ('.__LINE__.') '.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($account_id));
582
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
583
		if (is_array($_ImapServerId))
584
		{
585
			// called via hook
586
			$account_id = $_ImapServerId['account_id'];
587
			unset($_ImapServerId);
588
			$_ImapServerId = null;
589
		}
590
		if (is_null($_ImapServerId))
591
		{
592
			$isConError = array();
593
			$waitOnFailure = array();
594
		}
595
		else
596
		{
597
			$isConError = Cache::getCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id));
598
			if (isset($isConError[$_ImapServerId]))
599
			{
600
				unset($isConError[$_ImapServerId]);
601
			}
602
			$waitOnFailure = Cache::getCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),null,array(),60*60*2);
603
			if (isset($waitOnFailure[$_ImapServerId]))
604
			{
605
				unset($waitOnFailure[$_ImapServerId]);
606
			}
607
		}
608
		Cache::setCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,60*15);
609
		Cache::setCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),$waitOnFailure,60*60*2);
610
	}
611
612
	/**
613
	 * resets the various cache objects where Folder Objects may be cached
614
	 *
615
	 * @param int $_ImapServerId the profileID to look for
616
	 * @param int $account_id the egw account to look for
617
	 */
618
	static function resetFolderObjectCache($_ImapServerId=null,$account_id=null)
619
	{
620
		//error_log(__METHOD__.' ('.__LINE__.') '.' called for Profile:'.array2string($_ImapServerId).'->'.function_backtrace());
621
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
622
		// on [location] => verify_settings we coud either use [prefs] => Array([ActiveProfileID] => 9, .. as $_ImapServerId
623
		// or treat it as not given. we try that path
624
		if (is_null($_ImapServerId)||is_array($_ImapServerId))
625
		{
626
			$folders2return = array();
627
			$folderInfo = array();
628
			$folderBasicInfo = array();
629
			$_specialUseFolders = array();
630
		}
631
		else
632
		{
633
			$folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),null,array(),60*60*1);
634
			if (!empty($folders2return) && isset($folders2return[$_ImapServerId]))
635
			{
636
				unset($folders2return[$_ImapServerId]);
637
			}
638
			$folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),null,array(),60*60*5);
639
			if (!empty($folderInfo) && isset($folderInfo[$_ImapServerId]))
640
			{
641
				unset($folderInfo[$_ImapServerId]);
642
			}
643
			/*
644
			$lastFolderUsedForMove = Cache::getCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),null,array(),$expiration=60*60*1);
645
			if (isset($lastFolderUsedForMove[$_ImapServerId]))
646
			{
647
				unset($lastFolderUsedForMove[$_ImapServerId]);
648
			}
649
			*/
650
			$folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),null,array(),60*60*1);
651
			if (!empty($folderBasicInfo) && isset($folderBasicInfo[$_ImapServerId]))
652
			{
653
				unset($folderBasicInfo[$_ImapServerId]);
654
			}
655
			$_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),null,array(),60*60*12);
656
			if (!empty($_specialUseFolders) && isset($_specialUseFolders[$_ImapServerId]))
657
			{
658
				unset($_specialUseFolders[$_ImapServerId]);
659
				self::$specialUseFolders=null;
660
			}
661
		}
662
		Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),$folders2return, 60*60*1);
663
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),$folderInfo,60*60*5);
664
		//Cache::setCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),$lastFolderUsedForMove,$expiration=60*60*1);
665
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),$folderBasicInfo,60*60*1);
666
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),$_specialUseFolders,60*60*12);
667
	}
668
669
	/**
670
	 * checks if the imap server supports a given capability
671
	 *
672
	 * @param string $_capability the name of the capability to check for
673
	 * @return bool
674
	 */
675
	function hasCapability($_capability)
676
	{
677
		$rv = $this->icServer->hasCapability(strtoupper($_capability));
678
		//error_log(__METHOD__.' ('.__LINE__.') '." $_capability:".array2string($rv));
679
		return $rv;
680
	}
681
682
	/**
683
	 * getUserEMailAddresses - function to gather the emailadresses connected to the current mail-account
684
	 * @param string $_profileID the ID of the mailaccount to check for identities, if null current mail-account is used
685
	 * @return array - array(email=>realname)
686
	 */
687
	function getUserEMailAddresses($_profileID=null)
688
	{
689
		$acc = Mail\Account::read((!empty($_profileID)?$_profileID:$this->profileID));
690
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($acc));
691
		$identities = Mail\Account::identities($acc);
692
693
		$userEMailAdresses = array($acc['ident_email']=>$acc['ident_realname']);
694
695
		foreach($identities as $ik => $ident) {
696
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
697
			$identity = Mail\Account::read_identity($ik);
698
			if (!empty($identity['ident_email']) && !isset($userEMailAdresses[$identity['ident_email']])) $userEMailAdresses[$identity['ident_email']] = $identity['ident_realname'];
699
		}
700
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
701
		return $userEMailAdresses;
702
	}
703
704
	/**
705
	 * getAllIdentities - function to gather the identities connected to the current user
706
	 * @param string/int $_accountToSearch = null if set search accounts for user specified
707
	 * @param boolean $resolve_placeholders wether or not resolve possible placeholders in identities
708
	 * @return array - array(email=>realname)
709
	 */
710
	static function getAllIdentities($_accountToSearch=null,$resolve_placeholders=false)
711
	{
712
		$userEMailAdresses = array();
713
		foreach(Mail\Account::search($only_current_user=($_accountToSearch?$_accountToSearch:true), $just_name=true) as $acc_id => $identity_name)
714
		{
715
			$acc = Mail\Account::read($acc_id,($_accountToSearch?$_accountToSearch:null));
716
			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']);
717
718
			foreach(Mail\Account::identities($acc) as $ik => $ident) {
719
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
720
				$identity = Mail\Account::read_identity($ik,$resolve_placeholders);
721
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
722 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']);
723
			}
724
		}
725
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
726
		return $userEMailAdresses;
727
	}
728
729
	/**
730
	 * Get all identities of given mailaccount
731
	 *
732
	 * @param int|Mail\Account $account account-object or acc_id
733
	 * @return array - array(email=>realname)
734
	 */
735
	function getAccountIdentities($account)
736
	{
737
		if (!$account instanceof Mail\Account)
738
		{
739
			$account = Mail\Account::read($account);
740
		}
741
		$userEMailAdresses = array();
742
		foreach(Mail\Account::identities($account, true, 'params') as $ik => $ident) {
743
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
744
			$identity = Mail\Account::read_identity($ik,true,null,$account);
745
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
746
			// standardIdentity has ident_id==acc_id (as it is done within account->identities)
747
			if (empty($identity['ident_id'])) $identity['ident_id'] = $identity['acc_id'];
748 View Code Duplication
			if (!isset($userEMailAdresses[$identity['ident_id']]))
749
			{
750
				$userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$identity['acc_id'],
751
																'ident_id'=>$identity['ident_id'],
752
																'ident_email'=>$identity['ident_email'],
753
																'ident_org'=>$identity['ident_org'],
754
																'ident_realname'=>$identity['ident_realname'],
755
																'ident_signature'=>$identity['ident_signature'],
756
																'ident_name'=>$identity['ident_name']);
757
			}
758
		}
759
760
		return $userEMailAdresses;
761
	}
762
763
	/**
764
	 * Function to gather the default identitiy connected to the current mailaccount
765
	 *
766
	 * @return int - id of the identity
767
	 */
768
	function getDefaultIdentity()
769
	{
770
		// retrieve the signature accociated with the identity
771
		$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...
772
		foreach(Mail\Account::identities($_accountData[$this->profileID] ?
773
			$this->profileID : $_accountData[$id],false,'ident_id') as $accountData)
774
		{
775
			return $accountData;
776
		}
777
	}
778
779
	/**
780
	 * getIdentitiesWithAccounts
781
	 *
782
	 * @param array reference to pass all identities back
783
	 * @return the default Identity (active) or 0
784
	 */
785
	function getIdentitiesWithAccounts(&$identities)
786
	{
787
		// account select box
788
		$selectedID = $this->profileID;
789
		$allAccountData = Mail\Account::search($only_current_user=true, false, null);
790
		if ($allAccountData) {
791
			$rememberFirst=$selectedFound=null;
792
			foreach ($allAccountData as $tmpkey => $icServers)
793
			{
794
				if (is_null($rememberFirst)) $rememberFirst = $tmpkey;
795
				if ($tmpkey == $selectedID) $selectedFound=true;
796
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($icServers->acc_imap_host));
797
				$host = $icServers->acc_imap_host;
798
				if (empty($host)) continue;
799
				$identities[$icServers->acc_id] = $icServers['ident_realname'].' '.$icServers['ident_org'].' <'.$icServers['ident_email'].'>';
800
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($identities[$icServers->acc_id]));
801
			}
802
		}
803
		return ($selectedFound?$selectedID:$rememberFirst);
804
	}
805
806
	/**
807
	 * construct the string representing an Identity passed by $identity
808
	 *
809
	 * @var array/object $identity, identity object that holds realname, organization, emailaddress and signatureid
810
	 * @var boolean $fullString full or false=NamePart only is returned
811
	 * @return string - constructed of identity object data as defined in mailConfig
812
	 */
813
	static function generateIdentityString($identity, $fullString=true)
814
	{
815
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($identity));
816
		//if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
817
		// not set? -> use default, means full display of all available data
818
		//if (!isset(self::$mailConfig['how2displayIdentities'])) self::$mailConfig['how2displayIdentities']='';
819
		$how2displayIdentities = '';
820
		switch ($how2displayIdentities)
821
		{
822
			case 'email';
823
				//$retData = str_replace('@',' ',$identity->emailAddress).($fullString===true?' <'.$identity->emailAddress.'>':'');
824
				$retData = $identity['ident_email'].($fullString===true?' <'.$identity['ident_email'].'>':'');
825
				break;
826 View Code Duplication
			case 'nameNemail';
827
				$retData = (!empty($identity['ident_realname'])?$identity['ident_realname']:substr_replace($identity['ident_email'],'',strpos($identity['ident_email'],'@'))).($fullString===true?' <'.$identity['ident_email'].'>':'');
828
				break;
829 View Code Duplication
			case 'orgNemail';
830
				$retData = (!empty($identity['ident_org'])?$identity['ident_org']:substr_replace($identity['ident_email'],'',0,strpos($identity['ident_email'],'@')+1)).($fullString===true?' <'.$identity['ident_email'].'>':'');
831
				break;
832
			default:
833
				$retData = $identity['ident_realname'].(!empty($identity['ident_org'])?' '.$identity['ident_org']:'').($fullString===true?' <'.$identity['ident_email'].'>':'');
834
		}
835
		return $retData;
836
	}
837
838
	/**
839
	 * closes a connection on the active Server ($this->icServer)
840
	 *
841
	 * @return void
842
	 */
843
	function closeConnection()
844
	{
845
		//if ($icServer->_connected) error_log(__METHOD__.' ('.__LINE__.') '.' disconnect from Server');
846
		//error_log(__METHOD__."() ".function_backtrace());
847
		$this->icServer->disconnect();
848
	}
849
850
	/**
851
	 * reopens a connection for the active Server ($this->icServer), and selects the folder given
852
	 *
853
	 * @param string $_foldername folder to open/select
854
	 * @return void
855
	 */
856
	function reopen($_foldername)
857
	{
858
		if (self::$debugTimes) $starttime = microtime (true);
859
860
		//error_log(__METHOD__.' ('.__LINE__.') '."('$_foldername') ".function_backtrace());
861
		// TODO: trying to reduce traffic to the IMAP Server here, introduces problems with fetching the bodies of
862
		// eMails when not in "current-Folder" (folder that is selected by UI)
863
		static $folderOpened;
864
		//if (empty($folderOpened) || $folderOpened!=$_foldername)
865
		//{
866
			//error_log( __METHOD__.' ('.__LINE__.') '." $_foldername ".function_backtrace());
867
			//error_log(__METHOD__.' ('.__LINE__.') '.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true));
868
			if ($this->folderIsSelectable($_foldername)) {
869
				$this->icServer->openMailbox($_foldername);
870
			}
871
			$folderOpened = $_foldername;
872
		//}
873
		if (self::$debugTimes) self::logRunTimes($starttime,null,'Folder:'.$_foldername,__METHOD__.' ('.__LINE__.') ');
874
	}
875
876
877
	/**
878
	 * openConnection
879
	 *
880
	 * @param int $_icServerID = 0
881
	 * @throws Horde_Imap_Client_Exception on connection error or authentication failure
882
	 * @throws InvalidArgumentException on missing credentials
883
	 */
884
	function openConnection($_icServerID=0)
885
	{
886
		//error_log( "-------------------------->open connection ".function_backtrace());
887
		//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
888
		if (self::$debugTimes) $starttime = microtime (true);
889
		$mailbox=null;
890
		try
891
		{
892
			if(isset($this->sessionData['mailbox'])&&$this->folderExists($this->sessionData['mailbox'])) $mailbox = $this->sessionData['mailbox'];
893
			if (empty($mailbox)) $mailbox = $this->icServer->getCurrentMailbox();
894
/*
895
			if (isset(Mail\Imap::$supports_keywords[$_icServerID]))
896
			{
897
				$this->icServer->openMailbox($mailbox);
898
			}
899
			else
900
			{
901
				$this->icServer->examineMailbox($mailbox);
902
			}
903
*/
904
			// the above should detect if there is a known information about supporting KEYWORDS
905
			// but does not work as expected :-(
906
			$this->icServer->examineMailbox($mailbox);
907
			//error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true));
908
			//error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID.function_backtrace());
909
910
			//make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache
911
			$this->getHierarchyDelimiter(false);
912
			self::$specialUseFolders = $this->getSpecialUseFolders();
913
		}
914
		catch (\Exception $e)
915
		{
916
			error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID." trying to examine ($mailbox) failed!".$e->getMessage());
917
			throw new Exception(__METHOD__." failed to ".__METHOD__." on Profile to $_icServerID while trying to examine $mailbox:".$e->getMessage());
918
		}
919
		if (self::$debugTimes) self::logRunTimes($starttime,null,'ProfileID:'.$_icServerID,__METHOD__.' ('.__LINE__.') ');
920
	}
921
922
	/**
923
	 * getQuotaRoot
924
	 * return the qouta of the users INBOX
925
	 *
926
	 * @return mixed array/boolean
927
	 */
928
	function getQuotaRoot()
929
	{
930
		static $quota;
931
		if (isset($quota)) return $quota;
932
		if (isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID]))
933
		{
934
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
935
			return false;
936
		}
937
		try
938
		{
939
			$this->icServer->getCurrentMailbox();
940
			if(!$this->icServer->hasCapability('QUOTA')) {
941
				$quota = false;
942
				return false;
943
			}
944
			$quota = $this->icServer->getStorageQuotaRoot('INBOX');
945
		}
946
		catch (Exception $e)
947
		{
948
			//error_log(__METHOD__.array2string($e));
949
			//error_log(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
950
			if ($e->getCode()==102)
951
			{
952
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
0 ignored issues
show
Bug introduced by
The property details does not seem to exist 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...
953
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
954
				throw new Exception(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
955
			}
956
		}
957
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($quota));
958
		if(is_array($quota)) {
959
			$quota = array(
960
				'usage'	=> $quota['USED'],
961
				'limit'	=> $quota['QMAX'],
962
			);
963
		} else {
964
			$quota = false;
965
		}
966
		return $quota;
967
	}
968
969
	/**
970
	 * getTimeOut
971
	 *
972
	 * @param string _use decide if the use is for IMAP or SIEVE, by now only the default differs
973
	 *
974
	 * @return int - timeout (either set or default 20/10)
975
	 */
976
	static function getTimeOut($_use='IMAP')
977
	{
978
		$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
979
		if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
980
		return $timeout;
981
	}
982
983
	/**
984
	 * Fetch the namespace from icServer
985
	 *
986
	 * An IMAPServer may present several namespaces under each key:
987
	 * so we return an array of namespacearrays for our needs
988
	 *
989
	 * @return array array(prefix_present=>mixed (bool/string) ,prefix=>string,delimiter=>string,type=>string (personal|others|shared))
990
	 */
991 View Code Duplication
	function _getNameSpaces()
992
	{
993
		static $nameSpace = null;
994
		$foldersNameSpace = array();
995
		$delimiter = $this->getHierarchyDelimiter();
996
		// TODO: cache by $this->icServer->ImapServerId
997
		if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaceArray();
998
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($nameSpace));
999
		if (is_array($nameSpace)) {
1000
			foreach($nameSpace as $type => $singleNameSpaceArray)
1001
			{
1002
				foreach ($singleNameSpaceArray as $singleNameSpace)
1003
				{
1004
					$_foldersNameSpace = array();
1005
					if($type == 'personal' && $singleNameSpace['name'] == '#mh/' && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
1006
					{
1007
						$_foldersNameSpace['prefix_present'] = 'forced';
1008
						// uw-imap server with mailbox prefix or dovecot maybe
1009
						$_foldersNameSpace['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace['name'])?$singleNameSpace['name']:''));
1010
					}
1011
					elseif($type == 'personal' && ($singleNameSpace['name'] == '#mh/') && $this->folderExists('mail'))
1012
					{
1013
						$_foldersNameSpace['prefix_present'] = 'forced';
1014
						// uw-imap server with mailbox prefix or dovecot maybe
1015
						$_foldersNameSpace['prefix'] = 'mail';
1016
					} else {
1017
						$_foldersNameSpace['prefix_present'] = !empty($singleNameSpace['name']);
1018
						$_foldersNameSpace['prefix'] = $singleNameSpace['name'];
1019
					}
1020
					$_foldersNameSpace['delimiter'] = ($singleNameSpace['delimiter']?$singleNameSpace['delimiter']:$delimiter);
1021
					$_foldersNameSpace['type'] = $type;
1022
					$foldersNameSpace[] =$_foldersNameSpace;
1023
				}
1024
				//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################<br>";
1025
			}
1026
		}
1027
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($foldersNameSpace));
1028
		return $foldersNameSpace;
1029
	}
1030
1031
	/**
1032
	 * Wrapper to extract the folder prefix from folder compared to given namespace array
1033
	 *
1034
	 * @param array $nameSpace
1035
	 * @paam string $_folderName
1036
	 * @return string the prefix (may be an empty string)
1037
	 */
1038 View Code Duplication
	function getFolderPrefixFromNamespace($nameSpace, $folderName)
1039
	{
1040
		foreach($nameSpace as &$singleNameSpace)
1041
		{
1042
			//if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix'];
1043
			if (substr($folderName,0,strlen($singleNameSpace['prefix']))==$singleNameSpace['prefix']) return $singleNameSpace['prefix'];
1044
		}
1045
		return "";
1046
	}
1047
1048
	/**
1049
	 * getHierarchyDelimiter
1050
	 *
1051
	 * @var boolean $_useCache
1052
	 * @return string the hierarchyDelimiter
1053
	 */
1054
	function getHierarchyDelimiter($_useCache=true)
1055
	{
1056
		static $HierarchyDelimiter = null;
1057
		if (is_null($HierarchyDelimiter)) $HierarchyDelimiter = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
1058
		if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]);
1059 View Code Duplication
		if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId]))
1060
		{
1061
			return $HierarchyDelimiter[$this->icServer->ImapServerId];
1062
		}
1063
		$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1064
		try
1065
		{
1066
			$this->icServer->getCurrentMailbox();
1067
			$HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getDelimiter();
1068
		}
1069
		catch(\Exception $e)
1070
		{
1071
			if ($e->getCode()==102)
1072
			{
1073
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
0 ignored issues
show
Bug introduced by
The property details does not seem to exist 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...
1074
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1075
			}
1076
			unset($e);
1077
			$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1078
		}
1079
		Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),$HierarchyDelimiter, 60*60*24*5);
1080
		return $HierarchyDelimiter[$this->icServer->ImapServerId];
1081
	}
1082
1083
	/**
1084
	 * getSpecialUseFolders
1085
	 * @ToDo: could as well be static, when icServer is passed
1086
	 * @return mixed null/array
1087
	 */
1088
	function getSpecialUseFolders()
1089
	{
1090
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$this->icServer->ImapServerId.' Connected:'.$this->icServer->_connected);
1091
		static $_specialUseFolders = null;
1092
		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);
1093
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_trash));
1094
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
1095
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
1096
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
1097
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1098 View Code Duplication
		if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
1099
			return $_specialUseFolders[$this->icServer->ImapServerId];
1100
		$_specialUseFolders[$this->icServer->ImapServerId]=array();
1101
		//if (!empty($this->icServer->acc_folder_trash) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]))
1102
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]='Trash';
1103
		//if (!empty($this->icServer->acc_folder_draft) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]))
1104
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]='Drafts';
1105
		//if (!empty($this->icServer->acc_folder_sent) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]))
1106
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent';
1107
		//if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]))
1108
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates';
1109
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk';
1110
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_archive]='Archive';
1111
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer));
1112
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1113
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, 60*60*24*5);
1114
		return $_specialUseFolders[$this->icServer->ImapServerId];
1115
	}
1116
1117
	/**
1118
	 * get IMAP folder status regarding NoSelect
1119
	 *
1120
	 * @param foldertoselect string the foldername
1121
	 *
1122
	 * @return boolean true or false regarding the noselect attribute
1123
	 */
1124
	function folderIsSelectable($folderToSelect)
1125
	{
1126
		$retval = true;
1127
		if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) {
1128 View Code Duplication
			if (!empty($folderStatus['attributes']) && stripos(array2string($folderStatus['attributes']),'noselect')!==false)
1129
			{
1130
				$retval = false;
1131
			}
1132
		}
1133
		return $retval;
1134
	}
1135
1136
	/**
1137
	 * get IMAP folder status, wrapper to store results within a single request
1138
	 *
1139
	 * returns an array information about the imap folder
1140
	 *
1141
	 * @param folderName string the foldername
1142
	 * @param ignoreStatusCache bool ignore the cache used for counters
1143
	 *
1144
	 * @return array
1145
	 *
1146
	 * @throws Exception
1147
	 */
1148
	function _getStatus($folderName,$ignoreStatusCache=false)
1149
	{
1150
		static $folderStatus = null;
1151
		if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName]))
1152
		{
1153
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName]));
1154
			return $folderStatus[$this->icServer->ImapServerId][$folderName];
1155
		}
1156
		try
1157
		{
1158
			$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName,$ignoreStatusCache);
1159
		}
1160
		catch (\Exception $e)
1161
		{
1162
			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...
1163
		}
1164
		return $folderStatus[$this->icServer->ImapServerId][$folderName];
1165
	}
1166
1167
	/**
1168
	 * get IMAP folder status
1169
	 *
1170
	 * returns an array information about the imap folder, may be used as  wrapper to retrieve results from cache
1171
	 *
1172
	 * @param _folderName string the foldername
1173
	 * @param ignoreStatusCache bool ignore the cache used for counters
1174
	 * @param basicInfoOnly bool retrieve only names and stuff returned by getMailboxes
1175
	 * @param fetchSubscribedInfo bool fetch Subscribed Info on folder
1176
	 * @return array
1177
	 */
1178
	function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false,$fetchSubscribedInfo=true)
1179
	{
1180
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." called with:$_folderName,$ignoreStatusCache,$basicInfoOnly");
1181
		if (!is_string($_folderName) || empty($_folderName)||(isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID])))
1182
		{
1183
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
1184
			return false;
1185
		}
1186
		static $folderInfoCache = null; // reduce traffic on single request
1187
		static $folderBasicInfo = null;
1188
		if (isset($folderBasicInfo[$this->profileID]))
1189
		{
1190
			$folderInfoCache = $folderBasicInfo[$this->profileID];
1191
		}
1192
		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...
1193
		$retValue = array();
1194
		$retValue['subscribed'] = false;
1195
/*
1196
		if(!$icServer = Mail\Account::read($this->profileID)) {
1197
			if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName);
1198
			return false;
1199
		}
1200
*/
1201
		//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string(array_keys($folderInfoCache)));
1202
		// does the folder exist???
1203
		if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName]))
1204
		{
1205
			try
1206
			{
1207
				$ret = $this->icServer->getMailboxes($_folderName, 1, true);
1208
			}
1209
			catch (\Exception $e)
1210
			{
1211
				//error_log(__METHOD__.array2string($e));
1212
				//error_log(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1213
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
0 ignored issues
show
Bug introduced by
The property details does not seem to exist 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...
1214
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1215
				throw new Exception(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1216
			}
1217
			//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string($ret));
1218
			if (is_array($ret))
1219
			{
1220
				$retkeys = array_keys($ret);
1221
				if ($retkeys[0]==$_folderName) $folderInfoCache[$_folderName] = $ret[$retkeys[0]];
1222
			}
1223
			else
1224
			{
1225
				$folderInfoCache[$_folderName]=false;
1226
			}
1227
		}
1228
		$folderInfo = $folderInfoCache[$_folderName];
1229
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo).'->'.function_backtrace());
1230
		if($ignoreStatusCache||!$folderInfo|| !is_array($folderInfo)) {
1231
			try
1232
			{
1233
				$folderInfo = $this->_getStatus($_folderName,$ignoreStatusCache);
1234
			}
1235
			catch (\Exception $e)
1236
			{
1237
				//error_log(__METHOD__.array2string($e));
1238
				error_log(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1239
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
1240
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1241
				//throw new Exception(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1242
				$folderInfo=null;
1243
			}
1244
			if (!is_array($folderInfo))
1245
			{
1246
				// no folder info, but there is a status returned for the folder: something is wrong, try to cope with it
1247
				$folderInfo = is_array($folderInfo)?$folderInfo:array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(),
1248
					'ATTRIBUTES' => '');
1249
				if (!isset($folderInfo['HIERACHY_DELIMITER']) || empty($folderInfo['HIERACHY_DELIMITER']) || (isset($folderInfo['delimiter']) && empty($folderInfo['delimiter'])))
1250
				{
1251
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo));
1252
					$folderInfo['HIERACHY_DELIMITER'] = $this->getHierarchyDelimiter();
1253
				}
1254
			}
1255
		}
1256
		#if(!is_array($folderInfo)) {
1257
		#	return false;
1258
		#}
1259
		$retValue['delimiter']		= (isset($folderInfo['HIERACHY_DELIMITER']) && $folderInfo['HIERACHY_DELIMITER']?$folderInfo['HIERACHY_DELIMITER']:$folderInfo['delimiter']);
1260
		$retValue['attributes']		= (isset($folderInfo['ATTRIBUTES']) && $folderInfo['ATTRIBUTES']?$folderInfo['ATTRIBUTES']:$folderInfo['attributes']);
1261
		$shortNameParts			= explode($retValue['delimiter'], $_folderName);
1262
		$retValue['shortName']		= array_pop($shortNameParts);
1263
		$retValue['displayName']	= $_folderName;
1264
		$retValue['shortDisplayName']	= $retValue['shortName'];
1265
		if(strtoupper($retValue['shortName']) == 'INBOX') {
1266
			$retValue['displayName']	= lang('INBOX');
1267
			$retValue['shortDisplayName']	= lang('INBOX');
1268
		}
1269
		// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
1270
		elseif (in_array($retValue['shortName'],self::$autoFolders))
1271
		{
1272
			$retValue['displayName'] = $retValue['shortDisplayName'] = lang($retValue['shortName']);
1273
		}
1274
		if ($folderInfo) $folderBasicInfo[$this->profileID][$_folderName]=$retValue;
1275
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.$_folderName.array2string($retValue['attributes']));
1276 View Code Duplication
		if ($basicInfoOnly || (isset($retValue['attributes']) && stripos(array2string($retValue['attributes']),'noselect')!==false))
1277
		{
1278
			return $retValue;
1279
		}
1280
		// fetch all in one go for one request, instead of querying them one by one
1281
		// cache it for a minute 60*60*1
1282
		// this should reduce communication to the imap server
1283
		static $subscribedFolders = null;
1284
		static $nameSpace = null;
1285
		static $prefix = null;
1286
		if (is_null($nameSpace) || empty($nameSpace[$this->profileID])) $nameSpace[$this->profileID] = $this->_getNameSpaces();
1287
		if (!empty($nameSpace[$this->profileID]))
1288
		{
1289
			$nsNoPersonal=array();
1290
			foreach($nameSpace[$this->profileID] as &$ns)
1291
			{
1292
				if ($ns['type']!='personal') $nsNoPersonal[]=$ns;
1293
			}
1294
			$nameSpace[$this->profileID]=$nsNoPersonal;
1295
		}
1296
		if (is_null($prefix) || empty($prefix[$this->profileID]) || empty($prefix[$this->profileID][$_folderName])) $prefix[$this->profileID][$_folderName] = $this->getFolderPrefixFromNamespace($nameSpace[$this->profileID], $_folderName);
1297
1298
		if ($fetchSubscribedInfo && is_null($subscribedFolders) || empty($subscribedFolders[$this->profileID]))
1299
		{
1300
			$subscribedFolders[$this->profileID] = $this->icServer->listSubscribedMailboxes();
1301
		}
1302
1303
		if($fetchSubscribedInfo && is_array($subscribedFolders[$this->profileID]) && in_array($_folderName,$subscribedFolders[$this->profileID])) {
1304
			$retValue['subscribed'] = true;
1305
		}
1306
1307
		try
1308
		{
1309
			//$folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache);
1310
			$folderStatus = $this->getMailBoxCounters($_folderName,false);
1311
			$retValue['messages']		= $folderStatus['MESSAGES'];
1312
			$retValue['recent']		= $folderStatus['RECENT'];
1313
			$retValue['uidnext']		= $folderStatus['UIDNEXT'];
1314
			$retValue['uidvalidity']	= $folderStatus['UIDVALIDITY'];
1315
			$retValue['unseen']		= $folderStatus['UNSEEN'];
1316
			if (//$retValue['unseen']==0 &&
1317
				(isset($this->mailPreferences['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information
1318
				$this->mailPreferences['trustServersUnseenInfo']==false) ||
1319
				(isset($this->mailPreferences['trustServersUnseenInfo']) &&
1320
				$this->mailPreferences['trustServersUnseenInfo']==2 &&
1321
				$prefix[$this->profileID][$_folderName] != '' && stripos($_folderName,$prefix[$this->profileID][$_folderName]) !== false)
1322
			)
1323
			{
1324
				//error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences['trustServersUnseenInfo']);
1325
				// we filter for the combined status of unseen and undeleted, as this is what we show in list
1326
				try
1327
				{
1328
					$byUid=true;
1329
					$_reverse=1;
1330
					$sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse, array('status'=>array('UNSEEN','UNDELETED')),$byUid,false);
1331
					$retValue['unseen'] = $sortResult['count'];
1332
				}
1333
				catch (\Exception $ee)
1334
				{
1335
					if (self::$debug) error_log(__METHOD__." could not fetch/calculate unseen counter for $_folderName Reason:'".$ee->getMessage()."' but requested.");
1336
				}
1337
			}
1338
		}
1339
		catch (\Exception $e)
1340
		{
1341
			if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($e->getMessage(),true));
1342
		}
1343
1344
		return $retValue;
1345
	}
1346
1347
	/**
1348
	 * getHeaders
1349
	 *
1350
	 * this function is a wrapper function for getSortedList and populates the resultList thereof with headerdata
1351
	 *
1352
	 * @param string $_folderName
1353
	 * @param int $_startMessage
1354
	 * @param int $_numberOfMessages number of messages to return
1355
	 * @param array $_sort sort by criteria
1356
	 * @param boolean $_reverse reverse sorting of the result array (may be switched, as it is passed to getSortedList by reference)
1357
	 * @param array $_filter filter to apply to getSortedList
1358
	 * @param mixed $_thisUIDOnly = null, if given fetch the headers of this uid only (either one, or array of uids)
1359
	 * @param boolean $_cacheResult = true try touse the cache of getSortedList
1360
	 * @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))
1361
	 * @return array result as array(header=>array,total=>int,first=>int,last=>int)
1362
	 */
1363
	function getHeaders($_folderName, $_startMessage, $_numberOfMessages, $_sort, $_reverse, $_filter, $_thisUIDOnly=null, $_cacheResult=true, $_fetchPreviews=false)
1364
	{
1365
		//self::$debug=true;
1366
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.function_backtrace());
1367 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName,$_startMessage, $_numberOfMessages, $_sort, $_reverse, ".array2string($_filter).", $_thisUIDOnly");
1368
		$reverse = (bool)$_reverse;
1369
		// get the list of messages to fetch
1370
		$this->reopen($_folderName);
1371
		//$currentFolder = $this->icServer->getCurrentMailbox();
1372
		//if ($currentFolder != $_folderName); $this->icServer->openMailbox($_folderName);
1373
		$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
1374
		#print "<pre>";
1375
		#$this->icServer->setDebug(true);
1376
		$total=0;
1377
		if ($_thisUIDOnly === null)
1378
		{
1379
			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...
1380
			{
1381
				// 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
1382
				// if sort capability is applied to the range fetched, not sort first and fetch the range afterwards
1383
				//$start = $_startMessage-1;
1384
				//$end = $_startMessage-1+$_numberOfMessages;
1385
				//$_filter['range'] ="$start:$end";
1386
				//$_filter['range'] ="$_startMessage:*";
1387
			}
1388 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_sort, $reverse, ".array2string($_filter).", $rByUid");
1389
			if (self::$debug||self::$debugTimes) $starttime = microtime (true);
1390
			//see this example below for a 12 week datefilter (since)
1391
			//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $starttime-(3600*24*7*12)));
1392
			$_sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
1393
			$sortResult = $_sortResult['match']->ids;
1394
			//$modseq = $_sortResult['modseq'];
1395
			//error_log(__METHOD__.' ('.__LINE__.') '.'Modsequence:'.$modseq);
1396
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' call getSortedList for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly),__METHOD__.' ('.__LINE__.') ');
1397
1398
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1399
			#$this->icServer->setDebug(false);
1400
			#print "</pre>";
1401
			// nothing found
1402
			if(!is_array($sortResult) || empty($sortResult)) {
1403
				$retValue = array();
1404
				$retValue['info']['total']	= 0;
1405
				$retValue['info']['first']	= 0;
1406
				$retValue['info']['last']	= 0;
1407
				return $retValue;
1408
			}
1409
1410
			$total = $_sortResult['count'];
1411
			#_debug_array($sortResult);
1412
			#_debug_array(array_slice($sortResult, -5, -2));
1413
			//error_log("REVERSE: $reverse");
1414
			if($reverse === true) {
1415
				if  ($_startMessage<=$total)
1416
				{
1417
					$startMessage = $_startMessage-1;
1418
				}
1419
				else
1420
				{
1421
					//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1422
					if ($_startMessage+$_numberOfMessages>$total)
1423
					{
1424
						$numberOfMessages = $total%$_numberOfMessages;
1425
						//$numberOfMessages = abs($_startMessage-$total-1);
1426
						if ($numberOfMessages>0 && $numberOfMessages<=$_numberOfMessages) $_numberOfMessages = $numberOfMessages;
1427
						//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1428
					}
1429
					$startMessage=($total-$_numberOfMessages)-1;
1430
					//$retValue['info']['first'] = $startMessage;
1431
					//$retValue['info']['last'] = $total;
1432
1433
				}
1434
				if ($startMessage+$_numberOfMessages>$total)
1435
				{
1436
					$_numberOfMessages = $_numberOfMessages-($total-($startMessage+$_numberOfMessages));
1437
					//$retValue['info']['first'] = $startMessage;
1438
					//$retValue['info']['last'] = $total;
1439
				}
1440
				if($startMessage > 0) {
1441 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+$startMessage)).', '.-$startMessage.' Number of Messages:'.count($sortResult));
1442
					$sortResult = array_slice($sortResult, -($_numberOfMessages+$startMessage), -$startMessage);
1443
				} else {
1444 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+($_startMessage-1))).', AllTheRest, Number of Messages:'.count($sortResult));
1445
					$sortResult = array_slice($sortResult, -($_numberOfMessages+($_startMessage-1)));
1446
				}
1447
				$sortResult = array_reverse($sortResult);
1448
			} else {
1449 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.($_startMessage-1).', '.$_numberOfMessages.' Number of Messages:'.count($sortResult));
1450
				$sortResult = array_slice($sortResult, $_startMessage-1, $_numberOfMessages);
1451
			}
1452
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1453
		}
1454
		else
1455
		{
1456
			$sortResult = (is_array($_thisUIDOnly) ? $_thisUIDOnly:(array)$_thisUIDOnly);
1457
		}
1458
1459
1460
		// fetch the data for the selected messages
1461
		if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1462
		try
1463
		{
1464
			$uidsToFetch = new Horde_Imap_Client_Ids();
1465
			$uidsToFetch->add($sortResult);
1466
1467
			$fquery = new Horde_Imap_Client_Fetch_Query();
1468
1469
			// Pre-cache the headers we want, 'fetchHeaders' is a label into the cache
1470
			$fquery->headers('fetchHeaders',array(
1471
				'DISPOSITION-NOTIFICATION-TO','RETURN-RECEIPT-TO','X-CONFIRM-READING-TO',
1472
				'DATE','SUBJECT','FROM','TO','CC','REPLY-TO',
1473
				'X-PRIORITY'
1474
			),array(
1475
				// Cache headers, we'll look at them below
1476
				'cache' => true,//$_cacheResult,
1477
				// Set peek so messages are not flagged as read
1478
				'peek' => true
1479
			));
1480
			$fquery->size();
1481
			$fquery->structure();
1482
			$fquery->flags();
1483
			$fquery->imapDate();// needed to ensure getImapDate fetches the internaldate, not the current time
1484
			// if $_fetchPreviews is activated fetch part of the messages too
1485
			if ($_fetchPreviews) $fquery->fullText(array('peek'=>true,'length'=>((int)$_fetchPreviews<5000?5000:$_fetchPreviews),'start'=>0));
1486
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
1487
				'ids' => $uidsToFetch,
1488
			));
1489
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headersNew->ids()));
1490
		}
1491
		catch (\Exception $e)
1492
		{
1493
			$headersNew = array();
1494
			$sortResult = array();
1495
		}
1496
		if (self::$debug||self::$debugTimes)
1497
		{
1498
			self::logRunTimes($starttime,null,'HordeFetch: for Folder:'.$_folderName.' Filter:'.array2string($_filter),__METHOD__.' ('.__LINE__.') ');
1499
			if (self::$debug)
1500
			{
1501
				$queryString = implode(',', $sortResult);
1502
				error_log(__METHOD__.' ('.__LINE__.') '.' Query:'.$queryString.' Result:'.array2string($headersNew));
1503
			}
1504
		}
1505
1506
		$cnt = 0;
1507
1508
		foreach((array)$sortResult as $uid) {
1509
			$sortOrder[$uid] = $cnt++;
1510
		}
1511
1512
		$count = 0;
1513
		if (is_object($headersNew)) {
1514
			if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1515
			foreach($headersNew->ids() as $id) {
1516
				$_headerObject = $headersNew->get($id);
1517
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject));
1518
				$headerObject = array();
1519
				$bodyPreview = null;
1520
				$uid = $headerObject['UID']= ($_headerObject->getUid()?$_headerObject->getUid():$id);
1521
				$headerObject['MSG_NUM'] = $_headerObject->getSeq();
1522
				$headerObject['SIZE'] = $_headerObject->getSize();
1523
				$headerObject['INTERNALDATE'] = $_headerObject->getImapDate();
1524
1525
				// Get already cached headers, 'fetchHeaders' is a label matchimg above
1526
				$headerForPrio = $_headerObject->getHeaders('fetchHeaders',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1527
				// Try to fetch header with key='' as some servers might have no fetchHeaders index. e.g. yandex.com
1528
				if (empty($headerForPrio)) $headerForPrio = $_headerObject->getHeaders('',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1529
				//fetch the fullMsg part if all conditions match to be available in case $_headerObject->getHeaders returns
1530
				//nothing worthwhile (as it does for googlemail accounts, when preview is switched on
1531
				if ($_fetchPreviews)
1532
				{
1533
					// on enabled preview $bodyPreview is needed lateron. fetched here, for fallback-reasons
1534
					// in case of failed Header-Retrieval
1535
					$bodyPreview = $_headerObject->getFullMsg();
1536
					if (empty($headerForPrio)||(is_array($headerForPrio)&&count($headerForPrio)===1&&$headerForPrio['']))
1537
					{
1538
						$length = strpos($bodyPreview, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
1539
						if ($length===false) $length = strlen($bodyPreview);
1540
						$headerForPrio =  Horde_Mime_Headers::parseHeaders(substr($bodyPreview, 0,$length))->toArray();
1541
					}
1542
				}
1543
				$headerForPrio = array_change_key_case($headerForPrio, CASE_UPPER);
1544
				if (self::$debug) {
1545
					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'));
1546
					error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerForPrio));
1547
				}
1548
				// message deleted from server but cache still reporting its existence ; may happen on QRESYNC with No permanent modsequences
1549
				if (empty($headerForPrio))
1550
				{
1551
					$total--;
1552
					continue;
1553
				}
1554
				if ( isset($headerForPrio['DISPOSITION-NOTIFICATION-TO']) ) {
1555
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['DISPOSITION-NOTIFICATION-TO']));
1556
				} else if ( isset($headerForPrio['RETURN-RECEIPT-TO']) ) {
1557
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['RETURN-RECEIPT-TO']));
1558
				} else if ( isset($headerForPrio['X-CONFIRM-READING-TO']) ) {
1559
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['X-CONFIRM-READING-TO']));
1560
				} /*else $sent_not = "";*/
1561
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1562
				$headerObject['DATE'] = $headerForPrio['DATE'];
1563
				$headerObject['SUBJECT'] = (is_array($headerForPrio['SUBJECT'])?$headerForPrio['SUBJECT'][0]:$headerForPrio['SUBJECT']);
1564
				$headerObject['FROM'] = (array)($headerForPrio['FROM']?$headerForPrio['FROM']:($headerForPrio['REPLY-TO']?$headerForPrio['REPLY-TO']:$headerForPrio['RETURN-PATH']));
1565
				$headerObject['TO'] = (array)$headerForPrio['TO'];
1566
				$headerObject['CC'] = isset($headerForPrio['CC'])?(array)$headerForPrio['CC']:array();
1567
				$headerObject['REPLY-TO'] = isset($headerForPrio['REPLY-TO'])?(array)$headerForPrio['REPLY-TO']:array();
1568
				$headerObject['PRIORITY'] = isset($headerForPrio['X-PRIORITY'])?$headerForPrio['X-PRIORITY']:null;
1569
				foreach (array('FROM','TO','CC','REPLY-TO') as $key)
1570
				{
1571
					$address = array();
1572
					foreach ($headerObject[$key] as $k => $ad)
1573
					{
1574
						//the commented section below IS a simplified version of the section "make sure ..."
1575
						/*
1576
						if (stripos($ad,'@')===false)
1577
						{
1578
							$remember=$k;
1579
						}
1580
						else
1581
						{
1582
							$address[] = (!is_null($remember)?$headerObject[$key][$remember].' ':'').$ad;
1583
							$remember=null;
1584
						}
1585
						*/
1586
						// make sure addresses are real emailaddresses one by one in the array as expected
1587
						$rfcAddr = self::parseAddressList($ad); // does some fixing of known problems too
1588
						foreach ($rfcAddr as $_rfcAddr)
1589
						{
1590
							if (!$_rfcAddr->valid)	continue; // skip. not a valid address
1591
							$address[] = imap_rfc822_write_address($_rfcAddr->mailbox,$_rfcAddr->host,$_rfcAddr->personal);
1592
						}
1593
					}
1594
					$headerObject[$key] = $address;
1595
				}
1596
				$headerObject['FLAGS'] = $_headerObject->getFlags();
1597
				$headerObject['BODYPREVIEW']=null;
1598
				// this section fetches part of the message-body (if enabled) for some kind of preview
1599
				// if we fail to succeed, we fall back to the retrieval of the message-body with
1600
				// fetchPartContents (see below, when we iterate over the structure to determine the
1601
				// existance (and the details) for attachments)
1602
				if ($_fetchPreviews)
1603
				{
1604
					// $bodyPreview is populated at the beginning of the loop, as it may be
1605
					// needed to parse the Headers of the Message
1606
					if (empty($bodyPreview)) $bodyPreview = $_headerObject->getFullMsg();
1607
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($bodyPreview));
1608
					$base = Horde_Mime_Part::parseMessage($bodyPreview);
1609
					foreach($base->partIterator() as $part)
1610
					{
1611
						//error_log(__METHOD__.__LINE__.'Part:'.$part->getPrimaryType());
1612
						if (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'text')
1613
						{
1614
							$charset = $part->getContentTypeParameter('charset');
1615
							$buffer = Mail\Html::convertHTMLToText($part->toString(array(
1616
												'encode' => Horde_Mime_Part::ENCODE_BINARY,	// otherwise we cant recode charset
1617
											)), $charset, 'utf-8');
1618
							$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...
1619
						} 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...
1620
						{
1621
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part));
1622
						}
1623
					}
1624
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject['BODYPREVIEW']));
1625
				}
1626
				$mailStructureObject = $_headerObject->getStructure();
1627
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1628
				//error_log(__METHOD__.' ('.__LINE__.') '.' MimeMap:'.array2string($mailStructureObject->contentTypeMap()));
1629
				//foreach ($_headerObject->getStructure()->getParts() as $p => $part)
1630
				$headerObject['ATTACHMENTS']=null;
1631
				$skipParts=array();
1632
				$messageMimeType='';
1633
				foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
1634
				{
1635
					if ($mime_id==0 || $messageMimeType==='') $messageMimeType = $mime_type;
1636
					$part = $mailStructureObject->getPart($mime_id);
1637
					$partdisposition = $part->getDisposition();
1638
					$partPrimaryType = $part->getPrimaryType();
1639
					// this section fetches the body for the purpose of previewing a few lines
1640
					// drawback here it is talking to the mailserver for each mail thus consuming
1641
					// more time than expected; so we call this section only when there is no
1642
					// bodypreview could be found (multipart/....)
1643
					if ($_fetchPreviews && empty($headerObject['BODYPREVIEW'])&&($partPrimaryType == 'text') &&
1644
						((intval($mime_id) === 1) || !$mime_id) &&
1645
						($partdisposition !== 'attachment')) {
1646
							$_structure=$part;
1647
							$this->fetchPartContents($uid, $_structure, false,true);
1648
							$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...
1649
							$charSet=Translation::detect_encoding($headerObject['BODYPREVIEW']);
1650
							// add line breaks to $bodyParts
1651
							//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
1652
							$headerObject['BODYPREVIEW']  = Translation::convert_jsonsafe($headerObject['BODYPREVIEW'], $charSet);
1653
							//error_log(__METHOD__.__LINE__.$headerObject['BODYPREVIEW']);
1654
					}
1655
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType);
1656
					$cid = $part->getContentId();
1657
					if (empty($partdisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
1658
					{
1659
						// the presence of an cid does not necessarily indicate its inline. it may lack the needed
1660
						// link to show the image. Considering this: we "list" everything that matches the above criteria
1661
						// as attachment in order to not loose/miss information on our data
1662
						$partdisposition='attachment';//($partPrimaryType == 'image'&&!empty($cid)?'inline':'attachment');
1663
					}
1664 View Code Duplication
					if ($mime_type=='message/rfc822')
1665
					{
1666
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
1667
						foreach($part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
1668
					}
1669
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType.' Skip:'.array2string($skipParts));
1670
					if (array_key_exists($mime_id,$skipParts)) continue;
1671
					if ($partdisposition=='attachment' ||
1672
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&$mime_type=='image/tiff') || // as we are not able to display tiffs
1673
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&empty($cid)) ||
1674
						($partdisposition=='inline' && $partPrimaryType != 'image' && $partPrimaryType != 'multipart' && $partPrimaryType != 'text'))
1675
					{
1676
						$headerObject['ATTACHMENTS'][$mime_id]=$part->getAllDispositionParameters();
1677
						$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=$mime_type;
1678
						$headerObject['ATTACHMENTS'][$mime_id]['uid']=$uid;
1679
						$headerObject['ATTACHMENTS'][$mime_id]['cid'] = $cid;
1680
						$headerObject['ATTACHMENTS'][$mime_id]['partID']=$mime_id;
1681
						if (!isset($headerObject['ATTACHMENTS'][$mime_id]['name']))$headerObject['ATTACHMENTS'][$mime_id]['name']=$part->getName();
1682
						if (!strcasecmp($headerObject['ATTACHMENTS'][$mime_id]['name'],'winmail.dat') ||
1683
							$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=='application/ms-tnef')
1684
						{
1685
							$headerObject['ATTACHMENTS'][$mime_id]['is_winmail'] = true;
1686
						}
1687
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getName()));
1688
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getAllDispositionParameters()));
1689
						//error_log(__METHOD__.' ('.__LINE__.') '.' Attachment:'.$mime_id.'->'.array2string($headerObject['ATTACHMENTS'][$mime_id]));
1690
					}
1691
				}
1692
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (plain):'.array2string($mailStructureObject->findBody('plain')));
1693
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (html):'.array2string($mailStructureObject->findBody('html')));
1694
				//if($count == 0) error_log(__METHOD__.array2string($headerObject));
1695
				if (empty($headerObject['UID'])) continue;
1696
				//$uid = ($rByUid ? $headerObject['UID'] : $headerObject['MSG_NUM']);
1697
				// make dates like "Mon, 23 Apr 2007 10:11:06 UT" working with strtotime
1698
				if(substr($headerObject['DATE'],-2) === 'UT') {
1699
					$headerObject['DATE'] .= 'C';
1700
				}
1701
				if(substr($headerObject['INTERNALDATE'],-2) === 'UT') {
1702
					$headerObject['INTERNALDATE'] .= 'C';
1703
				}
1704
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$headerObject['SUBJECT'].'->'.$headerObject['DATE'].'<->'.$headerObject['INTERNALDATE'] .'#');
1705
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']);
1706
				if (isset($headerObject['ATTACHMENTS']) && count($headerObject['ATTACHMENTS'])) foreach ($headerObject['ATTACHMENTS'] as &$a) { $retValue['header'][$sortOrder[$uid]]['attachments'][]=$a;}
1707
				$retValue['header'][$sortOrder[$uid]]['subject']	= $this->decode_subject($headerObject['SUBJECT']);
1708
				$retValue['header'][$sortOrder[$uid]]['size'] 		= $headerObject['SIZE'];
1709
				$retValue['header'][$sortOrder[$uid]]['date']		= self::_strtotime(($headerObject['DATE']&&!($headerObject['DATE']=='NIL')?$headerObject['DATE']:$headerObject['INTERNALDATE']),'ts',true);
1710
				$retValue['header'][$sortOrder[$uid]]['internaldate']= self::_strtotime($headerObject['INTERNALDATE'],'ts',true);
1711
				$retValue['header'][$sortOrder[$uid]]['mimetype']	= $messageMimeType;
1712
				$retValue['header'][$sortOrder[$uid]]['id']		= $headerObject['MSG_NUM'];
1713
				$retValue['header'][$sortOrder[$uid]]['uid']		= $headerObject['UID'];
1714
				$retValue['header'][$sortOrder[$uid]]['bodypreview']		= $headerObject['BODYPREVIEW'];
1715
				$retValue['header'][$sortOrder[$uid]]['priority']		= ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3);
1716
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.array2string($retValue['header'][$sortOrder[$uid]]));
1717
				if (isset($headerObject['DISPOSITION-NOTIFICATION-TO'])) $retValue['header'][$sortOrder[$uid]]['disposition-notification-to'] = $headerObject['DISPOSITION-NOTIFICATION-TO'];
1718
				if (is_array($headerObject['FLAGS'])) {
1719
					$retValue['header'][$sortOrder[$uid]] = array_merge($retValue['header'][$sortOrder[$uid]],self::prepareFlagsArray($headerObject));
1720
				}
1721
				//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].'->'.array2string($_headerObject->getEnvelope()->__get('from')));
1722 View Code Duplication
				if(is_array($headerObject['FROM']) && $headerObject['FROM'][0]) {
1723
					$retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0],true);
1724
				}
1725 View Code Duplication
				if(is_array($headerObject['REPLY-TO']) && $headerObject['REPLY-TO'][0]) {
1726
					$retValue['header'][$sortOrder[$uid]]['reply_to_address'] = self::decode_header($headerObject['REPLY-TO'][0],true);
1727
				}
1728
				if(is_array($headerObject['TO']) && $headerObject['TO'][0]) {
1729
					$retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0],true);
1730
					if (count($headerObject['TO'])>1)
1731
					{
1732
						$ki=0;
1733 View Code Duplication
						foreach($headerObject['TO'] as $k => $add)
1734
						{
1735
							if ($k==0) continue;
1736
							//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1737
							$retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki] = self::decode_header($add,true);
1738
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1739
							$ki++;
1740
						}
1741
					}
1742
				}
1743
				if(is_array($headerObject['CC']) && count($headerObject['CC'])>0) {
1744
					$ki=0;
1745 View Code Duplication
					foreach($headerObject['CC'] as $k => $add)
1746
					{
1747
						//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1748
						$retValue['header'][$sortOrder[$uid]]['cc_addresses'][$ki] = self::decode_header($add,true);
1749
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1750
						$ki++;
1751
					}
1752
				}
1753
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]));
1754
1755
				$count++;
1756
			}
1757
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' fetching Headers and stuff for Folder:'.$_folderName,__METHOD__.' ('.__LINE__.') ');
1758
			//self::$debug=false;
1759
			// sort the messages to the requested displayorder
1760
			if(is_array($retValue['header'])) {
1761
				$countMessages = $total;
1762
				if (isset($_filter['range'])) $countMessages = self::$folderStatusCache[$this->profileID][$_folderName]['messages'];
1763
				ksort($retValue['header']);
1764
				$retValue['info']['total']	= $total;
1765
				//if ($_startMessage>$total) $_startMessage = $total-($count-1);
1766
				$retValue['info']['first']	= $_startMessage;
1767
				$retValue['info']['last']	= $_startMessage + $count - 1 ;
1768
				return $retValue;
1769
			} else {
1770
				$retValue = array();
1771
				$retValue['info']['total']	= 0;
1772
				$retValue['info']['first']	= 0;
1773
				$retValue['info']['last']	= 0;
1774
				return $retValue;
1775
			}
1776
		} else {
1777
			if ($headersNew == null && empty($_thisUIDOnly)) error_log(__METHOD__." -> retrieval of Message Details to Query $queryString failed: ".print_r($headersNew,TRUE));
1778
			$retValue = array();
1779
			$retValue['info']['total']  = 0;
1780
			$retValue['info']['first']  = 0;
1781
			$retValue['info']['last']   = 0;
1782
			return $retValue;
1783
		}
1784
	}
1785
1786
	/**
1787
	 * static function prepareFlagsArray
1788
	 * prepare headerObject to return some standardized array to tell which flags are set for a message
1789
	 * @param array $headerObject  - array to process, a full return array from icServer->getSummary
1790
	 * @return array array of flags
1791
	 */
1792
	static function prepareFlagsArray($headerObject)
1793
	{
1794
		if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']);
1795
		$retValue = array();
1796
		$retValue['recent']		= in_array('\\recent', $headerFlags);
1797
		$retValue['flagged']	= in_array('\\flagged', $headerFlags);
1798
		$retValue['answered']	= in_array('\\answered', $headerFlags);
1799
		$retValue['forwarded']   = in_array('$forwarded', $headerFlags);
1800
		$retValue['deleted']	= in_array('\\deleted', $headerFlags);
1801
		$retValue['seen']		= in_array('\\seen', $headerFlags);
1802
		$retValue['draft']		= in_array('\\draft', $headerFlags);
1803
		$retValue['mdnsent']	= in_array('$mdnsent', $headerFlags)||in_array('mdnsent', $headerFlags);
1804
		$retValue['mdnnotsent']	= in_array('$mdnnotsent', $headerFlags)||in_array('mdnnotsent', $headerFlags);
1805
		$retValue['label1']   = in_array('$label1', $headerFlags);
1806
		$retValue['label2']   = in_array('$label2', $headerFlags);
1807
		$retValue['label3']   = in_array('$label3', $headerFlags);
1808
		$retValue['label4']   = in_array('$label4', $headerFlags);
1809
		$retValue['label5']   = in_array('$label5', $headerFlags);
1810
		//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].':'.array2string($retValue));
1811
		return $retValue;
1812
	}
1813
1814
	/**
1815
	 * fetches a sorted list of messages from the imap server
1816
	 * private function
1817
	 *
1818
	 * @todo implement sort based on Net_IMAP
1819
	 * @param string $_folderName the name of the folder in which the messages get searched
1820
	 * @param integer $_sort the primary sort key
1821
	 * @param bool $_reverse sort the messages ascending or descending
1822
	 * @param array $_filter the search filter
1823
	 * @param bool $resultByUid if set to true, the result is to be returned by uid, if the server does not reply
1824
	 * 			on a query for uids, the result may be returned by IDs only, this will be indicated by this param
1825
	 * @param bool $setSession if set to true the session will be populated with the result of the query
1826
	 * @return mixed bool/array false or array of ids
1827
	 */
1828
	function getSortedList($_folderName, $_sort, &$_reverse, $_filter, &$resultByUid=true, $setSession=true)
1829
	{
1830
		static $cachedFolderStatus = null;
1831
		// in the past we needed examineMailbox to figure out if the server with the serverID support keywords
1832
		// this information is filled/provided by examineMailbox; but caching within one request seems o.k.
1833
		if (is_null($cachedFolderStatus) || !isset($cachedFolderStatus[$this->profileID][$_folderName]) )
1834
		{
1835
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName] = $this->icServer->examineMailbox($_folderName);
1836
		}
1837
		else
1838
		{
1839
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName];
1840
		}
1841
		//error_log(__METHOD__.' ('.__LINE__.') '.' F:'.$_folderName.' S:'.array2string($folderStatus));
1842
		//error_log(__METHOD__.' ('.__LINE__.') '.' Filter:'.array2string($_filter));
1843
		$try2useCache = true;
1844
		static $eMailListContainsDeletedMessages = null;
1845
		if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
1846
		// this indicates, that there is no Filter set, and the returned set/subset should not contain DELETED Messages, nor filtered for UNDELETED
1847
		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...
1848
		{
1849
			if (self::$debugTimes) $starttime = microtime(true);
1850
			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);
1851
			$five=true;
1852
			$dReverse=1;
1853
			$deletedMessages = $this->getSortedList($_folderName, 0, $dReverse, array('status'=>array('DELETED')),$five,false);
1854
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') Found DeletedMessages:'.array2string($eMailListContainsDeletedMessages));
1855
			$eMailListContainsDeletedMessages[$this->profileID][$_folderName] =$deletedMessages['count'];
1856
			Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, 60*60*1);
1857
			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']);
1858
		}
1859
		$try2useCache = false;
1860
		//self::$supportsORinQuery[$this->profileID]=true;
1861
		if (is_null(self::$supportsORinQuery) || !isset(self::$supportsORinQuery[$this->profileID]))
1862
		{
1863
			self::$supportsORinQuery = Cache::getCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
1864
			if (!isset(self::$supportsORinQuery[$this->profileID])) self::$supportsORinQuery[$this->profileID]=true;
1865
		}
1866
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_filter).' SupportsOrInQuery:'.self::$supportsORinQuery[$this->profileID]);
1867
		$filter = $this->createIMAPFilter($_folderName, $_filter,self::$supportsORinQuery[$this->profileID]);
1868
		if (self::$debug)
1869
		{
1870
			$query_str = $filter->build();
1871
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query']);
1872
		}
1873
		//_debug_array($filter);
1874
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($filter).'#'.array2string($this->icServer->capability()));
1875
		if($this->icServer->hasCapability('SORT')) {
1876
			// when using an orQuery and we sort by date. sort seems to fail on certain servers => ZIMBRA with Horde_Imap_Client
1877
			// thus we translate the search request from date to Horde_Imap_Client::SORT_SEQUENCE (which should be the same, if
1878
			// there is no messing with the dates)
1879
			//if (self::$supportsORinQuery[$this->profileID]&&$_sort=='date'&&$_filter['type']=='quick'&&!empty($_filter['string']))$_sort='INTERNALDATE';
1880
			if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: ".array2string($_sort)." Reverse: $_reverse");
1881
			$sortOrder = $this->_getSortString($_sort, $_reverse);
1882
			if ($_reverse && in_array(Horde_Imap_Client::SORT_REVERSE,$sortOrder)) $_reverse=false; // as we reversed the result already
1883
			if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy:".array2string($_sort)."->".array2string($sortOrder)." Filter: ".array2string($filter));
1884
			try
1885
			{
1886
				$sortResult = $this->icServer->search($_folderName, $filter, array(
1887
					'sort' => $sortOrder,));
1888
1889
				// Attempt another search without sorting filter if first try failed with
1890
				// no result, as may some servers do not coupe well with sort option
1891
				// eventhough they claim to support SORT capability.
1892
				if (!isset($sortResult['count'])) $sortResult = $this->icServer->search($_folderName, $filter);
1893
1894
			// if there is an Error, we assume that the server is not capable of sorting
1895
			}
1896
			catch(\Exception $e)
1897
			{
1898
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1899
				$resultByUid = false;
1900
				$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1901
				if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1902
				try
1903
				{
1904
					$sortResult = $this->icServer->search($_folderName, $filter, array(
1905
						'sort' => $sortOrder));
1906
				}
1907
				catch(\Exception $e)
1908
				{
1909
					error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1910
					$sortResult = self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'];
1911
				}
1912
			}
1913
			if (self::$debug) error_log(__METHOD__.print_r($sortResult,true));
1914
		} else {
1915
			if (self::$debug) error_log(__METHOD__." Mailserver has NO SORT Capability");
1916
			//$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1917
			//if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1918
			try
1919
			{
1920
				$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1921
					'sort' => $sortOrder)*/);
1922
			}
1923
			catch(\Exception $e)
1924
			{
1925
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1926
				// possible error OR Query. But Horde gives no detailed Info :-(
1927
				self::$supportsORinQuery[$this->profileID]=false;
1928
				Cache::setCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),self::$supportsORinQuery,60*60*10);
1929
				if (self::$debug) error_log(__METHOD__.__LINE__." Mailserver seems to have NO OR Capability for Search:".$sortResult->message);
1930
				$filter = $this->createIMAPFilter($_folderName, $_filter, self::$supportsORinQuery[$this->profileID]);
1931
				try
1932
				{
1933
					$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1934
						'sort' => $sortOrder)*/);
1935
				}
1936
				catch(\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1937
				{
1938
				}
1939
			}
1940
			if(is_array($sortResult['match'])) {
1941
					// not sure that this is going so succeed as $sortResult['match'] is a hordeObject
1942
					sort($sortResult['match'], SORT_NUMERIC);
1943
			}
1944
			if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1945
		}
1946
		if ($setSession)
1947
		{
1948
			self::$folderStatusCache[$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY'];
1949
			self::$folderStatusCache[$this->profileID][$_folderName]['messages']	= $folderStatus['MESSAGES'];
1950
			self::$folderStatusCache[$this->profileID][$_folderName]['deleted']	= $eMailListContainsDeletedMessages[$this->profileID][$_folderName];
1951
			self::$folderStatusCache[$this->profileID][$_folderName]['uidnext']	= $folderStatus['UIDNEXT'];
1952
			self::$folderStatusCache[$this->profileID][$_folderName]['filter']	= $_filter;
1953
			self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'] = $sortResult;
1954
			self::$folderStatusCache[$this->profileID][$_folderName]['sort']	= $_sort;
1955
		}
1956
		//error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1957
		//_debug_array($sortResult['match']->ids);
1958
		return $sortResult;
1959
	}
1960
1961
	/**
1962
	 * convert the sort value from the gui(integer) into a string
1963
	 *
1964
	 * @param mixed _sort the integer sort order / or a valid and handeled SORTSTRING (right now: UID/ARRIVAL/INTERNALDATE (->ARRIVAL))
1965
	 * @param bool _reverse wether to add REVERSE to the Sort String or not
1966
	 * @return the sort sequence for horde search
1967
	 */
1968
	function _getSortString($_sort, $_reverse=false)
1969
	{
1970
		$_reverse=false;
1971
		if (is_numeric($_sort))
1972
		{
1973
			switch($_sort) {
1974
				case 2:
1975
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1976
					break;
1977
				case 4:
1978
					$retValue = array(Horde_Imap_Client::SORT_TO);
1979
					break;
1980
				case 3:
1981
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
1982
					break;
1983
				case 6:
1984
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
1985
					break;
1986
				case 0:
1987
				default:
1988
					$retValue = array(Horde_Imap_Client::SORT_DATE);
1989
					//$retValue = 'ARRIVAL';
1990
					break;
1991
			}
1992
		}
1993
		else
1994
		{
1995
			switch(strtoupper($_sort)) {
1996
				case 'FROMADDRESS':
1997
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1998
					break;
1999
				case 'TOADDRESS':
2000
					$retValue = array(Horde_Imap_Client::SORT_TO);
2001
					break;
2002
				case 'SUBJECT':
2003
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
2004
					break;
2005
				case 'SIZE':
2006
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
2007
					break;
2008
				case 'ARRIVAL':
2009
					$retValue = array(Horde_Imap_Client::SORT_ARRIVAL);
2010
					break;
2011
				case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date
2012
				case 'INTERNALDATE':
2013
					$retValue = array(Horde_Imap_Client::SORT_SEQUENCE);
2014
					break;
2015
				case 'DATE':
2016
				default:
2017
					$retValue = array(Horde_Imap_Client::SORT_DATE);
2018
					break;
2019
			}
2020
		}
2021
		if ($_reverse) array_unshift($retValue,Horde_Imap_Client::SORT_REVERSE);
2022
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue);
2023
		return $retValue;
2024
	}
2025
2026
	/**
2027
	 * this function creates an IMAP filter from the criterias given
2028
	 *
2029
	 * @param string $_folder used to determine the search to TO or FROM on QUICK Search wether it is a send-folder or not
2030
	 * @param array $_criterias contains the search/filter criteria
2031
	 * @param boolean $_supportsOrInQuery wether to use the OR Query on QuickSearch
2032
	 * @return Horde_Imap_Client_Search_Query the IMAP filter
2033
	 */
2034
	function createIMAPFilter($_folder, $_criterias, $_supportsOrInQuery=true)
2035
	{
2036
		$imapFilter = new Horde_Imap_Client_Search_Query();
2037
		$imapFilter->charset('UTF-8');
2038
2039
		//_debug_array($_criterias);
2040 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2041
		if((!is_array($_criterias) || $_criterias['status']=='any') &&
2042
			(!isset($_criterias['string']) || empty($_criterias['string'])) &&
2043
			(!isset($_criterias['range'])|| empty($_criterias['range']) ||
2044
			( !empty($_criterias['range'])&& ($_criterias['range']!='BETWEEN' && empty($_criterias['date'])||
2045
			($_criterias['range']=='BETWEEN' && empty($_criterias['since'])&& empty($_criterias['before']))))))
2046
		{
2047
			//error_log(__METHOD__.' ('.__LINE__.') returning early Criterias:'.print_r($_criterias, true));
2048
			$imapFilter->flag('DELETED', $set=false);
2049
			return $imapFilter;
2050
		}
2051
		$queryValid = false;
2052
		// statusQuery MUST be placed first, as search for subject/mailbody and such is
2053
		// depending on charset. flagSearch is not BUT messes the charset if called afterwards
2054
		$statusQueryValid = false;
2055
		foreach((array)$_criterias['status'] as $k => $criteria) {
2056
			$imapStatusFilter = new Horde_Imap_Client_Search_Query();
2057
			$imapStatusFilter->charset('UTF-8');
2058
			$criteria = strtoupper($criteria);
2059
			switch ($criteria) {
2060
				case 'ANSWERED':
2061
				case 'DELETED':
2062
				case 'FLAGGED':
2063
				case 'RECENT':
2064
				case 'SEEN':
2065
					$imapStatusFilter->flag($criteria, $set=true);
2066
					$queryValid = $statusQueryValid =true;
2067
					break;
2068 View Code Duplication
				case 'READ':
2069
					$imapStatusFilter->flag('SEEN', $set=true);
2070
					$queryValid = $statusQueryValid =true;
2071
					break;
2072
				case 'LABEL1':
2073
				case 'KEYWORD1':
2074
				case 'LABEL2':
2075
				case 'KEYWORD2':
2076
				case 'LABEL3':
2077
				case 'KEYWORD3':
2078
				case 'LABEL4':
2079
				case 'KEYWORD4':
2080
				case 'LABEL5':
2081
				case 'KEYWORD5':
2082
					$imapStatusFilter->flag(str_ireplace('KEYWORD','$LABEL',$criteria), $set=true);
2083
					$queryValid = $statusQueryValid =true;
2084
					break;
2085
				case 'NEW':
2086
					$imapStatusFilter->flag('RECENT', $set=true);
2087
					$imapStatusFilter->flag('SEEN', $set=false);
2088
					$queryValid = $statusQueryValid =true;
2089
					break;
2090
				case 'OLD':
2091
					$imapStatusFilter->flag('RECENT', $set=false);
2092
					$queryValid = $statusQueryValid =true;
2093
					break;
2094
// operate only on system flags
2095
//        $systemflags = array(
2096
//            'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
2097
//        );
2098
				case 'UNANSWERED':
2099
					$imapStatusFilter->flag('ANSWERED', $set=false);
2100
					$queryValid = $statusQueryValid =true;
2101
					break;
2102
				case 'UNDELETED':
2103
					$imapFilter->flag('DELETED', $set=false);
2104
					$queryValid = true;
2105
					break;
2106
				case 'UNFLAGGED':
2107
					$imapStatusFilter->flag('FLAGGED', $set=false);
2108
					$queryValid = $statusQueryValid =true;
2109
					break;
2110
				case 'UNREAD':
2111 View Code Duplication
				case 'UNSEEN':
2112
					$imapStatusFilter->flag('SEEN', $set=false);
2113
					$queryValid = $statusQueryValid =true;
2114
					break;
2115
				case 'UNLABEL1':
2116
				case 'UNKEYWORD1':
2117
				case 'UNLABEL2':
2118
				case 'UNKEYWORD2':
2119
				case 'UNLABEL3':
2120
				case 'UNKEYWORD3':
2121
				case 'UNLABEL4':
2122
				case 'UNKEYWORD4':
2123
				case 'UNLABEL5':
2124
				case 'UNKEYWORD5':
2125
					$imapStatusFilter->flag(str_ireplace(array('UNKEYWORD','UNLABEL'),'$LABEL',$criteria), $set=false);
2126
					$queryValid = $statusQueryValid =true;
2127
					break;
2128
				default:
2129
					$statusQueryValid = false;
2130
			}
2131
			if ($statusQueryValid)
2132
			{
2133
				$imapFilter->andSearch($imapStatusFilter);
2134
			}
2135
		}
2136
2137
2138
		//error_log(__METHOD__.' ('.__LINE__.') '.print_r($_criterias, true));
2139
		$imapSearchFilter = new Horde_Imap_Client_Search_Query();
2140
		$imapSearchFilter->charset('UTF-8');
2141
2142
		if(!empty($_criterias['string'])) {
2143
			$criteria = strtoupper($_criterias['type']);
2144
			switch ($criteria) {
2145
				case 'BYDATE':
2146
				case 'QUICK':
2147
				case 'QUICKWITHCC':
2148
					$imapSearchFilter->headerText('SUBJECT', $_criterias['string'], $not=false);
2149
					//$imapSearchFilter->charset('UTF-8');
2150
					$imapFilter2 = new Horde_Imap_Client_Search_Query();
2151
					$imapFilter2->charset('UTF-8');
2152
					if($this->isSentFolder($_folder)) {
2153
						$imapFilter2->headerText('TO', $_criterias['string'], $not=false);
2154
					} else {
2155
						$imapFilter2->headerText('FROM', $_criterias['string'], $not=false);
2156
					}
2157
					if ($_supportsOrInQuery)
2158
					{
2159
						$imapSearchFilter->orSearch($imapFilter2);
2160
					}
2161
					else
2162
					{
2163
						$imapSearchFilter->andSearch($imapFilter2);
2164
					}
2165
					if ($_supportsOrInQuery && $criteria=='QUICKWITHCC')
2166
					{
2167
						$imapFilter3 = new Horde_Imap_Client_Search_Query();
2168
						$imapFilter3->charset('UTF-8');
2169
						$imapFilter3->headerText('CC', $_criterias['string'], $not=false);
2170
						$imapSearchFilter->orSearch($imapFilter3);
2171
					}
2172
					$queryValid = true;
2173
					break;
2174
				case 'LARGER':
2175
				case 'SMALLER':
2176
					if (strlen(trim($_criterias['string'])) != strlen((float) trim($_criterias['string'])))
2177
					{
2178
						//examine string to evaluate size
2179
						$unit = strtoupper(trim(substr(trim($_criterias['string']),strlen((float) trim($_criterias['string'])))));
2180
						$multipleBy = array('KB'=>1024,'K'=>1024,
2181
											'MB'=>1024*1000,'M'=>1024*1000,
2182
											'GB'=>1024*1000*1000,'G'=>1024*1000*1000,
2183
											'TB'=>1024*1000*1000*1000,'T'=>1024*1000*1000*1000);
2184
						$numberinBytes=(float)$_criterias['string'];
2185
						if (isset($multipleBy[$unit])) $numberinBytes=(float)$_criterias['string']*$multipleBy[$unit];
2186
						//error_log(__METHOD__.__LINE__.'#'.$_criterias['string'].'->'.(float)$_criterias['string'].'#'.$unit.' ='.$numberinBytes);
2187
						$_criterias['string']=$numberinBytes;
2188
					}
2189
					$imapSearchFilter->size( $_criterias['string'], ($criteria=='LARGER'?true:false), $not=false);
2190
					//$imapSearchFilter->charset('UTF-8');
2191
					$queryValid = true;
2192
					break;
2193
				case 'FROM':
2194
				case 'TO':
2195
				case 'CC':
2196
				case 'BCC':
2197
				case 'SUBJECT':
2198
					$imapSearchFilter->headerText($criteria, $_criterias['string'], $not=false);
2199
					//$imapSearchFilter->charset('UTF-8');
2200
					$queryValid = true;
2201
					break;
2202
				case 'BODY':
2203
				case 'TEXT':
2204
					$imapSearchFilter->text($_criterias['string'],($criteria=='BODY'?true:false), $not=false);
2205
					//$imapSearchFilter->charset('UTF-8');
2206
					$queryValid = true;
2207
					break;
2208
				case 'SINCE':
2209
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2210
					$queryValid = true;
2211
					break;
2212
				case 'BEFORE':
2213
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2214
					$queryValid = true;
2215
					break;
2216 View Code Duplication
				case 'ON':
2217
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2218
					$queryValid = true;
2219
					break;
2220
			}
2221
		}
2222
		if ($statusQueryValid && !$queryValid) $queryValid=true;
2223
		if ($queryValid) $imapFilter->andSearch($imapSearchFilter);
2224
2225
		if (isset($_criterias['range']) && !empty($_criterias['range']))
2226
		{
2227
			$rangeValid = false;
2228
			$imapRangeFilter = new Horde_Imap_Client_Search_Query();
2229
			$imapRangeFilter->charset('UTF-8');
2230
			$criteria = strtoupper($_criterias['range']);
2231
			if ($_criterias['range'] == "BETWEEN" && isset($_criterias['since']) && isset($_criterias['before']) && $_criterias['since']==$_criterias['before'])
2232
			{
2233
				$_criterias['date']=$_criterias['since'];
2234
				unset($_criterias['since']);
2235
				unset($_criterias['before']);
2236
				$criteria=$_criterias['range']='ON';
2237
			}
2238
			switch ($criteria) {
2239
				case 'BETWEEN':
2240
					//try to be smart about missing
2241
					//enddate
2242
					if ($_criterias['since'])
2243
					{
2244
						$imapRangeFilter->dateSearch(new DateTime($_criterias['since']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2245
						$rangeValid = true;
2246
					}
2247
					//startdate
2248
					if ($_criterias['before'])
2249
					{
2250
						$imapRangeFilter2 = new Horde_Imap_Client_Search_Query();
2251
						$imapRangeFilter2->charset('UTF-8');
2252
						//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2253
						$_criterias['before'] = date("d-M-Y",DateTime::to($_criterias['before'],'ts')+(3600*24));
2254
						$imapRangeFilter2->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2255
						$imapRangeFilter->andSearch($imapRangeFilter2);
2256
						$rangeValid = true;
2257
					}
2258
					break;
2259
				case 'SINCE'://enddate
2260
					$imapRangeFilter->dateSearch(new DateTime(($_criterias['since']?$_criterias['since']:$_criterias['date'])), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2261
					$rangeValid = true;
2262
					break;
2263
				case 'BEFORE'://startdate
2264
					//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2265
					$_criterias['before'] = date("d-M-Y",DateTime::to(($_criterias['before']?$_criterias['before']:$_criterias['date']),'ts')+(3600*24));
2266
					$imapRangeFilter->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2267
					$rangeValid = true;
2268
					break;
2269 View Code Duplication
				case 'ON':
2270
					$imapRangeFilter->dateSearch(new DateTime($_criterias['date']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2271
					$rangeValid = true;
2272
					break;
2273
			}
2274
			if ($rangeValid && !$queryValid) $queryValid=true;
2275
			if ($rangeValid) $imapFilter->andSearch($imapRangeFilter);
2276
		}
2277
		if (self::$debug)
2278
		{
2279
			//$imapFilter->charset('UTF-8');
2280
			$query_str = $imapFilter->build();
2281
			//error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query'].' created by Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2282
		}
2283
		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...
2284
			$imapFilter->flag('DELETED', $set=false);
2285
			return $imapFilter;
2286
		} else {
2287
			return $imapFilter;
2288
		}
2289
	}
2290
2291
	/**
2292
	 * decode header (or envelope information)
2293
	 * if array given, note that only values will be converted
2294
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2295
	 * @param  mixed/boolean $_tryIDNConversion (true/false AND FORCE): try IDN Conversion on domainparts of emailADRESSES
2296
	 * @return mixed - based on the input type
2297
	 */
2298
	static function decode_header($_string, $_tryIDNConversion=false)
2299
	{
2300
		if (is_array($_string))
2301
		{
2302
			foreach($_string as $k=>$v)
2303
			{
2304
				$_string[$k] = self::decode_header($v, $_tryIDNConversion);
2305
			}
2306
			return $_string;
2307
		}
2308
		else
2309
		{
2310
			$_string = Mail\Html::decodeMailHeader($_string,self::$displayCharset);
2311
			$test = @json_encode($_string);
2312
			//error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#');
2313
			if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2314
			{
2315
				// try to fix broken utf8
2316
				$x = utf8_encode($_string);
2317
				$test = @json_encode($x);
2318
				if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2319
				{
2320
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
2321
					$_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));
2322
				}
2323
				else
2324
				{
2325
					$_string = $x;
2326
				}
2327
			}
2328
2329
			if ($_tryIDNConversion===true && stripos($_string,'@')!==false)
2330
			{
2331
				$rfcAddr = self::parseAddressList($_string);
2332
				$stringA = array();
2333
				//$_string = str_replace($rfcAddr[0]->host,Horde_Idna::decode($rfcAddr[0]->host),$_string);
2334
				foreach ($rfcAddr as $_rfcAddr)
2335
				{
2336
					if (!$_rfcAddr->valid)
2337
					{
2338
						$stringA = array();
2339
						break; // skip idna conversion if we encounter an error here
2340
					}
2341
					$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox,Horde_Idna::decode($_rfcAddr->host),$_rfcAddr->personal);
2342
				}
2343
				if (!empty($stringA)) $_string = implode(',',$stringA);
2344
			}
2345
			if ($_tryIDNConversion==='FORCE')
2346
			{
2347
				//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_string.'='.Horde_Idna::decode($_string));
2348
				$_string = Horde_Idna::decode($_string);
2349
			}
2350
			return $_string;
2351
		}
2352
	}
2353
2354
	/**
2355
	 * decode subject
2356
	 * if array given, note that only values will be converted
2357
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2358
	 * @param  boolean $decode try decoding
2359
	 * @return mixed - based on the input type
2360
	 */
2361
	function decode_subject($_string,$decode=true)
2362
	{
2363
		#$string = $_string;
2364
		if($_string=='NIL')
2365
		{
2366
			return 'No Subject';
2367
		}
2368
		if ($decode) $_string = self::decode_header($_string);
2369
		// make sure its utf-8
2370
		$test = @json_encode($_string);
2371
		if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2372
		{
2373
			$_string = utf8_encode($_string);
2374
		}
2375
		return $_string;
2376
2377
	}
2378
2379
	/**
2380
	 * decodeEntityFolderName - remove html entities
2381
	 * @param string _folderName the foldername
2382
	 * @return string the converted string
2383
	 */
2384
	function decodeEntityFolderName($_folderName)
2385
	{
2386
		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...
2387
	}
2388
2389
	/**
2390
	 * convert a mailboxname from utf7-imap to displaycharset
2391
	 *
2392
	 * @param string _folderName the foldername
2393
	 * @return string the converted string
2394
	 */
2395
	function encodeFolderName($_folderName)
2396
	{
2397
		return Translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset);
2398
	}
2399
2400
	/**
2401
	 * convert the foldername from display charset to UTF-7
2402
	 *
2403
	 * @param string _parent the parent foldername
2404
	 * @return ISO-8859-1 / UTF7-IMAP encoded string
2405
	 */
2406
	function _encodeFolderName($_folderName) {
2407
		return Translation::convert($_folderName, self::$displayCharset, 'ISO-8859-1');
2408
		#return Translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
2409
	}
2410
2411
	/**
2412
	 * create a new folder under given parent folder
2413
	 *
2414
	 * @param string _parent the parent foldername
2415
	 * @param string _folderName the new foldername
2416
	 * @param string _error pass possible error back to caller
2417
	 *
2418
	 * @return mixed name of the newly created folder or false on error
2419
	 */
2420
	function createFolder($_parent, $_folderName, &$_error)
2421
	{
2422 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."->"."$_parent, $_folderName called from:".function_backtrace());
2423
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2424
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2425
2426 View Code Duplication
		if(empty($parent)) {
2427
			$newFolderName = $folderName;
2428
		} else {
2429
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2430
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2431
		}
2432
		if (empty($newFolderName)) return false;
2433
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.'->'.$newFolderName);
2434
		if ($this->folderExists($newFolderName,true))
2435
		{
2436
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." Folder $newFolderName already exists.");
2437
			return $newFolderName;
2438
		}
2439
		try
2440
		{
2441
			$opts = array();
2442
			// if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too
2443
			if (isset(self::$specialUseFolders[$newFolderName]))
2444
			{
2445
				$opts['special_use'] = self::$specialUseFolders[$newFolderName];
2446
			}
2447
			$this->icServer->createMailbox($newFolderName, $opts);
2448
		}
2449
		catch (\Exception $e)
2450
		{
2451
			$_error = lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());
2452
			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...
2453
			return false;
2454
		}
2455
		try
2456
		{
2457
			$this->icServer->subscribeMailbox($newFolderName);
2458
		}
2459
		catch (\Exception $e)
2460
		{
2461
			error_log(__METHOD__.' ('.__LINE__.') '.' subscribe to new folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details);
2462
			return false;
2463
		}
2464
2465
		return $newFolderName;
2466
	}
2467
2468
	/**
2469
	 * rename a folder
2470
	 *
2471
	 * @param string _oldFolderName the old foldername
2472
	 * @param string _parent the parent foldername
2473
	 * @param string _folderName the new foldername
2474
	 *
2475
	 * @return mixed name of the newly created folder or false on error
2476
	 * @throws Exception
2477
	 */
2478
	function renameFolder($_oldFolderName, $_parent, $_folderName)
2479
	{
2480
		$oldFolderName	= $_oldFolderName;//$this->_encodeFolderName($_oldFolderName);
2481
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2482
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2483
2484 View Code Duplication
		if(empty($parent)) {
2485
			$newFolderName = $folderName;
2486
		} else {
2487
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2488
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2489
		}
2490
		if (self::$debug) error_log("create folder: $newFolderName");
2491
		try
2492
		{
2493
			$this->icServer->renameMailbox($oldFolderName, $newFolderName);
2494
		}
2495
		catch (\Exception $e)
2496
		{
2497
			throw new Exception(__METHOD__." failed for $oldFolderName (rename to: $newFolderName) with error:".$e->getMessage());;
2498
		}
2499
		// clear FolderExistsInfoCache
2500
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2501
2502
		return $newFolderName;
2503
2504
	}
2505
2506
	/**
2507
	 * delete an existing folder
2508
	 *
2509
	 * @param string _folderName the name of the folder to be deleted
2510
	 *
2511
	 * @return bool true on success, PEAR Error on failure
2512
	 * @throws Exception
2513
	 */
2514
	function deleteFolder($_folderName)
2515
	{
2516
		//$folderName = $this->_encodeFolderName($_folderName);
2517
		try
2518
		{
2519
			$this->icServer->subscribeMailbox($_folderName,false);
2520
			$this->icServer->deleteMailbox($_folderName);
2521
		}
2522
		catch (\Exception $e)
2523
		{
2524
			throw new Exception("Deleting Folder $_folderName failed! Error:".$e->getMessage());;
2525
		}
2526
		// clear FolderExistsInfoCache
2527
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2528
2529
		return true;
2530
	}
2531
2532
	/**
2533
	 * fetchUnSubscribedFolders: get unsubscribed IMAP folder list
2534
	 *
2535
	 * returns an array of unsubscribed IMAP folder names.
2536
	 *
2537
	 * @return array with folder names. eg.: 1 => INBOX/TEST
2538
	 */
2539
	function fetchUnSubscribedFolders()
2540
	{
2541
		$unSubscribedMailboxes = $this->icServer->listUnSubscribedMailboxes();
2542
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($unSubscribedMailboxes));
2543
		return $unSubscribedMailboxes;
2544
	}
2545
2546
	/**
2547
	 * get IMAP folder objects
2548
	 *
2549
	 * returns an array of IMAP folder objects. Put INBOX folder in first
2550
	 * position. Preserves the folder seperator for later use. The returned
2551
	 * array is indexed using the foldername. Use cachedObjects when retrieving subscribedFolders
2552
	 *
2553
	 * @param boolean _subscribedOnly  get subscribed or all folders
2554
	 * @param boolean _getCounters   get get messages counters
2555
	 * @param boolean _alwaysGetDefaultFolders  this triggers to ignore the possible notavailableautofolders - preference
2556
	 *			as activeSync needs all folders like sent, trash, drafts, templates and outbox - if not present devices may crash
2557
	 *			-> autoFolders should be created if needed / accessed (if possible and configured)
2558
	 * @param boolean _useCacheIfPossible  - if set to false cache will be ignored and reinitialized
2559
	 *
2560
	 * @return array with folder objects. eg.: INBOX => {inbox object}
2561
	 */
2562
	function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false,$_useCacheIfPossible=true)
2563
	{
2564
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' ServerID:'.$this->icServer->ImapServerId.", subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders, _useCacheIfPossible:$_useCacheIfPossible");
2565
		if (self::$debugTimes) $starttime = microtime (true);
2566
		static $folders2return;
2567
		//$_subscribedOnly=false;
2568
		// always use static on single request if info is available;
2569
		// so if you require subscribed/unsubscribed results on a single request you MUST
2570
		// set $_useCacheIfPossible to false !
2571 View Code Duplication
		if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2572
		{
2573
			if (self::$debugTimes) self::logRunTimes($starttime,null,'using static',__METHOD__.' ('.__LINE__.') ');
2574
			return $folders2return[$this->icServer->ImapServerId];
2575
		}
2576
2577
		if ($_subscribedOnly && $_getCounters===false)
2578
		{
2579
			if (is_null($folders2return)) $folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
2580 View Code Duplication
			if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2581
			{
2582
				//error_log(__METHOD__.' ('.__LINE__.') '.' using Cached folderObjects'.array2string($folders2return[$this->icServer->ImapServerId]));
2583
				if (self::$debugTimes) self::logRunTimes($starttime,null,'from Cache',__METHOD__.' ('.__LINE__.') ');
2584
				return $folders2return[$this->icServer->ImapServerId];
2585
			}
2586
		}
2587
		// use $folderBasicInfo for holding attributes and other basic folderinfo $folderBasicInfo[$this->icServer->ImapServerId]
2588
		static $folderBasicInfo;
2589 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);
2590
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($folderBasicInfo[$this->icServer->ImapServerId])));
2591
2592
		$delimiter = $this->getHierarchyDelimiter();
2593
2594
		$inboxData = new \stdClass;
2595
		$inboxData->name 		= 'INBOX';
2596
		$inboxData->folderName		= 'INBOX';
2597
		$inboxData->displayName		= lang('INBOX');
2598
		$inboxData->delimiter 		= $delimiter;
2599
		$inboxData->shortFolderName	= 'INBOX';
2600
		$inboxData->shortDisplayName	= lang('INBOX');
2601
		$inboxData->subscribed = true;
2602
		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...
2603
			$inboxData->counter = $this->getMailBoxCounters('INBOX');
2604
		}
2605
		// force unsubscribed by preference showAllFoldersInFolderPane
2606
		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...
2607
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2608
			$this->mailPreferences['showAllFoldersInFolderPane']==1)
2609
		{
2610
			$_subscribedOnly = false;
2611
		}
2612
		$inboxFolderObject = array('INBOX' => $inboxData);
2613
2614
		//$nameSpace = $this->icServer->getNameSpaces();
2615
		$nameSpace = $this->_getNameSpaces();
2616
		$fetchedAllInOneGo = false;
2617
		$subscribedFoldersForCache = $foldersNameSpace = array();
2618
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2619
		if (is_array($nameSpace))
2620
		{
2621
			foreach($nameSpace as $k => $singleNameSpace) {
2622
				$type = $singleNameSpace['type'];
2623
				// the following line (assumption that for the same namespace the delimiter should be equal) may be wrong
2624
				$foldersNameSpace[$type]['delimiter']  = $singleNameSpace['delimiter'];
2625
2626
				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...
2627
					// fetch and sort the subscribed folders
2628
					// we alway fetch the subscribed, as this provides the only way to tell
2629
					// if a folder is subscribed or not
2630
					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...
2631
					{
2632
						try
2633
						{
2634
							$subscribedMailboxes = $this->icServer->listSubscribedMailboxes('',0,true);
2635
							if (!empty($subscribedMailboxes))
2636
							{
2637
								$fetchedAllInOneGo = true;
2638
							}
2639
							else
2640
							{
2641
								$subscribedMailboxes = $this->icServer->listSubscribedMailboxes($singleNameSpace['prefix'],0,true);
2642
							}
2643
						}
2644
						catch(Exception $e)
2645
						{
2646
							continue;
2647
						}
2648
						//echo "subscribedMailboxes";_debug_array($subscribedMailboxes);
2649
						$subscribedFoldersPerNS = (!empty($subscribedMailboxes)?array_keys($subscribedMailboxes):array());
2650
						//if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']);
2651
						//_debug_array($foldersNameSpace);
2652
						//error_log(__METHOD__.__LINE__.array2string($singleNameSpace).':#:'.array2string($subscribedFoldersPerNS));
2653
						if (!empty($subscribedFoldersPerNS) && !empty($subscribedMailboxes))
2654
						{
2655
							//error_log(__METHOD__.' ('.__LINE__.') '." $type / subscribed:". array2string($subscribedMailboxes));
2656
							foreach ($subscribedMailboxes as $k => $finfo)
2657
							{
2658
								//error_log(__METHOD__.__LINE__.$k.':#:'.array2string($finfo));
2659
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$k]=
2660
								$folderBasicInfo[$this->icServer->ImapServerId][$k]=array(
2661
									'MAILBOX'=>$finfo['MAILBOX'],
2662
									'ATTRIBUTES'=>$finfo['ATTRIBUTES'],
2663
									'delimiter'=>$finfo['delimiter'],//lowercase for some reason???
2664
									'SUBSCRIBED'=>$finfo['SUBSCRIBED'],//seeded by getMailboxes
2665
								);
2666 View Code Duplication
								if (empty($foldersNameSpace[$type]['subscribed']) || !in_array($k,$foldersNameSpace[$type]['subscribed']))
2667
								{
2668
									$foldersNameSpace[$type]['subscribed'][] = $k;
2669
								}
2670 View Code Duplication
								if (empty($foldersNameSpace[$type]['all']) || !in_array($k,$foldersNameSpace[$type]['all']))
2671
								{
2672
									$foldersNameSpace[$type]['all'][] = $k;
2673
								}
2674
							}
2675
						}
2676
						//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($foldersNameSpace[$type]['subscribed']));
2677
						if (!is_array($foldersNameSpace[$type]['all'])) $foldersNameSpace[$type]['all'] = array();
2678
						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...
2679
							continue;
2680
						}
2681
2682
					}
2683
2684
					// fetch and sort all folders
2685
					//echo $type.'->'.$singleNameSpace['prefix'].'->'.($type=='shared'?0:2)."<br>";
2686
					try
2687
					{
2688
						// calling with 2 lists all mailboxes on that level with fetches all
2689
						// we switch to all, to avoid further calls for subsequent levels
2690
						// that may produce problems, when encountering recursions probably
2691
						// horde is handling that, so we do not; keep that in mind!
2692
						//$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],2,true);
2693
						$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],0,true);
2694
					}
2695
					catch (\Exception $e)
2696
					{
2697
						error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve all Boxes:'.$e->getMessage());
2698
						$allMailboxesExt = array();
2699
					}
2700
					if (!is_array($allMailboxesExt))
2701
					{
2702
						//error_log(__METHOD__.' ('.__LINE__.') '.' Expected Array but got:'.array2string($allMailboxesExt). 'Type:'.$type.' Prefix:'.$singleNameSpace['prefix']);
2703
						continue;
2704
						//$allMailboxesExt=array();
2705
					}
2706
2707
					//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($allMailboxesExt));
2708
					foreach ($allMailboxesExt as $mbx) {
2709
						if (!isset($folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2710
						{
2711
							$folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]=array(
2712
								'MAILBOX'=>$mbx['MAILBOX'],
2713
								'ATTRIBUTES'=>$mbx['ATTRIBUTES'],
2714
								'delimiter'=>$mbx['delimiter'],//lowercase for some reason???
2715
								'SUBSCRIBED'=>$mbx['SUBSCRIBED'],//seeded by getMailboxes
2716
							);
2717
							if ($mbx['SUBSCRIBED'] && !isset($subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2718
							{
2719
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']] = $folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']];
2720
							}
2721
						}
2722
						if ($mbx['SUBSCRIBED'] && (empty($foldersNameSpace[$type]['subscribed']) || !in_array($mbx['MAILBOX'],$foldersNameSpace[$type]['subscribed'])))
2723
						{
2724
							$foldersNameSpace[$type]['subscribed'][] = $mbx['MAILBOX'];
2725
						}
2726
						//echo __METHOD__;_debug_array($mbx);
2727
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
2728
						if (isset($allMailBoxesExtSorted[$mbx['MAILBOX']])||
2729
							isset($allMailBoxesExtSorted[$mbx['MAILBOX'].$foldersNameSpace[$type]['delimiter']])||
2730
							(substr($mbx['MAILBOX'],-1)==$foldersNameSpace[$type]['delimiter'] && isset($allMailBoxesExtSorted[substr($mbx['MAILBOX'],0,-1)]))
2731
						) continue;
2732
2733
						//echo '#'.$mbx['MAILBOX'].':'.array2string($mbx)."#<br>";
2734
						$allMailBoxesExtSorted[$mbx['MAILBOX']] = $mbx;
2735
					}
2736
					if (is_array($allMailBoxesExtSorted)) ksort($allMailBoxesExtSorted);
2737
					//_debug_array(array_keys($allMailBoxesExtSorted));
2738
					$allMailboxes = array();
2739
					foreach ((array)$allMailBoxesExtSorted as $mbx) {
2740
						if (!in_array($mbx['MAILBOX'],$allMailboxes)) $allMailboxes[] = $mbx['MAILBOX'];
2741
						//echo "Result:";_debug_array($allMailboxes);
2742
					}
2743
					$foldersNameSpace[$type]['all'] = $allMailboxes;
2744
					if (is_array($foldersNameSpace[$type]['all'])) sort($foldersNameSpace[$type]['all']);
2745
				}
2746
			}
2747
		}
2748
		//subscribed folders may be used in getFolderStatus
2749
		Cache::setCache(Cache::INSTANCE,'email','subscribedFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$subscribedFoldersForCache,$expiration=60*60*1);
2750
		//echo "<br>FolderNameSpace To Process:";_debug_array($foldersNameSpace);
2751
		$autoFolderObjects = $folders = array();
2752
		$autofolder_exists = array();
2753
		foreach( array('personal', 'others', 'shared') as $type) {
2754
			if(isset($foldersNameSpace[$type])) {
2755
				if($_subscribedOnly) {
2756
					if( !empty($foldersNameSpace[$type]['subscribed']) ) $listOfFolders = $foldersNameSpace[$type]['subscribed'];
2757
				} else {
2758
					if( !empty($foldersNameSpace[$type]['all'])) $listOfFolders = $foldersNameSpace[$type]['all'];
2759
				}
2760
				foreach((array)$listOfFolders as $folderName) {
2761
					//echo "<br>FolderToCheck:$folderName<br>";
2762
					//error_log(__METHOD__.__LINE__.'#Delimiter:'.$delimiter.':#'.$folderName);
2763
					if ($_subscribedOnly && empty($foldersNameSpace[$type]['all'])) continue;//when subscribedonly, we fetch all folders in one go.
2764
					if($_subscribedOnly && !(in_array($folderName, $foldersNameSpace[$type]['all'])||in_array($folderName.$foldersNameSpace[$type]['delimiter'], $foldersNameSpace[$type]['all']))) {
2765
						#echo "$folderName failed to be here <br>";
2766
						continue;
2767
					}
2768
					if (isset($folders[$folderName])) continue;
2769
					if (isset($autoFolderObjects[$folderName])) continue;
2770
					if (empty($delimiter)||$delimiter != $foldersNameSpace[$type]['delimiter']) $delimiter = $foldersNameSpace[$type]['delimiter'];
2771
					$folderParts = explode($delimiter, $folderName);
2772
					$shortName = array_pop($folderParts);
2773
2774
					$folderObject = new \stdClass;
2775
					$folderObject->delimiter	= $delimiter;
2776
					$folderObject->folderName	= $folderName;
2777
					$folderObject->shortFolderName	= $shortName;
2778
					if(!$_subscribedOnly) {
2779
						#echo $folderName."->".$type."<br>";
2780
						#_debug_array($foldersNameSpace[$type]['subscribed']);
2781
						$folderObject->subscribed = in_array($folderName, (array)$foldersNameSpace[$type]['subscribed']);
2782
					}
2783
2784
					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...
2785
						//error_log(__METHOD__.' ('.__LINE__.') '.' getCounter forFolder:'.$folderName);
2786
						$folderObject->counter = $this->getMailBoxCounters($folderName);
2787
					}
2788
					if(strtoupper($folderName) == 'INBOX') {
2789
						$folderName = 'INBOX';
2790
						$folderObject->folderName	= 'INBOX';
2791
						$folderObject->shortFolderName	= 'INBOX';
2792
						$folderObject->displayName	= lang('INBOX');
2793
						$folderObject->shortDisplayName = lang('INBOX');
2794
						$folderObject->subscribed	= true;
2795
					// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
2796
					} elseif (in_array($shortName,self::$autoFolders)) {
2797
						$tmpfolderparts = explode($delimiter,$folderObject->folderName);
2798
						array_pop($tmpfolderparts);
2799
						$folderObject->displayName = implode($delimiter,$tmpfolderparts).$delimiter.lang($shortName);
2800
						$folderObject->shortDisplayName = lang($shortName);
2801
						unset($tmpfolderparts);
2802
					} else {
2803
						$folderObject->displayName = $folderObject->folderName;
2804
						$folderObject->shortDisplayName = $shortName;
2805
					}
2806
					//$folderName = $folderName;
2807
					if (in_array($shortName,self::$autoFolders)&&self::searchValueInFolderObjects($shortName,$autoFolderObjects)===false) {
2808
						$autoFolderObjects[$folderName] = $folderObject;
2809
					} else {
2810
						$folders[$folderName] = $folderObject;
2811
					}
2812
					//error_log(__METHOD__.' ('.__LINE__.') '.':'.$folderObject->folderName);
2813
					if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders ();
2814
					if (isset(self::$specialUseFolders[$folderName]))
2815
					{
2816
						$autofolder_exists[$folderName] = self::$specialUseFolders[$folderName];
2817
					}
2818
				}
2819
			}
2820
		}
2821
		if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) {
2822
			uasort($autoFolderObjects,array($this,"sortByAutoFolderPos"));
2823
		}
2824
		// check if some standard folders are missing and need to be created
2825
		if (count($autofolder_exists) < count(self::$autoFolders) && $this->check_create_autofolders($autofolder_exists))
2826
		{
2827
			// if new folders have been created, re-read folders ignoring the cache
2828
			return $this->getFolderObjects($_subscribedOnly, $_getCounters, $_alwaysGetDefaultFolders, false);	// false = do NOT use cache
2829
		}
2830
		if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName"));
2831
		//$folders2return = array_merge($autoFolderObjects,$folders);
2832
		//_debug_array($folders2return); #exit;
2833
		$folders2return[$this->icServer->ImapServerId] = array_merge((array)$inboxFolderObject,(array)$autoFolderObjects,(array)$folders);
2834
		if (($_subscribedOnly && $_getCounters===false) ||
2835
			($_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...
2836
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2837
			$this->mailPreferences['showAllFoldersInFolderPane']==1))
2838
		{
2839
			Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return,$expiration=60*60*1);
2840
		}
2841
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1);
2842
		if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') ');
2843
		return $folders2return[$this->icServer->ImapServerId];
2844
	}
2845
2846
	/**
2847
	 * Get IMAP folders for a mailbox
2848
	 *
2849
	 * @param string $_nodePath = null folder name to fetch from IMAP,
2850
	 *			null means all folders
2851
	 * @param boolean $_onlyTopLevel if set to true only top level objects
2852
	 *			will be return and nodePath would be ignored
2853
	 * @param int $_search = 2 search restriction in given mailbox
2854
	 *	0:All folders recursively from the $_nodePath
2855
	 *  1:Only folder of specified $_nodePath
2856
	 *	2:All folders of $_nodePath in the same heirachy level
2857
	 *
2858
	 * @param boolean $_subscribedOnly = false Command to fetch only the subscribed folders
2859
	 * @param boolean $_getCounter = false Command to fetch mailbox counter
2860
	 *
2861
	 * @return array arrays of folders
2862
	 */
2863
	function getFolderArrays ($_nodePath = null, $_onlyTopLevel = false, $_search= 2, $_subscribedOnly = false, $_getCounter = false)
2864
	{
2865
		// delimiter
2866
		$delimiter = $this->getHierarchyDelimiter();
2867
2868
		$folders = $nameSpace =  array();
2869
		$nameSpaceTmp = $this->_getNameSpaces();
2870
		foreach($nameSpaceTmp as $k => $singleNameSpace) {
2871
			$nameSpace[$singleNameSpace['type']]=$singleNameSpace;
2872
		}
2873
		unset($nameSpaceTmp);
2874
2875
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2876
		// Get special use folders
2877
		if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders (); // Set self::$specialUseFolders
2878
		// topLevelQueries generally ignore the $_search param. Except for Config::examineNamespace
2879
		if ($_onlyTopLevel) // top level leaves
2880
		{
2881
			// Get top mailboxes of icServer
2882
			$topFolders = $this->icServer->getMailboxes("", 2, true);
2883
			// Trigger examination of namespace to retrieve
2884
			// folders located in other and shared; needed only for some servers
2885
			if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
2886
			if (self::$mailConfig['examineNamespace'])
2887
			{
2888
				$prefixes=array();
2889
				if (is_array($nameSpace))
2890
				{
2891
					foreach($nameSpace as $k => $singleNameSpace) {
2892
						$type = $singleNameSpace['type'];
2893
2894
						if(is_array($singleNameSpace) && $singleNameSpace['prefix']){
2895
							$prefixes[$type] = $singleNameSpace['prefix'];
2896
							//regard extra care for nameSpacequeries when configured AND respect $_search
2897
							$result = $this->icServer->getMailboxes($singleNameSpace['prefix'], $_search==0?0:2, true);
2898
							if (is_array($result))
2899
							{
2900
								ksort($result);
2901
								$topFolders = array_merge($topFolders,$result);
2902
							}
2903
						}
2904
					}
2905
				}
2906
			}
2907
2908
			$autofolders = array();
2909
2910
			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...
2911
			{
2912
				if ($this->folderExists($path))
2913
				{
2914
					$autofolders[$folder] = $folder;
2915
				}
2916
			}
2917
			// Check if the special use folders are there, otherwise try to create them
2918
			if (count($autofolders) < count(self::$autoFolders) && $this->check_create_autofolders ($autofolders))
2919
			{
2920
				return $this->getFolderArrays ($_nodePath, $_onlyTopLevel, $_search, $_subscribedOnly, $_getCounter);
2921
			}
2922
2923
			// now process topFolders for next level
2924
			foreach ($topFolders as &$node)
2925
			{
2926
				$pattern = "/\\".$delimiter."/";
2927
				$reference = preg_replace($pattern, '', $node['MAILBOX']);
2928
				if(!empty($prefixes))
2929
				{
2930
					$reference = '';
2931
					$tmpArray = explode($delimiter,$node['MAILBOX']);
2932
					foreach($tmpArray as $p)
2933
					{
2934
						$reference = empty($reference)?$p:$reference.$delimiter.$p;
2935
					}
2936
				}
2937
				$mainFolder = $subFolders = array();
2938
2939
				if ($_subscribedOnly)
2940
				{
2941
					$mainFolder = $this->icServer->listSubscribedMailboxes($reference, 1, true);
2942
					$subFolders = $this->icServer->listSubscribedMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2943
				}
2944
				else
2945
				{
2946
					$mainFolder = $this->icServer->getMailboxes($reference, 1, true);
2947
					$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2948
				}
2949
2950
				if (is_array($mainFolder['INBOX']))
2951
				{
2952
					// Array container of auto folders
2953
					$aFolders = array();
2954
2955
					// Array container of non auto folders
2956
					$nFolders = array();
2957
2958
					foreach ((array)$subFolders as $path => $folder)
2959
					{
2960
						$folderInfo = self::pathToFolderData($folder['MAILBOX'], $folder['delimiter']);
2961
						if (in_array(trim($folderInfo['name']), $autofolders) || in_array(trim($folderInfo['name']), self::$autoFolders))
2962
						{
2963
							$aFolders [$path] = $folder;
2964
						}
2965
						else
2966
						{
2967
							$nFolders [$path] = $folder;
2968
						}
2969
					}
2970
					if (is_array($aFolders)) uasort ($aFolders, array($this,'sortByAutofolder'));
2971
					//ksort($aFolders);
2972
2973
					// Sort none auto folders base on mailbox name
2974
					uasort($nFolders,array($this,'sortByMailbox'));
2975
2976
					$subFolders = array_merge($aFolders,$nFolders);
2977
				}
2978
				else
2979
				{
2980
					if (is_array($subFolders)) ksort($subFolders);
2981
				}
2982
				$folders = array_merge($folders,(array)$mainFolder, (array)$subFolders);
2983
			}
2984
		}
2985
		elseif ($_nodePath) // single node
2986
		{
2987
			switch ($_search)
2988
			{
2989
				// Including children
2990
				case 0:
2991
				case 2:
2992
					$path = $_nodePath.''.$delimiter;
2993
					break;
2994
				// Node itself
2995
				// shouldn't contain next level delimiter
2996
				case 1:
2997
					$path = $_nodePath;
2998
					break;
2999
			}
3000
			if ($_subscribedOnly)
3001
			{
3002
				$folders = $this->icServer->listSubscribedMailboxes($path, $_search, true);
3003
			}
3004
			else
3005
			{
3006
				$folders = $this->icServer->getMailboxes($path, $_search, true);
3007
			}
3008
3009
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
3010
		}
3011
		elseif(!$_nodePath) // all
3012
		{
3013
			if ($_subscribedOnly)
3014
			{
3015
				$folders = $this->icServer->listSubscribedMailboxes('', 0, true);
3016
			}
3017
			else
3018
			{
3019
				$folders = $this->icServer->getMailboxes('', 0, true);
3020
			}
3021
		}
3022
		// only sort (autofolders, shared, others ...) when retrieving all folders or toplevelquery
3023
		if ($_onlyTopLevel || !$_nodePath)
3024
		{
3025
			// SORTING FOLDERS
3026
			//self::$debugTimes=true;
3027
			if (self::$debugTimes) $starttime = microtime (true);
3028
			// Merge of all auto folders and specialusefolders
3029
			$autoFoldersTmp = array_unique((array_merge(self::$autoFolders, array_values(self::$specialUseFolders))));
3030
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
3031
			$tmpFolders = $folders;
3032
			$inboxFolderObject=$inboxSubFolderObjects=$autoFolderObjects=$typeFolderObject=$mySpecialUseFolders=array();
3033
			$googleMailFolderObject=$googleAutoFolderObjects=$googleSubFolderObjects=array();
3034
			$isGoogleMail=false;
3035
			foreach($autoFoldersTmp as $afk=>$aF)
3036
			{
3037
				if (!isset($mySpecialUseFolders[$aF]) && $aF) $mySpecialUseFolders[$aF]=$this->getFolderByType($aF,false);
3038
				//error_log($afk.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3039
			}
3040
			//error_log(array2string($mySpecialUseFolders));
3041
			foreach ($tmpFolders as $k => $f) {
3042
				$sorted=false;
3043
				if (strtoupper(substr($k,0,5))=='INBOX') {
3044
					if (strtoupper($k)=='INBOX') {
3045
						//error_log(__METHOD__.__LINE__.':'.strtoupper(substr($k,0,5)).':'.$k);
3046
						$inboxFolderObject[$k]=$f;
3047
						unset($folders[$k]);
3048
						$sorted=true;
3049 View Code Duplication
					} else {
3050
						$isAutoFolder=false;
3051
						foreach($autoFoldersTmp as $afk=>$aF)
3052
						{
3053
							//error_log(__METHOD__.__LINE__.$k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3054
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3055
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter || //k may be child of an autofolder
3056
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3057
							{
3058
								//error_log(__METHOD__.__LINE__.$k.'->'.$mySpecialUseFolders[$aF]);
3059
								$isAutoFolder=true;
3060
								$autoFolderObjects[$k]=$f;
3061
								break;
3062
							}
3063
						}
3064
						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...
3065
						unset($folders[$k]);
3066
						$sorted=true;
3067
					}
3068
				} elseif (strtoupper(substr($k,0,13))=='[GOOGLE MAIL]') {
3069
					$isGoogleMail=true;
3070
					if (strtoupper($k)=='[GOOGLE MAIL]') {
3071
						$googleMailFolderObject[$k]=$f;
3072
						unset($folders[$k]);
3073
						$sorted=true;
3074 View Code Duplication
					} else {
3075
						$isAutoFolder=false;
3076
						foreach($autoFoldersTmp as $afk=>$aF)
3077
						{
3078
							//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3079
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3080
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3081
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3082
							{
3083
								//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3084
								$isAutoFolder=true;
3085
								$googleAutoFolderObjects[$k]=$f;
3086
								break;
3087
							}
3088
						}
3089
						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...
3090
						unset($folders[$k]);
3091
						$sorted=true;
3092
					}
3093
				} else {
3094
					$isAutoFolder=false;
3095
					foreach($autoFoldersTmp as $afk=>$aF)
3096
					{
3097
						//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3098
						if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3099
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3100
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3101
						{
3102
							//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3103
							$isAutoFolder=true;
3104
							$autoFolderObjects[$k]=$f;
3105
							unset($folders[$k]);
3106
							$sorted=true;
3107
							break;
3108
						}
3109
					}
3110
				}
3111
3112
				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...
3113
				{
3114
					foreach(array('others','shared') as $type)
3115
					{
3116
						if ($nameSpace[$type]['prefix_present']&&$nameSpace[$type]['prefix'])
3117
						{
3118
							if (substr($k,0,strlen($nameSpace[$type]['prefix']))==$nameSpace[$type]['prefix']||
3119
								substr($k,0,strlen($nameSpace[$type]['prefix'])-strlen($nameSpace[$type]['delimiter']))==substr($nameSpace[$type]['prefix'],0,strlen($nameSpace[$type]['delimiter'])*-1)) {
3120
								//error_log(__METHOD__.__LINE__.':'.substr($k,0,strlen($nameSpace[$type]['prefix'])).':'.$k);
3121
								$typeFolderObject[$type][$k]=$f;
3122
								unset($folders[$k]);
3123
							}
3124
						}
3125
					}
3126
				}
3127
			}
3128
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3129
			// avoid calling sortByAutoFolder as it is not regarding subfolders
3130
			$autoFolderObjectsTmp = $autoFolderObjects;
3131
			unset($autoFolderObjects);
3132
			uasort($autoFolderObjectsTmp, array($this,'sortByMailbox'));
3133
			foreach($autoFoldersTmp as $afk=>$aF)
3134
			{
3135
				foreach($autoFolderObjectsTmp as $k => $f)
3136
				{
3137
					if($aF && ($mySpecialUseFolders[$aF]==$k ||
3138
						substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter ||
3139
						stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false))
3140
					{
3141
						$autoFolderObjects[$k]=$f;
3142
					}
3143
				}
3144
			}
3145
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3146
			if (!$isGoogleMail) {
3147
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3148
			} else {
3149
				// avoid calling sortByAutoFolder as it is not regarding subfolders
3150
				$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
3151
				unset($googleAutoFolderObjects);
3152
				uasort($gAutoFolderObjectsTmp, array($this,'sortByMailbox'));
3153
				foreach($autoFoldersTmp as $afk=>$aF)
3154
				{
3155
					foreach($gAutoFolderObjectsTmp as $k => $f)
3156
					{
3157
						if($aF && ($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter))
3158
						{
3159
							$googleAutoFolderObjects[$k]=$f;
3160
						}
3161
					}
3162
				}
3163
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$folders,(array)$googleMailFolderObject,$googleAutoFolderObjects,$googleSubFolderObjects,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3164
			}
3165
			if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') Sorting:');
3166
			//self::$debugTimes=false;
3167
		}
3168
		// Get counter information and add them to each fetched folders array
3169
		// TODO:  do not fetch counters for user .... as in shared / others
3170
		if ($_getCounter)
3171
		{
3172
			foreach ($folders as &$folder)
3173
			{
3174
				$folder['counter'] = $this->icServer->getMailboxCounters($folder['MAILBOX']);
3175
			}
3176
		}
3177
		return $folders;
3178
	}
3179
3180
3181
	/**
3182
	 * Check if all automatic folders exist and create them if not
3183
	 *
3184
	 * @param array $autofolders_exists existing folders, no need to check their existance again
3185
	 * @return int number of new folders created
3186
	 */
3187
	function check_create_autofolders(array $autofolders_exists=array())
3188
	{
3189
		$num_created = 0;
3190
		foreach(self::$autoFolders as $folder)
3191
		{
3192
			$created = false;
3193
			if (!in_array($folder, $autofolders_exists) && $this->_getSpecialUseFolder($folder, true, $created) &&
3194
				$created && $folder != 'Outbox')
3195
			{
3196
				$num_created++;
3197
			}
3198
		}
3199
		return $num_created;
3200
	}
3201
3202
	/**
3203
	 * search Value In FolderObjects
3204
	 *
3205
	 * Helper function to search for a specific value within the foldertree objects
3206
	 * @param string $needle
3207
	 * @param array $haystack array of folderobjects
3208
	 * @return MIXED false or key
3209
	 */
3210
	static function searchValueInFolderObjects($needle, $haystack)
3211
	{
3212
		$rv = false;
3213
		foreach ($haystack as $k => $v)
3214
		{
3215
			foreach($v as &$sv) {if (trim($sv)==trim($needle)) return $k;}
3216
		}
3217
		return $rv;
3218
	}
3219
3220
	/**
3221
	 * sortByMailbox
3222
	 *
3223
	 * Helper function to sort folders array by mailbox
3224
	 * @param array $a
3225
	 * @param array $b array of folders
3226
	 * @return int expect values (0, 1 or -1)
3227
	 */
3228
	function sortByMailbox($a,$b)
3229
	{
3230
		return strcasecmp($a['MAILBOX'],$b['MAILBOX']);
3231
	}
3232
3233
	/**
3234
	 * Get folder data from path
3235
	 *
3236
	 * @param string $_path a node path
3237
	 * @param string $_hDelimiter hierarchy delimiter
3238
	 * @return array returns an array of data extracted from given node path
3239
	 */
3240
	static function pathToFolderData ($_path, $_hDelimiter)
3241
	{
3242
		if (!strpos($_path, self::DELIMITER)) $_path = self::DELIMITER.$_path;
3243
		list(,$path) = explode(self::DELIMITER, $_path);
3244
		$path_chain = $parts = explode($_hDelimiter, $path);
3245
		$name = array_pop($parts);
3246
		return array (
3247
			'name' => $name,
3248
			'mailbox' => $path,
3249
			'parent' => implode($_hDelimiter, $parts),
3250
			'text' => $name,
3251
			'tooltip' => $name,
3252
			'path' => $path_chain
3253
		);
3254
	}
3255
3256
	/**
3257
	 * sortByAutoFolder
3258
	 *
3259
	 * Helper function to sort folder-objects by auto Folder Position
3260
	 * @param array $_a
3261
	 * @param array $_b
3262
	 * @return int expect values (0, 1 or -1)
3263
	 */
3264
	function sortByAutoFolder($_a, $_b)
3265
	{
3266
		// 0, 1 und -1
3267
		$a = self::pathToFolderData($_a['MAILBOX'], $_a['delimiter']);
3268
		$b = self::pathToFolderData($_b['MAILBOX'], $_b['delimiter']);
3269
		$pos1 = array_search(trim($a['name']),self::$autoFolders);
3270
		$pos2 = array_search(trim($b['name']),self::$autoFolders);
3271
		if ($pos1 == $pos2) return 0;
3272
		return ($pos1 < $pos2) ? -1 : 1;
3273
	}
3274
3275
	/**
3276
	 * sortByDisplayName
3277
	 *
3278
	 * Helper function to sort folder-objects by displayname
3279
	 * @param object $a
3280
	 * @param object $b array of folderobjects
3281
	 * @return int expect values (0, 1 or -1)
3282
	 */
3283
	function sortByDisplayName($a,$b)
3284
	{
3285
		// 0, 1 und -1
3286
		return strcasecmp($a->displayName,$b->displayName);
3287
	}
3288
3289
	/**
3290
	 * sortByAutoFolderPos
3291
	 *
3292
	 * Helper function to sort folder-objects by auto Folder Position
3293
	 * @param object $a
3294
	 * @param object $b array of folderobjects
3295
	 * @return int expect values (0, 1 or -1)
3296
	 */
3297
	function sortByAutoFolderPos($a,$b)
3298
	{
3299
		// 0, 1 und -1
3300
		$pos1 = array_search(trim($a->shortFolderName),self::$autoFolders);
3301
		$pos2 = array_search(trim($b->shortFolderName),self::$autoFolders);
3302
		if ($pos1 == $pos2) return 0;
3303
		return ($pos1 < $pos2) ? -1 : 1;
3304
	}
3305
3306
	/**
3307
	 * getMailBoxCounters
3308
	 *
3309
	 * function to retrieve the counters for a given folder
3310
	 * @param string $folderName
3311
	 * @param boolean $_returnObject return the counters as object rather than an array
3312
	 * @return mixed false or array of counters array(MESSAGES,UNSEEN,RECENT,UIDNEXT,UIDVALIDITY) or object
3313
	 */
3314
	function getMailBoxCounters($folderName,$_returnObject=true)
3315
	{
3316
		try
3317
		{
3318
			$folderStatus = $this->icServer->getMailboxCounters($folderName);
3319
			//error_log(__METHOD__.' ('.__LINE__.') '.$folderName.": FolderStatus:".array2string($folderStatus).function_backtrace());
3320
		}
3321
		catch (\Exception $e)
3322
		{
3323
			if (self::$debug) error_log(__METHOD__." returned FolderStatus for Folder $folderName:".$e->getMessage());
3324
			return false;
3325
		}
3326
		if(is_array($folderStatus)) {
3327
			if ($_returnObject===false) return $folderStatus;
3328
			$status =  new \stdClass;
3329
			$status->messages   = $folderStatus['MESSAGES'];
3330
			$status->unseen     = $folderStatus['UNSEEN'];
3331
			$status->recent     = $folderStatus['RECENT'];
3332
			$status->uidnext        = $folderStatus['UIDNEXT'];
3333
			$status->uidvalidity    = $folderStatus['UIDVALIDITY'];
3334
3335
			return $status;
3336
		}
3337
		return false;
3338
	}
3339
3340
	/**
3341
	 * getMailBoxesRecursive
3342
	 *
3343
	 * function to retrieve mailboxes recursively from given mailbox
3344
	 * @param string $_mailbox
3345
	 * @param string $delimiter
3346
	 * @param string $prefix
3347
	 * @param string $reclevel 0, counter to keep track of the current recursionlevel
3348
	 * @return array of mailboxes
3349
	 */
3350 View Code Duplication
	function getMailBoxesRecursive($_mailbox, $delimiter, $prefix, $reclevel=0)
3351
	{
3352
		#echo __METHOD__." retrieve SubFolders for $_mailbox$delimiter <br>";
3353
		$maxreclevel=25;
3354
		if ($reclevel > $maxreclevel) {
3355
			error_log( __METHOD__." Recursion Level Exeeded ($reclevel) while looking up $_mailbox$delimiter ");
3356
			return array();
3357
		}
3358
		$reclevel++;
3359
		// clean up double delimiters
3360
		$_mailbox = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$_mailbox);
3361
		//get that mailbox in question
3362
		$mbx = $this->icServer->getMailboxes($_mailbox,1,true);
3363
		$mbxkeys = array_keys($mbx);
3364
		#_debug_array($mbx);
3365
//error_log(__METHOD__.' ('.__LINE__.') '.' Delimiter:'.array2string($delimiter));
3366
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
3367
		// Example: Array([INBOX/GaGa] => Array([MAILBOX] => INBOX/GaGa[ATTRIBUTES] => Array([0] => \\unmarked)[delimiter] => /))
3368
		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"]))) {
3369
			// if there are children fetch them
3370
			//echo $mbx[$mbxkeys[0]]['MAILBOX']."<br>";
3371
3372
			$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'].($mbx[$mbxkeys[0]]['MAILBOX'] == $prefix ? '':$delimiter),2,false);
3373
			//$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'],2,false);
3374
			//_debug_array($buff);
3375
			$allMailboxes = array();
3376
			foreach ($buff as $mbxname) {
3377
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbxname));
3378
				$mbxname = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$mbxname['MAILBOX']);
3379
				#echo "About to recur in level $reclevel:".$mbxname."<br>";
3380
				if ( $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'] && $mbxname != $prefix  && $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'].$delimiter)
3381
				{
3382
					$allMailboxes = array_merge($allMailboxes, self::getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel));
3383
				}
3384
			}
3385
			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'];
3386
			return $allMailboxes;
3387
		} else {
3388
			return array($_mailbox);
3389
		}
3390
	}
3391
3392
	/**
3393
	 * _getSpecialUseFolder
3394
	 * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder
3395
	 * @param string $_type the type to fetch (Drafts|Template|Trash|Sent)
3396
	 * @param boolean $_checkexistance trigger check for existance
3397
	 * @param boolean& $created =null on return true: if folder was just created, false if not
3398
	 * @return mixed string or false
3399
	 */
3400
	function _getSpecialUseFolder($_type, $_checkexistance=TRUE, &$created=null)
3401
	{
3402
		static $types = array(
3403
			'Drafts'   => array('profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'),
3404
			'Template' => array('profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'),
3405
			'Trash'    => array('profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'),
3406
			'Sent'     => array('profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'),
3407
			'Junk'     => array('profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'),
3408
			'Outbox'   => array('profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'),
3409
			'Archive'   => array('profileKey'=>'acc_folder_archive','autoFolderName'=>'Archive'),
3410
		);
3411
		if ($_type == 'Templates') $_type = 'Template';	// for some reason self::$autofolders uses 'Templates'!
3412
		$created = false;
3413
		if (!isset($types[$_type]))
3414
		{
3415
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$_type.' not supported for '.__METHOD__);
3416
			return false;
3417
		}
3418
		if (is_null(self::$specialUseFolders) || empty(self::$specialUseFolders)) self::$specialUseFolders = $this->getSpecialUseFolders();
3419
3420
		//highest precedence
3421
		try
3422
		{
3423
			$_folderName = $this->icServer->{$types[$_type]['profileKey']};
3424
		}
3425
		catch (\Exception $e)
3426
		{
3427
			// we know that outbox is not supported, but we use this here, as we autocreate expected SpecialUseFolders in this function
3428
			if ($_type != 'Outbox') error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve Folder'.$_folderName." for ".array2string($types[$_type]).":".$e->getMessage());
3429
			$_folderName = false;
3430
		}
3431
		// do not try to autocreate configured Archive-Folder. Return false if configured folder does not exist
3432
		if ($_type == 'Archive') {
3433
			if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3434
				return false;
3435
			} else {
3436
				return $_folderName;
3437
			}
3438
3439
		}
3440
		// does the folder exist??? (is configured/preset, but non-existent)
3441
		if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3442
			try
3443
			{
3444
				$error = null;
3445
				if (($_folderName = $this->createFolder('', $_folderName, $error))) $created = true;
3446 View Code Duplication
				if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3447
			}
3448
			catch(Exception $e)
3449
			{
3450
				error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage().':'.function_backtrace());
3451
				$_folderName = false;
3452
			}
3453
		}
3454
		// not sure yet if false is the correct behavior on none
3455
		if ($_folderName =='none') return 'none' ; //false;
3456
		//no (valid) folder found yet; try specialUseFolders
3457
		if (empty($_folderName) && is_array(self::$specialUseFolders) && ($f = array_search($_type,self::$specialUseFolders))) $_folderName = $f;
3458
		//no specialUseFolder; try some Defaults
3459
		if (empty($_folderName) && isset($types[$_type]))
3460
		{
3461
			$nameSpace = $this->_getNameSpaces();
3462
			$prefix='';
3463
			foreach ($nameSpace as $nSp)
3464
			{
3465
				if ($nSp['type']=='personal')
3466
				{
3467
					//error_log(__METHOD__.__LINE__.array2string($nSp));
3468
					$prefix = $nSp['prefix'];
3469
					break;
3470
				}
3471
			}
3472
			if ($this->folderExists($prefix.$types[$_type]['autoFolderName'],true))
3473
			{
3474
				$_folderName = $prefix.$types[$_type]['autoFolderName'];
3475
			}
3476
			else
3477
			{
3478
				try
3479
				{
3480
					$error = null;
3481
					$this->createFolder('', $prefix.$types[$_type]['autoFolderName'],$error);
3482
					$_folderName = $prefix.$types[$_type]['autoFolderName'];
3483 View Code Duplication
					if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3484
				}
3485
				catch(Exception $e)
3486
				{
3487
					error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage());
3488
					$_folderName = false;
3489
				}
3490
			}
3491
		}
3492
		return $_folderName;
3493
	}
3494
3495
	/**
3496
	 * getFolderByType wrapper for _getSpecialUseFolder Type as param
3497
	 * @param string $type foldertype to look for
3498
	 * @param boolean $_checkexistance trigger check for existance
3499
	 * @return mixed string or false
3500
	 */
3501
	function getFolderByType($type, $_checkexistance=false)
3502
	{
3503
		return $this->_getSpecialUseFolder($type, $_checkexistance);
3504
	}
3505
3506
	/**
3507
	 * getJunkFolder wrapper for _getSpecialUseFolder Type Junk
3508
	 * @param boolean $_checkexistance trigger check for existance
3509
	 * @return mixed string or false
3510
	 */
3511
	function getJunkFolder($_checkexistance=TRUE)
3512
	{
3513
		return $this->_getSpecialUseFolder('Junk', $_checkexistance);
3514
	}
3515
3516
	/**
3517
	 * getDraftFolder wrapper for _getSpecialUseFolder Type Drafts
3518
	 * @param boolean $_checkexistance trigger check for existance
3519
	 * @return mixed string or false
3520
	 */
3521
	function getDraftFolder($_checkexistance=TRUE)
3522
	{
3523
		return $this->_getSpecialUseFolder('Drafts', $_checkexistance);
3524
	}
3525
3526
	/**
3527
	 * getTemplateFolder wrapper for _getSpecialUseFolder Type Template
3528
	 * @param boolean $_checkexistance trigger check for existance
3529
	 * @return mixed string or false
3530
	 */
3531
	function getTemplateFolder($_checkexistance=TRUE)
3532
	{
3533
		return $this->_getSpecialUseFolder('Template', $_checkexistance);
3534
	}
3535
3536
	/**
3537
	 * getTrashFolder wrapper for _getSpecialUseFolder Type Trash
3538
	 * @param boolean $_checkexistance trigger check for existance
3539
	 * @return mixed string or false
3540
	 */
3541
	function getTrashFolder($_checkexistance=TRUE)
3542
	{
3543
		return $this->_getSpecialUseFolder('Trash', $_checkexistance);
3544
	}
3545
3546
	/**
3547
	 * getSentFolder wrapper for _getSpecialUseFolder Type Sent
3548
	 * @param boolean $_checkexistance trigger check for existance
3549
	 * @return mixed string or false
3550
	 */
3551
	function getSentFolder($_checkexistance=TRUE)
3552
	{
3553
		return $this->_getSpecialUseFolder('Sent', $_checkexistance);
3554
	}
3555
3556
	/**
3557
	 * getOutboxFolder wrapper for _getSpecialUseFolder Type Outbox
3558
	 * @param boolean $_checkexistance trigger check for existance
3559
	 * @return mixed string or false
3560
	 */
3561
	function getOutboxFolder($_checkexistance=TRUE)
3562
	{
3563
		return $this->_getSpecialUseFolder('Outbox', $_checkexistance);
3564
	}
3565
3566
	/**
3567
	 * getArchiveFolder wrapper for _getSpecialUseFolder Type Archive
3568
	 * @param boolean $_checkexistance trigger check for existance . We do no autocreation for configured Archive folder
3569
	 * @return mixed string or false
3570
	 */
3571
	function getArchiveFolder($_checkexistance=TRUE)
3572
	{
3573
		return $this->_getSpecialUseFolder('Archive', $_checkexistance);
3574
	}
3575
3576
	/**
3577
	 * isSentFolder is the given folder the sent folder or at least a subfolder of it
3578
	 * @param string $_folderName folder to perform the check on
3579
	 * @param boolean $_checkexistance trigger check for existance
3580
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3581
	 * @return boolean
3582
	 */
3583 View Code Duplication
	function isSentFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3584
	{
3585
		$sentFolder = $this->getSentFolder($_checkexistance);
3586
		if(empty($sentFolder)) {
3587
			return false;
3588
		}
3589
		// does the folder exist???
3590
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3591
			return false;
3592
		}
3593
3594
		if ($_exactMatch)
3595
		{
3596
			if(false !== stripos($_folderName, $sentFolder)&& strlen($_folderName)==strlen($sentFolder)) {
3597
				return true;
3598
			} else {
3599
				return false;
3600
			}
3601
		} else {
3602
			if(false !== stripos($_folderName, $sentFolder)) {
3603
				return true;
3604
			} else {
3605
				return false;
3606
			}
3607
		}
3608
	}
3609
3610
	/**
3611
	 * checks if the Outbox folder exists and is part of the foldername to be checked
3612
	 * @param string $_folderName folder to perform the check on
3613
	 * @param boolean $_checkexistance trigger check for existance
3614
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3615
	 * @return boolean
3616
	 */
3617
	function isOutbox($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3618
	{
3619
		if (stripos($_folderName, 'Outbox')===false) {
3620
			return false;
3621
		}
3622
		// does the folder exist???
3623
		if ($_checkexistance && $GLOBALS['egw_info']['user']['apps']['activesync'] && !$this->folderExists($_folderName)) {
3624
			$outboxFolder = $this->getOutboxFolder($_checkexistance);
3625
			if ($_exactMatch)
3626
			{
3627
				if(false !== stripos($_folderName, $outboxFolder)&& strlen($_folderName)==strlen($outboxFolder)) {
3628
					return true;
3629
				} else {
3630
					return false;
3631
				}
3632
			} else {
3633
				if(false !== stripos($_folderName, $outboxFolder)) {
3634
					return true;
3635
				} else {
3636
					return false;
3637
				}
3638
			}
3639
		}
3640
		return true;
3641
	}
3642
3643
	/**
3644
	 * isDraftFolder is the given folder the sent folder or at least a subfolder of it
3645
	 * @param string $_folderName folder to perform the check on
3646
	 * @param boolean $_checkexistance trigger check for existance
3647
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3648
	 * @return boolean
3649
	 */
3650
	function isDraftFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3651
	{
3652
		$draftFolder = $this->getDraftFolder($_checkexistance);
3653
		if(empty($draftFolder)) {
3654
			return false;
3655
		}
3656
		// does the folder exist???
3657
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3658
			return false;
3659
		}
3660
		if (is_a($_folderName,"Horde_Imap_Client_Mailbox")) $_folderName = $_folderName->utf8;
3661
		if ($_exactMatch)
3662
		{
3663
			if(false !== stripos($_folderName, $draftFolder)&& strlen($_folderName)==strlen($draftFolder)) {
3664
				return true;
3665
			} else {
3666
				return false;
3667
			}
3668
		} else {
3669
			if(false !== stripos($_folderName, $draftFolder)) {
3670
				return true;
3671
			} else {
3672
				return false;
3673
			}
3674
		}
3675
	}
3676
3677
	/**
3678
	 * isTrashFolder is the given folder the sent folder or at least a subfolder of it
3679
	 * @param string $_folderName folder to perform the check on
3680
	 * @param boolean $_checkexistance trigger check for existance
3681
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3682
	 * @return boolean
3683
	 */
3684 View Code Duplication
	function isTrashFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3685
	{
3686
		$trashFolder = $this->getTrashFolder($_checkexistance);
3687
		if(empty($trashFolder)) {
3688
			return false;
3689
		}
3690
		// does the folder exist???
3691
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3692
			return false;
3693
		}
3694
3695
		if ($_exactMatch)
3696
		{
3697
			if(false !== stripos($_folderName, $trashFolder)&& strlen($_folderName)==strlen($trashFolder)) {
3698
				return true;
3699
			} else {
3700
				return false;
3701
			}
3702
		} else {
3703
			if(false !== stripos($_folderName, $trashFolder)) {
3704
				return true;
3705
			} else {
3706
				return false;
3707
			}
3708
		}
3709
	}
3710
3711
	/**
3712
	 * isTemplateFolder is the given folder the sent folder or at least a subfolder of it
3713
	 * @param string $_folderName folder to perform the check on
3714
	 * @param boolean $_checkexistance trigger check for existance
3715
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3716
	 * @return boolean
3717
	 */
3718 View Code Duplication
	function isTemplateFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3719
	{
3720
		$templateFolder = $this->getTemplateFolder($_checkexistance);
3721
		if(empty($templateFolder)) {
3722
			return false;
3723
		}
3724
		// does the folder exist???
3725
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3726
			return false;
3727
		}
3728
		if ($_exactMatch)
3729
		{
3730
			if(false !== stripos($_folderName, $templateFolder)&& strlen($_folderName)==strlen($templateFolder)) {
3731
				return true;
3732
			} else {
3733
				return false;
3734
			}
3735
		} else {
3736
			if(false !== stripos($_folderName, $templateFolder)) {
3737
				return true;
3738
			} else {
3739
				return false;
3740
			}
3741
		}
3742
	}
3743
3744
	/**
3745
	 * folderExists checks for existance of a given folder
3746
	 * @param string $_folder folder to perform the check on
3747
	 * @param boolean $_forceCheck trigger check for existance on icServer
3748
	 * @return mixed string or false
3749
	 */
3750
	function folderExists($_folder, $_forceCheck=false)
3751
	{
3752
		static $folderInfo;
3753
		$forceCheck = $_forceCheck;
3754
		if (empty($_folder))
3755
		{
3756
			// this error is more or less without significance, unless we force the check
3757
			if ($_forceCheck===true) error_log(__METHOD__.' ('.__LINE__.') '.' Called with empty Folder:'.$_folder.function_backtrace());
3758
			return false;
3759
		}
3760
		// when check is not enforced , we assume a folder represented as Horde_Imap_Client_Mailbox as existing folder
3761
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")&&$_forceCheck===false) return true;
3762
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")) $_folder =  $_folder->utf8;
3763
		// reduce traffic within the Instance per User; Expire every 5 hours
3764
		//error_log(__METHOD__.' ('.__LINE__.') '.' Called with Folder:'.$_folder.function_backtrace());
3765
		if (is_null($folderInfo)) $folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*5);
3766
		//error_log(__METHOD__.' ('.__LINE__.') '.'Cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.($forceCheck?'(forcedCheck)':'').':'.array2string($folderInfo));
3767
		if (!empty($folderInfo) && isset($folderInfo[$this->profileID]) && isset($folderInfo[$this->profileID][$_folder]) && $forceCheck===false)
3768
		{
3769
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID);
3770
			return $folderInfo[$this->profileID][$_folder];
3771
		}
3772
		else
3773
		{
3774
			if ($forceCheck === false)
3775
			{
3776
				//error_log(__METHOD__.' ('.__LINE__.') '.' No cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.' FolderExistsInfoCache:'.array2string($folderInfo[$this->profileID]));
3777
				$forceCheck = true; // try to force the check, in case there is no connection, we may need that
3778
			}
3779
		}
3780
3781
		// does the folder exist???
3782
		//error_log(__METHOD__."->Connected?".$this->icServer->_connected.", ".$_folder.", ".($forceCheck?' forceCheck activated':'dont check on server'));
3783
		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...
3784
			//error_log(__METHOD__."->NotConnected and forceCheck with profile:".$this->profileID);
3785
			//return false;
3786
			//try to connect
3787
		}
3788
		try
3789
		{
3790
			$folderInfo[$this->profileID][$_folder] = $this->icServer->mailboxExist($_folder);
3791
		}
3792
		catch (\Exception $e)
3793
		{
3794
			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...
3795
			self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
3796
			$folderInfo[$this->profileID][$_folder] = false;
3797
		}
3798
		//error_log(__METHOD__.' ('.__LINE__.') '.' Folder Exists:'.$folderInfo[$this->profileID][$_folder].function_backtrace());
3799
3800
		if(!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) &&
3801
			$folderInfo[$this->profileID][$_folder] !== true)
3802
		{
3803
			$folderInfo[$this->profileID][$_folder] = false; // set to false, whatever it was (to have a valid returnvalue for the static return)
3804
		}
3805
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*60*5);
3806
		return (!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) ? $folderInfo[$this->profileID][$_folder] : false);
3807
	}
3808
3809
	/**
3810
	 * remove any messages which are marked as deleted or
3811
	 * remove any messages from the trashfolder
3812
	 *
3813
	 * @param string _folderName the foldername
3814
	 * @return nothing
3815
	 */
3816
	function compressFolder($_folderName = false)
3817
	{
3818
		$folderName	= ($_folderName ? $_folderName : $this->sessionData['mailbox']);
3819
		$deleteOptions	= $GLOBALS['egw_info']['user']['preferences']['mail']['deleteOptions'];
3820
		$trashFolder	= $this->getTrashFolder();
3821
3822
		$this->icServer->openMailbox($folderName);
3823
3824
		if(strtolower($folderName) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") {
3825
			$this->deleteMessages('all',$folderName,'remove_immediately');
3826
		} else {
3827
			$this->icServer->expunge($folderName);
3828
		}
3829
	}
3830
3831
	/**
3832
	 * delete a Message
3833
	 *
3834
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
3835
	 * @param string _folder foldername
3836
	 * @param string _forceDeleteMethod - "no", or deleteMethod like 'move_to_trash',"mark_as_deleted","remove_immediately"
3837
	 *
3838
	 * @return bool true, as we do not handle return values yet
3839
	 * @throws Exception
3840
	 */
3841
	function deleteMessages($_messageUID, $_folder=NULL, $_forceDeleteMethod='no')
3842
	{
3843
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.array2string($_folder).', '.$_forceDeleteMethod);
3844
		$oldMailbox = '';
3845
		if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
3846 View Code Duplication
		if (empty($_messageUID))
3847
		{
3848
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3849
			return false;
3850
		}
3851
		elseif ($_messageUID==='all')
3852
		{
3853
			$_messageUID= null;
3854
		}
3855
		else
3856
		{
3857
			$uidsToDelete = new Horde_Imap_Client_Ids();
3858
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3859
			$uidsToDelete->add($_messageUID);
3860
		}
3861
		$deleteOptions = $_forceDeleteMethod; // use forceDeleteMethod if not "no", or unknown method
3862
		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");
3863
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3864
		$trashFolder    = $this->getTrashFolder();
3865
		$draftFolder	= $this->getDraftFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['draftFolder'];
3866
		$templateFolder = $this->getTemplateFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['templateFolder'];
3867
		if((strtolower($_folder) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") ||
3868
		   (strtolower($_folder) == strtolower($draftFolder))) {
3869
			$deleteOptions = "remove_immediately";
3870
		}
3871
		if($this->icServer->getCurrentMailbox() != $_folder) {
3872
			$oldMailbox = $this->icServer->getCurrentMailbox();
3873
			$this->icServer->openMailbox($_folder);
3874
		}
3875
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3876
		$updateCache = false;
3877
		switch($deleteOptions) {
3878
			case "move_to_trash":
3879
				//error_log(__METHOD__.' ('.__LINE__.') ');
3880
				$updateCache = true;
3881
				if(!empty($trashFolder)) {
3882
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.implode(' : ', $_messageUID));
3883
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$trashFolder <= $_folder / ". $this->sessionData['mailbox']);
3884
					// copy messages
3885
					try
3886
					{
3887
						$this->icServer->copy($_folder, $trashFolder, array('ids'=>$uidsToDelete,'move'=>true));
3888
					}
3889
					catch (\Exception $e)
3890
					{
3891
						throw new Exception("Failed to move Messages (".array2string($uidsToDelete).") from Folder $_folder to $trashFolder Error:".$e->getMessage());
3892
					}
3893
				}
3894
				break;
3895
3896
			case "mark_as_deleted":
3897
				//error_log(__METHOD__.' ('.__LINE__.') ');
3898
				// mark messages as deleted
3899
				if (is_null($_messageUID)) $_messageUID='all';
3900
				foreach((array)$_messageUID as $key =>$uid)
3901
				{
3902
					//flag messages, that are flagged for deletion as seen too
3903
					$this->flagMessages('read', $uid, $_folder);
3904
					$flags = $this->getFlags($uid);
3905
					$this->flagMessages('delete', $uid, $_folder);
3906
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
3907
					if (strpos( array2string($flags),'Deleted')!==false) $undelete[] = $uid;
3908
					unset($flags);
3909
				}
3910
				foreach((array)$undelete as $key =>$uid)
3911
				{
3912
					$this->flagMessages('undelete', $uid, $_folder);
3913
				}
3914
				break;
3915
3916
			case "remove_immediately":
3917
				//error_log(__METHOD__.' ('.__LINE__.') ');
3918
				$updateCache = true;
3919
				if (is_null($_messageUID)) $_messageUID='all';
3920
				if (is_object($_messageUID))
3921
				{
3922
					$this->flagMessages('delete', $_messageUID, $_folder);
3923
				}
3924
				else
3925
				{
3926
					foreach((array)$_messageUID as $key =>$uid)
3927
					{
3928
						//flag messages, that are flagged for deletion as seen too
3929
						$this->flagMessages('delete', $uid, $_folder);
3930
					}
3931
				}
3932
				// delete the messages finaly
3933
				$this->icServer->expunge($_folder);
3934
				break;
3935
		}
3936
		if($oldMailbox != '') {
3937
			$this->icServer->openMailbox($oldMailbox);
3938
		}
3939
3940
		return true;
3941
	}
3942
3943
	/**
3944
	 * get flags for a Message
3945
	 *
3946
	 * @param mixed string _messageUID array of id to retrieve the flags for
3947
	 *
3948
	 * @return null/array flags
3949
	 */
3950
	function getFlags ($_messageUID) {
3951
		try
3952
		{
3953
			$uidsToFetch = new Horde_Imap_Client_Ids();
3954
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3955
			$uidsToFetch->add($_messageUID);
3956
			$_folderName = $this->icServer->getCurrentMailbox();
3957
			$fquery = new Horde_Imap_Client_Fetch_Query();
3958
			$fquery->flags();
3959
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
3960
				'ids' => $uidsToFetch,
3961
			));
3962
			if (is_object($headersNew)) {
3963
				foreach($headersNew->ids() as $id) {
3964
					$_headerObject = $headersNew->get($id);
3965
					$flags = $_headerObject->getFlags();
3966
				}
3967
			}
3968
		}
3969
		catch (\Exception $e)
3970
		{
3971
			error_log(__METHOD__.' ('.__LINE__.') '."Failed to fetch flags for ".array2string($_messageUID)." Error:".$e->getMessage());
3972
			return null;
3973
			//throw new Exception("Failed to fetch flags for ".array2string($_messageUID)" Error:".$e->getMessage());
3974
		}
3975
		return $flags;
3976
	}
3977
3978
	/**
3979
	 * get and parse the flags response for the Notifyflag for a Message
3980
	 *
3981
	 * @param string _messageUID array of id to retrieve the flags for
3982
	 * @param array flags - to avoid additional server call
3983
	 *
3984
	 * @return null/boolean
3985
	 */
3986
	function getNotifyFlags ($_messageUID, $flags=null)
3987
	{
3988
		if (self::$debug) error_log(__METHOD__.$_messageUID.' Flags:'.array2string($flags));
3989
		try
3990
		{
3991
			if($flags===null) $flags =  $this->getFlags($_messageUID);
3992
		}
3993
		catch (\Exception $e)
3994
		{
3995
			return null;
3996
		}
3997
3998
		if ( stripos( array2string($flags),'MDNSent')!==false)
3999
			return true;
4000
4001
		if ( stripos( array2string($flags),'MDNnotSent')!==false)
4002
			return false;
4003
4004
		return null;
4005
	}
4006
4007
	/**
4008
	 * flag a Message
4009
	 *
4010
	 * @param string _flag (readable name)
4011
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
4012
	 * @param string _folder foldername
4013
	 *
4014
	 * @todo handle handle icserver->setFlags returnValue
4015
	 *
4016
	 * @return bool true, as we do not handle icserver->setFlags returnValue
4017
	 */
4018
	function flagMessages($_flag, $_messageUID,$_folder=NULL)
4019
	{
4020
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']);
4021
		if (empty($_messageUID))
4022
		{
4023
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
4024
			return false;
4025
		}
4026
		$this->icServer->openMailbox(($_folder?$_folder:$this->sessionData['mailbox']));
4027
		$folder = $this->icServer->getCurrentMailbox();
4028
		if (is_array($_messageUID)&& count($_messageUID)>50)
4029
		{
4030
			$count = $this->getMailBoxCounters($folder,true);
4031
			if ($count->messages == count($_messageUID)) $_messageUID='all';
4032
		}
4033
4034
		if ($_messageUID==='all')
4035
		{
4036
			$messageUIDs = array('all');
4037
		}
4038
		else
4039
		{
4040
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4041
			$messageUIDs = array_chunk($_messageUID,50,true);
4042
		}
4043
		try
4044
		{
4045
			foreach($messageUIDs as &$uids)
4046
			{
4047
				if ($uids==='all')
4048
				{
4049
					$uidsToModify=null;
4050
				}
4051
				else
4052
				{
4053
					$uidsToModify = new Horde_Imap_Client_Ids();
4054
					$uidsToModify->add($uids);
4055
				}
4056
				switch($_flag) {
4057 View Code Duplication
					case "delete":
4058
						$ret = $this->icServer->store($folder, array('add'=>array('\\Deleted'), 'ids'=> $uidsToModify));
4059
						break;
4060 View Code Duplication
					case "undelete":
4061
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Deleted'), 'ids'=> $uidsToModify));
4062
						break;
4063 View Code Duplication
					case "flagged":
4064
						$ret = $this->icServer->store($folder, array('add'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4065
						break;
4066
					case "read":
4067 View Code Duplication
					case "seen":
4068
						$ret = $this->icServer->store($folder, array('add'=>array('\\Seen'), 'ids'=> $uidsToModify));
4069
						break;
4070 View Code Duplication
					case "forwarded":
4071
						$ret = $this->icServer->store($folder, array('add'=>array('$Forwarded'), 'ids'=> $uidsToModify));
4072 View Code Duplication
					case "answered":
4073
						$ret = $this->icServer->store($folder, array('add'=>array('\\Answered'), 'ids'=> $uidsToModify));
4074
						break;
4075 View Code Duplication
					case "unflagged":
4076
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4077
						break;
4078
					case "unread":
4079 View Code Duplication
					case "unseen":
4080
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Seen','\\Answered','$Forwarded'), 'ids'=> $uidsToModify));
4081
						break;
4082 View Code Duplication
					case "mdnsent":
4083
						$ret = $this->icServer->store($folder, array('add'=>array('MDNSent'), 'ids'=> $uidsToModify));
4084
						break;
4085 View Code Duplication
					case "mdnnotsent":
4086
						$ret = $this->icServer->store($folder, array('add'=>array('MDNnotSent'), 'ids'=> $uidsToModify));
4087
						break;
4088
					case "label1":
4089 View Code Duplication
					case "labelone":
4090
						$ret = $this->icServer->store($folder, array('add'=>array('$label1'), 'ids'=> $uidsToModify));
4091
						break;
4092
					case "unlabel1":
4093 View Code Duplication
					case "unlabelone":
4094
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4095
						break;
4096
					case "label2":
4097 View Code Duplication
					case "labeltwo":
4098
						$ret = $this->icServer->store($folder, array('add'=>array('$label2'), 'ids'=> $uidsToModify));
4099
						break;
4100
					case "unlabel2":
4101 View Code Duplication
					case "unlabeltwo":
4102
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4103
						break;
4104
					case "label3":
4105 View Code Duplication
					case "labelthree":
4106
						$ret = $this->icServer->store($folder, array('add'=>array('$label3'), 'ids'=> $uidsToModify));
4107
						break;
4108
					case "unlabel3":
4109 View Code Duplication
					case "unlabelthree":
4110
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4111
						break;
4112
					case "label4":
4113 View Code Duplication
					case "labelfour":
4114
						$ret = $this->icServer->store($folder, array('add'=>array('$label4'), 'ids'=> $uidsToModify));
4115
						break;
4116
					case "unlabel4":
4117 View Code Duplication
					case "unlabelfour":
4118
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4119
						break;
4120
					case "label5":
4121 View Code Duplication
					case "labelfive":
4122
						$ret = $this->icServer->store($folder, array('add'=>array('$label5'), 'ids'=> $uidsToModify));
4123
						break;
4124
					case "unlabel5":
4125 View Code Duplication
					case "unlabelfive":
4126
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4127
						break;
4128
					case "unlabel":
4129
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4130
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4131
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4132
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4133
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4134
						break;
4135
				}
4136
			}
4137
		}
4138
		catch(Exception $e)
4139
		{
4140
			error_log(__METHOD__.__LINE__.' Error, could not flag messages in folder '.$folder.' Reason:'.$e->getMessage());
4141
		}
4142
		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...
4143
		//error_log(__METHOD__.__LINE__.'#'.$this->icServer->ImapServerId.'#'.array2string($_folder).'#');
4144
		self::$folderStatusCache[$this->icServer->ImapServerId][(!empty($_folder)?$_folder: $this->sessionData['mailbox'])]['uidValidity'] = 0;
4145
4146
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
4147
		return true; // as we do not catch/examine setFlags returnValue
4148
	}
4149
4150
	/**
4151
	 * move Message(s)
4152
	 *
4153
	 * @param string _foldername target folder
4154
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
4155
	 * @param boolean $deleteAfterMove - decides if a mail is moved (true) or copied (false)
4156
	 * @param string $currentFolder
4157
	 * @param boolean $returnUIDs - control wether or not the action called should return the new uids
4158
	 *						caveat: not all servers do support that
4159
	 * @param int $_sourceProfileID - source profile ID, should be handed over, if not $this->icServer->ImapServerId is used
4160
	 * @param int $_targetProfileID - target profile ID, should only be handed over when target server is different from source
4161
	 *
4162
	 * @return mixed/bool true,false or new uid
4163
	 * @throws Exception
4164
	 */
4165
	function moveMessages($_foldername, $_messageUID, $deleteAfterMove=true, $currentFolder = Null, $returnUIDs = false, $_sourceProfileID = Null, $_targetProfileID = Null)
4166
	{
4167
		$source = Mail\Account::read(($_sourceProfileID?$_sourceProfileID:$this->icServer->ImapServerId))->imapServer();
4168
		//$deleteOptions  = $GLOBALS['egw_info']["user"]["preferences"]["mail"]["deleteOptions"];
4169 View Code Duplication
		if (empty($_messageUID))
4170
		{
4171
			if (self::$debug) error_log(__METHOD__." no Message(s): ".implode(',',$_messageUID));
4172
			return false;
4173
		}
4174
		elseif ($_messageUID==='all')
4175
		{
4176
			//error_log(__METHOD__." all Message(s): ".implode(',',$_messageUID));
4177
			$uidsToMove= null;
4178
		}
4179
		else
4180
		{
4181
			//error_log(__METHOD__." Message(s): ".implode(',',$_messageUID));
4182
			$uidsToMove = new Horde_Imap_Client_Ids();
4183
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4184
			$uidsToMove->add($_messageUID);
4185
		}
4186
		$sourceFolder = (!empty($currentFolder)?$currentFolder: $this->sessionData['mailbox']);
4187
		//error_log(__METHOD__.__LINE__."$_targetProfileID !== ".array2string($source->ImapServerId));
4188
		if (!is_null($_targetProfileID) && $_targetProfileID !== $source->ImapServerId)
4189
		{
4190
			$sourceFolder = $source->getMailbox($sourceFolder);
4191
			$source->openMailbox($sourceFolder);
4192
			$uidsToFetch = new Horde_Imap_Client_Ids();
4193
			$uidsToFetch->add($_messageUID);
4194
			$fquery = new Horde_Imap_Client_Fetch_Query();
4195
			$fquery->flags();
4196
			$fquery->headerText(array('peek'=>true));
4197
			$fquery->fullText(array('peek'=>true));
4198
			$fquery->imapDate();
4199
			$headersNew = $source->fetch($sourceFolder, $fquery, array(
4200
				'ids' => $uidsToFetch,
4201
			));
4202
4203
			//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' mailheaders:'.array2string($headersNew));
4204
4205
			if (is_object($headersNew)) {
4206
				$c=0;
4207
				$retUid = new Horde_Imap_Client_Ids();
4208
				// we copy chunks of 5 to avoid too much memory and/or server stress
4209
				// some servers seem not to allow/support the appendig of multiple messages. so we are down to one
4210
				foreach($headersNew as &$_headerObject) {
4211
					$c++;
4212
					$flags = $_headerObject->getFlags(); //unseen status seems to be lost when retrieving the full message
4213
					$date = $_headerObject->getImapDate();
4214
					$currentDate =  new Horde_Imap_Client_DateTime();
4215
					// if the internal Date of the message equals the current date; try using the header date
4216
					if ($date==$currentDate)
4217
					{
4218
						$headerForPrio = array_change_key_case($_headerObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray(), CASE_UPPER);
4219
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#'.$headerForPrio['DATE']);
4220
						$date = new Horde_Imap_Client_DateTime($headerForPrio['DATE']);
4221
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#');
4222
					}
4223
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject)));
4224
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
4225
					$body = $_headerObject->getFullMsg();
4226
					$dataNflags[] = array('data'=>$body, 'flags'=>$flags, 'internaldate'=>$date);
4227
					if ($c==1)
4228
					{
4229
						$target = Mail\Account::read($_targetProfileID)->imapServer();
4230
						//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($_foldername));
4231
						$foldername = $target->getMailbox($_foldername);
4232
						// make sure the target folder is open and ready
4233
						$target->openMailbox($foldername);
4234
						$ret = $target->append($foldername,$dataNflags);
4235
						$retUid->add($ret);
4236
						unset($dataNflags);
4237
						// sleep 500 miliseconds; AS some sERVERs seem not to be capable of the load this is
4238
						// inflicting in them. they "reply" with an unspecific IMAP Error
4239
						time_nanosleep(0,500000);
4240
						$c=0;
4241
					}
4242
				}
4243
				if (isset($dataNflags))
4244
				{
4245
					$target = Mail\Account::read($_targetProfileID)->imapServer();
4246
					//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($foldername));
4247
					$foldername = $target->getMailbox($_foldername);
4248
					// make sure the target folder is open and ready
4249
					$target->openMailbox($foldername);
4250
					$ret = $target->append($foldername,$dataNflags);
4251
					$retUid->add($ret);
4252
					unset($dataNflags);
4253
				}
4254
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4255
				// make sure we are back to source
4256
				$source->openMailbox($sourceFolder);
4257
				if ($deleteAfterMove)
4258
				{
4259
					$remember = $this->icServer;
4260
					$this->icServer = $source;
4261
					$this->deleteMessages($_messageUID, $sourceFolder, $_forceDeleteMethod='remove_immediately');
4262
					$this->icServer = $remember;
4263
				}
4264
			}
4265
		}
4266
		else
4267
		{
4268
			try
4269
			{
4270
				$retUid = $source->copy($sourceFolder, $_foldername, array('ids'=>$uidsToMove,'move'=>$deleteAfterMove));
4271
			}
4272
			catch (exception $e)
4273
			{
4274
				error_log(__METHOD__.' ('.__LINE__.') '."Copying to Folder $_foldername failed! Error:".$e->getMessage());
4275
				throw new Exception("Copying to Folder $_foldername failed! Error:".$e->getMessage());
4276
			}
4277
		}
4278
4279
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4280
		return ($returnUIDs ? $retUid : true);
4281
	}
4282
4283
	/**
4284
	 * Parse dates, also handle wrong or unrecognized timezones, falling back to current time
4285
	 *
4286
	 * @param string $_date to be parsed/formatted
4287
	 * @param string $format ='' if none is passed, use user prefs
4288
	 * @return string returns the date as it is parseable by strtotime, or current timestamp if everything fails
4289
	 */
4290
	static function _strtotime($_date='', $format='', $convert2usertime=false)
4291
	{
4292
		try {
4293
			$date = new DateTime($_date);	// parse date & time including timezone (throws exception, if not parsable)
4294
			if ($convert2usertime) $date->setUser();	// convert to user-time
4295
			$date2return = $date->format($format);
4296
		}
4297
		catch(\Exception $e)
4298
		{
4299
			unset($e);	// not used
4300
4301
			// remove last space-separated part and retry
4302
			$parts = explode(' ',$_date);
4303
			// try only 10 times to prevent of causing error by reaching
4304
			// maximum function nesting level.
4305
			if (count($parts) > 1 && count($parts)<10)
4306
			{
4307
				array_pop($parts);
4308
				$date2return = self::_strtotime(implode(' ', $parts), $format, $convert2usertime);
4309
			}
4310
			else	// not last part, use current time
4311
			{
4312
				$date2return = DateTime::to('now', $format);
4313
			}
4314
		}
4315
		return $date2return;
4316
	}
4317
4318
	/**
4319
	 * htmlentities
4320
	 * helperfunction to cope with wrong encoding in strings
4321
	 * @param string $_string  input to be converted
4322
	 * @param mixed $_charset false or string -> Target charset, if false Mail displayCharset will be used
4323
	 * @return string
4324
	 */
4325
	static function htmlentities($_string, $_charset=false)
4326
	{
4327
		//setting the charset (if not given)
4328
		if ($_charset===false) $_charset = self::$displayCharset;
4329
		$string = @htmlentities($_string, ENT_QUOTES, $_charset, false);
4330
		if (empty($string) && !empty($_string)) $string = @htmlentities(Translation::convert($_string,Translation::detect_encoding($_string),$_charset),ENT_QUOTES | ENT_IGNORE,$_charset, false);
4331
		return $string;
4332
	}
4333
4334
	/**
4335
	 * clean a message from elements regarded as potentially harmful
4336
	 * param string/reference $_html is the text to be processed
4337
	 * return nothing
4338
	 */
4339
	static function getCleanHTML(&$_html)
4340
	{
4341
		// remove CRLF and TAB as it is of no use in HTML.
4342
		// but they matter in <pre>, so we rather don't
4343
		//$_html = str_replace("\r\n",' ',$_html);
4344
		//$_html = str_replace("\t",' ',$_html);
4345
		//error_log(__METHOD__.__LINE__.':'.$_html);
4346
		//repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on
4347
		$_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>'),
4348
							 array('&amp;',    '<BR>',           '<BR>',             '<BR>',             '</font></td>','<td>',    '',         '',           '',  ''),$_html);
4349
		//$_html = str_replace(array('&amp;amp;'),array('&amp;'),$_html);
4350
		if (stripos($_html,'style')!==false) Mail\Html::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags
4351
		if (stripos($_html,'head')!==false) Mail\Html::replaceTagsCompletley($_html,'head'); // Strip out stuff in head
4352
		//if (stripos($_html,'![if')!==false && stripos($_html,'<![endif]>')!==false) Mail\Html::replaceTagsCompletley($_html,'!\[if','<!\[endif\]>',false); // Strip out stuff in ifs
4353
		//if (stripos($_html,'!--[if')!==false && stripos($_html,'<![endif]-->')!==false) Mail\Html::replaceTagsCompletley($_html,'!--\[if','<!\[endif\]-->',false); // Strip out stuff in ifs
4354
		//error_log(__METHOD__.' ('.__LINE__.') '.$_html);
4355
4356
		if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html);
4357
		// Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards
4358
		if (stripos($_html,'!doctype')!==false) Mail\Html::replaceTagsCompletley($_html,'!doctype');
4359
		if (stripos($_html,'?xml:namespace')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml:namespace','/>',false);
4360
		if (stripos($_html,'?xml version')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml version','\?>',false);
4361
		if (strpos($_html,'!CURSOR')!==false) Mail\Html::replaceTagsCompletley($_html,'!CURSOR');
4362
		// htmLawed filter only the 'body'
4363
		//preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $_html, $matches);
4364
		//if ($matches[2])
4365
		//{
4366
		//	$hasOther = true;
4367
		//	$_html = $matches[2];
4368
		//}
4369
		// purify got switched to htmLawed
4370
		// some testcode to test purifying / htmlawed
4371
		//$_html = "<BLOCKQUOTE>hi <div> there </div> kram <br> </blockquote>".$_html;
4372
		$_html = Html\HtmLawed::purify($_html,self::$htmLawed_config,array(),true);
4373
		//if ($hasOther) $_html = $matches[1]. $_html. $matches[3];
4374
		// clean out comments , should not be needed as purify should do the job.
4375
		$search = array(
4376
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
4377
			'@<!--[\s\S]*?[ \t\n\r]*-->@',         // Strip multi-line comments including CDATA
4378
		);
4379
		$_html = preg_replace($search,"",$_html);
4380
		// remove non printable chars
4381
		$_html = preg_replace('/([\000-\011])/','',$_html);
4382
		//error_log(__METHOD__.':'.__LINE__.':'.$_html);
4383
	}
4384
4385
	/**
4386
	 * Header and Bodystructure stuff
4387
	 */
4388
4389
	/**
4390
	 * getMimePartCharset - fetches the charset mimepart if it exists
4391
	 * @param $_mimePartObject structure object
4392
	 * @return mixed mimepart or false if no CHARSET is found, the missing charset has to be handled somewhere else,
4393
	 *		as we cannot safely assume any charset as we did earlier
4394
	 */
4395
	function getMimePartCharset($_mimePartObject)
4396
	{
4397
		//$charSet = 'iso-8859-1';//self::$displayCharset; //'iso-8859-1'; // self::displayCharset seems to be asmarter fallback than iso-8859-1
4398
		$CharsetFound=false;
4399
		//echo "#".$_mimePartObject->encoding.'#<br>';
4400
		if(is_array($_mimePartObject->parameters)) {
4401
			if(isset($_mimePartObject->parameters['CHARSET'])) {
4402
				$charSet = $_mimePartObject->parameters['CHARSET'];
4403
				$CharsetFound=true;
4404
			}
4405
		}
4406
		// this one is dirty, but until I find something that does the trick of detecting the encoding, ....
4407
		//if ($CharsetFound == false && $_mimePartObject->encoding == "QUOTED-PRINTABLE") $charSet = 'iso-8859-1'; //assume quoted-printable to be ISO
4408
		//if ($CharsetFound == false && $_mimePartObject->encoding == "BASE64") $charSet = 'utf-8'; // assume BASE64 to be UTF8
4409
		return ($CharsetFound ? $charSet : $CharsetFound);
4410
	}
4411
4412
	/**
4413
	 * decodeMimePart - fetches the charset mimepart if it exists
4414
	 * @param string $_mimeMessage - the message to be decoded
4415
	 * @param string $_encoding - the encoding used BASE64 and QUOTED-PRINTABLE is supported
4416
	 * @param string $_charset - not used
4417
	 * @return string decoded mimePart
4418
	 */
4419
	function decodeMimePart($_mimeMessage, $_encoding, $_charset = '')
4420
	{
4421
		// decode the part
4422
		if (self::$debug) error_log(__METHOD__."() with $_encoding and $_charset:".print_r($_mimeMessage,true));
4423
		switch (strtoupper($_encoding))
4424
		{
4425
			case 'BASE64':
4426
				// use imap_base64 to decode, not any longer, as it is strict, and fails if it encounters invalid chars
4427
				return base64_decode($_mimeMessage);
4428
4429
			case 'QUOTED-PRINTABLE':
4430
				// use imap_qprint to decode
4431
				return quoted_printable_decode($_mimeMessage);
4432
4433
			case 'WEDONTKNOWTHEENCODING':
4434
				// try base64
4435
				$r = base64_decode($_mimeMessage);
4436
				if (json_encode($r))
4437
				{
4438
					return $r;
4439
				}
4440
				//we do not know the encoding, so we do not decode
4441
			default:
4442
				// it is either not encoded or we don't know about it
4443
				return $_mimeMessage;
4444
		}
4445
	}
4446
4447
	/**
4448
	 * get part of the message, if its stucture is indicating its of multipart alternative style
4449
	 * a wrapper for multipartmixed
4450
	 * @param string/int $_uid the messageuid,
4451
	 * @param Horde_Mime_Part $_structure structure for parsing
4452
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4453
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4454
	 * @param Horde_Mime_Part& $partCalendar =null on return text/calendar part, if one was contained or false
4455
	 * @return array containing the desired part
4456
	 */
4457
	function getMultipartAlternative($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$partCalendar=null)
4458
	{
4459
		// a multipart/alternative has exactly 2 parts (text and html  OR  text and something else)
4460
		// sometimes there are 3 parts, when there is an ics/ical attached/included-> we want to show that
4461
		// as attachment AND as abstracted ical information (we use our notification style here).
4462
		$partText = $partCalendar = $partHTML = null;
4463
		if (self::$debug) _debug_array(array("METHOD"=>__METHOD__,"LINE"=>__LINE__,"STRUCTURE"=>$_structure));
4464
		//error_log(__METHOD__.' ('.__LINE__.') ');
4465
		$ignore_first_part = true;
4466
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4467
		{
4468
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type"." ignoreFirstPart:".$ignore_first_part);
4469
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4470
4471
			if ($ignore_first_part)
4472
			{
4473
				$ignore_first_part = false;
4474
				continue;	// ignore multipart/alternative itself
4475
			}
4476
4477
			$mimePart = $_structure->getPart($mime_id);
4478
4479
			switch($mimePart->getPrimaryType())
4480
			{
4481
				case 'text':
4482
					switch($mimePart->getSubType())
4483
					{
4484
						case 'plain':
4485
							if ($mimePart->getBytes() > 0) $partText = $mimePart;
4486
							break;
4487
4488
						case 'html':
4489
							if ($mimePart->getBytes() > 0)  $partHTML = $mimePart;
4490
							break;
4491
4492
						case 'calendar':
4493
							if ($mimePart->getBytes() > 0)  $partCalendar = $mimePart;
4494
							break;
4495
					}
4496
					break;
4497
4498
				case 'multipart':
4499
					switch($mimePart->getSubType())
4500
					{
4501
						case 'related':
4502
						case 'mixed':
4503
							if (count($mimePart->getParts()) > 1)
4504
							{
4505
								// in a multipart alternative we treat the multipart/related as html part
4506
								if (self::$debug) error_log(__METHOD__." process MULTIPART/".$mimePart->getSubType()." with array as subparts");
4507
								$partHTML = $mimePart;
4508
								break 3; // GET OUT OF LOOP, will be processed according to type
4509
							}
4510
							break;
4511
						case 'alternative':
4512
							if (count($mimePart->getParts()) > 1)
4513
							{
4514
								//cascading multipartAlternative structure, assuming only the first one is to be used
4515
								return $this->getMultipartAlternative($_uid, $mimePart, $_htmlMode, $_preserveSeen);
4516
							}
4517
					}
4518
			}
4519
		}
4520
4521
		switch($_htmlMode)
4522
		{
4523
			case 'html_only':
4524
			case 'always_display':
4525
				if ($partHTML)
4526
				{
4527
					switch($partHTML->getSubType())
4528
					{
4529
						case 'related':
4530
							return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4531
4532
						case 'mixed':
4533
							return $this->getMultipartMixed($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4534
4535
						default:
4536
							return $this->getTextPart($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4537
					}
4538
				}
4539
				elseif ($partText && $_htmlMode=='always_display')
4540
				{
4541
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4542
				}
4543
				break;
4544
4545
			case 'only_if_no_text':
4546
				if ($partText)
4547
				{
4548
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4549
				}
4550
				if ($partHTML)
4551
				{
4552
					if ($partHTML->getPrimaryType())
4553
					{
4554
						return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4555
					}
4556
					return $this->getTextPart($_uid, $partHTML, 'always_display', $_preserveSeen);
4557
				}
4558
				break;
4559
4560
			default:
4561
				if ($partText)
4562
				{
4563
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4564
				}
4565
				$bodyPart = array(
4566
					'body'		=> lang("no plain text part found"),
4567
					'mimeType'	=> 'text/plain',
4568
					'charSet'	=> self::$displayCharset,
4569
				);
4570
				break;
4571
		}
4572
		return $bodyPart;
4573
	}
4574
4575
	/**
4576
	 * Get part of the message, if its stucture is indicating its of multipart mixed style
4577
	 *
4578
	 * @param int $_uid the messageuid,
4579
	 * @param Horde_Mime_Part $_structure = '' if given use structure for parsing
4580
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4581
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4582
	 * @param array	&$skipParts - passed by reference to have control/knowledge which parts are already fetched
4583
	 * @return array containing the desired part
4584
	 */
4585
	function getMultipartMixed($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$skipParts=array())
4586
	{
4587
		if (self::$debug) echo __METHOD__."$_uid, $_htmlMode<br>";
4588
		$bodyPart = array();
4589
		if (self::$debug) _debug_array($_structure);
4590
4591
		$ignore_first_part = true;
4592
		//$skipParts = array();
4593
		//error_log(__METHOD__.__LINE__.array2string($_structure->contentTypeMap()));
4594
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4595
		{
4596
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type");
4597
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4598
			if ($ignore_first_part)
4599
			{
4600
				$ignore_first_part = false;
4601
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED FirstPart $mime_id: $mime_type");
4602
				continue;	// ignore multipart/mixed itself
4603
			}
4604
			if (array_key_exists($mime_id,$skipParts))
4605
			{
4606
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED $mime_id: $mime_type");
4607
				continue;
4608
			}
4609
4610
			$part = $_structure->getPart($mime_id);
4611
4612
			switch($part->getPrimaryType())
4613
			{
4614
				case 'multipart':
4615
					if ($part->getDisposition() == 'attachment') continue;
4616
					switch($part->getSubType())
4617
					{
4618
						case 'alternative':
4619
							return array($this->getMultipartAlternative($_uid, $part, $_htmlMode, $_preserveSeen));
4620
4621
						case 'mixed':
4622
						case 'signed':
4623
							$bodyPart = array_merge($bodyPart, $this->getMultipartMixed($_uid, $part, $_htmlMode, $_preserveSeen, $skipParts));
4624
							break;
4625
4626
						case 'related':
4627
							$bodyPart = array_merge($bodyPart, $this->getMultipartRelated($_uid, $part, $_htmlMode, $_preserveSeen));
4628
							break;
4629
					}
4630
					break;
4631
				case 'application':
4632
					switch($part->getSubType())
4633
					{
4634
						case 'pgp-encrypted':
4635
							if (($part = $_structure->getPart($mime_id+1)) &&
4636
								$part->getType() == 'application/octet-stream')
4637
							{
4638
								$this->fetchPartContents($_uid, $part);
4639
								$skipParts[$mime_id]=$mime_type;
4640
								$skipParts[$mime_id+1]=$part->getType();
4641
								$bodyPart[] = array(
4642
									'body'		=> $part->getContents(array(
4643
										'stream' => false,
4644
									)),
4645
									'mimeType'  => 'text/plain',
4646
									'charSet'	=> $_structure->getCharset(),
4647
								);
4648
							}
4649
							break;
4650
					}
4651
					break;
4652
4653
				case 'text':
4654
					switch($part->getSubType())
4655
					{
4656
						case 'plain':
4657
						case 'html':
4658
						case 'calendar': // inline ics/ical files
4659
							if($part->getDisposition() != 'attachment')
4660
							{
4661
								$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4662
								$skipParts[$mime_id]=$mime_type;
4663
							}
4664
							//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$part->type."/".$part->subType.' -> BodyPart:'.array2string($bodyPart[count($bodyPart)-1]));
4665
							break;
4666
					}
4667
					break;
4668
4669
				case 'message':
4670
					//skip attachments
4671
					if($part->getSubType() == 'delivery-status' && $part->getDisposition() != 'attachment')
4672
					{
4673
						$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4674
						$skipParts[$mime_id]=$mime_type;
4675
					}
4676
					// do not descend into attached Messages
4677
					if($part->getSubType() == 'rfc822' || $part->getDisposition() == 'attachment')
4678
					{
4679
						$skipParts[$mime_id.'.0'] = $mime_type;
4680
						foreach($part->contentTypeMap() as $sub_id => $sub_type){ $skipParts[$sub_id] = $sub_type;}
4681
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$_uid.' Part:'.$mime_id.':'.array2string($skipParts));
4682
						//break 2;
4683
					}
4684
					break;
4685
4686
				default:
4687
					// do nothing
4688
					// the part is a attachment
4689
			}
4690
		}
4691
		return $bodyPart;
4692
	}
4693
4694
	/**
4695
	 * getMultipartRelated
4696
	 * get part of the message, if its stucture is indicating its of multipart related style
4697
	 * a wrapper for multipartmixed
4698
	 * @param string/int $_uid the messageuid,
4699
	 * @param Horde_Mime_Part $_structure if given use structure for parsing
4700
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4701
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4702
	 * @return array containing the desired part
4703
	 */
4704
	function getMultipartRelated($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false)
4705
	{
4706
		return $this->getMultipartMixed($_uid, $_structure, $_htmlMode, $_preserveSeen);
4707
	}
4708
4709
	/**
4710
	 * Fetch a body part
4711
	 *
4712
	 * @param int $_uid
4713
	 * @param string $_partID = null
4714
	 * @param string $_folder = null
4715
	 * @param boolean $_preserveSeen = false
4716
	 * @param boolean $_stream = false true return a stream, false return string
4717
	 * @param string &$_encoding = null on return: transfer encoding of returned part
4718
	 * @param boolean $_tryDecodingServerside = true; wether to try to fetch Data with BINARY instead of BODY
4719
	 * @return string|resource
4720
	 */
4721
	function getBodyPart($_uid, $_partID=null, $_folder=null, $_preserveSeen=false, $_stream=false, &$_encoding=null, $_tryDecodingServerside=true)
4722
	{
4723
		if (self::$debug) error_log( __METHOD__.__LINE__."(".array2string($_uid).", $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, $_tryDecodingServerside)");
4724
4725 View Code Duplication
		if (empty($_folder))
4726
		{
4727
			$_folder = (isset($this->sessionData['mailbox'])&&$this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
4728
		}
4729
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_folder).'/'.$this->icServer->getCurrentMailbox().'/'. $this->sessionData['mailbox']);
4730
		// querying contents of body part
4731
		$uidsToFetch = new Horde_Imap_Client_Ids();
4732
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
4733
		$uidsToFetch->add($_uid);
4734
4735
		$fquery = new Horde_Imap_Client_Fetch_Query();
4736
		$fetchParams = array(
4737
			'peek' => $_preserveSeen,
4738
			'decode' => true,	// try decode on server, does NOT neccessary work
4739
		);
4740
		if ($_tryDecodingServerside===false)// || ($_tryDecodingServerside&&$this->isDraftFolder($_folder)))
4741
		{
4742
			$_tryDecodingServerside=false;
4743
			$fetchParams = array(
4744
				'peek' => $_preserveSeen,
4745
			);
4746
		}
4747
		$fquery->bodyPart($_partID, $fetchParams);
4748
4749
		$part = $this->icServer->fetch($_folder, $fquery, array(
4750
			'ids' => $uidsToFetch,
4751
		))->first();
4752
		$partToReturn = null;
4753
		if ($part)
4754
		{
4755
			$_encoding = $part->getBodyPartDecode($_partID);
4756
			//error_log(__METHOD__.__LINE__.':'.$_encoding.'#');
4757
			$partToReturn = $part->getBodyPart($_partID, $_stream);
4758
			//error_log(__METHOD__.__LINE__.':'.$partToReturn.'#');
4759
		}
4760
		// if we get an empty result, server may have trouble fetching data with UID FETCH $_uid (BINARY.PEEK[$_partID])
4761
		// thus we trigger a second go with UID FETCH $_uid (BODY.PEEK[$_partID])
4762
		if (empty($partToReturn)&&$_tryDecodingServerside===true)
4763
		{
4764
			error_log(__METHOD__.__LINE__.' failed to fetch bodyPart in  BINARY. Try BODY');
4765
			$partToReturn = $this->getBodyPart($_uid, $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, false);
4766
		}
4767
		return ($partToReturn?$partToReturn:null);
4768
	}
4769
4770
	/**
4771
	 * Get Body from message
4772
	 *
4773
	 * @param int $_uid the messageuid
4774
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4775
	 * @param string $_htmlMode how to display a message: 'html_only', 'always_display', 'only_if_no_text' or ''
4776
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4777
	 * @param boolean $_stream = false true return a stream, false return string
4778
	 * @return array containing the desired text part, mimeType and charset
4779
	 */
4780
	function getTextPart($_uid, Horde_Mime_Part $_structure, $_htmlMode='', $_preserveSeen=false, $_stream=false)
4781
	{
4782
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_uid.':'.array2string($_structure).' '.function_backtrace());
4783
		$bodyPart = array();
4784
		if (self::$debug) _debug_array(array($_structure,function_backtrace()));
4785
4786
		if($_structure->getSubType() == 'html' && !in_array($_htmlMode, array('html_only', 'always_display', 'only_if_no_text')))
4787
		{
4788
			$bodyPart = array(
4789
				'error'		=> 1,
4790
				'body'		=> lang("displaying html messages is disabled"),
4791
				'mimeType'	=> 'text/html',
4792
				'charSet'	=> self::$displayCharset,
4793
			);
4794
		}
4795
		elseif ($_structure->getSubType() == 'plain' && $_htmlMode == 'html_only')
4796
		{
4797
			$bodyPart = array(
4798
				'error'		=> 1,
4799
				'body'      => lang("displaying plain messages is disabled"),
4800
				'mimeType'  => 'text/plain', // make sure we do not return mimeType text/html
4801
				'charSet'   => self::$displayCharset,
4802
			);
4803
		}
4804
		else
4805
		{
4806
			// some Servers append PropertyFile___ ; strip that here for display
4807
			// RB: not sure what this is: preg_replace('/PropertyFile___$/','',$this->decodeMimePart($mimePartBody, $_structure->encoding, $this->getMimePartCharset($_structure))),
4808
			$this->fetchPartContents($_uid, $_structure, $_stream, $_preserveSeen);
4809
4810
			$bodyPart = array(
4811
				'body'		=> $_structure->getContents(array(
4812
					'stream' => $_stream,
4813
				)),
4814
				'mimeType'  => $_structure->getType() == 'text/html' ? 'text/html' : 'text/plain',
4815
				'charSet'	=> $_structure->getCharset(),
4816
			);
4817
		}
4818
		return $bodyPart;
4819
	}
4820
4821
	/**
4822
	 * Get Body of message
4823
	 *
4824
	 * @param int $_uid the messageuid,
4825
	 * @param string $_htmlOptions how to display a message, html, plain text, ...
4826
	 * @param string $_partID = null the partID, may be omitted
4827
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4828
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4829
	 * @param string $_folder folder to work on
4830
	 * @param Horde_Mime_part& $calendar_part =null on return calendar-part or null, if there is none
4831
	 * @return array containing the message body, mimeType and charset
4832
	 */
4833
	function getMessageBody($_uid, $_htmlOptions='', $_partID=null, Horde_Mime_Part $_structure=null, $_preserveSeen = false, $_folder = '', &$calendar_part=null)
4834
	{
4835
		if (self::$debug) echo __METHOD__."$_uid, $_htmlOptions, $_partID<br>";
4836
		if($_htmlOptions != '') {
4837
			$this->htmlOptions = $_htmlOptions;
4838
		}
4839
		if (empty($_folder))
4840
		{
4841
			$_folder = $this->sessionData['mailbox'];
4842
		}
4843
		if (empty($this->sessionData['mailbox']) && !empty($_folder))
4844
		{
4845
			$this->sessionData['mailbox'] = $_folder;
4846
		}
4847
4848
		if (!isset($_structure))
4849
		{
4850
			$_structure = $this->getStructure($_uid, $_partID, $_folder, $_preserveSeen);
4851
		}
4852
		if (!is_object($_structure))
4853
		{
4854
			return array(
4855
				array(
4856
					'error'		=> 1,
4857
					'body'		=> 'Error: Could not fetch structure on mail:'.$_uid." as $_htmlOptions". 'for Mailprofile'.$this->icServer->ImapServerId.' User:'.$GLOBALS['egw_info']['user']['account_lid'],
4858
					'mimeType'	=> 'text/plain',
4859
					'charSet'	=> self::$displayCharset,
4860
				)
4861
			);
4862
		}
4863
		if (!empty($_partID))
4864
		{
4865
			$_structure->contentTypeMap();
4866
			$_structure = $_structure->getPart($_partID);
4867
			//_debug_array($_structure->getMimeId()); exit;
4868
		}
4869
4870
		switch($_structure->getPrimaryType())
4871
		{
4872
			case 'application':
4873
				return array(
4874
					array(
4875
						'body'		=> '',
4876
						'mimeType'	=> 'text/plain',
4877
						'charSet'	=> 'iso-8859-1',
4878
					)
4879
				);
4880
4881
			case 'multipart':
4882
				switch($_structure->getSubType())
4883
				{
4884
					case 'alternative':
4885
						$bodyParts = array($this->getMultipartAlternative($_uid, $_structure, $this->htmlOptions, $_preserveSeen, $calendar_part));
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...
4886
						break;
4887
4888
					case 'nil': // multipart with no Alternative
4889
					case 'mixed':
4890
					case 'report':
4891
					case 'signed':
4892
					case 'encrypted':
4893
						$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...
4894
						break;
4895
4896
					case 'related':
4897
						$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...
4898
						break;
4899
				}
4900
				return self::normalizeBodyParts($bodyParts);
4901
4902
			case 'video':
4903
			case 'audio': // some servers send audiofiles and imagesfiles directly, without any stuff surround it
4904
			case 'image': // they are displayed as Attachment NOT INLINE
4905
				return array(
4906
					array(
4907
						'body'      => '',
4908
						'mimeType'  => $_structure->getSubType(),
4909
					),
4910
				);
4911
4912
			case 'text':
4913
				$bodyPart = array();
4914
				if ($_structure->getDisposition() != 'attachment')
4915
				{
4916
					switch($_structure->getSubType())
4917
					{
4918
						case 'calendar':
4919
							// this is handeled in getTextPart
4920
						case 'html':
4921
						case 'plain':
4922
						default:
4923
							$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...
4924
					}
4925
				} 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...
4926
					// what if the structure->disposition is attachment ,...
4927
				}
4928
				return self::normalizeBodyParts($bodyPart);
4929
4930
			case 'attachment':
4931
			case 'message':
4932
				switch($_structure->getSubType())
4933
				{
4934
					case 'rfc822':
4935
						$newStructure = $_structure->getParts();
4936
						if (self::$debug) {echo __METHOD__." Message -> RFC -> NewStructure:"; _debug_array($newStructure[0]);}
4937
						return self::normalizeBodyParts($this->getMessageBody($_uid, $_htmlOptions, $newStructure[0]->getMimeId(), $newStructure[0], $_preserveSeen, $_folder));
4938
				}
4939
				break;
4940
4941
			default:
4942
				if (self::$debug) _debug_array($_structure);
4943
				return array(
4944
					array(
4945
						'body'		=> lang('The mimeparser can not parse this message.').$_structure->getType(),
4946
						'mimeType'	=> 'text/plain',
4947
						'charSet'	=> self::$displayCharset,
4948
					)
4949
				);
4950
		}
4951
	}
4952
4953
	/**
4954
	 * normalizeBodyParts - function to gather and normalize all body Information
4955
	 * as we may recieve a bodyParts structure from within getMessageBody nested deeper than expected
4956
	 * so this is used to normalize the output, so we are able to rely on our expectation
4957
	 * @param _bodyParts - Body Array
4958
	 * @return array - a normalized Bodyarray
4959
	 */
4960
	static function normalizeBodyParts($_bodyParts)
4961
	{
4962
		if (is_array($_bodyParts))
4963
		{
4964
			foreach($_bodyParts as $singleBodyPart)
4965
			{
4966
				if (!isset($singleBodyPart['body'])) {
4967
					$buff = self::normalizeBodyParts($singleBodyPart);
4968
					foreach ((array)$buff as $val) { $body2return[] = $val;}
4969
					continue;
4970
				}
4971
				$body2return[] = $singleBodyPart;
4972
			}
4973
		}
4974
		else
4975
		{
4976
			$body2return = $_bodyParts;
4977
		}
4978
		return $body2return;
4979
	}
4980
4981
	/**
4982
	 * getdisplayableBody - creates the bodypart of the email as textual representation
4983
	 * @param object $mailClass the mailClass object to be used
4984
	 * @param array $bodyParts  with the bodyparts
4985
	 * @param boolean $preserveHTML  switch to preserve HTML
4986
	 * @param boolean $useTidy  switch to use tidy
4987
	 * @return string a preformatted string with the mails converted to text
4988
	 */
4989
	static function &getdisplayableBody(&$mailClass, $bodyParts, $preserveHTML = false,  $useTidy = true)
4990
	{
4991
		$message='';
4992
		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...
4993
		{
4994
			if (!isset($bodyParts[$i]['body'])) {
4995
				$bodyParts[$i]['body'] = self::getdisplayableBody($mailClass, $bodyParts[$i], $preserveHTML, $useTidy);
4996
				$message .= empty($bodyParts[$i]['body'])?'':$bodyParts[$i]['body'];
4997
				continue;
4998
			}
4999
			if (isset($bodyParts[$i]['error'])) continue;
5000
			if (empty($bodyParts[$i]['body'])) continue;
5001
			// some characterreplacements, as they fail to translate
5002
			$sar = array(
5003
				'@(\x84|\x93|\x94)@',
5004
				'@(\x96|\x97|\x1a)@',
5005
				'@(\x82|\x91|\x92)@',
5006
				'@(\x85)@',
5007
				'@(\x86)@',
5008
				'@(\x99)@',
5009
				'@(\xae)@',
5010
			);
5011
			$rar = array(
5012
				'"',
5013
				'-',
5014
				'\'',
5015
				'...',
5016
				'&',
5017
				'(TM)',
5018
				'(R)',
5019
			);
5020
5021
			if(($bodyParts[$i]['mimeType'] == 'text/html' || $bodyParts[$i]['mimeType'] == 'text/plain') &&
5022
				strtoupper($bodyParts[$i]['charSet']) != 'UTF-8')
5023
			{
5024
				$bodyParts[$i]['body'] = preg_replace($sar,$rar,$bodyParts[$i]['body']);
5025
			}
5026
5027 View Code Duplication
			if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Translation::detect_encoding($bodyParts[$i]['body']);
5028
			// add line breaks to $bodyParts
5029
			//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
5030
			$newBody  = Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
5031
			//error_log(__METHOD__.' ('.__LINE__.') '.' MimeType:'.$bodyParts[$i]['mimeType'].'->'.$newBody);
5032
			$mailClass->activeMimeType = 'text/plain';
5033
			if ($bodyParts[$i]['mimeType'] == 'text/html') {
5034
				$mailClass->activeMimeType = $bodyParts[$i]['mimeType'];
5035
				if (!$preserveHTML)
5036
				{
5037
					$alreadyHtmlLawed=false;
5038
					// as Translation::convert reduces \r\n to \n and purifier eats \n -> peplace it with a single space
5039
					$newBody = str_replace("\n"," ",$newBody);
5040
					// convert HTML to text, as we dont want HTML in infologs
5041 View Code Duplication
					if ($useTidy && extension_loaded('tidy'))
5042
					{
5043
						$tidy = new tidy();
5044
						$cleaned = $tidy->repairString($newBody, self::$tidy_config,'utf8');
5045
						// Found errors. Strip it all so there's some output
5046
						if($tidy->getStatus() == 2)
5047
						{
5048
							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...
5049
						}
5050
						else
5051
						{
5052
							$newBody = $cleaned;
5053
						}
5054
						if (!$preserveHTML)
5055
						{
5056
							// filter only the 'body', as we only want that part, if we throw away the html
5057
							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...
5058
							if ($matches[2])
5059
							{
5060
								$hasOther = true;
5061
								$newBody = $matches[2];
5062
							}
5063
						}
5064
					}
5065
					else
5066
					{
5067
						// htmLawed filter only the 'body'
5068
						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...
5069
						if ($matches[2])
5070
						{
5071
							$hasOther = true;
5072
							$newBody = $matches[2];
5073
						}
5074
						$htmLawed = new Html\HtmLawed();
5075
						// the next line should not be needed, but produces better results on HTML 2 Text conversion,
5076
						// as we switched off HTMLaweds tidy functionality
5077
						$newBody = str_replace(array('&amp;amp;','<DIV><BR></DIV>',"<DIV>&nbsp;</DIV>",'<div>&nbsp;</div>'),array('&amp;','<BR>','<BR>','<BR>'),$newBody);
5078
						$newBody = $htmLawed->run($newBody,self::$htmLawed_config);
5079
						if ($hasOther && $preserveHTML) $newBody = $matches[1]. $newBody. $matches[3];
5080
						$alreadyHtmlLawed=true;
5081
					}
5082
					//error_log(__METHOD__.' ('.__LINE__.') '.' after purify:'.$newBody);
5083
					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...
5084
					//error_log(__METHOD__.' ('.__LINE__.') '.' after convertHTMLToText:'.$newBody);
5085
					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...
5086
					/*if (!$alreadyHtmlLawed) */ $mailClass->getCleanHTML($newBody); // remove stuff we regard as unwanted
5087
					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...
5088
					//error_log(__METHOD__.' ('.__LINE__.') '.' after getClean:'.$newBody);
5089
				}
5090
				$message .= $newBody;
5091
				continue;
5092
			}
5093
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after specialchars):'.$newBody);
5094
			//use Mail\Html::convertHTMLToText instead of strip_tags, (even message is plain text) as strip_tags eats away too much
5095
			//$newBody = strip_tags($newBody); //we need to fix broken tags (or just stuff like "<800 USD/p" )
5096
			$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...
5097
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after strip tags):'.$newBody);
5098
			$newBody = htmlspecialchars_decode($newBody,ENT_QUOTES);
5099
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body (after hmlspc_decode):'.$newBody);
5100
			$message .= $newBody;
5101
			//continue;
5102
		}
5103
		return $message;
5104
	}
5105
5106
	static function wordwrap($str, $cols, $cut, $dontbreaklinesstartingwith=false)
5107
	{
5108
		$lines = explode("\n", $str);
5109
		$newStr = '';
5110
		foreach($lines as $line)
5111
		{
5112
			// replace tabs by 8 space chars, or any tab only counts one char
5113
			//$line = str_replace("\t","        ",$line);
5114
			//$newStr .= wordwrap($line, $cols, $cut);
5115
			$allowedLength = $cols-strlen($cut);
5116
			//dont try to break lines with links, chance is we mess up the text is way too big
5117
			if (strlen($line) > $allowedLength && stripos($line,'href=')===false &&
5118
				($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...
5119
				 ($dontbreaklinesstartingwith &&
5120
				  strlen($dontbreaklinesstartingwith)>=1 &&
5121
				  substr($line,0,strlen($dontbreaklinesstartingwith)) != $dontbreaklinesstartingwith
5122
				 )
5123
				)
5124
			   )
5125
			{
5126
				$s=explode(" ", $line);
5127
				$line = "";
5128
				$linecnt = 0;
5129
				foreach ($s as &$v) {
5130
					$cnt = strlen($v);
5131
					// only break long words within the wordboundaries,
5132
					// but it may destroy links, so we check for href and dont do it if we find one
5133
					// we check for any html within the word, because we do not want to break html by accident
5134
					if($cnt > $allowedLength && stripos($v,'href=')===false && stripos($v,'onclick=')===false && $cnt == strlen(html_entity_decode($v)))
5135
					{
5136
						$v=wordwrap($v, $allowedLength, $cut, true);
5137
					}
5138
					// the rest should be broken at the start of the new word that exceeds the limit
5139
					if ($linecnt+$cnt > $allowedLength) {
5140
						$v=$cut.$v;
5141
						#$linecnt = 0;
5142
						$linecnt =strlen($v)-strlen($cut);
5143
					} else {
5144
						$linecnt += $cnt;
5145
					}
5146
					if (strlen($v)) $line .= (strlen($line) ? " " : "").$v;
5147
				}
5148
			}
5149
			$newStr .= $line . "\n";
5150
		}
5151
		return $newStr;
5152
	}
5153
5154
	/**
5155
	 * getMessageEnvelope
5156
	 * get parsed headers from message
5157
	 * @param string/int $_uid the messageuid,
5158
	 * @param string/int $_partID = '' , the partID, may be omitted
5159
	 * @param boolean $decode flag to do the decoding on the fly
5160
	 * @param string $_folder folder to work on
5161
	 * @param boolean $_useHeaderInsteadOfEnvelope - force getMessageHeader method to be used for fetching Envelope Information
5162
	 * @return array the message header
5163
	 */
5164
	function getMessageEnvelope($_uid, $_partID = '',$decode=false, $_folder='', $_useHeaderInsteadOfEnvelope=false)
5165
	{
5166
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder".function_backtrace());
5167 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5168
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder");
5169
		if((empty($_partID)||$_partID=='null')&&$_useHeaderInsteadOfEnvelope===false) {
5170
			$uidsToFetch = new Horde_Imap_Client_Ids();
5171
			if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5172
			$uidsToFetch->add($_uid);
5173
5174
			$fquery = new Horde_Imap_Client_Fetch_Query();
5175
			$envFields = new Horde_Mime_Headers();
5176
			$fquery->envelope();
5177
			$fquery->size();
5178
			$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5179
				'ids' => $uidsToFetch,
5180
			));
5181
			if (is_object($headersNew)) {
5182
				foreach($headersNew as &$_headerObject) {
5183
					$env = $_headerObject->getEnvelope();
5184
					//_debug_array($envFields->singleFields());
5185
					$singleFields = $envFields->singleFields();
5186
					foreach ($singleFields as &$v)
5187
					{
5188
						switch ($v)
5189
						{
5190
							case 'to':
5191
							case 'reply-to':
5192
							case 'from':
5193
							case 'cc':
5194
							case 'bcc':
5195
							case 'sender':
5196
								//error_log(__METHOD__.' ('.__LINE__.') '.$v.'->'.array2string($env->$v->addresses));
5197
								$envelope[$v]=$env->$v->addresses;
5198
								$address = array();
5199
								if (!is_array($envelope[$v])) break;
5200
								foreach ($envelope[$v] as $k => $ad)
5201
								{
5202
									if (stripos($ad,'@')===false)
5203
									{
5204
										$remember=$k;
5205
									}
5206
									else
5207
									{
5208
										$address[] = (!is_null($remember)?$envelope[$v][$remember].' ':'').$ad;
5209
										$remember=null;
5210
									}
5211
								}
5212
								$envelope[$v] = $address;
5213
								break;
5214
							case 'date':
5215
								$envelope[$v]=DateTime::to($env->$v);
5216
								break;
5217
							default:
5218
								$envelope[$v]=$env->$v;
5219
						}
5220
					}
5221
					$envelope['size']=$_headerObject->getSize();
5222
				}
5223
			}
5224
			$envelope = array_change_key_case($envelope,CASE_UPPER);
5225
			//if ($decode) _debug_array($envelope);
5226
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($envelope));
5227 View Code Duplication
			if ($decode)
5228
			{
5229
				foreach ($envelope as $key => $rvV)
5230
				{
5231
					//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5232
					$envelope[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5233
				}
5234
			}
5235
			return $envelope;
5236
		} else {
5237
5238
			$headers = $this->getMessageHeader($_uid, $_partID, true,true,$_folder);
5239
5240
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($headers));
5241
			//_debug_array($headers);
5242
			$newData = array(
5243
				'DATE'		=> $headers['DATE'],
5244
				'SUBJECT'	=> ($decode ? self::decode_header($headers['SUBJECT']):$headers['SUBJECT']),
5245
				'MESSAGE_ID'	=> $headers['MESSAGE-ID']
5246
			);
5247
			if (isset($headers['IN-REPLY-TO'])) $newData['IN-REPLY-TO'] = $headers['IN-REPLY-TO'];
5248
			if (isset($headers['REFERENCES'])) $newData['REFERENCES'] = $headers['REFERENCES'];
5249
			if (isset($headers['THREAD-TOPIC'])) $newData['THREAD-TOPIC'] = $headers['THREAD-TOPIC'];
5250
			if (isset($headers['THREAD-INDEX'])) $newData['THREAD-INDEX'] = $headers['THREAD-INDEX'];
5251
			if (isset($headers['LIST-ID'])) $newData['LIST-ID'] = $headers['LIST-ID'];
5252
			if (isset($headers['SIZE'])) $newData['SIZE'] = $headers['SIZE'];
5253
			//_debug_array($newData);
5254
			$recepientList = array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO');
5255
			foreach($recepientList as $recepientType) {
5256
				if(isset($headers[$recepientType])) {
5257
					if ($decode) $headers[$recepientType] =  self::decode_header($headers[$recepientType],true);
5258
					//error_log(__METHOD__.__LINE__." ".$recepientType."->".array2string($headers[$recepientType]));
5259
					foreach(self::parseAddressList($headers[$recepientType]) as $singleAddress) {
5260
						$addressData = array(
5261
							'PERSONAL_NAME'		=> $singleAddress->personal ? $singleAddress->personal : 'NIL',
5262
							'AT_DOMAIN_LIST'	=> $singleAddress->adl ? $singleAddress->adl : 'NIL',
5263
							'MAILBOX_NAME'		=> $singleAddress->mailbox ? $singleAddress->mailbox : 'NIL',
5264
							'HOST_NAME'		=> $singleAddress->host ? $singleAddress->host : 'NIL',
5265
							'EMAIL'			=> $singleAddress->host ? $singleAddress->mailbox.'@'.$singleAddress->host : $singleAddress->mailbox,
5266
						);
5267
						if($addressData['PERSONAL_NAME'] != 'NIL') {
5268
							$addressData['RFC822_EMAIL'] = imap_rfc822_write_address($singleAddress->mailbox, $singleAddress->host, $singleAddress->personal);
5269
						} else {
5270
							$addressData['RFC822_EMAIL'] = 'NIL';
5271
						}
5272
						$newData[$recepientType][] = ($addressData['RFC822_EMAIL']!='NIL'?$addressData['RFC822_EMAIL']:$addressData['EMAIL']);//$addressData;
5273
					}
5274
				} else {
5275
					if($recepientType == 'SENDER' || $recepientType == 'REPLY-TO') {
5276
						$newData[$recepientType] = $newData['FROM'];
5277
					} else {
5278
						$newData[$recepientType] = array();
5279
					}
5280
				}
5281
			}
5282
			//if ($decode) _debug_array($newData);
5283
			return $newData;
5284
		}
5285
	}
5286
5287
	/**
5288
	 * Get parsed headers from message
5289
	 *
5290
	 * @param string/int $_uid the messageuid,
5291
	 * @param string/int $_partID ='' , the partID, may be omitted
5292
	 * @param boolean|string $decode flag to do the decoding on the fly or "object"
5293
	 * @param boolean $preserveUnSeen flag to preserve the seen flag where applicable
5294
	 * @param string $_folder folder to work on
5295
	 * @return array|Horde_Mime_Headers message header as array or object
5296
	 */
5297
	function getMessageHeader($_uid, $_partID = '',$decode=false, $preserveUnSeen=false, $_folder='')
5298
	{
5299
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$_uid.', '.$_partID.', '.$decode.', '.$preserveUnSeen.', '.$_folder);
5300 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5301
		$uidsToFetch = new Horde_Imap_Client_Ids();
5302
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5303
		$uidsToFetch->add($_uid);
5304
5305
		$fquery = new Horde_Imap_Client_Fetch_Query();
5306
		if ($_partID != '')
5307
		{
5308
			$fquery->headerText(array('id'=>$_partID,'peek'=>$preserveUnSeen));
5309
			$fquery->structure();
5310
		}
5311
		else
5312
		{
5313
			$fquery->headerText(array('peek'=>$preserveUnSeen));
5314
		}
5315
		$fquery->size();
5316
5317
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5318
			'ids' => $uidsToFetch,
5319
		));
5320
		if (is_object($headersNew)) {
5321
			foreach($headersNew as $_fetchObject)
5322
			{
5323
				$headers = $_fetchObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5324
				if ($_partID != '')
5325
				{
5326
					$mailStructureObject = $_fetchObject->getStructure();
5327
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5328
					{
5329
						if ($mime_id==$_partID)
5330
						{
5331
							//error_log(__METHOD__.' ('.__LINE__.') '."$mime_id == $_partID".array2string($_headerObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray()));
5332
							$headers = $_fetchObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5333
							break;
5334
						}
5335
					}
5336
				}
5337
				$size = $_fetchObject->getSize();
5338
				//error_log(__METHOD__.__LINE__.'#'.$size);
5339
			}
5340
			if ($decode === 'object')
5341
			{
5342
				if (is_object($headers)) $headers->setUserAgent('EGroupware API '.$GLOBALS['egw_info']['server']['versions']['phpgwapi']);
5343
				return $headers;
5344
			}
5345
			$retValue = is_object($headers) ? $headers->toArray():array();
5346
			if ($size) $retValue['size'] = $size;
5347
		}
5348
		$retValue = array_change_key_case($retValue,CASE_UPPER);
5349
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue));
5350
		// if SUBJECT is an array, use thelast one, as we assume something with the unfolding for the subject did not work
5351
		if (is_array($retValue['SUBJECT']))
5352
		{
5353
			$retValue['SUBJECT'] = $retValue['SUBJECT'][count($retValue['SUBJECT'])-1];
5354
		}
5355
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($decode ? self::decode_header($retValue,true):$retValue));
5356 View Code Duplication
		if ($decode)
5357
		{
5358
			foreach ($retValue as $key => $rvV)
5359
			{
5360
				//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5361
				$retValue[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5362
			}
5363
		}
5364
		return $retValue;
5365
	}
5366
5367
	/**
5368
	 * getMessageRawHeader
5369
	 * get messages raw header data
5370
	 * @param string/int $_uid the messageuid,
5371
	 * @param string/int $_partID = '' , the partID, may be omitted
5372
	 * @param string $_folder folder to work on
5373
	 * @return string the message header
5374
	 */
5375
	function getMessageRawHeader($_uid, $_partID = '', $_folder = '')
5376
	{
5377
		static $rawHeaders;
5378 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5379
		//error_log(__METHOD__.' ('.__LINE__.') '." Try Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5380
5381 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);
5382 View Code Duplication
		if (isset($rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5383
		{
5384
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5385
			return $rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5386
		}
5387
		$uidsToFetch = new Horde_Imap_Client_Ids();
5388
		$uid = $_uid;
5389
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5390
		$uidsToFetch->add($uid);
5391
5392
		$fquery = new Horde_Imap_Client_Fetch_Query();
5393
		if ($_partID != '')
5394
		{
5395
			$fquery->headerText(array('id'=>$_partID,'peek'=>true));
5396
			$fquery->structure();
5397
		}
5398
		else
5399
		{
5400
			$fquery->headerText(array('peek'=>true));
5401
		}
5402
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5403
			'ids' => $uidsToFetch,
5404
		));
5405
		if (is_object($headersNew)) {
5406 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5407
				$retValue = $_headerObject->getHeaderText();
5408
				if ($_partID != '')
5409
				{
5410
					$mailStructureObject = $_headerObject->getStructure();
5411
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5412
					{
5413
						if ($mime_id==$_partID)
5414
						{
5415
							$retValue = $_headerObject->getHeaderText($mime_id);
5416
						}
5417
					}
5418
				}
5419
			}
5420
		}
5421
		$rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]=$retValue;
5422
		Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($GLOBALS['egw_info']['user']['account_id']),$rawHeaders,60*60*1);
5423
		return $retValue;
5424
	}
5425
5426
	/**
5427
	 * getStyles - extracts the styles from the given bodyparts
5428
	 * @param array $_bodyParts  with the bodyparts
5429
	 * @return string a preformatted string with the mails converted to text
5430
	 */
5431
	static function &getStyles($_bodyParts)
5432
	{
5433
		$style = '';
5434
		if (empty($_bodyParts)) return "";
5435
		foreach((array)$_bodyParts as $singleBodyPart) {
5436
			if (!isset($singleBodyPart['body'])) {
5437
				$singleBodyPart['body'] = self::getStyles($singleBodyPart);
5438
				$style .= $singleBodyPart['body'];
5439
				continue;
5440
			}
5441
5442
			if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = Translation::detect_encoding($singleBodyPart['body']);
5443
			$singleBodyPart['body'] = Translation::convert(
5444
				$singleBodyPart['body'],
5445
				strtolower($singleBodyPart['charSet'])
5446
			);
5447
			$ct = 0;
5448
			$newStyle=array();
5449
			if (stripos($singleBodyPart['body'],'<style')!==false)  $ct = preg_match_all('#<style(?:\s.*)?>(.+)</style>#isU', $singleBodyPart['body'], $newStyle);
5450
			if ($ct>0)
5451
			{
5452
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$ct.'#'.array2string($newStyle));
5453
				$style2buffer = implode('',$newStyle[0]);
5454
			}
5455
			if ($style2buffer && strtoupper(self::$displayCharset) == 'UTF-8')
5456
			{
5457
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($style2buffer));
5458
				$test = json_encode($style2buffer);
5459
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$test.'# ->'.strlen($style2buffer).' Error:'.json_last_error());
5460
				//if (json_last_error() != JSON_ERROR_NONE && strlen($style2buffer)>0)
5461
				if ($test=="null" && strlen($style2buffer)>0)
5462
				{
5463
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
5464
					error_log(__METHOD__.' ('.__LINE__.') '.' Found Invalid sequence for utf-8 in CSS:'.$style2buffer.' Charset Reported:'.$singleBodyPart['charSet'].' Carset Detected:'.Translation::detect_encoding($style2buffer));
5465
					$style2buffer = utf8_encode($style2buffer);
5466
				}
5467
			}
5468
			$style .= $style2buffer;
5469
		}
5470
		// clean out comments and stuff
5471
		$search = array(
5472
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
5473
//			'@<!--[\s\S]*?[ \t\n\r]*-->@',   // Strip multi-line comments including CDATA
5474
//			'@<!--[\s\S]*?[ \t\n\r]*--@',    // Strip broken multi-line comments including CDATA
5475
		);
5476
		$style = preg_replace($search,"",$style);
5477
5478
		// CSS Security
5479
		// http://code.google.com/p/browsersec/wiki/Part1#Cascading_stylesheets
5480
		$css = preg_replace('/(javascript|expression|-moz-binding)/i','',$style);
5481
		if (stripos($css,'script')!==false) Mail\Html::replaceTagsCompletley($css,'script'); // Strip out script that may be included
5482
		// 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
5483
		// as the comments as <!-- styledefinition --> in stylesheet are outdated, and ck-editor does not understand it, we remove it
5484
		$css = str_replace(array(':','<!--','-->'),array(': ','',''),$css);
5485
		//error_log(__METHOD__.' ('.__LINE__.') '.$css);
5486
		// TODO: we may have to strip urls and maybe comments and ifs
5487
		return $css;
5488
	}
5489
5490
	/**
5491
	 * getMessageRawBody
5492
	 * get the message raw body
5493
	 * @param string/int $_uid the messageuid,
5494
	 * @param string/int $_partID = '' , the partID, may be omitted
5495
	 * @param string $_folder folder to work on
5496
	 * @param boolean $_stream =false true: return a stream, false: return string, stream suppresses any caching
5497
	 * @return string the message body
5498
	 */
5499
	function getMessageRawBody($_uid, $_partID = '', $_folder='', $_stream=false)
5500
	{
5501
		//TODO: caching einbauen static!
5502
		static $rawBody;
5503
		if (is_null($rawBody)) $rawBody = array();
5504 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5505 View Code Duplication
		if (!$_stream && isset($rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5506
		{
5507
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder");
5508
			return $rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5509
		}
5510
5511
		$uidsToFetch = new Horde_Imap_Client_Ids();
5512
		$uid = $_uid;
5513
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5514
		$uidsToFetch->add($uid);
5515
5516
		$fquery = new Horde_Imap_Client_Fetch_Query();
5517
		$fquery->fullText(array('peek'=>true));
5518
		if ($_partID != '')
5519
		{
5520
			$fquery->structure();
5521
			$fquery->bodyPart($_partID,array('peek'=>true));
5522
		}
5523
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5524
			'ids' => $uidsToFetch,
5525
		));
5526
		if (is_object($headersNew)) {
5527 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5528
				$body = $_headerObject->getFullMsg($_stream);
5529
				if ($_partID != '')
5530
				{
5531
					$mailStructureObject = $_headerObject->getStructure();
5532
					//_debug_array($mailStructureObject->contentTypeMap());
5533
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5534
					{
5535
						if ($mime_id==$_partID)
5536
						{
5537
							$body = $_headerObject->getBodyPart($mime_id, $_stream);
5538
						}
5539
					}
5540
				}
5541
			}
5542
		}
5543
		//error_log(__METHOD__.' ('.__LINE__.') '."[$this->icServer->ImapServerId][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]");
5544
		if (!$_stream)
5545
		{
5546
			$rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)] = $body;
5547
		}
5548
		return $body;
5549
	}
5550
5551
	/**
5552
	 * Get structure of a mail or part of a mail
5553
	 *
5554
	 * @param int $_uid
5555
	 * @param string $_partID = null
5556
	 * @param string $_folder = null
5557
	 * @param boolean $_preserveSeen = false flag to preserve the seenflag by using body.peek
5558
	 * @param Horde_Imap_Client_Fetch_Query $fquery=null default query just structure
5559
	 * @return Horde_Mime_Part
5560
	 */
5561
	function getStructure($_uid, $_partID=null, $_folder=null, $_preserveSeen=false)
5562
	{
5563
		if (self::$debug) error_log( __METHOD__.' ('.__LINE__.') '.":$_uid, $_partID");
5564
5565 View Code Duplication
		if (empty($_folder))
5566
		{
5567
			$_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5568
		}
5569
		$uidsToFetch = new Horde_Imap_Client_Ids();
5570
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5571
		$uidsToFetch->add($_uid);
5572
		try
5573
		{
5574
			$_fquery = new Horde_Imap_Client_Fetch_Query();
5575
	// not sure why Klaus add these, seem not necessary
5576
	//		$fquery->envelope();
5577
	//		$fquery->size();
5578
			$_fquery->structure();
5579
			if ($_partID) $_fquery->bodyPart($_partID, array('peek' => $_preserveSeen));
5580
5581
			$mail = $this->icServer->fetch($_folder, $_fquery, array(
5582
				'ids' => $uidsToFetch,
5583
			))->first();
5584
5585
			return is_object($mail)?$mail->getStructure():null;
5586
		}
5587
		catch (\Exception $e)
5588
		{
5589
			error_log(__METHOD__.' ('.__LINE__.') '.' Could not fetch structure on mail:'.$_uid.' Serverprofile->'.$this->icServer->ImapServerId.' Message:'.$e->getMessage().' Stack:'.function_backtrace());
5590
			return null;
5591
		}
5592
	}
5593
5594
	/**
5595
	 * Parse the structure for attachments
5596
	 *
5597
	 * Returns not the attachments itself, but an array of information about the attachment
5598
	 *
5599
	 * @param int $_uid the messageuid,
5600
	 * @param string $_partID = null , the partID, may be omitted
5601
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
5602
	 * @param boolean $fetchEmbeddedImages = true,
5603
	 * @param boolean $fetchTextCalendar = false,
5604
	 * @param boolean $resolveTNEF = true
5605
	 * @param string $_folder folder to work on
5606
	 * @return array  an array of information about the attachment: array of array(name, size, mimeType, partID, encoding)
5607
	 */
5608
	function getMessageAttachments($_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folder='')
5609
	{
5610
		if (self::$debug) error_log( __METHOD__.":$_uid, $_partID");
5611 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5612
		$attachments = array();
5613
		if (!isset($_structure))
5614
		{
5615
			$_structure = $this->getStructure($_uid, $_partID,$_folder,true);
5616
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.print_r($_structure->contentTypeMap(),true));
5617
		}
5618
		if (!$_structure || !$_structure->contentTypeMap()) return array();
5619
		if (!empty($_partID)) $_structure = $_structure->getPart($_partID);
5620
		$skipParts = array();
5621
		$tnefParts = array();
5622
		$skip = 0;
5623
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
5624
		{
5625
			// skip multipart/encrypted incl. its two sub-parts, as we show 2. sub-part as body to be decrypted client-side
5626
			if ($mime_type == 'multipart/encrypted')
5627
			{
5628
				$skip = 2;
5629
				continue;
5630
			}
5631
			elseif($skip)
5632
			{
5633
				$skip--;
5634
				continue;
5635
			}
5636
			$part = $_structure->getPart($mime_id);
5637
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getMimeId()));
5638
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.$part->getPrimaryType().'/'.$part->getSubType().'->'.$part->getDisposition());
5639
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllDispositionParameters()));
5640
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllContentTypeParameters()));
5641
			$partDisposition = $part->getDisposition();
5642
			$partPrimaryType = $part->getPrimaryType();
5643
			// we only want to retrieve the attachments of the current mail, not those of possible
5644
			// attached mails
5645 View Code Duplication
			if ($mime_type=='message/rfc822' && $_partID!=$mime_id)
5646
			{
5647
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
5648
				foreach($part->contentTypeMap() as $sub_id => $sub_type) {if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
5649
			}
5650
			if (empty($partDisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
5651
			{
5652
				// the absence of an partDisposition does not necessarily indicate there is no attachment. it may be an
5653
				// attachment with no link to show the attachment inline.
5654
				// Considering this: we "list" everything that matches the above criteria
5655
				// as attachment in order to not loose/miss information on our data
5656
				$partDisposition='attachment';
5657
			}
5658
			//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($skipParts));
5659
			if (array_key_exists($mime_id,$skipParts)) continue;
5660
5661
			if ($partDisposition == 'attachment' ||
5662
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image' && $part->getContentId()=='') ||
5663
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType != 'image' && $partPrimaryType != 'text' && $partPrimaryType != 'multipart') ||
5664
				($mime_type=='image/tiff') || //always fetch. even if $fetchEmbeddedImages is false. as we cannot display tiffs
5665
				($fetchEmbeddedImages && ($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image') ||
5666
				($fetchTextCalendar && $partPrimaryType == 'text' && $part->getSubType() == 'calendar'))
5667
			{
5668
				// if type is message/rfc822 and _partID is given, and MimeID equals partID
5669
				// we attempt to fetch "ourselves"
5670
				if ($_partID==$part->getMimeId() && $part->getPrimaryType()=='message') continue;
5671
				$attachment = $part->getAllDispositionParameters();
5672
				$attachment['disposition'] = $part->getDisposition();
5673
				$attachment['mimeType'] = $mime_type;
5674
				$attachment['uid'] = $_uid;
5675
				$attachment['partID'] = $mime_id;
5676 View Code Duplication
				if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5677
				if ($fetchTextCalendar)
5678
				{
5679
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part->getAllContentTypeParameters()));
5680
					$method = $part->getContentTypeParameter('method');
5681
					if ($method) $attachment['method'] = $method;
5682
					if (!isset($attachment['name'])) $attachment['name'] = 'event.ics';
5683
				}
5684
				$attachment['size'] = $part->getBytes();
5685
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5686 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);
5687
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($attachment));
5688
				//typical winmail.dat attachment is
5689
				//Array([size] => 1462762[filename] => winmail.dat[mimeType] => application/ms-tnef[uid] => 100[partID] => 2[name] => winmail.dat)
5690
				if ($resolveTNEF && ($attachment['mimeType']=='application/ms-tnef' || !strcasecmp($attachment['name'],'winmail.dat')))
5691
				{
5692
					$tnefParts[] = $attachment;
5693
				}
5694
				else
5695
				{
5696
					$attachments[] = $attachment;
5697
				}
5698
			}
5699
		}
5700
		if ($resolveTNEF && !empty($tnefParts))
5701
		{
5702
			//error_log(__METHOD__.__LINE__.array2string($tnefParts));
5703
			foreach ($tnefParts as $k => $tnp)
5704
			{
5705
				$tnefResolved=false;
5706
				$tnef_data = $this->getAttachment($tnp['uid'],$tnp['partID'],$k,false);
5707
				$myTnef = $this->tnef_decoder($tnef_data['attachment']);
5708
				//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5709
				// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5710
				// we need to build Something that meets the needs
5711
				if ($myTnef)
5712
				{
5713
					foreach($myTnef->getParts() as $mime_id => $part)
5714
					{
5715
						$tnefResolved=true;
5716
						$attachment = $part->getAllDispositionParameters();
5717
						$attachment['disposition'] = $part->getDisposition();
5718
						$attachment['mimeType'] = $part->getType();
5719
						$attachment['uid'] = $tnp['uid'];
5720
						$attachment['partID'] = $tnp['partID'];
5721
						$attachment['is_winmail'] = $tnp['uid'].'@'.$tnp['partID'].'@'.$mime_id;
5722 View Code Duplication
						if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5723
						$attachment['size'] = $part->getBytes();
5724
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5725 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']);
5726
						$attachments[] = $attachment;
5727
					}
5728
				}
5729
				if ($tnefResolved===false) $attachments[]=$tnp;
5730
			}
5731
		}
5732
		//error_log(__METHOD__.__LINE__.array2string($attachments));
5733
		return $attachments;
5734
	}
5735
5736
	/**
5737
	 * Decode TNEF type attachment into Multipart/mixed attachment
5738
	 *
5739
	 * @param MIME object $data Mime part object
5740
	 *
5741
	 * @return boolean|Horde_Mime_part Multipart/Mixed part decoded attachments |
5742
	 *	return false if there's no attachments or failure
5743
	 */
5744
	public function tnef_decoder( $data )
5745
	{
5746
		foreach(array('Horde_Compress', 'Horde_Icalendar', 'Horde_Mapi') as $class)
5747
		{
5748
			if (!class_exists($class))
5749
			{
5750
				error_log(__METHOD__."() missing required PEAR package $class --> aborting");
5751
				return false;
5752
			}
5753
		}
5754
		$parts_obj = new Horde_Mime_part;
5755
		$parts_obj->setType('multipart/mixed');
5756
5757
		$tnef_object = Horde_Compress::factory('tnef');
5758
		try
5759
		{
5760
			$tnef_data = $tnef_object->decompress($data);
5761
		}
5762
		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...
5763
		{
5764
			error_log(__METHOD__."() ".$ex->getMessage().' --> aborting');
5765
			_egw_log_exception($ex);
5766
			return false;
5767
		}
5768
		if (is_array($tnef_data))
5769
		{
5770
			foreach ($tnef_data as &$data)
5771
			{
5772
				$tmp_part = new Horde_Mime_part;
5773
5774
				$tmp_part->setName($data['name']);
5775
				$tmp_part->setContents($data['stream']);
5776
				$tmp_part->setDescription($data['name']);
5777
5778
				$type = $data['type'] . '/' . $data['subtype'];
5779
				if (in_array($type, array('application/octet-stream', 'application/base64')))
5780
				{
5781
					$type = Horde_Mime_Magic::filenameToMIME($data['name']);
5782
				}
5783
				$tmp_part->setType($type);
5784
				//error_log(__METHOD__.__LINE__.array2string($tmp_part));
5785
				$parts_obj->addPart($tmp_part);
5786
			}
5787
			$parts_obj->buildMimeIds();
5788
			return $parts_obj;
5789
		}
5790
		return false;
5791
	}
5792
5793
	/**
5794
	 * Get attachment data as string, to be used with Link::(get|set)_data()
5795
	 *
5796
	 * @param int $acc_id
5797
	 * @param string $_mailbox
5798
	 * @param int $_uid
5799
	 * @param string $_partID
5800
	 * @param int $_winmail_nr
5801
	 * @return resource stream with attachment content
5802
	 */
5803
	public static function getAttachmentAccount($acc_id, $_mailbox, $_uid, $_partID, $_winmail_nr)
5804
	{
5805
		$bo = self::getInstance(false, $acc_id);
5806
5807
		$attachment = $bo->getAttachment($_uid, $_partID, $_winmail_nr, false, true, $_mailbox);
5808
5809
		return $attachment['attachment'];
5810
	}
5811
5812
	/**
5813
	 * Retrieve tnef attachments
5814
	 *
5815
	 * @param int $_uid the uid of the message
5816
	 * @param string $_partID the id of the part, which holds the attachment
5817
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5818
	 *
5819
	 * @return array returns an array of all resolved embeded attachments from winmail.dat
5820
	 */
5821
	function getTnefAttachments ($_uid, $_partID, $_stream=false)
5822
	{
5823
		$tnef_data = $this->getAttachment($_uid, $_partID,0,false);
5824
		$tnef_parts = $this->tnef_decoder($tnef_data['attachment']);
5825
		$attachments = array();
5826
		if ($tnef_parts)
5827
		{
5828
			foreach($tnef_parts->getParts() as $mime_id => $part)
5829
			{
5830
5831
				$attachment = $part->getAllDispositionParameters();
5832
				$attachment['mimeType'] = $part->getType();
5833 View Code Duplication
				if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5834
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5835 View Code Duplication
				if (empty($attachment['filename']))
5836
				{
5837
					$attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?
5838
						$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
5839
				}
5840
5841
				$attachment['attachment'] = $part->getContents(array('stream'=>$_stream));
5842
5843
				$attachments[$_uid.'@'.$_partID.'@'.$mime_id] = $attachment;
5844
			}
5845
		}
5846
		if (!is_array($attachments)) return false;
5847
		return $attachments;
5848
	}
5849
5850
	/**
5851
	 * Retrieve a attachment
5852
	 *
5853
	 * @param int $_uid the uid of the message
5854
	 * @param string $_partID the id of the part, which holds the attachment
5855
	 * @param int $_winmail_nr = 0 winmail.dat attachment nr.
5856
	 * @param boolean $_returnPart =true flag to indicate if the attachment is to be returned as horde mime part object
5857
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5858
	 * @param string $_folder =null folder to use if not current folder
5859
	 *
5860
	 * @return array
5861
	 */
5862
	function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false, $_folder=null)
5863
	{
5864
		//error_log(__METHOD__.__LINE__."Uid:$_uid, PartId:$_partID, WinMailNr:$_winmail_nr, ReturnPart:$_returnPart, Stream:$_stream, Folder:$_folder".function_backtrace());
5865 View Code Duplication
		if (!isset($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5866
5867
		$uidsToFetch = new Horde_Imap_Client_Ids();
5868
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5869
		$uidsToFetch->add($_uid);
5870
5871
		$fquery = new Horde_Imap_Client_Fetch_Query();
5872
		$fquery->structure();
5873
		$fquery->bodyPart($_partID, array('peek'=>true));
5874
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5875
			'ids' => $uidsToFetch,
5876
		));
5877
		if (is_object($headersNew)) {
5878
			foreach($headersNew as $id=>$_headerObject) {
5879
				$body = $_headerObject->getFullMsg();
5880
				if ($_partID != '')
5881
				{
5882
					$mailStructureObject = $_headerObject->getStructure();
5883
					$mailStructureObject->contentTypeMap();
5884
					$part = $mailStructureObject->getPart($_partID);
5885
					$partDisposition = ($part?$part->getDisposition():'failed');
5886
					if ($partDisposition=='failed')
5887
					{
5888
						error_log(__METHOD__.'('.__LINE__.'):'.array2string($_uid).','.$_partID.' ID:'.$id.' HObject:'.array2string($_headerObject).' StructureObject:'.array2string($mailStructureObject->contentTypeMap()).'->'.function_backtrace());
5889
					}
5890
					// if $partDisposition is empty, we assume attachment, and hope that the function
5891
					// itself is only triggered to fetch attachments
5892
					if (empty($partDisposition)) $partDisposition='attachment';
5893
					if ($part && ($partDisposition=='attachment' || $partDisposition=='inline' || ($part->getPrimaryType() == 'text' && $part->getSubType() == 'calendar')))
5894
					{
5895
						//$headerObject=$part->getAllDispositionParameters();//not used anywhere around here
5896
						$structure_mime = $part->getType();
5897
						$filename = $part->getName();
5898
						$charset = $part->getContentTypeParameter('charset');
5899
						//$structure_bytes = $part->getBytes(); $structure_partID=$part->getMimeId(); error_log(__METHOD__.__LINE__." fetchPartContents(".array2string($_uid).", $structure_partID, $_stream, $_preserveSeen,$structure_mime)" );
5900
						$this->fetchPartContents($_uid, $part, $_stream, $_preserveSeen=true,$structure_mime);
5901
						if ($_returnPart) return $part;
5902
					}
5903
				}
5904
			}
5905
		}
5906
		$ext = MimeMagic::mime2ext($structure_mime);
5907
		if ($ext && stripos($filename,'.')===false && stripos($filename,$ext)===false) $filename = trim($filename).'.'.$ext;
5908
		if (!$part)
5909
		{
5910
			throw new Exception\WrongParameter("Error: Could not fetch attachment for Uid=".array2string($_uid).", PartId=$_partID, WinMailNr=$_winmail_nr, folder=$_folder");
5911
		}
5912
		$attachmentData = array(
5913
			'type'		=> $structure_mime,
5914
			'charset' => $charset,
5915
			'filename'	=> $filename,
5916
			'attachment'	=> $part->getContents(array(
5917
				// tnef_decode needs strings not a stream
5918
				'stream' => $_stream && !($filename == 'winmail.dat' && $_winmail_nr)
5919
			)),
5920
		);
5921
5922
		// try guessing the mimetype, if we get the application/octet-stream
5923
		if (strtolower($attachmentData['type']) == 'application/octet-stream') $attachmentData['type'] = MimeMagic::filename2mime($attachmentData['filename']);
5924
		# if the attachment holds a winmail number and is a winmail.dat then we have to handle that.
5925
		if ( $filename == 'winmail.dat' && $_winmail_nr)
5926
		{
5927
			//by now _uid is of type array
5928
			$tnefResolved=false;
5929
			$wantedPart=$_uid[0].'@'.$_partID;
5930
			$myTnef = $this->tnef_decoder($attachmentData['attachment']);
5931
			//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5932
			// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5933
			// we need to build Something that meets the needs
5934
			if ($myTnef)
5935
			{
5936
				foreach($myTnef->getParts() as $mime_id => $part)
5937
				{
5938
					$tnefResolved=true;
5939
					$attachment = $part->getAllDispositionParameters();
5940
					$attachment['mimeType'] = $part->getType();
5941
					//error_log(__METHOD__.__LINE__.'#'.$mime_id.'#'.$filename.'#'.array2string($attachment));
5942
					//error_log(__METHOD__.__LINE__." $_winmail_nr == $wantedPart@$mime_id");
5943
					if ($_winmail_nr == $wantedPart.'@'.$mime_id)
5944
					{
5945
						//error_log(__METHOD__.__LINE__.'#'.$structure_mime.'#'.$filename.'#'.array2string($attachment));
5946 View Code Duplication
						if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5947
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5948 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']);
5949
						$wmattach = $attachment;
5950
						$wmattach['attachment'] = $part->getContents(array('stream'=>$_stream));
5951
5952
					}
5953
				}
5954
			}
5955
			if ($tnefResolved)
5956
			{
5957
				$ext = MimeMagic::mime2ext($wmattach['mimeType']);
5958
				if ($ext && stripos($wmattach['filename'],'.')===false && stripos($wmattach['filename'],$ext)===false) $wmattach['filename'] = trim($wmattach['filename']).'.'.$ext;
5959
				$attachmentData = array(
5960
					'type'       => $wmattach['mimeType'],
5961
					'filename'   => $wmattach['filename'],
5962
					'attachment' => $wmattach['attachment'],
5963
				);
5964
			}
5965
		}
5966
		return $attachmentData;
5967
	}
5968
5969
	/**
5970
	 * Fetch a specific attachment from a message by it's cid
5971
	 *
5972
	 * this function is based on a on "Building A PHP-Based Mail Client"
5973
	 * http://www.devshed.com
5974
	 *
5975
	 * @param string|int $_uid
5976
	 * @param string $_cid
5977
	 * @param string $_part
5978
	 * @param boolean $_stream = null null do NOT fetch content, use fetchPartContents later
5979
	 *	true:
5980
	 * @return Horde_Mime_Part
5981
	 */
5982
	function getAttachmentByCID($_uid, $_cid, $_part, $_stream=null)
5983
	{
5984
		// some static variables to avoid fetching the same mail multiple times
5985
		static $uid=null, $part=null, $structure=null;
5986
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid, $_cid, $_part");
5987
5988
		if(empty($_cid)) return false;
5989
5990
		if ($_uid != $uid || $_part != $part)
5991
		{
5992
			$structure = $this->getStructure($uid=$_uid, $part=$_part);
5993
		}
5994
		/** @var Horde_Mime_Part */
5995
		$attachment = null;
5996
		foreach($structure->contentTypeMap() as $mime_id => $mime_type)
5997
		{
5998
			$part = $structure->getPart($mime_id);
5999
6000
			if ($part->getPrimaryType() == 'image' &&
6001
				(($cid = $part->getContentId()) &&
6002
				// RB: seem a bit fague to search for inclusion in both ways
6003
				(strpos($cid, $_cid) !== false || strpos($_cid, $cid) !== false)) ||
6004
				(($name = $part->getName()) &&
6005
				(strpos($name, $_cid) !== false || strpos($_cid, $name) !== false)))
6006
			{
6007
				// if we have a direct match, dont search any further
6008
				if ($cid == $_cid)
6009
				{
6010
					$attachment = $part;
6011
				}
6012
				// everything else we only consider after we checked all
6013
				if (!isset($attachment)) $attachment = $part;
6014
				// do we want content fetched, can be done later, if not needed
6015
				if (isset($_stream))
6016
				{
6017
					$this->fetchPartContents($_uid, $attachment, $_stream);
6018
				}
6019
				if (isset($attachment)) break;
6020
			}
6021
		}
6022
		// set name as filename, if not set
6023
		if ($attachment && !$attachment->getDispositionParameter('filename'))
6024
		{
6025
			$attachment->setDispositionParameter('filename', $attachment->getName());
6026
		}
6027
		// guess type, if not set
6028
		if ($attachment && $attachment->getType() == 'application/octet-stream')
6029
		{
6030
			$attachment->setType(MimeMagic::filename2mime($attachment->getDispositionParameter('filename')));
6031
		}
6032
		//error_log(__METHOD__."($_uid, '$_cid', '$_part') returning ".array2string($attachment));
6033
		return $attachment;
6034
	}
6035
6036
	/**
6037
	 * Fetch and add contents to a part
6038
	 *
6039
	 * To get contents you use $part->getContents();
6040
	 *
6041
	 * @param int $_uid
6042
	 * @param Horde_Mime_Part $part
6043
	 * @param boolean $_stream = false true return a stream, false a string
6044
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
6045
	 * @param string  $_mimetype to decide wether to try to fetch part as binary or not
6046
	 * @return Horde_Mime_Part
6047
	 */
6048
	public function fetchPartContents($_uid, Horde_Mime_Part $part=null, $_stream=false, $_preserveSeen=false, $_mimetype=null)
6049
	{
6050
		if (is_null($part)) return null;//new Horde_Mime_Part;
6051
		$encoding = null;
6052
		$fetchAsBinary = true;
6053
		if ($_mimetype && strtolower($_mimetype)=='message/rfc822') $fetchAsBinary = false;
6054
		// we need to set content on structure to decode transfer encoding
6055
		$part->setContents(
6056
			$this->getBodyPart($_uid, $part->getMimeId(), null, $_preserveSeen, $_stream, $encoding, $fetchAsBinary),
6057
			array('encoding' => (!$fetchAsBinary&&!$encoding?'8bit':$encoding)));
6058
6059
		return $part;
6060
	}
6061
6062
	/**
6063
	 * save a message in folder
6064
	 *	throws exception on failure
6065
	 * @todo set flags again
6066
	 *
6067
	 * @param string _folderName the foldername
6068
	 * @param string|resource _header header part of message or resource with hole message
6069
	 * @param string _body body part of message, only used if _header is NO resource
6070
	 * @param string _flags = '\\Recent'the imap flags to set for the saved message
6071
	 *
6072
	 * @return the id of the message appended or exception
6073
	 * @throws Exception\WrongUserinput
6074
	 */
6075
	function appendMessage($_folderName, $_header, $_body, $_flags='\\Recent')
6076
	{
6077
		if (!is_resource($_header))
6078
		{
6079
			if (stripos($_header,'message-id:')===false)
6080
			{
6081
				$_header = 'Message-ID: <'.self::getRandomString().'@localhost>'."\n".$_header;
6082
			}
6083
			//error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_header, $_body, $_flags");
6084
			$_header = ltrim(str_replace("\n","\r\n",$_header));
6085
			$_header .= str_replace("\n","\r\n",$_body);
6086
		}
6087
		// the recent flag is the default enforced here ; as we assume the _flags is always set,
6088
		// we default it to hordes default (Recent) (, other wise we should not pass the parameter
6089
		// for flags at all)
6090
		if (empty($_flags)) $_flags = '\\Recent';
6091
		//if (!is_array($_flags) && stripos($_flags,',')!==false) $_flags=explode(',',$_flags);
6092
		//if (!is_array($_flags)) $_flags = (array) $_flags;
6093
		try
6094
		{
6095
			$dataNflags = array();
6096
			// both methods below are valid for appending a message to a mailbox.
6097
			// the commented version fails in retrieving the uid of the created message if the server
6098
			// is not returning the uid upon creation, as the method in append for detecting the uid
6099
			// expects data to be a string. this string is parsed for message-id, and the mailbox
6100
			// searched for the message-id then returning the uid found
6101
			//$dataNflags[] = array('data'=>array(array('t'=>'text','v'=>"$header"."$body")), 'flags'=>array($_flags));
6102
			$dataNflags[] = array('data' => $_header, 'flags'=>array($_flags));
6103
			$messageid = $this->icServer->append($_folderName,$dataNflags);
6104
		}
6105
		catch (\Exception $e)
6106
		{
6107
			if (self::$debug) error_log("Could not append Message: ".$e->getMessage());
6108
			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...
6109
			//return false;
6110
		}
6111
		//error_log(__METHOD__.' ('.__LINE__.') '.' appended UID:'.$messageid);
6112
		//$messageid = true; // for debug reasons only
6113
		if ($messageid === true || empty($messageid)) // try to figure out the message uid
6114
		{
6115
			$list = $this->getHeaders($_folderName, $_startMessage=1, 1, 'INTERNALDATE', true, array(),null, false);
6116
			if ($list)
6117
			{
6118 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' MessageUid:'.$messageid.' but found:'.array2string($list));
6119
				$messageid = $list['header'][0]['uid'];
6120
			}
6121
		}
6122
		return $messageid;
6123
	}
6124
6125
	/**
6126
	 * Get a random string of 32 chars
6127
	 *
6128
	 * @return string
6129
	 */
6130
	static function getRandomString()
6131
	{
6132
		return Auth::randomstring(32);
6133
	}
6134
6135
	/**
6136
	 * functions to allow access to mails through other apps to fetch content
6137
	 * used in infolog, tracker
6138
	 */
6139
6140
	/**
6141
	 * get_mailcontent - fetches the actual mailcontent, and returns it as well defined array
6142
	 * @param object mailClass the mailClassobject to be used
6143
	 * @param uid the uid of the email to be processed
6144
	 * @param partid the partid of the email
6145
	 * @param mailbox the mailbox, that holds the message
6146
	 * @param preserveHTML flag to pass through to getdisplayableBody
6147
	 * @param addHeaderSection flag to be able to supress headersection
6148
	 * @param includeAttachments flag to be able to supress possible attachments
6149
	 * @return array/bool with 'mailaddress'=>$mailaddress,
6150
	 *				'subject'=>$subject,
6151
	 *				'message'=>$message,
6152
	 *				'attachments'=>$attachments,
6153
	 *				'headers'=>$headers,; boolean false on failure
6154
	 */
6155
	static function get_mailcontent(&$mailClass,$uid,$partid='',$mailbox='', $preserveHTML = false, $addHeaderSection=true, $includeAttachments=true)
6156
	{
6157
			//echo __METHOD__." called for $uid,$partid <br>";
6158
			$headers = $mailClass->getMessageHeader($uid,$partid,true,false,$mailbox);
6159
			if (empty($headers)) return false;
6160
			// dont force retrieval of the textpart, let mailClass preferences decide
6161
			$bodyParts = $mailClass->getMessageBody($uid,($preserveHTML?'always_display':'only_if_no_text'),$partid,null,false,$mailbox);
6162
			// if we do not want HTML but there is no TextRepresentation with the message itself, try converting
6163
			if ( !$preserveHTML && $bodyParts[0]['mimeType']=='text/html')
6164
			{
6165
				foreach($bodyParts as $i => $part)
6166
				{
6167
					if ($bodyParts[$i]['mimeType']=='text/html')
6168
					{
6169
						$bodyParts[$i]['body'] = Mail\Html::convertHTMLToText($bodyParts[$i]['body'],$bodyParts[$i]['charSet'],true,$stripalltags=true);
6170
						$bodyParts[$i]['mimeType']='text/plain';
6171
					}
6172
				}
6173
			}
6174
			//error_log(array2string($bodyParts));
6175
			$attachments = $includeAttachments?$mailClass->getMessageAttachments($uid,$partid,null,true,false,true,$mailbox):array();
6176
6177
			if ($mailClass->isSentFolder($mailbox)) $mailaddress = $headers['TO'];
6178
			elseif (isset($headers['FROM'])) $mailaddress = $headers['FROM'];
6179
			elseif (isset($headers['SENDER'])) $mailaddress = $headers['SENDER'];
6180
			if (isset($headers['CC'])) $mailaddress .= ','.$headers['CC'];
6181
			//_debug_array(array($headers,$mailaddress));
6182
			$subject = $headers['SUBJECT'];
6183
6184
			$message = self::getdisplayableBody($mailClass, $bodyParts, $preserveHTML);
6185
			if ($preserveHTML && $mailClass->activeMimeType == 'text/plain') $message = '<pre>'.$message.'</pre>';
6186
			$headdata = ($addHeaderSection ? self::createHeaderInfoSection($headers, '',$preserveHTML) : '');
6187
			$message = $headdata.$message;
6188
			//echo __METHOD__.'<br>';
6189
			//_debug_array($attachments);
6190
			if (is_array($attachments))
6191
			{
6192
				// For dealing with multiple files of the same name
6193
				$dupe_count = $file_list = array();
6194
6195
				foreach ($attachments as $num => $attachment)
6196
				{
6197
					if ($attachment['mimeType'] == 'MESSAGE/RFC822')
6198
					{
6199
						//_debug_array($mailClass->getMessageHeader($uid, $attachment['partID']));
6200
						//_debug_array($mailClass->getMessageBody($uid,'', $attachment['partID']));
6201
						//_debug_array($mailClass->getMessageAttachments($uid, $attachment['partID']));
6202
						$mailcontent = self::get_mailcontent($mailClass,$uid,$attachment['partID'],$mailbox);
6203
						$headdata ='';
6204
						if ($mailcontent['headers'])
6205
						{
6206
							$headdata = self::createHeaderInfoSection($mailcontent['headers'],'',$preserveHTML);
6207
						}
6208
						if ($mailcontent['message'])
6209
						{
6210
							$tempname =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6211
							$attachedMessages[] = array(
6212
								'type' => 'TEXT/PLAIN',
6213
								'name' => $mailcontent['subject'].'.txt',
6214
								'tmp_name' => $tempname,
6215
							);
6216
							$tmpfile = fopen($tempname,'w');
6217
							fwrite($tmpfile,$headdata.$mailcontent['message']);
6218
							fclose($tmpfile);
6219
						}
6220
						foreach($mailcontent['attachments'] as &$tmpval)
6221
						{
6222
							$attachedMessages[] = $tmpval;
6223
						}
6224
						unset($attachments[$num]);
6225
					}
6226
					else
6227
					{
6228
						$attachments[$num] = array_merge($attachments[$num],$mailClass->getAttachment($uid, $attachment['partID'],0,false,false));
6229
6230
						if (empty($attachments[$num]['attachment'])&&$attachments[$num]['cid'])
6231
						{
6232
							$c = $mailClass->getAttachmentByCID($uid, $attachment['cid'], $attachment['partID'],true);
6233
							$attachments[$num]['attachment'] = $c->getContents();
6234
						}
6235
						// no attempt to convert, if we dont know about the charset
6236
						if (isset($attachments[$num]['charset'])&&!empty($attachments[$num]['charset'])) {
6237
							// we do not try guessing the charset, if it is not set
6238
							//if ($attachments[$num]['charset']===false) $attachments[$num]['charset'] = Translation::detect_encoding($attachments[$num]['attachment']);
6239
							Translation::convert($attachments[$num]['attachment'],$attachments[$num]['charset']);
6240
						}
6241
						if(in_array($attachments[$num]['name'], $file_list))
6242
						{
6243
							$dupe_count[$attachments[$num]['name']]++;
6244
							$attachments[$num]['name'] = pathinfo($attachments[$num]['name'], PATHINFO_FILENAME) .
6245
								' ('.($dupe_count[$attachments[$num]['name']] + 1).')' . '.' .
6246
								pathinfo($attachments[$num]['name'], PATHINFO_EXTENSION);
6247
						}
6248
						$attachments[$num]['type'] = $attachments[$num]['mimeType'];
6249
						$attachments[$num]['tmp_name'] = tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6250
						$tmpfile = fopen($attachments[$num]['tmp_name'],'w');
6251
						fwrite($tmpfile,$attachments[$num]['attachment']);
6252
						fclose($tmpfile);
6253
						$file_list[] = $attachments[$num]['name'];
6254
						unset($attachments[$num]['attachment']);
6255
					}
6256
				}
6257
				if (is_array($attachedMessages)) $attachments = array_merge($attachments,$attachedMessages);
6258
			}
6259
			return array(
6260
					'mailaddress'=>$mailaddress,
6261
					'subject'=>$subject,
6262
					'message'=>$message,
6263
					'attachments'=>$attachments,
6264
					'headers'=>$headers,
6265
					);
6266
	}
6267
6268
	/**
6269
	 * getStandardIdentityForProfile
6270
	 * get either the first identity out of the given identities or the one matching the profile_id
6271
	 * @param object/array $_identities identity iterator object or array with identities from Mail\Account
6272
	 * @param integer $_profile_id the acc_id/profileID the identity with the matching key is the standard one
6273
	 * @return array the identity
6274
	 */
6275
	static function getStandardIdentityForProfile($_identities, $_profile_id)
6276
	{
6277
		$c = 0;
6278
		// use the standardIdentity
6279
		foreach($_identities as $key => $acc) {
6280
			if ($c==0) $identity = $acc;
6281
			//error_log(__METHOD__.__LINE__." $key == $_profile_id ");
6282
			if ($key==$_profile_id) $identity = $acc;
6283
			$c++;
6284
		}
6285
		return $identity;
6286
	}
6287
	/**
6288
	 * createHeaderInfoSection - creates a textual headersection from headerobject
6289
	 * @param array header headerarray may contain SUBJECT,FROM,SENDER,TO,CC,BCC,DATE,PRIORITY,IMPORTANCE
6290
	 * @param string headline Text tom use for headline, if SUPPRESS, supress headline and footerline
6291
	 * @param bool createHTML do it with HTML breaks
6292
	 * @return string a preformatted string with the information of the header worked into it
6293
	 */
6294
	static function createHeaderInfoSection($header,$headline='', $createHTML = false)
6295
	{
6296
		$headdata = null;
6297
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($header).function_backtrace());
6298
		if ($header['SUBJECT']) $headdata = lang('subject').': '.$header['SUBJECT'].($createHTML?"<br />":"\n");
6299 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...
6300 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...
6301 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...
6302 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...
6303 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...
6304 View Code Duplication
		if ($header['DATE']) $headdata .= lang('date').': '.$header['DATE'].($createHTML?"<br />":"\n");
6305 View Code Duplication
		if ($header['PRIORITY'] && $header['PRIORITY'] != 'normal') $headdata .= lang('priority').': '.$header['PRIORITY'].($createHTML?"<br />":"\n");
6306 View Code Duplication
		if ($header['IMPORTANCE'] && $header['IMPORTANCE'] !='normal') $headdata .= lang('importance').': '.$header['IMPORTANCE'].($createHTML?"<br />":"\n");
6307
		//if ($mailcontent['headers']['ORGANIZATION']) $headdata .= lang('organization').': '.$mailcontent['headers']['ORGANIZATION']."\
6308
		if (!empty($headdata))
6309
		{
6310 View Code Duplication
			if (!empty($headline) && $headline != 'SUPPRESS') $headdata = "---------------------------- $headline ----------------------------".($createHTML?"<br />":"\n").$headdata;
6311 View Code Duplication
			if (empty($headline)) $headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'').$headdata;
6312
			$headdata .= ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6313
		}
6314
		else
6315
		{
6316
			$headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6317
		}
6318
		return $headdata;
6319
	}
6320
6321
	/**
6322
	 * adaptSubjectForImport - strips subject from unwanted Characters, and does some normalization
6323
	 * to meet expectations
6324
	 * @param string $subject string to process
6325
	 * @return string
6326
	 */
6327
	static function adaptSubjectForImport($subject)
6328
	{
6329
		$subject = str_replace('$$','__',($subject?$subject:lang('(no subject)')));
6330
		$subject = str_ireplace(array('[FWD]','[',']','{','}','<','>'),array('Fwd:',' ',' ',' ',' ',' ',' '),trim($subject));
6331
		return $subject;
6332
	}
6333
6334
	/**
6335
	 * convertAddressArrayToString - converts an mail envelope Address Array To String
6336
	 * @param array $rfcAddressArray  an addressarray as provided by mail retieved via egw_pear....
6337
	 * @return string a comma separated string with the mailaddress(es) converted to text
6338
	 */
6339
	static function convertAddressArrayToString($rfcAddressArray)
6340
	{
6341
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($rfcAddressArray));
6342
		$returnAddr ='';
6343
		if (is_array($rfcAddressArray))
6344
		{
6345
			foreach((array)$rfcAddressArray as $addressData) {
6346
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressData));
6347
				if($addressData['MAILBOX_NAME'] == 'NIL') {
6348
					continue;
6349
				}
6350
				if(strtolower($addressData['MAILBOX_NAME']) == 'undisclosed-recipients') {
6351
					continue;
6352
				}
6353
				if ($addressData['RFC822_EMAIL'])
6354
				{
6355
					$addressObjectA = self::parseAddressList($addressData['RFC822_EMAIL']);
6356
				}
6357
				else
6358
				{
6359
					$emailaddress = ($addressData['PERSONAL_NAME']?$addressData['PERSONAL_NAME'].' <'.$addressData['EMAIL'].'>':$addressData['EMAIL']);
6360
					$addressObjectA = self::parseAddressList($emailaddress);
6361
				}
6362
				$addressObject = $addressObjectA[0];
6363
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressObject));
6364
				if (!$addressObject->valid) continue;
6365
				//$mb =(string)$addressObject->mailbox;
6366
				//$h = (string)$addressObject->host;
6367
				//$p = (string)$addressObject->personal;
6368
				$returnAddr .= (strlen($returnAddr)>0?',':'');
6369
				//error_log(__METHOD__.' ('.__LINE__.') '.$p.' <'.$mb.'@'.$h.'>');
6370
				$buff = imap_rfc822_write_address($addressObject->mailbox, Horde_Idna::decode($addressObject->host), $addressObject->personal);
6371
				$buff = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$buff);
6372
				//error_log(__METHOD__.' ('.__LINE__.') '.' Address: '.$returnAddr);
6373
				$returnAddr .= $buff;
6374
			}
6375
		}
6376
		else
6377
		{
6378
			// do not mess with strings, return them untouched /* ToDo: validate string as Address */
6379
			$rfcAddressArray = self::decode_header($rfcAddressArray,true);
6380
			$rfcAddressArray = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$rfcAddressArray);
6381
			if (is_string($rfcAddressArray)) return $rfcAddressArray;
6382
		}
6383
		return $returnAddr;
6384
	}
6385
6386
	/**
6387
	 * Merges a given content with contact data
6388
	 *
6389
	 * @param string $content
6390
	 * @param array $ids array with contact id(s)
6391
	 * @param string &$err error-message on error
6392
	 * @return string/boolean merged content or false on error
6393
	 */
6394
	static function merge($content,$ids,$mimetype='')
6395
	{
6396
		$mergeobj = new Contacts\Merge();
6397
6398
		if (empty($mimetype)) $mimetype = (strlen(strip_tags($content)) == strlen($content) ?'text/plain':'text/html');
6399
		$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...
6400
		if (empty($rv) && !empty($content) && !empty($err)) $rv = $content;
6401
		if (!empty($err) && !empty($content) && !empty($ids)) error_log(__METHOD__.' ('.__LINE__.') '.' Merge failed for Ids:'.array2string($ids).' ContentType:'.$mimetype.' Content:'.$content.' Reason:'.array2string($err));
6402
		return $rv;
6403
	}
6404
6405
	/**
6406
	 * Returns a string showing the size of the message/attachment
6407
	 *
6408
	 * @param integer $bytes
6409
	 * @return string formatted string
6410
	 */
6411
	static function show_readable_size($bytes)
6412
	{
6413
		$bytes /= 1024;
6414
		$type = 'k';
6415
6416
		if ($bytes / 1024 > 1)
6417
		{
6418
			$bytes /= 1024;
6419
			$type = 'M';
6420
6421
			if ($bytes / 1024 > 1)
6422
			{
6423
				$bytes *= 10;
6424
				settype($bytes, 'integer');
6425
				$bytes /= 10;
6426
				$bytes /= 1024;
6427
				$type = 'G';
6428
			}
6429
6430
		}
6431
6432
		if ($bytes < 10)
6433
		{
6434
			$bytes *= 10;
6435
			settype($bytes, 'integer');
6436
			$bytes /= 10;
6437
		}
6438
		else
6439
			settype($bytes, 'integer');
6440
6441
		return $bytes . ' ' . $type ;
6442
	}
6443
6444
	static function detect_qp(&$sting) {
6445
		$needle = '/(=[0-9][A-F])|(=[A-F][0-9])|(=[A-F][A-F])|(=[0-9][0-9])/';
6446
		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...
6447
	}
6448
6449
	/**
6450
	 * logRunTimes
6451
	 *	logs to the error log all parameters given; output only if self::$debugTimes is true
6452
	 *
6453
	 * @param int $_starttime starttime of the action measured based on microtime(true)
6454
	 * @param int $_endtime endtime of the action measured, if not given microtime(true) is used
6455
	 * @param string $_message message to output details or params, whatever seems neccesary
6456
	 * @param string $_methodNline - Information where the log was taken
6457
	 * @return void
6458
	 */
6459
	static function logRunTimes($_starttime,$_endtime=null,$_message='',$_methodNline='')
6460
	{
6461
		if (is_null($_endtime)) $_endtime = microtime(true);
6462
		$usagetime = microtime(true) - $_starttime;
6463
		if (self::$debugTimes) error_log($_methodNline.' took:'.number_format($usagetime,5).'(s) '.($_message?'Details:'.$_message:''));
6464
	}
6465
6466
	/**
6467
	 * check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6468
	 *
6469
	 * @param array $_formData passed by reference Array with information of name, type, file and size, mimetype may be adapted
6470
	 * @param string $IDtoAddToFileName id to enrich the returned tmpfilename
6471
	 * @param string $reqMimeType /(default message/rfc822, if set to false, mimetype check will not be performed
6472
	 * @return mixed $fullPathtoFile or exception
6473
	 *
6474
	 * @throws Exception\WrongUserinput
6475
	 */
6476
	static function checkFileBasics(&$_formData, $IDtoAddToFileName='', $reqMimeType='message/rfc822')
6477
	{
6478
		if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'egw-data') return $_formData['file'];
6479
6480
		//error_log(__METHOD__.__FILE__.array2string($_formData).' Id:'.$IDtoAddToFileName.' ReqMimeType:'.$reqMimeType);
6481
		$importfailed = $tmpFileName = false;
6482
		// ignore empty files, but allow to share vfs directories (which can have 0 size)
6483
		if ($_formData['size'] == 0 && parse_url($_formData['file'], PHP_URL_SCHEME) != 'vfs' && is_dir($_formData['file']))
6484
		{
6485
			$importfailed = true;
6486
			$alert_msg .= lang("Empty file %1 ignored.", $_formData['name']);
6487
		}
6488
		elseif (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs' || is_uploaded_file($_formData['file']) ||
6489
			realpath(dirname($_formData['file'])) == realpath($GLOBALS['egw_info']['server']['temp_dir']))
6490
		{
6491
			// ensure existance of eGW temp dir
6492
			// note: this is different from apache temp dir,
6493
			// and different from any other temp file location set in php.ini
6494
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6495
			{
6496
				@mkdir($GLOBALS['egw_info']['server']['temp_dir'],0700);
6497
			}
6498
6499
			// if we were NOT able to create this temp directory, then make an ERROR report
6500
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6501
			{
6502
				$alert_msg .= 'Error:'.'<br>'
6503
					.'Server is unable to access EGroupware tmp directory'.'<br>'
6504
					.$GLOBALS['egw_info']['server']['temp_dir'].'<br>'
6505
					.'Please check your configuration'.'<br>'
6506
					.'<br>';
6507
			}
6508
6509
			// sometimes PHP is very clue-less about MIME types, and gives NO file_type
6510
			// rfc default for unknown MIME type is:
6511
			if ($reqMimeType == 'message/rfc822')
6512
			{
6513
				$mime_type_default = 'message/rfc';
6514
			}
6515
			else
6516
			{
6517
				$mime_type_default = $reqMimeType;
6518
			}
6519
			// check the mimetype by extension. as browsers seem to report crap
6520
			// maybe its application/octet-stream -> this may mean that we could not determine the type
6521
			// so we check for the suffix too
6522
			// trust vfs mime-types, trust the mimetype if it contains a method
6523
			if ((substr($_formData['file'],0,6) !== 'vfs://' || $_formData['type'] == 'application/octet-stream') && stripos($_formData['type'],'method=')===false)
6524
			{
6525
				$buff = explode('.',$_formData['name']);
6526
				$suffix = '';
6527
				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
6528
				if (!empty($suffix)) $sfxMimeType = MimeMagic::ext2mime($suffix);
6529
				if (!empty($suffix) && !empty($sfxMimeType) &&
6530
					(strlen(trim($_formData['type']))==0 || (strtolower(trim($_formData['type'])) != $sfxMimeType)))
6531
				{
6532
					error_log(__METHOD__.' ('.__LINE__.') '.' Data:'.array2string($_formData));
6533
					error_log(__METHOD__.' ('.__LINE__.') '.' Form reported Mimetype:'.$_formData['type'].' but seems to be:'.$sfxMimeType);
6534
					$_formData['type'] = $sfxMimeType;
6535
				}
6536
			}
6537
			if (trim($_formData['type']) == '')
6538
			{
6539
				$_formData['type'] = 'application/octet-stream';
6540
			}
6541
			// if reqMimeType is set to false do not test for that
6542
			if ($reqMimeType)
6543
			{
6544
				// so if PHP did not pass any file_type info, then substitute the rfc default value
6545
				if (substr(strtolower(trim($_formData['type'])),0,strlen($mime_type_default)) != $mime_type_default)
6546
				{
6547
					if (!(strtolower(trim($_formData['type'])) == "application/octet-stream" && $sfxMimeType == $reqMimeType))
6548
					{
6549
						//error_log("Message rejected, no message/rfc. Is:".$_formData['type']);
6550
						$importfailed = true;
6551
						$alert_msg .= lang("File rejected, no %2. Is:%1",$_formData['type'],$reqMimeType);
6552
					}
6553
					if ((strtolower(trim($_formData['type'])) != $reqMimeType && $sfxMimeType == $reqMimeType))
6554
					{
6555
						$_formData['type'] = MimeMagic::ext2mime($suffix);
6556
					}
6557
				}
6558
			}
6559
			// as FreeBSD seems to have problems with the generated temp names we append some more random stuff
6560
			$randomString = chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90));
6561
			$tmpFileName = $GLOBALS['egw_info']['user']['account_id'].
6562
				trim($IDtoAddToFileName).basename($_formData['file']).'_'.$randomString;
6563
6564
			if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs')
6565
			{
6566
				$tmpFileName = $_formData['file'];	// no need to store it somewhere
6567
			}
6568
			elseif (is_uploaded_file($_formData['file']))
6569
			{
6570
				move_uploaded_file($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);	// requirement for safe_mode!
6571
			}
6572
			else
6573
			{
6574
				rename($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);
6575
			}
6576
		} else {
6577
			//error_log("Import of message ".$_formData['file']." failes to meet basic restrictions");
6578
			$importfailed = true;
6579
			$alert_msg .= lang("Processing of file %1 failed. Failed to meet basic restrictions.",$_formData['name']);
6580
		}
6581
		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...
6582
		{
6583
			throw new Exception\WrongUserinput($alert_msg);
6584
		}
6585
		else
6586
		{
6587
			if (parse_url($tmpFileName,PHP_URL_SCHEME) == 'vfs')
6588
			{
6589
				Vfs::load_wrapper('vfs');
6590
			}
6591
			return $tmpFileName;
6592
		}
6593
	}
6594
6595
	/**
6596
	 * Parses a html text for images, and adds them as inline attachment
6597
	 *
6598
	 * Images can be data-urls, own VFS webdav.php urls or absolute path.
6599
	 *
6600
	 * @param Mailer $_mailObject instance of the Mailer Object to be used
6601
	 * @param string $_html2parse the html to parse and to be altered, if conditions meet
6602
	 * @param $mail_bo mail bo object
6603
	 * @return array|null return inline images stored as tmp file in vfs as array of attachments otherwise null
6604
	 */
6605
	static function processURL2InlineImages(Mailer $_mailObject, &$_html2parse, $mail_bo)
6606
	{
6607
		//error_log(__METHOD__."()");
6608
		$imageC = 0;
6609
		$images = null;
6610
		if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
6611
		{
6612
			foreach($images[2] as $i => $url)
6613
			{
6614
				//$isData = false;
6615
				$basedir = $data = '';
6616
				$needTempFile = true;
6617
6618
				// do not change urls for absolute images (thanks to corvuscorax)
6619
				if (substr($url, 0, 5) !== 'data:')
6620
				{
6621
					$filename = basename($url);
6622
					if (($directory = dirname($url)) == '.') $directory = '';
6623
					$ext = pathinfo($filename, PATHINFO_EXTENSION);
6624
					$mimeType  = MimeMagic::ext2mime($ext);
6625
					if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; }
6626
					$myUrl = $directory.$filename;
6627
					if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
6628
					{
6629
						$basedir = ($_SERVER['HTTPS']?'https://':'http://'.$_SERVER['HTTP_HOST']);
6630
					}
6631
					// use vfs instead of url containing webdav.php
6632
					// ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign
6633
					// webdav.php urls as vfs
6634
					if (strpos($myUrl,'/webdav.php') !== false) // we have a webdav link, so we build a vfs/sqlfs link of it.
6635
					{
6636
						Vfs::load_wrapper('vfs');
6637
						list(,$myUrl) = explode('/webdav.php',$myUrl,2);
6638
						$basedir = 'vfs://default';
6639
						$needTempFile = false;
6640
					}
6641
6642
					// If it is an inline image url, we need to fetch the actuall attachment
6643
					// content and later on to be able to store its content as temp file
6644
					if (strpos($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage') !== false)
6645
					{
6646
						$URI_params = array();
6647
						// Strips the url and store it into a temp for further procss
6648
						$tmp_url = html_entity_decode($myUrl);
6649
6650
						parse_str(parse_url($tmp_url, PHP_URL_QUERY),$URI_params);
6651
						if ($URI_params['mailbox'] && $URI_params['uid'] && $URI_params['cid'])
6652
						{
6653
							$mail_bo->reopen(base64_decode($URI_params['mailbox']));
6654
							$attachment = $mail_bo->getAttachmentByCID($URI_params['uid'], base64_decode($URI_params['cid']),base64_decode($URI_params['partID']),true);
6655
							$mail_bo->closeConnection();
6656
							if ($attachment)
6657
							{
6658
								$data = $attachment->getContents();
6659
								$mimeType = $attachment->getType();
6660
								$filename = $attachment->getDispositionParameter('filename');
6661
							}
6662
						}
6663
					}
6664
6665
					if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; }
6666
					if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl));
6667
				}
6668
				if (substr($url,0,strlen('data:'))=='data:')
6669
				{
6670
					//error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i]));
6671
					// we only support base64 encoded data
6672
					$tmp = substr($url,strlen('data:'));
6673
					list($mimeType,$data_base64) = explode(';base64,',$tmp);
6674
					$data = base64_decode($data_base64);
6675
					// FF currently does NOT add any mime-type
6676
					if (strtolower(substr($mimeType, 0, 6)) != 'image/')
6677
					{
6678
						$mimeType = MimeMagic::analyze_data($data);
6679
					}
6680
					list($what,$exactly) = explode('/',$mimeType);
6681
					$needTempFile = true;
6682
					$filename = ($what?$what:'data').$imageC++.'.'.$exactly;
6683
				}
6684
				if ($data || $needTempFile === false)
6685
				{
6686
					if ($needTempFile)
6687
					{
6688
						$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6689
						$tmpfile = fopen($attachment_file,'w');
6690
						fwrite($tmpfile,$data);
6691
						fclose($tmpfile);
6692
					}
6693
					else
6694
					{
6695
						$attachment_file = $basedir.urldecode($myUrl);
6696
					}
6697
					// we use $attachment_file as base for cid instead of filename, as it may be image.png
6698
					// (or similar) in all cases (when cut&paste). This may lead to more attached files, in case
6699
					// we use the same image multiple times, but, if we do this, we should try to detect that
6700
					// on upload. filename itself is not sufficient to determine the sameness of images
6701
					$cid = 'cid:' . md5($attachment_file);
6702
					if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null)
6703
					{
6704
						//$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse);
6705
						$_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse);
6706
					}
6707
				}
6708
				$attachments [] = array(
6709
					'name' => $filename,
6710
					'type' => $mimeType,
6711
					'file' => $attachment_file,
6712
					'tmp_name' => $attachment_file
6713
				);
6714
			}
6715
			return is_array($attachments) ? $attachments : null;
6716
		}
6717
	}
6718
6719
	/**
6720
	 * importMessageToMergeAndSend
6721
	 *
6722
	 * @param Storage\Merge Storage\Merge bo_merge object
6723
	 * @param string $document the full filename
6724
	 * @param array $SendAndMergeTocontacts array of contact ids
6725
	 * @param string& $_folder (passed by reference) will set the folder used. must be set with a folder, but will hold modifications if
6726
	 *					folder is modified
6727
	 * @param string& $importID ID for the imported message, used by attachments to identify them unambiguously
6728
	 * @return mixed array of messages with success and failed messages or exception
6729
	 */
6730
	function importMessageToMergeAndSend(Storage\Merge $bo_merge, $document, $SendAndMergeTocontacts, &$_folder, &$importID='')
6731
	{
6732
		$importfailed = false;
6733
		$processStats = array('success'=>array(),'failed'=>array());
6734
		if (empty($SendAndMergeTocontacts))
6735
		{
6736
			$importfailed = true;
6737
			$alert_msg .= lang("Import of message %1 failed. No Contacts to merge and send to specified.", '');
6738
		}
6739
6740
		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6741
		/* as the file is provided by Storage\Merge, we do not check
6742
		try
6743
		{
6744
			$tmpFileName = Mail::checkFileBasics($_formData,$importID);
6745
		}
6746
		catch (\Exception\WrongUserinput $e)
6747
		{
6748
			$importfailed = true;
6749
			$alert_msg .= $e->getMessage();
6750
		}
6751
		*/
6752
		$tmpFileName = $document;
6753
		// -----------------------------------------------------------------------
6754
		if ($importfailed === false)
6755
		{
6756
			$mailObject = new Mailer($this->profileID);
6757
			try
6758
			{
6759
				$this->parseFileIntoMailObject($mailObject, $tmpFileName);
6760
			}
6761
			catch (Exception\AssertionFailed $e)
6762
			{
6763
				$importfailed = true;
6764
				$alert_msg .= $e->getMessage();
6765
			}
6766
6767
			//_debug_array($Body);
6768
			$this->openConnection();
6769
			if (empty($_folder))
6770
			{
6771
				$_folder = $this->getSentFolder();
6772
			}
6773
			$delimiter = $this->getHierarchyDelimiter();
6774
			if($_folder=='INBOX'.$delimiter) $_folder='INBOX';
6775
			if ($importfailed === false)
6776
			{
6777
				$Subject = $mailObject->getHeader('Subject');
6778
				//error_log(__METHOD__.' ('.__LINE__.') '.' Subject:'.$Subject);
6779
				$Body = ($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null;
6780
				//error_log(__METHOD__.' ('.__LINE__.') '.' Body:'.$Body);
6781
				//error_log(__METHOD__.' ('.__LINE__.') '.' BodyContentType:'.$mailObject->BodyContentType);
6782
				$AltBody = ($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null;
6783
				//error_log(__METHOD__.' ('.__LINE__.') '.' AltBody:'.$AltBody);
6784
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject->GetReplyTo()));
6785
6786
				// Fetch ReplyTo - Address if existing to check if we are to replace it
6787
				$replyTo = $mailObject->getReplyTo();
6788
				if (isset($replyTo['[email protected]']))
6789
				{
6790
					$mailObject->clearReplyTos();
6791
					$activeMailProfiles = $this->mail->getAccountIdentities($this->profileID);
6792
					$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6793
6794
					$mailObject->addReplyTo(Horde_Idna::encode($activeMailProfile['ident_email']),Mail::generateIdentityString($activeMailProfile,false));
6795
				}
6796
				foreach ($SendAndMergeTocontacts as $k => $val)
6797
				{
6798
					$errorInfo = $email = '';
6799
					$sendOK = $openComposeWindow = $openAsDraft = null;
6800
					//error_log(__METHOD__.' ('.__LINE__.') '.' Id To Merge:'.$val);
6801
					if (/*$GLOBALS['egw_info']['flags']['currentapp'] == 'addressbook' &&*/
6802
						count($SendAndMergeTocontacts) > 1 && $val &&
6803
						(is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val))) // do the merge
6804
					{
6805
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6806
6807
						// Parse destinations for placeholders
6808
						foreach(Mailer::$type2header as $type => $h)
6809
						{
6810
							//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));
6811
							$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...
6812
							$mailObject->addAddress($merged,'',$type);
6813
							if($type == 'to')
6814
							{
6815
								$email = $merged;
6816
							}
6817
						}
6818
6819
						// No addresses from placeholders?  Treat it as just a contact ID
6820
						if (!$email)
6821
						{
6822
							$contact = $bo_merge->contacts->read($val);
6823
							//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
6824
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6825
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6826
							if($email)
6827
							{
6828
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6829
							}
6830
						}
6831
6832
						$activeMailProfiles = $this->getAccountIdentities($this->profileID);
6833
						$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6834
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($activeMailProfile));
6835
						$mailObject->setFrom($activeMailProfile['ident_email'],
6836
							self::generateIdentityString($activeMailProfile,false));
6837
6838
						$mailObject->removeHeader('Message-ID');
6839
						$mailObject->removeHeader('Date');
6840
						$mailObject->clearCustomHeaders();
6841
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6842
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6843 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));
6844
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6845 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));
6846
6847
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6848
						// set a higher timeout for big messages
6849
						@set_time_limit(120);
6850
						$sendOK = true;
6851
						try {
6852
							$mailObject->send();
6853
						}
6854
						catch(Exception $e) {
6855
							$sendOK = false;
6856
							$errorInfo = $e->getMessage();
6857
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($errorInfo));
6858
						}
6859
					}
6860
					elseif (!$k)	// 1. entry, further entries will fail for apps other then addressbook
6861
					{
6862
						$openAsDraft = true;
6863
						$mailObject->removeHeader('Message-ID');
6864
						$mailObject->removeHeader('Date');
6865
						$mailObject->clearCustomHeaders();
6866
6867
						// Parse destinations for placeholders
6868
						foreach(Mailer::$type2header as $type => $h)
6869
						{
6870
							$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...
6871
							//error_log($type . ': ' . $mailObject->getHeader(Mailer::$type2header[$type]) . ' -> ' .$merged);
6872
							$mailObject->addAddress(trim($merged,'"'),'',$type);
6873
						}
6874
6875
						// No addresses from placeholders?  Treat it as just a contact ID
6876
						if (count($mailObject->getAddresses('to',true)) == 0 &&
6877
							is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val)) // do the merge
6878
						{
6879
							$contact = $bo_merge->contacts->read($val);
6880
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($contact));
6881
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6882
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6883
							if($email)
6884
							{
6885
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6886
							}
6887
						}
6888
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6889
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6890 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));
6891
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6892 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));
6893
						$_folder = $this->getDraftFolder();
6894
					}
6895
					if ($sendOK || $openAsDraft)
6896
					{
6897
						if ($this->folderExists($_folder,true))
6898
						{
6899
						    if($this->isSentFolder($_folder))
6900
							{
6901
						        $flags = '\\Seen';
6902
						    } elseif($this->isDraftFolder($_folder)) {
6903
						        $flags = '\\Draft';
6904
						    } else {
6905
						        $flags = '';
6906
						    }
6907
							$savefailed = false;
6908
							try
6909
							{
6910
								$messageUid =$this->appendMessage($_folder,
6911
									$mailObject->getRaw(),
6912
									null,
6913
									$flags);
6914
							}
6915
							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...
6916
							{
6917
								$savefailed = true;
6918
								$alert_msg .= lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",$Subject,$_folder,$e->getMessage());
6919
							}
6920
							// no send, save successful, and message_uid present
6921
							if ($savefailed===false && $messageUid && is_null($sendOK))
6922
							{
6923
								$importID = $messageUid;
6924
								$openComposeWindow = true;
6925
							}
6926
						}
6927
						else
6928
						{
6929
							$savefailed = true;
6930
							$alert_msg .= lang("Saving of message %1 failed. Destination Folder %2 does not exist.",$Subject,$_folder);
6931
						}
6932
						if ($sendOK)
6933
						{
6934
							$processStats['success'][$val] = 'Send succeeded to '.$nfn.'<'.$email.'>'.($savefailed?' but failed to store to Folder:'.$_folder:'');
6935
						}
6936
						else
6937
						{
6938
							if (!$openComposeWindow) $processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
6939
						}
6940
					}
6941
					if (!is_null($sendOK) && $sendOK===false && is_null($openComposeWindow))
6942
					{
6943
						$processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
6944
					}
6945
				}
6946
			}
6947
			unset($mailObject);
6948
		}
6949
		// set the url to open when refreshing
6950
		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...
6951
		{
6952
			throw new Exception\WrongUserinput($alert_msg);
6953
		}
6954
		else
6955
		{
6956
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($processStats));
6957
			return $processStats;
6958
		}
6959
	}
6960
6961
	/**
6962
	 * functions to allow the parsing of message/rfc files
6963
	 * used in felamimail to import mails, or parsev a message from file enrich it with addressdata (merge) and send it right away.
6964
	 */
6965
6966
	/**
6967
	 * Parses a message/rfc mail from file to the mailobject
6968
	 *
6969
	 * @param object $mailer instance of the SMTP Mailer Object
6970
	 * @param string $tmpFileName string that points/leads to the file to be imported
6971
	 * @throws Exception\NotFound if $fle is not found
6972
	 */
6973
	function parseFileIntoMailObject(Mailer $mailer, $tmpFileName)
6974
	{
6975
		switch (parse_url($tmpFileName, PHP_URL_SCHEME))
6976
		{
6977
			case 'vfs':
6978
				break;
6979
			case 'egw-data':
6980
				$message = ($host = parse_url($tmpFileName, PHP_URL_HOST)) ? Link::get_data($host, true) : false;
6981
				break;
6982
			default:
6983
				$tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($tmpFileName);
6984
				break;
6985
		}
6986
		if (!isset($message)) $message = fopen($tmpFileName, 'r');
6987
6988
		if (!$message)
6989
		{
6990
			throw new Exception\NotFound("File '$tmpFileName' not found!");
6991
		}
6992
		$this->parseRawMessageIntoMailObject($mailer, $message);
6993
6994
		fclose($message);
6995
	}
6996
6997
	/**
6998
	 * Check and fix headers of raw message for headers with line width
6999
	 * more than 998 chars per line, as none folding long headers might
7000
	 * break the mail content. RFC 2822 (2.2.3 Long Header fields)
7001
	 * https://www.ietf.org/rfc/rfc2822.txt
7002
	 *
7003
	 * @param string|resource $message
7004
	 * @return string
7005
	 */
7006
	static private function _checkAndfixLongHeaderFields($message)
7007
	{
7008
		$eol = Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL;
7009
		$needsFix = false;
7010
		if (is_resource($message))
7011
		{
7012
			fseek($message, 0, SEEK_SET);
7013
			$m = '';
7014
			while (!feof($message)) {
7015
				$m .= fread($message, 8192);
7016
			}
7017
			$message = $m;
7018
		}
7019
7020
		if (is_string($message))
7021
		{
7022
			$start = substr($message,0, strpos($message, $eol));
7023
			$body = substr($message, strlen($start));
7024
			$hlength = strpos($start, $eol) ? strpos($start, $eol) : strlen($start);
7025
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$hlength));
7026
			foreach($headers->toArray() as $header => $value)
7027
			{
7028
				$needsReplacement = false;
7029
				foreach((array)$value as &$val)
0 ignored issues
show
Bug introduced by
The expression (array) $value cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
7030
				{
7031
					if (strlen($val) > 998)
7032
					{
7033
						$needsReplacement = $needsFix = true;
7034
					}
7035
				}
7036
				if ($needsReplacement) {
7037
					$headers->removeHeader($header);
7038
					$headers->addHeader($header, $value);
7039
				}
7040
			}
7041
		}
7042
		return $needsFix ? ($headers->toString(array('canonical'=>true)).$body) : $message;
7043
	}
7044
7045
	/**
7046
	 * Parses a message/rfc mail from file to the mailobject
7047
	 *
7048
	 * @param Mailer $mailer instance of SMTP Mailer object
7049
	 * @param string|ressource|Horde_Mime_Part $message string or resource containing the RawMessage / object Mail_mimeDecoded message (part))
7050
	 * @param boolean $force8bitOnPrimaryPart (default false. force transferEncoding and charset to 8bit/utf8 if we have a textpart as primaryPart)
7051
	 * @throws Exception\WrongParameter when the required Horde_Mail_Part not found
7052
	 */
7053
	function parseRawMessageIntoMailObject(Mailer $mailer, $message, $force8bitOnPrimaryPart=false)
7054
	{
7055
		if (is_string($message) || is_resource($message))
7056
		{
7057
			// Check and fix long header fields
7058
			$message = self::_checkAndfixLongHeaderFields($message);
7059
7060
			$structure = Horde_Mime_Part::parseMessage($message);
7061
			//error_log(__METHOD__.__LINE__.'#'.$structure->getPrimaryType().'#');
7062
			if ($force8bitOnPrimaryPart&&$structure->getPrimaryType()=='text')
7063
			{
7064
				$structure->setTransferEncoding('8bit');
7065
				$structure->setCharset('utf-8');
7066
			}
7067
			$mailer->setBasePart($structure);
7068
			//error_log(__METHOD__.__LINE__.':'.array2string($structure));
7069
7070
			// unfortunately parseMessage does NOT return parsed headers (we assume header is shorter then 8k)
7071
			$start = is_string($message) ? substr($message, 0, 8192) :
7072
				(fseek($message, 0, SEEK_SET) == -1 ? '' : fread($message, 8192));
7073
7074
			$length = strpos($start, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
7075
			if ($length===false) $length = strlen($start);
7076
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$length));
7077
7078
			foreach($headers->toArray(array('nowrap' => true)) as $header => $value)
7079
			{
7080
				foreach((array)$value as $n => $val)
7081
				{
7082
					$overwrite = !$n;
7083
					switch($header)
7084
					{
7085
						case 'Content-Transfer-Encoding':
7086
							//as we parse the message and this sets the part with a Content-Transfer-Encoding, we
7087
							//should not overwrite it with the header-values of the source-message as the encoding
7088
							//may be altered when retrieving the message e.g. from server
7089
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val.'<->'.$mailer->getHeader('Content-Transfer-Encoding'));
7090
							break;
7091
						case 'Bcc':
7092
						case 'bcc':
7093
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
7094
							$mailer->addBcc($val);
7095
							break;
7096
						default:
7097
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
7098
							$mailer->addHeader($header, $val, $overwrite);
7099
							//error_log(__METHOD__.__LINE__.':'.'getHeader('.$header.')'.array2string($mailer->getHeader($header)));
7100
					}
7101
				}
7102
			}
7103
		}
7104
		elseif (is_a($message, 'Horde_Mime_Part'))
7105
		{
7106
			$mailer->setBasePart($message);
7107
		}
7108
		else
7109
		{
7110
			if (($type = gettype($message)) == 'object') $type = get_class ($message);
7111
			throw new Exception\WrongParameter('Wrong parameter type for message: '.$type);
7112
		}
7113
	}
7114
7115
	/**
7116
	 * Parse an address-list
7117
	 *
7118
	 * Replaces imap_rfc822_parse_adrlist, which fails for utf-8, if not our replacement in common_functions is used!
7119
	 *
7120
	 * @param string $addresses
7121
	 * @param string $default_domain
7122
	 * @return Horde_Mail_Rfc822_List iteratable Horde_Mail_Rfc822_Address objects with attributes mailbox, host, personal and valid
7123
	 */
7124
	public static function parseAddressList($addresses, $default_domain=null)
7125
	{
7126
		$rfc822 = new Horde_Mail_Rfc822();
7127
		$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7128
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count.function_backtrace());
7129
		if ((empty($ret) || $ret->count()==0)&& is_string($addresses) && strlen($addresses)>0)
7130
		{
7131
			$matches = array();
7132
			preg_match_all("/[\w\.,-.,_.,0-9.]+@[\w\.,-.,_.,0-9.]+/",$addresses,$matches);
7133
			//error_log(__METHOD__.__LINE__.array2string($matches));
7134
			foreach ($matches[0] as &$match) {$match = trim($match,', ');}
7135
			$addresses = implode(',',$matches[0]);
7136
			//error_log(__METHOD__.__LINE__.array2string($addresses));
7137
			$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7138
			//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count);
7139
		}
7140
		$previousFailed=false;
7141
		$ret2 = new Horde_Mail_Rfc822_List();
7142
		// handle known problems on emailaddresses
7143
		foreach($ret as $i => $adr)
7144
		{
7145
			//mailaddresses enclosed in single quotes like '[email protected]' show up as 'me as mailbox and you.com' as host
7146
			if ($adr->mailbox && stripos($adr->mailbox,"'")== 0 &&
7147
					$adr->host && stripos($adr->host,"'")== (strlen($adr->host) -1))
7148
			{
7149
				$adr->mailbox = str_replace("'","",$adr->mailbox);
7150
				$adr->host = str_replace("'","",$adr->host);
7151
			}
7152
			// no mailbox or host part as 'Xr\xc3\xa4hlyz, User <[email protected]>' is parsed as 2 addresses separated by ','
7153
			//#'Xr\xc3\xa4hlyz, User <[email protected]>'
7154
			//#Horde_Mail_Rfc822_List Object([_data:protected] => Array(
7155
			//[0] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => Xr\xc3\xa4hlyz[_host:protected] => [_personal:protected] => )
7156
			//[1] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => mailboxpart1.mailboxpart2[_host:protected] => youthost.com[_personal:protected] => User))[_filter:protected] => Array()[_ptr:protected] => )#2#,
7157
			if (strlen($adr->mailbox)==0||strlen($adr->host)==0)
7158
			{
7159
				$remember = ($adr->mailbox?$adr->mailbox:($adr->host?$adr->host:''));
7160
				$previousFailed=true;
7161
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7162
			}
7163
			else
7164
			{
7165
				if ($previousFailed && $remember) $adr->personal = $remember. ' ' . $adr->personal;
7166
				$remember = '';
7167
				$previousFailed=false;
7168
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7169
				$ret2->add($adr);
7170
			}
7171
		}
7172
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret2).'#'.$ret2->count().'#'.$ret2->count);
7173
		return $ret2;
7174
	}
7175
7176
	/**
7177
	 * Send a read notification
7178
	 *
7179
	 * @param string $uid
7180
	 * @param string $_folder
7181
	 * @return boolean
7182
	 */
7183
	function sendMDN($uid,$_folder)
7184
	{
7185
		$acc = Mail\Account::read($this->profileID);
7186
		$identity = Mail\Account::read_identity($acc['ident_id'], true, null, $acc);
7187
		if (self::$debug) error_log(__METHOD__.__LINE__.array2string($identity));
7188
		$headers = $this->getMessageHeader($uid, '', 'object', true, $_folder);
7189
7190
		$mdn = new Horde_Mime_Mdn($headers);
7191
		$mdn->generate(true, true, 'displayed', php_uname('n'), $acc->smtpTransport(), array(
7192
			'charset' => 'utf-8',
7193
			'from_addr' => self::generateIdentityString($identity),
7194
		));
7195
7196
		return true;
7197
	}
7198
7199
	/**
7200
	 * Hook stuff
7201
	 */
7202
7203
	/**
7204
	 * hook to add account
7205
	 *
7206
	 * this function is a wrapper function for emailadmin
7207
	 *
7208
	 * @param _hookValues contains the hook values as array
7209
	 * @return nothing
7210
	 */
7211
	function addAccount($_hookValues)
7212
	{
7213
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7214
7215
	}
7216
7217
	/**
7218
	 * hook to delete account
7219
	 *
7220
	 * this function is a wrapper function for emailadmin
7221
	 *
7222
	 * @param _hookValues contains the hook values as array
7223
	 * @return nothing
7224
	 */
7225
	function deleteAccount($_hookValues)
7226
	{
7227
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7228
7229
	}
7230
7231
	/**
7232
	 * hook to update account
7233
	 *
7234
	 * this function is a wrapper function for emailadmin
7235
	 *
7236
	 * @param _hookValues contains the hook values as array
7237
	 * @return nothing
7238
	 */
7239
	function updateAccount($_hookValues)
7240
	{
7241
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7242
7243
	}
7244
}
7245