Completed
Push — master ( fb037c...c173de )
by Klaus
57:17 queued 36:58
created

Mail::adaptSubjectForImport()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware - Mail - worker class
4
 *
5
 * @link http://www.egroupware.org
6
 * @package api
7
 * @subpackage amil
8
 * @author Stylite AG [[email protected]]
9
 * @copyright (c) 2013-2016 by Stylite AG <info-AT-stylite.de>
10
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
11
 * @version $Id$
12
 */
13
14
namespace EGroupware\Api;
15
16
use Horde_Imap_Client;
17
use Horde_Imap_Client_Ids;
18
use Horde_Imap_Client_Fetch_Query;
19
use Horde_Imap_Client_Data_Fetch;
20
use Horde_Mime_Part;
21
use Horde_Imap_Client_Search_Query;
22
use Horde_Idna;
23
use Horde_Imap_Client_DateTime;
24
use Horde_Mime_Headers;
25
use Horde_Compress;
26
use Horde_Mime_Magic;
27
use Horde_Mail_Rfc822;
28
use Horde_Mail_Rfc822_List;
29
use Horde_Mime_Mdn;
30
31
use tidy;
32
33
/**
34
 * Mail worker class
35
 *  -provides backend functionality for all classes in Mail
36
 *  -provides classes that may be used by other apps too
37
 *
38
 * @link https://github.com/horde/horde/blob/master/imp/lib/Contents.php
39
 */
40
class Mail
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
41
{
42
	/**
43
	 * the current selected user profile
44
	 * @var int
45
	 */
46
	var $profileID = 0;
47
48
	/**
49
	 * delimiter - used to separate acc_id from mailbox / folder-tree-structure
50
	 *
51
	 * @var string
52
	 */
53
	const DELIMITER = '::';
54
55
	/**
56
	 * the current display char set
57
	 * @var string
58
	 */
59
	static $displayCharset;
60
	static $activeFolderCache;
61
	static $folderStatusCache;
62
	static $supportsORinQuery;
63
64
	/**
65
	 * Active preferences
66
	 *
67
	 * @var array
68
	 */
69
	var $mailPreferences;
70
71
	/**
72
	 * active html Options
73
	 *
74
	 * @var array
75
	 */
76
	var $htmlOptions;
77
78
	/**
79
	 * Active mimeType
80
	 *
81
	 * @var string
82
	 */
83
	var $activeMimeType;
84
85
	/**
86
	 * Active incomming (IMAP) Server Object
87
	 *
88
	 * @var Api\Mail\Imap
89
	 */
90
	var $icServer;
91
92
	/**
93
	 * Active outgoing (smtp) Server Object
94
	 *
95
	 * @var Api\Mail\Smtp
96
	 */
97
	var $ogServer;
98
99
	/**
100
	 * errorMessage
101
	 *
102
	 * @var string $errorMessage
103
	 */
104
	var $errorMessage;
105
106
	/**
107
	 * switch to enable debug; sometimes debuging is quite handy, to see things. check with the error log to see results
108
	 * @var boolean
109
	 */
110
	static $debug = false; //true;
111
	static $debugTimes = false; //true;
112
113
	/**
114
	 * static used to hold the mail Config values
115
	 * @array
116
	 */
117
	static $mailConfig;
118
119
	/**
120
	 * static used to configure tidy - if tidy is loadable, this config is used with tidy to straighten out html, instead of using purifiers tidy mode
121
	 *
122
	 * @array
123
	 */
124
	static $tidy_config = array('clean'=>false,'output-html'=>true,'join-classes'=>true,'join-styles'=>true,'show-body-only'=>"auto",'word-2000'=>true,'wrap'=>0);
125
126
	/**
127
	 * static used to configure htmLawed, for use with emails
128
	 *
129
	 * @array
130
	 */
131
	static $htmLawed_config = array('comment'=>1, //remove comments
132
		'make_tag_strict' => 3, // 3 is a new own config value, to indicate that transformation is to be performed, but don't transform font as size transformation of numeric sizes to keywords alters the intended result too much
133
		'keep_bad'=>2, //remove tags but keep element content (4 and 6 keep element content only if text (pcdata) is valid in parent element as per specs, this may lead to textloss if balance is switched on)
134
		'balance'=>1,//turn off tag-balancing (config['balance']=>0). That will not introduce any security risk; only standards-compliant tag nesting check/filtering will be turned off (basic tag-balance will remain; i.e., there won't be any unclosed tag, etc., after filtering)
135
		'direct_list_nest' => 1,
136
		'allow_for_inline' => array('table','div','li','p'),//block elements allowed for nesting when only inline is allowed; Example span does not allow block elements as table; table is the only element tested so far
137
		// tidy eats away even some wanted whitespace, so we switch it off;
138
		// we used it for its compacting and beautifying capabilities, which resulted in better html for further processing
139
		'tidy'=>0,
140
		'elements' => "* -script",
141
		'deny_attribute' => 'on*',
142
		'schemes'=>'href: file, ftp, http, https, mailto, phone; src: cid, data, file, ftp, http, https; *:file, http, https, cid, src',
143
		'hook_tag' =>"hl_email_tag_transform",
144
	);
145
146
	/**
147
	 * static used define abbrevations for common access rights
148
	 *
149
	 * @array
150
	 */
151
	static $aclShortCuts = array('' => array('label'=>'none','title'=>'The user has no rights whatsoever.'),
152
		'lrs'		=> array('label'=>'readable','title'=>'Allows a user to read the contents of the mailbox.'),
153
		'lprs'		=> array('label'=>'post','title'=>'Allows a user to read the mailbox and post to it through the delivery system by sending mail to the submission address of the mailbox.'),
154
		'ilprs'		=> array('label'=>'append','title'=>'Allows a user to read the mailbox and append messages to it, either via IMAP or through the delivery system.'),
155
		'cdilprsw'	=> array('label'=>'write','title'=>'Allows a user to read the maibox, post to it, append messages to it, and delete messages or the mailbox itself. The only right not given is the right to change the ACL of the mailbox.'),
156
		'acdilprsw'	=> array('label'=>'all','title'=>'The user has all possible rights on the mailbox. This is usually granted to users only on the mailboxes they own.'),
157
		'custom'	=> array('label'=>'custom','title'=>'User defined combination of rights for the ACL'),
158
	);
159
160
	/**
161
	 * Folders that get automatic created AND get translated to the users language
162
	 * their creation is also controlled by users mailpreferences. if set to none / dont use folder
163
	 * the folder will not be automatically created. This is controlled in Mail->getFolderObjects
164
	 * so changing names here, must include a change of keywords there as well. Since these
165
	 * foldernames are subject to translation, keep that in mind too, if you change names here.
166
	 * lang('Drafts'), lang('Templates'), lang('Sent'), lang('Trash'), lang('Junk'), lang('Outbox')
167
	 * ActiveSync:
168
	 *  Outbox is needed by Nokia Clients to be able to send Mails
169
	 * @var array
170
	 */
171
	static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox');
172
173
	/**
174
	 * Array to cache the specialUseFolders, if existing
175
	 * @var array
176
	 */
177
	static $specialUseFolders;
178
179
	/**
180
	 * Hold instances by profileID for getInstance() singleton
181
	 *
182
	 * @var array
183
	 */
184
	private static $instances = array();
185
	private static $profileDefunct = array();
186
187
	/**
188
	 * Singleton for Mail
189
	 *
190
	 * @param boolean $_restoreSession = true
191
	 * @param int $_profileID = 0
192
	 * @param boolean $_validate = true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile
193
	 *                                  not matching the input profileID, if we can not find a profile matching the given ID
194
	 * @param mixed boolean/object $_icServerObject - if object, return instance with object set as icServer
195
	 *												  immediately, if boolean === true use oldImapServer in constructor
196
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
197
	 * @return Mail
198
	 */
199
	public static function getInstance($_restoreSession=true, &$_profileID=0, $_validate=true, $_oldImapServerObject=false, $_reuseCache=null)
200
	{
201
		//$_restoreSession=false;
202
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
203
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.'/'.Mail\Account::get_default_acc_id().' for user:'.$GLOBALS['egw_info']['user']['account_lid'].' called from:'.function_backtrace());
204
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_oldImapServerObject));
205
		self::$profileDefunct = Cache::getCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),5*1);
206
		if (isset(self::$profileDefunct[$_profileID]) && strlen(self::$profileDefunct[$_profileID]))
207
		{
208
			throw new Exception(__METHOD__." failed to instanciate Mail for Profile #$_profileID Reason:".self::$profileDefunct[$_profileID]);
209
		}
210
		if ($_oldImapServerObject instanceof Mail\Imap)
211
		{
212
			if (!is_object(self::$instances[$_profileID]))
213
			{
214
				self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
215
			}
216
			self::$instances[$_profileID]->icServer = $_oldImapServerObject;
217
			self::$instances[$_profileID]->accountid= $_oldImapServerObject->ImapServerId;
218
			self::$instances[$_profileID]->profileID= $_oldImapServerObject->ImapServerId;
219
			self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
220
			self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
221
			return self::$instances[$_profileID];
222
		}
223
		if ($_profileID == 0)
224
		{
225 View Code Duplication
			if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
226
			{
227
				$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
228
			}
229
			else
230
			{
231
				$profileID = Mail\Account::get_default_acc_id();
232
			}
233
			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...
234
			$_profileID=$profileID;
235 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace());
236
		}
237
		// no validation or restoreSession for old ImapServer Object, just fetch it and return it
238
		if ($_oldImapServerObject===true)
239
		{
240
			return new Mail('utf-8',false,$_profileID,true,$_reuseCache);
241
		}
242
		if ($_profileID != 0 && $_validate)
243
		{
244
			$profileID = self::validateProfileID($_profileID);
245
			if ($profileID != $_profileID)
246
			{
247
				if (self::$debug)
248
				{
249
					error_log(__METHOD__.' ('.__LINE__.') '.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.');
250
					error_log(__METHOD__.' ('.__LINE__.') '.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);
251
				}
252
				$_profileID = $profileID;
253
				//$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
254
				// save prefs
255
				//$GLOBALS['egw']->preferences->save_repository(true);
256
			}
257
			//Cache::setSession('mail','activeProfileID',$_profileID);
258
		}
259
		//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
260
		if ($_profileID && (!isset(self::$instances[$_profileID]) || $_restoreSession===false))
261
		{
262
			self::$instances[$_profileID] = new Mail('utf-8',$_restoreSession,$_profileID,false,$_reuseCache);
263
		}
264
		else
265
		{
266
			//refresh objects
267
			try
268
			{
269
				self::$instances[$_profileID]->icServer = Mail\Account::read($_profileID)->imapServer();
270
				self::$instances[$_profileID]->ogServer = Mail\Account::read($_profileID)->smtpServer();
271
				// TODO: merge mailprefs into userprefs, for easy treatment
272
				self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
273
				self::$instances[$_profileID]->htmlOptions  = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
274
			} catch (\Exception $e)
275
			{
276
				$newprofileID = Mail\Account::get_default_acc_id();
277
				// try loading the default profile for the user
278
				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());
279
				if ($newprofileID)
280
				{
281
					self::$instances[$newprofileID] = new Mail('utf-8',false,$newprofileID,false,$_reuseCache);
282
					$_profileID = $newprofileID;
283
				}
284
				else
285
				{
286
					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...
287
				}
288
			}
289
			self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );
290
		}
291
		self::$instances[$_profileID]->profileID = $_profileID;
292
		if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new Horde_Idna;
293
		//if ($_profileID==0); error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID);
294
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
295
		return self::$instances[$_profileID];
296
	}
297
298
	/**
299
	 * store given ProfileID to Session and pref
300
	 *
301
	 * @param int $_profileID = 0
302
	 * @param boolean $_testConnection = 0
303
	 * @return mixed $_profileID or false on failed ConnectionTest
304
	 */
305
	public static function storeActiveProfileIDToPref($_icServerObject, $_profileID=0, $_testConnection=true)
306
	{
307 View Code Duplication
		if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
308
		{
309
			$oldProfileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
310
		}
311
		if ($_testConnection)
312
		{
313
			try
314
			{
315
				$_icServerObject->getCurrentMailbox();
316
			}
317
			catch (\Exception $e)
318
			{
319
				if ($_profileID != Mail\Account::get_default_acc_id()) $_profileID = Mail\Account::get_default_acc_id();
320
				error_log(__METHOD__.__LINE__.' '.$e->getMessage());
321
				return false;
322
			}
323
		}
324
		if ($oldProfileID != $_profileID)
325
		{
326
			if ($oldProfileID && $_profileID==0) $_profileID = $oldProfileID;
327
			$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
328
			// save prefs
329
			$GLOBALS['egw']->preferences->save_repository(true);
330
			$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $_profileID;
331
			Cache::setSession('mail','activeProfileID',$_profileID);
332
		}
333
		return $_profileID;
334
	}
335
336
	/**
337
	 * Validate given account acc_id to make sure account is valid for current user
338
	 *
339
	 * Validation checks:
340
	 * - non-empty imap-host
341
	 * - non-empty imap-username
342
	 *
343
	 * @param int $_acc_id = 0
344
	 * @return int validated acc_id -> either acc_id given, or first valid one
345
	 */
346
	public static function validateProfileID($_acc_id=0)
347
	{
348
		if ($_acc_id)
349
		{
350
			try {
351
				$account = Mail\Account::read($_acc_id);
352
				if ($account->is_imap())
353
				{
354
					return $_acc_id;
355
				}
356
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT valid, no imap-host!");
357
			}
358
			catch (\Exception $e) {
359
				unset($e);
360
				if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT found!");
361
			}
362
		}
363
		// no account specified or specified account not found or not valid
364
		// --> search existing account for first valid one and return that
365
		foreach(Mail\Account::search($only_current_user=true, 'acc_imap_host') as $acc_id => $imap_host)
366
		{
367
			if (!empty($imap_host) && ($account = Mail\Account::read($acc_id)) && $account->is_imap())
368
			{
369
				if (self::$debug && $_acc_id) error_log(__METHOD__."($_acc_id) using $acc_id instead");
370
				return $acc_id;
371
			}
372
		}
373
		if (self::$debug) error_log(__METHOD__."($_acc_id) NO valid account found!");
374
		return 0;
375
	}
376
377
378
	/**
379
	 * Private constructor, use Mail::getInstance() instead
380
	 *
381
	 * @param string $_displayCharset = 'utf-8'
382
	 * @param boolean $_restoreSession = true
383
	 * @param int $_profileID = 0 if not nummeric, we assume we only want an empty class object
384
	 * @param boolean $_oldImapServerObject = false
385
	 * @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
386
	 */
387
	private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0, $_oldImapServerObject=false, $_reuseCache=null)
388
	{
389
		if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
390
		if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset;
391
		// not nummeric, we assume we only want an empty class object
392
		if (!is_numeric($_profileID)) return true;
393
		if ($_restoreSession)
394
		{
395
			//error_log(__METHOD__." Session restore ".function_backtrace());
396
			$this->restoreSessionData();
397
			$lv_mailbox = $this->sessionData['mailbox'];
398
			$firstMessage = $this->sessionData['previewMessage'];
399
		}
400
		else
401
		{
402
			$this->restoreSessionData();
403
			$lv_mailbox = $this->sessionData['mailbox'];
404
			$firstMessage = $this->sessionData['previewMessage'];
405
			$this->sessionData = array();
406
		}
407
		if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
408
		try
409
		{
410
			$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...
411
			$this->accountid	= $GLOBALS['egw_info']['user']['account_id'];
412
413
			//error_log(__METHOD__.' ('.__LINE__.') '." ProfileID ".$this->profileID.' called from:'.function_backtrace());
414
			$acc = Mail\Account::read($this->profileID);
415
		}
416
		catch (\Exception $e)
417
		{
418
			throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage());
419
		}
420
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($acc->imapServer()));
421
		$this->icServer = ($_oldImapServerObject?$acc->oldImapServer():$acc->imapServer());
422
		$this->ogServer = $acc->smtpServer();
423
		// TODO: merge mailprefs into userprefs, for easy treatment
424
		$this->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
425
		$this->htmlOptions  = $this->mailPreferences['htmlOptions'];
426
		if (isset($this->icServer->ImapServerId) && !empty($this->icServer->ImapServerId))
427
		{
428
			$_profileID = $this->profileID = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->icServer->ImapServerId;
429
		}
430
431
		if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
432
	}
433
434
	/**
435
	 * forceEAProfileLoad
436
	 * used to force the load of a specific emailadmin profile; we assume administrative use only (as of now)
437
	 * @param int $_profile_id
438
	 * @return object instance of Mail (by reference)
439
	 */
440
	public static function &forceEAProfileLoad($_profile_id)
441
	{
442
		self::unsetCachedObjects($_profile_id);
443
		$mail = self::getInstance(false, $_profile_id,false);
444
		//_debug_array( $_profile_id);
445
		$mail->icServer = Mail\Account::read($_profile_id)->imapServer();
446
		$mail->ogServer = Mail\Account::read($_profile_id)->smtpServer();
447
		return $mail;
448
	}
449
450
	/**
451
	 * trigger the force of the reload of the SessionData by resetting the session to an empty array
452
	 * @param int $_profile_id
453
	 * @param boolean $_resetFolderObjects
454
	 */
455
	public static function forcePrefReload($_profile_id=null,$_resetFolderObjects=true)
456
	{
457
		// unset the mail_preferences session object, to force the reload/rebuild
458
		Cache::setSession('mail','mail_preferences',serialize(array()));
459
		Cache::setSession('emailadmin','session_data',serialize(array()));
460
		if ($_resetFolderObjects) self::resetFolderObjectCache($_profile_id);
461
	}
462
463
	/**
464
	 * restore the SessionData
465
	 */
466
	function restoreSessionData()
467
	{
468
		$this->sessionData = array();//Cache::getCache(Cache::SESSION,'mail','session_data',$callback=null,$callback_params=array(),$expiration=60*60*1);
469
		self::$activeFolderCache = Cache::getCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
470 View Code Duplication
		if (!empty(self::$activeFolderCache[$this->profileID])) $this->sessionData['mailbox'] = self::$activeFolderCache[$this->profileID];
471
	}
472
473
	/**
474
	 * saveSessionData saves session data
475
	 */
476
	function saveSessionData()
477
	{
478
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($this->sessionData)));
479 View Code Duplication
		if (!empty($this->sessionData['mailbox'])) self::$activeFolderCache[$this->profileID]=$this->sessionData['mailbox'];
480
		if (isset(self::$activeFolderCache) && is_array(self::$activeFolderCache))
481
		{
482
			Cache::setCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),self::$activeFolderCache, 60*60*10);
483
		}
484
	}
485
486
	/**
487
	 * unset certain CachedObjects for the given profile id, unsets the profile for default ID=0 as well
488
	 *
489
	 * 1) icServerIMAP_connectionError
490
	 * 2) icServerSIEVE_connectionError
491
	 * 3) INSTANCE OF MAIL_BO
492
	 * 4) HierarchyDelimiter
493
	 * 5) VacationNotice
494
	 *
495
	 * @param int $_profileID = null default profile of user as returned by getUserDefaultProfileID
496
	 * @return void
497
	 */
498
	static function unsetCachedObjects($_profileID=null)
499
	{
500
		if (is_null($_profileID)) $_profileID = Mail\Account::get_default_acc_id();
501
		if (is_array($_profileID) && $_profileID['account_id']) $account_id = $_profileID['account_id'];
502
		//error_log(__METHOD__.__LINE__.' called with ProfileID:'.array2string($_profileID).' from '.function_backtrace());
503
		if (!is_array($_profileID) && (is_numeric($_profileID) || !(stripos($_profileID,'tracker_')===false)))
504
		{
505
			self::resetConnectionErrorCache($_profileID);
506
			$rawHeadersCache = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
507 View Code Duplication
			if (isset($rawHeadersCache[$_profileID]))
508
			{
509
				unset($rawHeadersCache[$_profileID]);
510
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$rawHeadersCache, $expiration=60*60*1);
511
			}
512
			$HierarchyDelimiterCache = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*5);
513 View Code Duplication
			if (isset($HierarchyDelimiterCache[$_profileID]))
514
			{
515
				unset($HierarchyDelimiterCache[$_profileID]);
516
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$HierarchyDelimiterCache, $expiration=60*60*24*5);
517
			}
518
			//reset folderObject cache, to trigger reload
519
			self::resetFolderObjectCache($_profileID);
520
			//reset counter of deleted messages per folder
521
			$eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
522 View Code Duplication
			if (isset($eMailListContainsDeletedMessages[$_profileID]))
523
			{
524
				unset($eMailListContainsDeletedMessages[$_profileID]);
525
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$eMailListContainsDeletedMessages, $expiration=60*60*1);
526
			}
527
			$vacationCached = Cache::getCache(Cache::INSTANCE, 'email', 'vacationNotice'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*1);
528 View Code Duplication
			if (isset($vacationCached[$_profileID]))
529
			{
530
				unset($vacationCached[$_profileID]);
531
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),$vacationCached, $expiration=60*60*24*1);
532
			}
533
534
			if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
535
		}
536
		if (is_array($_profileID) && $_profileID['location'] == 'clear_cache')
537
		{
538
			// called via hook
539
			foreach($GLOBALS['egw']->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
540
			{
541
				//error_log(__METHOD__.__LINE__.array2string($account));
542
				$account_id = $account['account_id'];
543
				$_profileID = null;
544
				self::resetConnectionErrorCache($_profileID,$account_id);
545
				self::resetFolderObjectCache($_profileID,$account_id);
546
				Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),array(), 60*60*1);
547
				Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),array(), 60*60*24*5);
548
				Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),array(), 60*60*1);
549
				Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),array(), 60*60*24*1);
550
			}
551
		}
552
	}
553
554
	/**
555
	 * resets the various cache objects where connection error Objects may be cached
556
	 *
557
	 * @param int $_ImapServerId the profileID to look for
558
	 * @param int $account_id the egw account to look for
559
	 */
560
	static function resetConnectionErrorCache($_ImapServerId=null,$account_id=null)
561
	{
562
		//error_log(__METHOD__.' ('.__LINE__.') '.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($account_id));
563
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
564
		if (is_array($_ImapServerId))
565
		{
566
			// called via hook
567
			$account_id = $_ImapServerId['account_id'];
568
			unset($_ImapServerId);
569
			$_ImapServerId = null;
570
		}
571
		if (is_null($_ImapServerId))
572
		{
573
			$isConError = array();
574
			$waitOnFailure = array();
575
		}
576
		else
577
		{
578
			$isConError = Cache::getCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id));
579
			if (isset($isConError[$_ImapServerId]))
580
			{
581
				unset($isConError[$_ImapServerId]);
582
			}
583
			$waitOnFailure = Cache::getCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),null,array(),60*60*2);
584
			if (isset($waitOnFailure[$_ImapServerId]))
585
			{
586
				unset($waitOnFailure[$_ImapServerId]);
587
			}
588
		}
589
		Cache::setCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,60*15);
590
		Cache::setCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),$waitOnFailure,60*60*2);
591
	}
592
593
	/**
594
	 * resets the various cache objects where Folder Objects may be cached
595
	 *
596
	 * @param int $_ImapServerId the profileID to look for
597
	 * @param int $account_id the egw account to look for
598
	 */
599
	static function resetFolderObjectCache($_ImapServerId=null,$account_id=null)
600
	{
601
		//error_log(__METHOD__.' ('.__LINE__.') '.' called for Profile:'.array2string($_ImapServerId).'->'.function_backtrace());
602
		if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
603
		// on [location] => verify_settings we coud either use [prefs] => Array([ActiveProfileID] => 9, .. as $_ImapServerId
604
		// or treat it as not given. we try that path
605
		if (is_null($_ImapServerId)||is_array($_ImapServerId))
606
		{
607
			$folders2return = array();
608
			$folderInfo = array();
609
			$folderBasicInfo = array();
610
			$_specialUseFolders = array();
611
		}
612
		else
613
		{
614
			$folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),null,array(),60*60*1);
615
			if (!empty($folders2return) && isset($folders2return[$_ImapServerId]))
616
			{
617
				unset($folders2return[$_ImapServerId]);
618
			}
619
			$folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),null,array(),60*60*5);
620
			if (!empty($folderInfo) && isset($folderInfo[$_ImapServerId]))
621
			{
622
				unset($folderInfo[$_ImapServerId]);
623
			}
624
			/*
625
			$lastFolderUsedForMove = Cache::getCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),null,array(),$expiration=60*60*1);
626
			if (isset($lastFolderUsedForMove[$_ImapServerId]))
627
			{
628
				unset($lastFolderUsedForMove[$_ImapServerId]);
629
			}
630
			*/
631
			$folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),null,array(),60*60*1);
632
			if (!empty($folderBasicInfo) && isset($folderBasicInfo[$_ImapServerId]))
633
			{
634
				unset($folderBasicInfo[$_ImapServerId]);
635
			}
636
			$_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),null,array(),60*60*12);
637
			if (!empty($_specialUseFolders) && isset($_specialUseFolders[$_ImapServerId]))
638
			{
639
				unset($_specialUseFolders[$_ImapServerId]);
640
				self::$specialUseFolders=null;
641
			}
642
		}
643
		Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),$folders2return, 60*60*1);
644
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),$folderInfo,60*60*5);
645
		//Cache::setCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),$lastFolderUsedForMove,$expiration=60*60*1);
646
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),$folderBasicInfo,60*60*1);
647
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),$_specialUseFolders,60*60*12);
648
	}
649
650
	/**
651
	 * checks if the imap server supports a given capability
652
	 *
653
	 * @param string $_capability the name of the capability to check for
654
	 * @return bool
655
	 */
656
	function hasCapability($_capability)
657
	{
658
		$rv = $this->icServer->hasCapability(strtoupper($_capability));
659
		//error_log(__METHOD__.' ('.__LINE__.') '." $_capability:".array2string($rv));
660
		return $rv;
661
	}
662
663
	/**
664
	 * getUserEMailAddresses - function to gather the emailadresses connected to the current mail-account
665
	 * @param string $_profileID the ID of the mailaccount to check for identities, if null current mail-account is used
666
	 * @return array - array(email=>realname)
667
	 */
668
	function getUserEMailAddresses($_profileID=null)
669
	{
670
		$acc = Mail\Account::read((!empty($_profileID)?$_profileID:$this->profileID));
671
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($acc));
672
		$identities = Mail\Account::identities($acc);
673
674
		$userEMailAdresses = array($acc['ident_email']=>$acc['ident_realname']);
675
676
		foreach($identities as $ik => $ident) {
677
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
678
			$identity = Mail\Account::read_identity($ik);
679
			if (!empty($identity['ident_email']) && !isset($userEMailAdresses[$identity['ident_email']])) $userEMailAdresses[$identity['ident_email']] = $identity['ident_realname'];
680
		}
681
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
682
		return $userEMailAdresses;
683
	}
684
685
	/**
686
	 * getAllIdentities - function to gather the identities connected to the current user
687
	 * @param string/int $_accountToSearch = null if set search accounts for user specified
688
	 * @param boolean $resolve_placeholders wether or not resolve possible placeholders in identities
689
	 * @return array - array(email=>realname)
690
	 */
691
	static function getAllIdentities($_accountToSearch=null,$resolve_placeholders=false)
692
	{
693
		$userEMailAdresses = array();
694
		foreach(Mail\Account::search($only_current_user=($_accountToSearch?$_accountToSearch:true), $just_name=true) as $acc_id => $identity_name)
695
		{
696
			$acc = Mail\Account::read($acc_id,($_accountToSearch?$_accountToSearch:null));
697
			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']);
698
699
			foreach(Mail\Account::identities($acc) as $ik => $ident) {
700
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
701
				$identity = Mail\Account::read_identity($ik,$resolve_placeholders);
702
				//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
703 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']);
704
			}
705
		}
706
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
707
		return $userEMailAdresses;
708
	}
709
710
	/**
711
	 * Get all identities of given mailaccount
712
	 *
713
	 * @param int|Mail\Account $account account-object or acc_id
714
	 * @return array - array(email=>realname)
715
	 */
716
	function getAccountIdentities($account)
717
	{
718
		if (!$account instanceof Mail\Account)
719
		{
720
			$account = Mail\Account::read($account);
721
		}
722
		$userEMailAdresses = array();
723
		foreach(Mail\Account::identities($account, true, 'params') as $ik => $ident) {
724
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
725
			$identity = Mail\Account::read_identity($ik,true,null,$account);
726
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
727
			// standardIdentity has ident_id==acc_id (as it is done within account->identities)
728
			if (empty($identity['ident_id'])) $identity['ident_id'] = $identity['acc_id'];
729 View Code Duplication
			if (!isset($userEMailAdresses[$identity['ident_id']]))
730
			{
731
				$userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$identity['acc_id'],
732
																'ident_id'=>$identity['ident_id'],
733
																'ident_email'=>$identity['ident_email'],
734
																'ident_org'=>$identity['ident_org'],
735
																'ident_realname'=>$identity['ident_realname'],
736
																'ident_signature'=>$identity['ident_signature'],
737
																'ident_name'=>$identity['ident_name']);
738
			}
739
		}
740
741
		return $userEMailAdresses;
742
	}
743
744
	/**
745
	 * Function to gather the default identitiy connected to the current mailaccount
746
	 *
747
	 * @return int - id of the identity
748
	 */
749
	function getDefaultIdentity()
750
	{
751
		// retrieve the signature accociated with the identity
752
		$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...
753
		foreach(Mail\Account::identities($_accountData[$this->profileID] ?
754
			$this->profileID : $_accountData[$id],false,'ident_id') as $accountData)
755
		{
756
			return $accountData;
757
		}
758
	}
759
760
	/**
761
	 * getIdentitiesWithAccounts
762
	 *
763
	 * @param array reference to pass all identities back
764
	 * @return the default Identity (active) or 0
765
	 */
766
	function getIdentitiesWithAccounts(&$identities)
767
	{
768
		// account select box
769
		$selectedID = $this->profileID;
770
		$allAccountData = Mail\Account::search($only_current_user=true, false, null);
771
		if ($allAccountData) {
772
			$rememberFirst=$selectedFound=null;
773
			foreach ($allAccountData as $tmpkey => $icServers)
774
			{
775
				if (is_null($rememberFirst)) $rememberFirst = $tmpkey;
776
				if ($tmpkey == $selectedID) $selectedFound=true;
777
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($icServers->acc_imap_host));
778
				$host = $icServers->acc_imap_host;
779
				if (empty($host)) continue;
780
				$identities[$icServers->acc_id] = $icServers['ident_realname'].' '.$icServers['ident_org'].' <'.$icServers['ident_email'].'>';
781
				//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($identities[$icServers->acc_id]));
782
			}
783
		}
784
		return ($selectedFound?$selectedID:$rememberFirst);
785
	}
786
787
	/**
788
	 * construct the string representing an Identity passed by $identity
789
	 *
790
	 * @var array/object $identity, identity object that holds realname, organization, emailaddress and signatureid
791
	 * @var boolean $fullString full or false=NamePart only is returned
792
	 * @return string - constructed of identity object data as defined in mailConfig
793
	 */
794
	static function generateIdentityString($identity, $fullString=true)
795
	{
796
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($identity));
797
		//if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
798
		// not set? -> use default, means full display of all available data
799
		//if (!isset(self::$mailConfig['how2displayIdentities'])) self::$mailConfig['how2displayIdentities']='';
800
		$how2displayIdentities = '';
801
		switch ($how2displayIdentities)
802
		{
803
			case 'email';
804
				//$retData = str_replace('@',' ',$identity->emailAddress).($fullString===true?' <'.$identity->emailAddress.'>':'');
805
				$retData = $identity['ident_email'].($fullString===true?' <'.$identity['ident_email'].'>':'');
806
				break;
807 View Code Duplication
			case 'nameNemail';
808
				$retData = (!empty($identity['ident_realname'])?$identity['ident_realname']:substr_replace($identity['ident_email'],'',strpos($identity['ident_email'],'@'))).($fullString===true?' <'.$identity['ident_email'].'>':'');
809
				break;
810 View Code Duplication
			case 'orgNemail';
811
				$retData = (!empty($identity['ident_org'])?$identity['ident_org']:substr_replace($identity['ident_email'],'',0,strpos($identity['ident_email'],'@')+1)).($fullString===true?' <'.$identity['ident_email'].'>':'');
812
				break;
813
			default:
814
				$retData = $identity['ident_realname'].(!empty($identity['ident_org'])?' '.$identity['ident_org']:'').($fullString===true?' <'.$identity['ident_email'].'>':'');
815
		}
816
		return $retData;
817
	}
818
819
	/**
820
	 * closes a connection on the active Server ($this->icServer)
821
	 *
822
	 * @return void
823
	 */
824
	function closeConnection()
825
	{
826
		//if ($icServer->_connected) error_log(__METHOD__.' ('.__LINE__.') '.' disconnect from Server');
827
		//error_log(__METHOD__."() ".function_backtrace());
828
		$this->icServer->disconnect();
829
	}
830
831
	/**
832
	 * reopens a connection for the active Server ($this->icServer), and selects the folder given
833
	 *
834
	 * @param string $_foldername folder to open/select
835
	 * @return void
836
	 */
837
	function reopen($_foldername)
838
	{
839
		if (self::$debugTimes) $starttime = microtime (true);
840
841
		//error_log(__METHOD__.' ('.__LINE__.') '."('$_foldername') ".function_backtrace());
842
		// TODO: trying to reduce traffic to the IMAP Server here, introduces problems with fetching the bodies of
843
		// eMails when not in "current-Folder" (folder that is selected by UI)
844
		static $folderOpened;
845
		//if (empty($folderOpened) || $folderOpened!=$_foldername)
846
		//{
847
			//error_log( __METHOD__.' ('.__LINE__.') '." $_foldername ".function_backtrace());
848
			//error_log(__METHOD__.' ('.__LINE__.') '.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true));
849
			if ($this->folderIsSelectable($_foldername)) {
850
				$this->icServer->openMailbox($_foldername);
851
			}
852
			$folderOpened = $_foldername;
853
		//}
854
		if (self::$debugTimes) self::logRunTimes($starttime,null,'Folder:'.$_foldername,__METHOD__.' ('.__LINE__.') ');
855
	}
856
857
858
	/**
859
	 * openConnection
860
	 *
861
	 * @param int $_icServerID = 0
862
	 * @throws Horde_Imap_Client_Exception on connection error or authentication failure
863
	 * @throws InvalidArgumentException on missing credentials
864
	 */
865
	function openConnection($_icServerID=0)
866
	{
867
		//error_log( "-------------------------->open connection ".function_backtrace());
868
		//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
869
		if (self::$debugTimes) $starttime = microtime (true);
870
		$mailbox=null;
871
		try
872
		{
873
			if(isset($this->sessionData['mailbox'])&&$this->folderExists($this->sessionData['mailbox'])) $mailbox = $this->sessionData['mailbox'];
874
			if (empty($mailbox)) $mailbox = $this->icServer->getCurrentMailbox();
875
/*
876
			if (isset(Mail\Imap::$supports_keywords[$_icServerID]))
877
			{
878
				$this->icServer->openMailbox($mailbox);
879
			}
880
			else
881
			{
882
				$this->icServer->examineMailbox($mailbox);
883
			}
884
*/
885
			// the above should detect if there is a known information about supporting KEYWORDS
886
			// but does not work as expected :-(
887
			$this->icServer->examineMailbox($mailbox);
888
			//error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true));
889
			//error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID.function_backtrace());
890
891
			//make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache
892
			$this->getHierarchyDelimiter(false);
893
			self::$specialUseFolders = $this->getSpecialUseFolders();
894
		}
895
		catch (\Exception $e)
896
		{
897
			error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID." trying to examine ($mailbox) failed!".$e->getMessage());
898
			throw new Exception(__METHOD__." failed to ".__METHOD__." on Profile to $_icServerID while trying to examine $mailbox:".$e->getMessage());
899
		}
900
		if (self::$debugTimes) self::logRunTimes($starttime,null,'ProfileID:'.$_icServerID,__METHOD__.' ('.__LINE__.') ');
901
	}
902
903
	/**
904
	 * getQuotaRoot
905
	 * return the qouta of the users INBOX
906
	 *
907
	 * @return mixed array/boolean
908
	 */
909
	function getQuotaRoot()
910
	{
911
		static $quota;
912
		if (isset($quota)) return $quota;
913
		if (isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID]))
914
		{
915
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
916
			return false;
917
		}
918
		try
919
		{
920
			$this->icServer->getCurrentMailbox();
921
			if(!$this->icServer->hasCapability('QUOTA')) {
922
				$quota = false;
923
				return false;
924
			}
925
			$quota = $this->icServer->getStorageQuotaRoot('INBOX');
926
		}
927
		catch (Exception $e)
928
		{
929
			//error_log(__METHOD__.array2string($e));
930
			//error_log(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
931
			if ($e->getCode()==102)
932
			{
933
				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...
934
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
935
				throw new Exception(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
936
			}
937
		}
938
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($quota));
939
		if(is_array($quota)) {
940
			$quota = array(
941
				'usage'	=> $quota['USED'],
942
				'limit'	=> $quota['QMAX'],
943
			);
944
		} else {
945
			$quota = false;
946
		}
947
		return $quota;
948
	}
949
950
	/**
951
	 * getTimeOut
952
	 *
953
	 * @param string _use decide if the use is for IMAP or SIEVE, by now only the default differs
954
	 *
955
	 * @return int - timeout (either set or default 20/10)
956
	 */
957
	static function getTimeOut($_use='IMAP')
958
	{
959
		$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
960
		if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
961
		return $timeout;
962
	}
963
964
	/**
965
	 * Fetch the namespace from icServer
966
	 *
967
	 * An IMAPServer may present several namespaces under each key:
968
	 * so we return an array of namespacearrays for our needs
969
	 *
970
	 * @return array array(prefix_present=>mixed (bool/string) ,prefix=>string,delimiter=>string,type=>string (personal|others|shared))
971
	 */
972 View Code Duplication
	function _getNameSpaces()
973
	{
974
		static $nameSpace = null;
975
		$foldersNameSpace = array();
976
		$delimiter = $this->getHierarchyDelimiter();
977
		// TODO: cache by $this->icServer->ImapServerId
978
		if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaceArray();
979
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($nameSpace));
980
		if (is_array($nameSpace)) {
981
			foreach($nameSpace as $type => $singleNameSpaceArray)
982
			{
983
				foreach ($singleNameSpaceArray as $singleNameSpace)
984
				{
985
					$_foldersNameSpace = array();
986
					if($type == 'personal' && $singleNameSpace['name'] == '#mh/' && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
987
					{
988
						$_foldersNameSpace['prefix_present'] = 'forced';
989
						// uw-imap server with mailbox prefix or dovecot maybe
990
						$_foldersNameSpace['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace['name'])?$singleNameSpace['name']:''));
991
					}
992
					elseif($type == 'personal' && ($singleNameSpace['name'] == '#mh/') && $this->folderExists('mail'))
993
					{
994
						$_foldersNameSpace['prefix_present'] = 'forced';
995
						// uw-imap server with mailbox prefix or dovecot maybe
996
						$_foldersNameSpace['prefix'] = 'mail';
997
					} else {
998
						$_foldersNameSpace['prefix_present'] = !empty($singleNameSpace['name']);
999
						$_foldersNameSpace['prefix'] = $singleNameSpace['name'];
1000
					}
1001
					$_foldersNameSpace['delimiter'] = ($singleNameSpace['delimiter']?$singleNameSpace['delimiter']:$delimiter);
1002
					$_foldersNameSpace['type'] = $type;
1003
					$foldersNameSpace[] =$_foldersNameSpace;
1004
				}
1005
				//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################<br>";
1006
			}
1007
		}
1008
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($foldersNameSpace));
1009
		return $foldersNameSpace;
1010
	}
1011
1012
	/**
1013
	 * Wrapper to extract the folder prefix from folder compared to given namespace array
1014
	 *
1015
	 * @param array $nameSpace
1016
	 * @paam string $_folderName
1017
	 * @return string the prefix (may be an empty string)
1018
	 */
1019 View Code Duplication
	function getFolderPrefixFromNamespace($nameSpace, $folderName)
1020
	{
1021
		foreach($nameSpace as &$singleNameSpace)
1022
		{
1023
			//if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix'];
1024
			if (substr($folderName,0,strlen($singleNameSpace['prefix']))==$singleNameSpace['prefix']) return $singleNameSpace['prefix'];
1025
		}
1026
		return "";
1027
	}
1028
1029
	/**
1030
	 * getHierarchyDelimiter
1031
	 *
1032
	 * @var boolean $_useCache
1033
	 * @return string the hierarchyDelimiter
1034
	 */
1035
	function getHierarchyDelimiter($_useCache=true)
1036
	{
1037
		static $HierarchyDelimiter = null;
1038
		if (is_null($HierarchyDelimiter)) $HierarchyDelimiter = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
1039
		if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]);
1040 View Code Duplication
		if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId]))
1041
		{
1042
			return $HierarchyDelimiter[$this->icServer->ImapServerId];
1043
		}
1044
		$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1045
		try
1046
		{
1047
			$this->icServer->getCurrentMailbox();
1048
			$HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getDelimiter();
1049
		}
1050
		catch(\Exception $e)
1051
		{
1052
			if ($e->getCode()==102)
1053
			{
1054
				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...
1055
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1056
			}
1057
			unset($e);
1058
			$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
1059
		}
1060
		Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),$HierarchyDelimiter, 60*60*24*5);
1061
		return $HierarchyDelimiter[$this->icServer->ImapServerId];
1062
	}
1063
1064
	/**
1065
	 * getSpecialUseFolders
1066
	 * @ToDo: could as well be static, when icServer is passed
1067
	 * @return mixed null/array
1068
	 */
1069
	function getSpecialUseFolders()
1070
	{
1071
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$this->icServer->ImapServerId.' Connected:'.$this->icServer->_connected);
1072
		static $_specialUseFolders = null;
1073
		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);
1074
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_trash));
1075
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
1076
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
1077
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
1078
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1079 View Code Duplication
		if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
1080
			return $_specialUseFolders[$this->icServer->ImapServerId];
1081
		$_specialUseFolders[$this->icServer->ImapServerId]=array();
1082
		//if (!empty($this->icServer->acc_folder_trash) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]))
1083
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]='Trash';
1084
		//if (!empty($this->icServer->acc_folder_draft) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]))
1085
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]='Drafts';
1086
		//if (!empty($this->icServer->acc_folder_sent) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]))
1087
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent';
1088
		//if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]))
1089
			$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates';
1090
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk';
1091
		$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_archive]='Archive';
1092
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer));
1093
		self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
1094
		Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, 60*60*24*5);
1095
		return $_specialUseFolders[$this->icServer->ImapServerId];
1096
	}
1097
1098
	/**
1099
	 * get IMAP folder status regarding NoSelect
1100
	 *
1101
	 * @param foldertoselect string the foldername
1102
	 *
1103
	 * @return boolean true or false regarding the noselect attribute
1104
	 */
1105
	function folderIsSelectable($folderToSelect)
1106
	{
1107
		$retval = true;
1108
		if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) {
1109 View Code Duplication
			if (!empty($folderStatus['attributes']) && stripos(array2string($folderStatus['attributes']),'noselect')!==false)
1110
			{
1111
				$retval = false;
1112
			}
1113
		}
1114
		return $retval;
1115
	}
1116
1117
	/**
1118
	 * get IMAP folder status, wrapper to store results within a single request
1119
	 *
1120
	 * returns an array information about the imap folder
1121
	 *
1122
	 * @param folderName string the foldername
1123
	 * @param ignoreStatusCache bool ignore the cache used for counters
1124
	 *
1125
	 * @return array
1126
	 *
1127
	 * @throws Exception
1128
	 */
1129
	function _getStatus($folderName,$ignoreStatusCache=false)
1130
	{
1131
		static $folderStatus = null;
1132
		if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName]))
1133
		{
1134
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName]));
1135
			return $folderStatus[$this->icServer->ImapServerId][$folderName];
1136
		}
1137
		try
1138
		{
1139
			$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName,$ignoreStatusCache);
1140
		}
1141
		catch (\Exception $e)
1142
		{
1143
			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...
1144
		}
1145
		return $folderStatus[$this->icServer->ImapServerId][$folderName];
1146
	}
1147
1148
	/**
1149
	 * get IMAP folder status
1150
	 *
1151
	 * returns an array information about the imap folder, may be used as  wrapper to retrieve results from cache
1152
	 *
1153
	 * @param _folderName string the foldername
1154
	 * @param ignoreStatusCache bool ignore the cache used for counters
1155
	 * @param basicInfoOnly bool retrieve only names and stuff returned by getMailboxes
1156
	 * @param fetchSubscribedInfo bool fetch Subscribed Info on folder
1157
	 * @return array
1158
	 */
1159
	function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false,$fetchSubscribedInfo=true)
1160
	{
1161
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." called with:$_folderName,$ignoreStatusCache,$basicInfoOnly");
1162
		if (!is_string($_folderName) || empty($_folderName)||(isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID])))
1163
		{
1164
			// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
1165
			return false;
1166
		}
1167
		static $folderInfoCache = null; // reduce traffic on single request
1168
		static $folderBasicInfo = null;
1169
		if (isset($folderBasicInfo[$this->profileID]))
1170
		{
1171
			$folderInfoCache = $folderBasicInfo[$this->profileID];
1172
		}
1173
		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...
1174
		$retValue = array();
1175
		$retValue['subscribed'] = false;
1176
/*
1177
		if(!$icServer = Mail\Account::read($this->profileID)) {
1178
			if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName);
1179
			return false;
1180
		}
1181
*/
1182
		//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string(array_keys($folderInfoCache)));
1183
		// does the folder exist???
1184
		if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName]))
1185
		{
1186
			try
1187
			{
1188
				$ret = $this->icServer->getMailboxes($_folderName, 1, true);
1189
			}
1190
			catch (\Exception $e)
1191
			{
1192
				//error_log(__METHOD__.array2string($e));
1193
				//error_log(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1194
				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...
1195
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1196
				throw new Exception(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1197
			}
1198
			//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string($ret));
1199
			if (is_array($ret))
1200
			{
1201
				$retkeys = array_keys($ret);
1202
				if ($retkeys[0]==$_folderName) $folderInfoCache[$_folderName] = $ret[$retkeys[0]];
1203
			}
1204
			else
1205
			{
1206
				$folderInfoCache[$_folderName]=false;
1207
			}
1208
		}
1209
		$folderInfo = $folderInfoCache[$_folderName];
1210
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo).'->'.function_backtrace());
1211
		if($ignoreStatusCache||!$folderInfo|| !is_array($folderInfo)) {
1212
			try
1213
			{
1214
				$folderInfo = $this->_getStatus($_folderName,$ignoreStatusCache);
1215
			}
1216
			catch (\Exception $e)
1217
			{
1218
				//error_log(__METHOD__.array2string($e));
1219
				error_log(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
1220
				self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
1221
				Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
1222
				//throw new Exception(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
1223
				$folderInfo=null;
1224
			}
1225
			if (!is_array($folderInfo))
1226
			{
1227
				// no folder info, but there is a status returned for the folder: something is wrong, try to cope with it
1228
				$folderInfo = is_array($folderInfo)?$folderInfo:array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(),
1229
					'ATTRIBUTES' => '');
1230
				if (!isset($folderInfo['HIERACHY_DELIMITER']) || empty($folderInfo['HIERACHY_DELIMITER']) || (isset($folderInfo['delimiter']) && empty($folderInfo['delimiter'])))
1231
				{
1232
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo));
1233
					$folderInfo['HIERACHY_DELIMITER'] = $this->getHierarchyDelimiter();
1234
				}
1235
			}
1236
		}
1237
		#if(!is_array($folderInfo)) {
1238
		#	return false;
1239
		#}
1240
		$retValue['delimiter']		= (isset($folderInfo['HIERACHY_DELIMITER']) && $folderInfo['HIERACHY_DELIMITER']?$folderInfo['HIERACHY_DELIMITER']:$folderInfo['delimiter']);
1241
		$retValue['attributes']		= (isset($folderInfo['ATTRIBUTES']) && $folderInfo['ATTRIBUTES']?$folderInfo['ATTRIBUTES']:$folderInfo['attributes']);
1242
		$shortNameParts			= explode($retValue['delimiter'], $_folderName);
1243
		$retValue['shortName']		= array_pop($shortNameParts);
1244
		$retValue['displayName']	= $_folderName;
1245
		$retValue['shortDisplayName']	= $retValue['shortName'];
1246
		if(strtoupper($retValue['shortName']) == 'INBOX') {
1247
			$retValue['displayName']	= lang('INBOX');
1248
			$retValue['shortDisplayName']	= lang('INBOX');
1249
		}
1250
		// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
1251
		elseif (in_array($retValue['shortName'],self::$autoFolders))
1252
		{
1253
			$retValue['displayName'] = $retValue['shortDisplayName'] = lang($retValue['shortName']);
1254
		}
1255
		if ($folderInfo) $folderBasicInfo[$this->profileID][$_folderName]=$retValue;
1256
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.$_folderName.array2string($retValue['attributes']));
1257 View Code Duplication
		if ($basicInfoOnly || (isset($retValue['attributes']) && stripos(array2string($retValue['attributes']),'noselect')!==false))
1258
		{
1259
			return $retValue;
1260
		}
1261
		// fetch all in one go for one request, instead of querying them one by one
1262
		// cache it for a minute 60*60*1
1263
		// this should reduce communication to the imap server
1264
		static $subscribedFolders = null;
1265
		static $nameSpace = null;
1266
		static $prefix = null;
1267
		if (is_null($nameSpace) || empty($nameSpace[$this->profileID])) $nameSpace[$this->profileID] = $this->_getNameSpaces();
1268
		if (!empty($nameSpace[$this->profileID]))
1269
		{
1270
			$nsNoPersonal=array();
1271
			foreach($nameSpace[$this->profileID] as &$ns)
1272
			{
1273
				if ($ns['type']!='personal') $nsNoPersonal[]=$ns;
1274
			}
1275
			$nameSpace[$this->profileID]=$nsNoPersonal;
1276
		}
1277
		if (is_null($prefix) || empty($prefix[$this->profileID]) || empty($prefix[$this->profileID][$_folderName])) $prefix[$this->profileID][$_folderName] = $this->getFolderPrefixFromNamespace($nameSpace[$this->profileID], $_folderName);
1278
1279
		if ($fetchSubscribedInfo && is_null($subscribedFolders) || empty($subscribedFolders[$this->profileID]))
1280
		{
1281
			$subscribedFolders[$this->profileID] = $this->icServer->listSubscribedMailboxes();
1282
		}
1283
1284
		if($fetchSubscribedInfo && is_array($subscribedFolders[$this->profileID]) && in_array($_folderName,$subscribedFolders[$this->profileID])) {
1285
			$retValue['subscribed'] = true;
1286
		}
1287
1288
		try
1289
		{
1290
			//$folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache);
1291
			$folderStatus = $this->getMailBoxCounters($_folderName,false);
1292
			$retValue['messages']		= $folderStatus['MESSAGES'];
1293
			$retValue['recent']		= $folderStatus['RECENT'];
1294
			$retValue['uidnext']		= $folderStatus['UIDNEXT'];
1295
			$retValue['uidvalidity']	= $folderStatus['UIDVALIDITY'];
1296
			$retValue['unseen']		= $folderStatus['UNSEEN'];
1297
			if (//$retValue['unseen']==0 &&
1298
				(isset($this->mailPreferences['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information
1299
				$this->mailPreferences['trustServersUnseenInfo']==false) ||
1300
				(isset($this->mailPreferences['trustServersUnseenInfo']) &&
1301
				$this->mailPreferences['trustServersUnseenInfo']==2 &&
1302
				$prefix[$this->profileID][$_folderName] != '' && stripos($_folderName,$prefix[$this->profileID][$_folderName]) !== false)
1303
			)
1304
			{
1305
				//error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences['trustServersUnseenInfo']);
1306
				// we filter for the combined status of unseen and undeleted, as this is what we show in list
1307
				try
1308
				{
1309
					$byUid=true;
1310
					$_reverse=1;
1311
					$sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse, array('status'=>array('UNSEEN','UNDELETED')),$byUid,false);
1312
					$retValue['unseen'] = $sortResult['count'];
1313
				}
1314
				catch (\Exception $ee)
1315
				{
1316
					if (self::$debug) error_log(__METHOD__." could not fetch/calculate unseen counter for $_folderName Reason:'".$ee->getMessage()."' but requested.");
1317
				}
1318
			}
1319
		}
1320
		catch (\Exception $e)
1321
		{
1322
			if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($e->getMessage(),true));
1323
		}
1324
1325
		return $retValue;
1326
	}
1327
1328
	/**
1329
	 * getHeaders
1330
	 *
1331
	 * this function is a wrapper function for getSortedList and populates the resultList thereof with headerdata
1332
	 *
1333
	 * @param string $_folderName
1334
	 * @param int $_startMessage
1335
	 * @param int $_numberOfMessages number of messages to return
1336
	 * @param array $_sort sort by criteria
1337
	 * @param boolean $_reverse reverse sorting of the result array (may be switched, as it is passed to getSortedList by reference)
1338
	 * @param array $_filter filter to apply to getSortedList
1339
	 * @param mixed $_thisUIDOnly = null, if given fetch the headers of this uid only (either one, or array of uids)
1340
	 * @param boolean $_cacheResult = true try touse the cache of getSortedList
1341
	 * @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))
1342
	 * @return array result as array(header=>array,total=>int,first=>int,last=>int)
1343
	 */
1344
	function getHeaders($_folderName, $_startMessage, $_numberOfMessages, $_sort, $_reverse, $_filter, $_thisUIDOnly=null, $_cacheResult=true, $_fetchPreviews=false)
1345
	{
1346
		//self::$debug=true;
1347
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.function_backtrace());
1348 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName,$_startMessage, $_numberOfMessages, $_sort, $_reverse, ".array2string($_filter).", $_thisUIDOnly");
1349
		$reverse = (bool)$_reverse;
1350
		// get the list of messages to fetch
1351
		$this->reopen($_folderName);
1352
		//$currentFolder = $this->icServer->getCurrentMailbox();
1353
		//if ($currentFolder != $_folderName); $this->icServer->openMailbox($_folderName);
1354
		$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
1355
		#print "<pre>";
1356
		#$this->icServer->setDebug(true);
1357
		$total=0;
1358
		if ($_thisUIDOnly === null)
1359
		{
1360
			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...
1361
			{
1362
				// 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
1363
				// if sort capability is applied to the range fetched, not sort first and fetch the range afterwards
1364
				//$start = $_startMessage-1;
1365
				//$end = $_startMessage-1+$_numberOfMessages;
1366
				//$_filter['range'] ="$start:$end";
1367
				//$_filter['range'] ="$_startMessage:*";
1368
			}
1369 View Code Duplication
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_sort, $reverse, ".array2string($_filter).", $rByUid");
1370
			if (self::$debug||self::$debugTimes) $starttime = microtime (true);
1371
			//see this example below for a 12 week datefilter (since)
1372
			//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $starttime-(3600*24*7*12)));
1373
			$_sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
1374
			$sortResult = $_sortResult['match']->ids;
1375
			//$modseq = $_sortResult['modseq'];
1376
			//error_log(__METHOD__.' ('.__LINE__.') '.'Modsequence:'.$modseq);
1377
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' call getSortedList for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly),__METHOD__.' ('.__LINE__.') ');
1378
1379
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1380
			#$this->icServer->setDebug(false);
1381
			#print "</pre>";
1382
			// nothing found
1383
			if(!is_array($sortResult) || empty($sortResult)) {
1384
				$retValue = array();
1385
				$retValue['info']['total']	= 0;
1386
				$retValue['info']['first']	= 0;
1387
				$retValue['info']['last']	= 0;
1388
				return $retValue;
1389
			}
1390
1391
			$total = $_sortResult['count'];
1392
			#_debug_array($sortResult);
1393
			#_debug_array(array_slice($sortResult, -5, -2));
1394
			//error_log("REVERSE: $reverse");
1395
			if($reverse === true) {
1396
				if  ($_startMessage<=$total)
1397
				{
1398
					$startMessage = $_startMessage-1;
1399
				}
1400
				else
1401
				{
1402
					//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1403
					if ($_startMessage+$_numberOfMessages>$total)
1404
					{
1405
						$numberOfMessages = $total%$_numberOfMessages;
1406
						//$numberOfMessages = abs($_startMessage-$total-1);
1407
						if ($numberOfMessages>0 && $numberOfMessages<=$_numberOfMessages) $_numberOfMessages = $numberOfMessages;
1408
						//error_log(__METHOD__.' ('.__LINE__.') '.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total);
1409
					}
1410
					$startMessage=($total-$_numberOfMessages)-1;
1411
					//$retValue['info']['first'] = $startMessage;
1412
					//$retValue['info']['last'] = $total;
1413
1414
				}
1415
				if ($startMessage+$_numberOfMessages>$total)
1416
				{
1417
					$_numberOfMessages = $_numberOfMessages-($total-($startMessage+$_numberOfMessages));
1418
					//$retValue['info']['first'] = $startMessage;
1419
					//$retValue['info']['last'] = $total;
1420
				}
1421
				if($startMessage > 0) {
1422 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+$startMessage)).', '.-$startMessage.' Number of Messages:'.count($sortResult));
1423
					$sortResult = array_slice($sortResult, -($_numberOfMessages+$startMessage), -$startMessage);
1424
				} else {
1425 View Code Duplication
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.(-($_numberOfMessages+($_startMessage-1))).', AllTheRest, Number of Messages:'.count($sortResult));
1426
					$sortResult = array_slice($sortResult, -($_numberOfMessages+($_startMessage-1)));
1427
				}
1428
				$sortResult = array_reverse($sortResult);
1429
			} else {
1430 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' StartMessage:'.($_startMessage-1).', '.$_numberOfMessages.' Number of Messages:'.count($sortResult));
1431
				$sortResult = array_slice($sortResult, $_startMessage-1, $_numberOfMessages);
1432
			}
1433
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.array2string($sortResult));
1434
		}
1435
		else
1436
		{
1437
			$sortResult = (is_array($_thisUIDOnly) ? $_thisUIDOnly:(array)$_thisUIDOnly);
1438
		}
1439
1440
1441
		// fetch the data for the selected messages
1442
		if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1443
		try
1444
		{
1445
			$uidsToFetch = new Horde_Imap_Client_Ids();
1446
			$uidsToFetch->add($sortResult);
1447
1448
			$fquery = new Horde_Imap_Client_Fetch_Query();
1449
1450
			// Pre-cache the headers we want, 'fetchHeaders' is a label into the cache
1451
			$fquery->headers('fetchHeaders',array(
1452
				'DISPOSITION-NOTIFICATION-TO','RETURN-RECEIPT-TO','X-CONFIRM-READING-TO',
1453
				'DATE','SUBJECT','FROM','TO','CC',
1454
				'X-PRIORITY'
1455
			),array(
1456
				// Cache headers, we'll look at them below
1457
				'cache' => true,//$_cacheResult,
1458
				// Set peek so messages are not flagged as read
1459
				'peek' => true
1460
			));
1461
			$fquery->size();
1462
			$fquery->structure();
1463
			$fquery->flags();
1464
			$fquery->imapDate();// needed to ensure getImapDate fetches the internaldate, not the current time
1465
			// if $_fetchPreviews is activated fetch part of the messages too
1466
			if ($_fetchPreviews) $fquery->fullText(array('peek'=>true,'length'=>((int)$_fetchPreviews<5000?5000:$_fetchPreviews),'start'=>0));
1467
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
1468
				'ids' => $uidsToFetch,
1469
			));
1470
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headersNew->ids()));
1471
		}
1472
		catch (\Exception $e)
1473
		{
1474
			$headersNew = array();
1475
			$sortResult = array();
1476
		}
1477
		if (self::$debug||self::$debugTimes)
1478
		{
1479
			self::logRunTimes($starttime,null,'HordeFetch: for Folder:'.$_folderName.' Filter:'.array2string($_filter),__METHOD__.' ('.__LINE__.') ');
1480
			if (self::$debug)
1481
			{
1482
				$queryString = implode(',', $sortResult);
1483
				error_log(__METHOD__.' ('.__LINE__.') '.' Query:'.$queryString.' Result:'.array2string($headersNew));
1484
			}
1485
		}
1486
1487
		$cnt = 0;
1488
1489
		foreach((array)$sortResult as $uid) {
1490
			$sortOrder[$uid] = $cnt++;
1491
		}
1492
1493
		$count = 0;
1494
		if (is_object($headersNew)) {
1495
			if (self::$debug||self::$debugTimes) $starttime = microtime(true);
1496
			foreach($headersNew->ids() as $id) {
1497
				$_headerObject = $headersNew->get($id);
1498
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject));
1499
				$headerObject = array();
1500
				$bodyPreview = null;
1501
				$uid = $headerObject['UID']= ($_headerObject->getUid()?$_headerObject->getUid():$id);
1502
				$headerObject['MSG_NUM'] = $_headerObject->getSeq();
1503
				$headerObject['SIZE'] = $_headerObject->getSize();
1504
				$headerObject['INTERNALDATE'] = $_headerObject->getImapDate();
1505
1506
				// Get already cached headers, 'fetchHeaders' is a label matchimg above
1507
				$headerForPrio = $_headerObject->getHeaders('fetchHeaders',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1508
				// Try to fetch header with key='' as some servers might have no fetchHeaders index. e.g. yandex.com
1509
				if (empty($headerForPrio)) $headerForPrio = $_headerObject->getHeaders('',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
1510
				//fetch the fullMsg part if all conditions match to be available in case $_headerObject->getHeaders returns
1511
				//nothing worthwhile (as it does for googlemail accounts, when preview is switched on
1512
				if ($_fetchPreviews)
1513
				{
1514
					// on enabled preview $bodyPreview is needed lateron. fetched here, for fallback-reasons
1515
					// in case of failed Header-Retrieval
1516
					$bodyPreview = $_headerObject->getFullMsg();
1517
					if (empty($headerForPrio)||(is_array($headerForPrio)&&count($headerForPrio)===1&&$headerForPrio['']))
1518
					{
1519
						$length = strpos($bodyPreview, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
1520
						if ($length===false) $length = strlen($bodyPreview);
1521
						$headerForPrio =  Horde_Mime_Headers::parseHeaders(substr($bodyPreview, 0,$length))->toArray();
1522
					}
1523
				}
1524
				$headerForPrio = array_change_key_case($headerForPrio, CASE_UPPER);
1525
				if (self::$debug) {
1526
					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'));
1527
					error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerForPrio));
1528
				}
1529
				// message deleted from server but cache still reporting its existence ; may happen on QRESYNC with No permanent modsequences
1530
				if (empty($headerForPrio))
1531
				{
1532
					$total--;
1533
					continue;
1534
				}
1535
				if ( isset($headerForPrio['DISPOSITION-NOTIFICATION-TO']) ) {
1536
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['DISPOSITION-NOTIFICATION-TO']));
1537
				} else if ( isset($headerForPrio['RETURN-RECEIPT-TO']) ) {
1538
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['RETURN-RECEIPT-TO']));
1539
				} else if ( isset($headerForPrio['X-CONFIRM-READING-TO']) ) {
1540
					$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['X-CONFIRM-READING-TO']));
1541
				} /*else $sent_not = "";*/
1542
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1543
				$headerObject['DATE'] = $headerForPrio['DATE'];
1544
				$headerObject['SUBJECT'] = (is_array($headerForPrio['SUBJECT'])?$headerForPrio['SUBJECT'][0]:$headerForPrio['SUBJECT']);
1545
				$headerObject['FROM'] = (array)($headerForPrio['FROM']?$headerForPrio['FROM']:($headerForPrio['REPLY-TO']?$headerForPrio['REPLY-TO']:$headerForPrio['RETURN-PATH']));
1546
				$headerObject['TO'] = (array)$headerForPrio['TO'];
1547
				$headerObject['CC'] = isset($headerForPrio['CC'])?(array)$headerForPrio['CC']:array();
1548
				$headerObject['PRIORITY'] = isset($headerForPrio['X-PRIORITY'])?$headerForPrio['X-PRIORITY']:null;
1549
				foreach (array('FROM','TO','CC') as $key)
1550
				{
1551
					$address = array();
1552
					foreach ($headerObject[$key] as $k => $ad)
1553
					{
1554
						//the commented section below IS a simplified version of the section "make sure ..."
1555
						/*
1556
						if (stripos($ad,'@')===false)
1557
						{
1558
							$remember=$k;
1559
						}
1560
						else
1561
						{
1562
							$address[] = (!is_null($remember)?$headerObject[$key][$remember].' ':'').$ad;
1563
							$remember=null;
1564
						}
1565
						*/
1566
						// make sure addresses are real emailaddresses one by one in the array as expected
1567
						$rfcAddr = self::parseAddressList($ad); // does some fixing of known problems too
1568
						foreach ($rfcAddr as $_rfcAddr)
1569
						{
1570
							if (!$_rfcAddr->valid)	continue; // skip. not a valid address
1571
							$address[] = imap_rfc822_write_address($_rfcAddr->mailbox,$_rfcAddr->host,$_rfcAddr->personal);
1572
						}
1573
					}
1574
					$headerObject[$key] = $address;
1575
				}
1576
				$headerObject['FLAGS'] = $_headerObject->getFlags();
1577
				$headerObject['BODYPREVIEW']=null;
1578
				// this section fetches part of the message-body (if enabled) for some kind of preview
1579
				// if we fail to succeed, we fall back to the retrieval of the message-body with
1580
				// fetchPartContents (see below, when we iterate over the structure to determine the
1581
				// existance (and the details) for attachments)
1582
				if ($_fetchPreviews)
1583
				{
1584
					// $bodyPreview is populated at the beginning of the loop, as it may be
1585
					// needed to parse the Headers of the Message
1586
					if (empty($bodyPreview)) $bodyPreview = $_headerObject->getFullMsg();
1587
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($bodyPreview));
1588
					$base = Horde_Mime_Part::parseMessage($bodyPreview);
1589
					foreach($base->partIterator() as $part)
1590
					{
1591
						//error_log(__METHOD__.__LINE__.'Part:'.$part->getPrimaryType());
1592
						if (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'text')
1593
						{
1594
							$charset = $part->getContentTypeParameter('charset');
1595
							$buffer = Mail\Html::convertHTMLToText($part->toString(array(
1596
												'encode' => Horde_Mime_Part::ENCODE_BINARY,	// otherwise we cant recode charset
1597
											)), $charset, 'utf-8');
1598
							$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...
1599
						} 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...
1600
						{
1601
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part));
1602
						}
1603
					}
1604
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject['BODYPREVIEW']));
1605
				}
1606
				$mailStructureObject = $_headerObject->getStructure();
1607
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
1608
				//error_log(__METHOD__.' ('.__LINE__.') '.' MimeMap:'.array2string($mailStructureObject->contentTypeMap()));
1609
				//foreach ($_headerObject->getStructure()->getParts() as $p => $part)
1610
				$headerObject['ATTACHMENTS']=null;
1611
				$skipParts=array();
1612
				$messageMimeType='';
1613
				foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
1614
				{
1615
					if ($mime_id==0 || $messageMimeType==='') $messageMimeType = $mime_type;
1616
					$part = $mailStructureObject->getPart($mime_id);
1617
					$partdisposition = $part->getDisposition();
1618
					$partPrimaryType = $part->getPrimaryType();
1619
					// this section fetches the body for the purpose of previewing a few lines
1620
					// drawback here it is talking to the mailserver for each mail thus consuming
1621
					// more time than expected; so we call this section only when there is no
1622
					// bodypreview could be found (multipart/....)
1623
					if ($_fetchPreviews && empty($headerObject['BODYPREVIEW'])&&($partPrimaryType == 'text') &&
1624
						((intval($mime_id) === 1) || !$mime_id) &&
1625
						($partdisposition !== 'attachment')) {
1626
							$_structure=$part;
1627
							$this->fetchPartContents($uid, $_structure, false,true);
1628
							$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...
1629
							$charSet=Translation::detect_encoding($headerObject['BODYPREVIEW']);
1630
							// add line breaks to $bodyParts
1631
							//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
1632
							$headerObject['BODYPREVIEW']  = Translation::convert_jsonsafe($headerObject['BODYPREVIEW'], $charSet);
1633
							//error_log(__METHOD__.__LINE__.$headerObject['BODYPREVIEW']);
1634
					}
1635
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType);
1636
					$cid = $part->getContentId();
1637
					if (empty($partdisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
1638
					{
1639
						// the presence of an cid does not necessarily indicate its inline. it may lack the needed
1640
						// link to show the image. Considering this: we "list" everything that matches the above criteria
1641
						// as attachment in order to not loose/miss information on our data
1642
						$partdisposition='attachment';//($partPrimaryType == 'image'&&!empty($cid)?'inline':'attachment');
1643
					}
1644 View Code Duplication
					if ($mime_type=='message/rfc822')
1645
					{
1646
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
1647
						foreach($part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
1648
					}
1649
					//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType.' Skip:'.array2string($skipParts));
1650
					if (array_key_exists($mime_id,$skipParts)) continue;
1651
					if ($partdisposition=='attachment' ||
1652
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&$mime_type=='image/tiff') || // as we are not able to display tiffs
1653
						($partdisposition=='inline'&&$partPrimaryType == 'image'&&empty($cid)) ||
1654
						($partdisposition=='inline' && $partPrimaryType != 'image' && $partPrimaryType != 'multipart' && $partPrimaryType != 'text'))
1655
					{
1656
						$headerObject['ATTACHMENTS'][$mime_id]=$part->getAllDispositionParameters();
1657
						$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=$mime_type;
1658
						$headerObject['ATTACHMENTS'][$mime_id]['uid']=$uid;
1659
						$headerObject['ATTACHMENTS'][$mime_id]['cid'] = $cid;
1660
						$headerObject['ATTACHMENTS'][$mime_id]['partID']=$mime_id;
1661
						if (!isset($headerObject['ATTACHMENTS'][$mime_id]['name']))$headerObject['ATTACHMENTS'][$mime_id]['name']=$part->getName();
1662
						if (!strcasecmp($headerObject['ATTACHMENTS'][$mime_id]['name'],'winmail.dat') ||
1663
							$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=='application/ms-tnef')
1664
						{
1665
							$headerObject['ATTACHMENTS'][$mime_id]['is_winmail'] = true;
1666
						}
1667
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getName()));
1668
						//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getAllDispositionParameters()));
1669
						//error_log(__METHOD__.' ('.__LINE__.') '.' Attachment:'.$mime_id.'->'.array2string($headerObject['ATTACHMENTS'][$mime_id]));
1670
					}
1671
				}
1672
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (plain):'.array2string($mailStructureObject->findBody('plain')));
1673
				//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (html):'.array2string($mailStructureObject->findBody('html')));
1674
				//if($count == 0) error_log(__METHOD__.array2string($headerObject));
1675
				if (empty($headerObject['UID'])) continue;
1676
				//$uid = ($rByUid ? $headerObject['UID'] : $headerObject['MSG_NUM']);
1677
				// make dates like "Mon, 23 Apr 2007 10:11:06 UT" working with strtotime
1678
				if(substr($headerObject['DATE'],-2) === 'UT') {
1679
					$headerObject['DATE'] .= 'C';
1680
				}
1681
				if(substr($headerObject['INTERNALDATE'],-2) === 'UT') {
1682
					$headerObject['INTERNALDATE'] .= 'C';
1683
				}
1684
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$headerObject['SUBJECT'].'->'.$headerObject['DATE'].'<->'.$headerObject['INTERNALDATE'] .'#');
1685
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']);
1686
				if (isset($headerObject['ATTACHMENTS']) && count($headerObject['ATTACHMENTS'])) foreach ($headerObject['ATTACHMENTS'] as &$a) { $retValue['header'][$sortOrder[$uid]]['attachments'][]=$a;}
1687
				$retValue['header'][$sortOrder[$uid]]['subject']	= $this->decode_subject($headerObject['SUBJECT']);
1688
				$retValue['header'][$sortOrder[$uid]]['size'] 		= $headerObject['SIZE'];
1689
				$retValue['header'][$sortOrder[$uid]]['date']		= self::_strtotime(($headerObject['DATE']&&!($headerObject['DATE']=='NIL')?$headerObject['DATE']:$headerObject['INTERNALDATE']),'ts',true);
1690
				$retValue['header'][$sortOrder[$uid]]['internaldate']= self::_strtotime($headerObject['INTERNALDATE'],'ts',true);
1691
				$retValue['header'][$sortOrder[$uid]]['mimetype']	= $messageMimeType;
1692
				$retValue['header'][$sortOrder[$uid]]['id']		= $headerObject['MSG_NUM'];
1693
				$retValue['header'][$sortOrder[$uid]]['uid']		= $headerObject['UID'];
1694
				$retValue['header'][$sortOrder[$uid]]['bodypreview']		= $headerObject['BODYPREVIEW'];
1695
				$retValue['header'][$sortOrder[$uid]]['priority']		= ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3);
1696
				//error_log(__METHOD__.' ('.__LINE__.') '.' '.array2string($retValue['header'][$sortOrder[$uid]]));
1697
				if (isset($headerObject['DISPOSITION-NOTIFICATION-TO'])) $retValue['header'][$sortOrder[$uid]]['disposition-notification-to'] = $headerObject['DISPOSITION-NOTIFICATION-TO'];
1698
				if (is_array($headerObject['FLAGS'])) {
1699
					$retValue['header'][$sortOrder[$uid]] = array_merge($retValue['header'][$sortOrder[$uid]],self::prepareFlagsArray($headerObject));
1700
				}
1701
				//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].'->'.array2string($_headerObject->getEnvelope()->__get('from')));
1702
				if(is_array($headerObject['FROM']) && $headerObject['FROM'][0]) {
1703
					$retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0],true);
1704
				}
1705
				if(is_array($headerObject['TO']) && $headerObject['TO'][0]) {
1706
					$retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0],true);
1707
					if (count($headerObject['TO'])>1)
1708
					{
1709
						$ki=0;
1710 View Code Duplication
						foreach($headerObject['TO'] as $k => $add)
1711
						{
1712
							if ($k==0) continue;
1713
							//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1714
							$retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki] = self::decode_header($add,true);
1715
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1716
							$ki++;
1717
						}
1718
					}
1719
				}
1720
				if(is_array($headerObject['CC']) && count($headerObject['CC'])>0) {
1721
					$ki=0;
1722 View Code Duplication
					foreach($headerObject['CC'] as $k => $add)
1723
					{
1724
						//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
1725
						$retValue['header'][$sortOrder[$uid]]['cc_addresses'][$ki] = self::decode_header($add,true);
1726
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
1727
						$ki++;
1728
					}
1729
				}
1730
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]));
1731
1732
				$count++;
1733
			}
1734
			if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' fetching Headers and stuff for Folder:'.$_folderName,__METHOD__.' ('.__LINE__.') ');
1735
			//self::$debug=false;
1736
			// sort the messages to the requested displayorder
1737
			if(is_array($retValue['header'])) {
1738
				$countMessages = $total;
1739
				if (isset($_filter['range'])) $countMessages = self::$folderStatusCache[$this->profileID][$_folderName]['messages'];
1740
				ksort($retValue['header']);
1741
				$retValue['info']['total']	= $total;
1742
				//if ($_startMessage>$total) $_startMessage = $total-($count-1);
1743
				$retValue['info']['first']	= $_startMessage;
1744
				$retValue['info']['last']	= $_startMessage + $count - 1 ;
1745
				return $retValue;
1746
			} else {
1747
				$retValue = array();
1748
				$retValue['info']['total']	= 0;
1749
				$retValue['info']['first']	= 0;
1750
				$retValue['info']['last']	= 0;
1751
				return $retValue;
1752
			}
1753
		} else {
1754
			if ($headersNew == null && empty($_thisUIDOnly)) error_log(__METHOD__." -> retrieval of Message Details to Query $queryString failed: ".print_r($headersNew,TRUE));
1755
			$retValue = array();
1756
			$retValue['info']['total']  = 0;
1757
			$retValue['info']['first']  = 0;
1758
			$retValue['info']['last']   = 0;
1759
			return $retValue;
1760
		}
1761
	}
1762
1763
	/**
1764
	 * static function prepareFlagsArray
1765
	 * prepare headerObject to return some standardized array to tell which flags are set for a message
1766
	 * @param array $headerObject  - array to process, a full return array from icServer->getSummary
1767
	 * @return array array of flags
1768
	 */
1769
	static function prepareFlagsArray($headerObject)
1770
	{
1771
		if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']);
1772
		$retValue = array();
1773
		$retValue['recent']		= in_array('\\recent', $headerFlags);
1774
		$retValue['flagged']	= in_array('\\flagged', $headerFlags);
1775
		$retValue['answered']	= in_array('\\answered', $headerFlags);
1776
		$retValue['forwarded']   = in_array('$forwarded', $headerFlags);
1777
		$retValue['deleted']	= in_array('\\deleted', $headerFlags);
1778
		$retValue['seen']		= in_array('\\seen', $headerFlags);
1779
		$retValue['draft']		= in_array('\\draft', $headerFlags);
1780
		$retValue['mdnsent']	= in_array('$mdnsent', $headerFlags)||in_array('mdnsent', $headerFlags);
1781
		$retValue['mdnnotsent']	= in_array('$mdnnotsent', $headerFlags)||in_array('mdnnotsent', $headerFlags);
1782
		$retValue['label1']   = in_array('$label1', $headerFlags);
1783
		$retValue['label2']   = in_array('$label2', $headerFlags);
1784
		$retValue['label3']   = in_array('$label3', $headerFlags);
1785
		$retValue['label4']   = in_array('$label4', $headerFlags);
1786
		$retValue['label5']   = in_array('$label5', $headerFlags);
1787
		//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].':'.array2string($retValue));
1788
		return $retValue;
1789
	}
1790
1791
	/**
1792
	 * fetches a sorted list of messages from the imap server
1793
	 * private function
1794
	 *
1795
	 * @todo implement sort based on Net_IMAP
1796
	 * @param string $_folderName the name of the folder in which the messages get searched
1797
	 * @param integer $_sort the primary sort key
1798
	 * @param bool $_reverse sort the messages ascending or descending
1799
	 * @param array $_filter the search filter
1800
	 * @param bool $resultByUid if set to true, the result is to be returned by uid, if the server does not reply
1801
	 * 			on a query for uids, the result may be returned by IDs only, this will be indicated by this param
1802
	 * @param bool $setSession if set to true the session will be populated with the result of the query
1803
	 * @return mixed bool/array false or array of ids
1804
	 */
1805
	function getSortedList($_folderName, $_sort, &$_reverse, $_filter, &$resultByUid=true, $setSession=true)
1806
	{
1807
		static $cachedFolderStatus = null;
1808
		// in the past we needed examineMailbox to figure out if the server with the serverID support keywords
1809
		// this information is filled/provided by examineMailbox; but caching within one request seems o.k.
1810
		if (is_null($cachedFolderStatus) || !isset($cachedFolderStatus[$this->profileID][$_folderName]) )
1811
		{
1812
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName] = $this->icServer->examineMailbox($_folderName);
1813
		}
1814
		else
1815
		{
1816
			$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName];
1817
		}
1818
		//error_log(__METHOD__.' ('.__LINE__.') '.' F:'.$_folderName.' S:'.array2string($folderStatus));
1819
		//error_log(__METHOD__.' ('.__LINE__.') '.' Filter:'.array2string($_filter));
1820
		$try2useCache = true;
1821
		static $eMailListContainsDeletedMessages = null;
1822
		if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
1823
		// this indicates, that there is no Filter set, and the returned set/subset should not contain DELETED Messages, nor filtered for UNDELETED
1824
		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...
1825
		{
1826
			if (self::$debugTimes) $starttime = microtime(true);
1827
			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);
1828
			$five=true;
1829
			$dReverse=1;
1830
			$deletedMessages = $this->getSortedList($_folderName, 0, $dReverse, array('status'=>array('DELETED')),$five,false);
1831
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') Found DeletedMessages:'.array2string($eMailListContainsDeletedMessages));
1832
			$eMailListContainsDeletedMessages[$this->profileID][$_folderName] =$deletedMessages['count'];
1833
			Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, 60*60*1);
1834
			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']);
1835
		}
1836
		$try2useCache = false;
1837
		//self::$supportsORinQuery[$this->profileID]=true;
1838
		if (is_null(self::$supportsORinQuery) || !isset(self::$supportsORinQuery[$this->profileID]))
1839
		{
1840
			self::$supportsORinQuery = Cache::getCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
1841
			if (!isset(self::$supportsORinQuery[$this->profileID])) self::$supportsORinQuery[$this->profileID]=true;
1842
		}
1843
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_filter).' SupportsOrInQuery:'.self::$supportsORinQuery[$this->profileID]);
1844
		$filter = $this->createIMAPFilter($_folderName, $_filter,self::$supportsORinQuery[$this->profileID]);
1845
		if (self::$debug)
1846
		{
1847
			$query_str = $filter->build();
1848
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query']);
1849
		}
1850
		//_debug_array($filter);
1851
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($filter).'#'.array2string($this->icServer->capability()));
1852
		if($this->icServer->hasCapability('SORT')) {
1853
			// when using an orQuery and we sort by date. sort seems to fail on certain servers => ZIMBRA with Horde_Imap_Client
1854
			// thus we translate the search request from date to Horde_Imap_Client::SORT_SEQUENCE (which should be the same, if
1855
			// there is no messing with the dates)
1856
			//if (self::$supportsORinQuery[$this->profileID]&&$_sort=='date'&&$_filter['type']=='quick'&&!empty($_filter['string']))$_sort='INTERNALDATE';
1857
			if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: ".array2string($_sort)." Reverse: $_reverse");
1858
			$sortOrder = $this->_getSortString($_sort, $_reverse);
1859
			if ($_reverse && in_array(Horde_Imap_Client::SORT_REVERSE,$sortOrder)) $_reverse=false; // as we reversed the result already
1860
			if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy:".array2string($_sort)."->".array2string($sortOrder)." Filter: ".array2string($filter));
1861
			try
1862
			{
1863
				$sortResult = $this->icServer->search($_folderName, $filter, array(
1864
					'sort' => $sortOrder,));
1865
			// if there is an Error, we assume that the server is not capable of sorting
1866
			}
1867
			catch(\Exception $e)
1868
			{
1869
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1870
				$resultByUid = false;
1871
				$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1872
				if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1873
				try
1874
				{
1875
					$sortResult = $this->icServer->search($_folderName, $filter, array(
1876
						'sort' => $sortOrder));
1877
				}
1878
				catch(\Exception $e)
1879
				{
1880
					error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1881
					$sortResult = self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'];
1882
				}
1883
			}
1884
			if (self::$debug) error_log(__METHOD__.print_r($sortResult,true));
1885
		} else {
1886
			if (self::$debug) error_log(__METHOD__." Mailserver has NO SORT Capability");
1887
			//$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
1888
			//if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
1889
			try
1890
			{
1891
				$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1892
					'sort' => $sortOrder)*/);
1893
			}
1894
			catch(\Exception $e)
1895
			{
1896
				//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
1897
				// possible error OR Query. But Horde gives no detailed Info :-(
1898
				self::$supportsORinQuery[$this->profileID]=false;
1899
				Cache::setCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),self::$supportsORinQuery,60*60*10);
1900
				if (self::$debug) error_log(__METHOD__.__LINE__." Mailserver seems to have NO OR Capability for Search:".$sortResult->message);
1901
				$filter = $this->createIMAPFilter($_folderName, $_filter, self::$supportsORinQuery[$this->profileID]);
1902
				try
1903
				{
1904
					$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
1905
						'sort' => $sortOrder)*/);
1906
				}
1907
				catch(\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1908
				{
1909
				}
1910
			}
1911
			if(is_array($sortResult['match'])) {
1912
					// not sure that this is going so succeed as $sortResult['match'] is a hordeObject
1913
					sort($sortResult['match'], SORT_NUMERIC);
1914
			}
1915
			if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1916
		}
1917
		if ($setSession)
1918
		{
1919
			self::$folderStatusCache[$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY'];
1920
			self::$folderStatusCache[$this->profileID][$_folderName]['messages']	= $folderStatus['MESSAGES'];
1921
			self::$folderStatusCache[$this->profileID][$_folderName]['deleted']	= $eMailListContainsDeletedMessages[$this->profileID][$_folderName];
1922
			self::$folderStatusCache[$this->profileID][$_folderName]['uidnext']	= $folderStatus['UIDNEXT'];
1923
			self::$folderStatusCache[$this->profileID][$_folderName]['filter']	= $_filter;
1924
			self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'] = $sortResult;
1925
			self::$folderStatusCache[$this->profileID][$_folderName]['sort']	= $_sort;
1926
		}
1927
		//error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
1928
		//_debug_array($sortResult['match']->ids);
1929
		return $sortResult;
1930
	}
1931
1932
	/**
1933
	 * convert the sort value from the gui(integer) into a string
1934
	 *
1935
	 * @param mixed _sort the integer sort order / or a valid and handeled SORTSTRING (right now: UID/ARRIVAL/INTERNALDATE (->ARRIVAL))
1936
	 * @param bool _reverse wether to add REVERSE to the Sort String or not
1937
	 * @return the sort sequence for horde search
1938
	 */
1939
	function _getSortString($_sort, $_reverse=false)
1940
	{
1941
		$_reverse=false;
1942
		if (is_numeric($_sort))
1943
		{
1944
			switch($_sort) {
1945
				case 2:
1946
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1947
					break;
1948
				case 4:
1949
					$retValue = array(Horde_Imap_Client::SORT_TO);
1950
					break;
1951
				case 3:
1952
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
1953
					break;
1954
				case 6:
1955
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
1956
					break;
1957
				case 0:
1958
				default:
1959
					$retValue = array(Horde_Imap_Client::SORT_DATE);
1960
					//$retValue = 'ARRIVAL';
1961
					break;
1962
			}
1963
		}
1964
		else
1965
		{
1966
			switch(strtoupper($_sort)) {
1967
				case 'FROMADDRESS':
1968
					$retValue = array(Horde_Imap_Client::SORT_FROM);
1969
					break;
1970
				case 'TOADDRESS':
1971
					$retValue = array(Horde_Imap_Client::SORT_TO);
1972
					break;
1973
				case 'SUBJECT':
1974
					$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
1975
					break;
1976
				case 'SIZE':
1977
					$retValue = array(Horde_Imap_Client::SORT_SIZE);
1978
					break;
1979
				case 'ARRIVAL':
1980
					$retValue = array(Horde_Imap_Client::SORT_ARRIVAL);
1981
					break;
1982
				case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date
1983
				case 'INTERNALDATE':
1984
					$retValue = array(Horde_Imap_Client::SORT_SEQUENCE);
1985
					break;
1986
				case 'DATE':
1987
				default:
1988
					$retValue = array(Horde_Imap_Client::SORT_DATE);
1989
					break;
1990
			}
1991
		}
1992
		if ($_reverse) array_unshift($retValue,Horde_Imap_Client::SORT_REVERSE);
1993
		//error_log(__METHOD__.' ('.__LINE__.') '.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue);
1994
		return $retValue;
1995
	}
1996
1997
	/**
1998
	 * this function creates an IMAP filter from the criterias given
1999
	 *
2000
	 * @param string $_folder used to determine the search to TO or FROM on QUICK Search wether it is a send-folder or not
2001
	 * @param array $_criterias contains the search/filter criteria
2002
	 * @param boolean $_supportsOrInQuery wether to use the OR Query on QuickSearch
2003
	 * @return Horde_Imap_Client_Search_Query the IMAP filter
2004
	 */
2005
	function createIMAPFilter($_folder, $_criterias, $_supportsOrInQuery=true)
2006
	{
2007
		$imapFilter = new Horde_Imap_Client_Search_Query();
2008
		$imapFilter->charset('UTF-8');
2009
2010
		//_debug_array($_criterias);
2011 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2012
		if((!is_array($_criterias) || $_criterias['status']=='any') &&
2013
			(!isset($_criterias['string']) || empty($_criterias['string'])) &&
2014
			(!isset($_criterias['range'])|| empty($_criterias['range']) ||
2015
			( !empty($_criterias['range'])&& ($_criterias['range']!='BETWEEN' && empty($_criterias['date'])||
2016
			($_criterias['range']=='BETWEEN' && empty($_criterias['since'])&& empty($_criterias['before']))))))
2017
		{
2018
			//error_log(__METHOD__.' ('.__LINE__.') returning early Criterias:'.print_r($_criterias, true));
2019
			$imapFilter->flag('DELETED', $set=false);
2020
			return $imapFilter;
2021
		}
2022
		$queryValid = false;
2023
		// statusQuery MUST be placed first, as search for subject/mailbody and such is
2024
		// depending on charset. flagSearch is not BUT messes the charset if called afterwards
2025
		$statusQueryValid = false;
2026
		foreach((array)$_criterias['status'] as $k => $criteria) {
2027
			$imapStatusFilter = new Horde_Imap_Client_Search_Query();
2028
			$imapStatusFilter->charset('UTF-8');
2029
			$criteria = strtoupper($criteria);
2030
			switch ($criteria) {
2031
				case 'ANSWERED':
2032
				case 'DELETED':
2033
				case 'FLAGGED':
2034
				case 'RECENT':
2035
				case 'SEEN':
2036
					$imapStatusFilter->flag($criteria, $set=true);
2037
					$queryValid = $statusQueryValid =true;
2038
					break;
2039 View Code Duplication
				case 'READ':
2040
					$imapStatusFilter->flag('SEEN', $set=true);
2041
					$queryValid = $statusQueryValid =true;
2042
					break;
2043
				case 'LABEL1':
2044
				case 'KEYWORD1':
2045
				case 'LABEL2':
2046
				case 'KEYWORD2':
2047
				case 'LABEL3':
2048
				case 'KEYWORD3':
2049
				case 'LABEL4':
2050
				case 'KEYWORD4':
2051
				case 'LABEL5':
2052
				case 'KEYWORD5':
2053
					$imapStatusFilter->flag(str_ireplace('KEYWORD','$LABEL',$criteria), $set=true);
2054
					$queryValid = $statusQueryValid =true;
2055
					break;
2056
				case 'NEW':
2057
					$imapStatusFilter->flag('RECENT', $set=true);
2058
					$imapStatusFilter->flag('SEEN', $set=false);
2059
					$queryValid = $statusQueryValid =true;
2060
					break;
2061
				case 'OLD':
2062
					$imapStatusFilter->flag('RECENT', $set=false);
2063
					$queryValid = $statusQueryValid =true;
2064
					break;
2065
// operate only on system flags
2066
//        $systemflags = array(
2067
//            'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
2068
//        );
2069
				case 'UNANSWERED':
2070
					$imapStatusFilter->flag('ANSWERED', $set=false);
2071
					$queryValid = $statusQueryValid =true;
2072
					break;
2073
				case 'UNDELETED':
2074
					$imapFilter->flag('DELETED', $set=false);
2075
					$queryValid = true;
2076
					break;
2077
				case 'UNFLAGGED':
2078
					$imapStatusFilter->flag('FLAGGED', $set=false);
2079
					$queryValid = $statusQueryValid =true;
2080
					break;
2081
				case 'UNREAD':
2082 View Code Duplication
				case 'UNSEEN':
2083
					$imapStatusFilter->flag('SEEN', $set=false);
2084
					$queryValid = $statusQueryValid =true;
2085
					break;
2086
				case 'UNLABEL1':
2087
				case 'UNKEYWORD1':
2088
				case 'UNLABEL2':
2089
				case 'UNKEYWORD2':
2090
				case 'UNLABEL3':
2091
				case 'UNKEYWORD3':
2092
				case 'UNLABEL4':
2093
				case 'UNKEYWORD4':
2094
				case 'UNLABEL5':
2095
				case 'UNKEYWORD5':
2096
					$imapStatusFilter->flag(str_ireplace(array('UNKEYWORD','UNLABEL'),'$LABEL',$criteria), $set=false);
2097
					$queryValid = $statusQueryValid =true;
2098
					break;
2099
				default:
2100
					$statusQueryValid = false;
2101
			}
2102
			if ($statusQueryValid)
2103
			{
2104
				$imapFilter->andSearch($imapStatusFilter);
2105
			}
2106
		}
2107
2108
2109
		//error_log(__METHOD__.' ('.__LINE__.') '.print_r($_criterias, true));
2110
		$imapSearchFilter = new Horde_Imap_Client_Search_Query();
2111
		$imapSearchFilter->charset('UTF-8');
2112
2113
		if(!empty($_criterias['string'])) {
2114
			$criteria = strtoupper($_criterias['type']);
2115
			switch ($criteria) {
2116
				case 'BYDATE':
2117
				case 'QUICK':
2118
				case 'QUICKWITHCC':
2119
					$imapSearchFilter->headerText('SUBJECT', $_criterias['string'], $not=false);
2120
					//$imapSearchFilter->charset('UTF-8');
2121
					$imapFilter2 = new Horde_Imap_Client_Search_Query();
2122
					$imapFilter2->charset('UTF-8');
2123
					if($this->isSentFolder($_folder)) {
2124
						$imapFilter2->headerText('TO', $_criterias['string'], $not=false);
2125
					} else {
2126
						$imapFilter2->headerText('FROM', $_criterias['string'], $not=false);
2127
					}
2128
					if ($_supportsOrInQuery)
2129
					{
2130
						$imapSearchFilter->orSearch($imapFilter2);
2131
					}
2132
					else
2133
					{
2134
						$imapSearchFilter->andSearch($imapFilter2);
2135
					}
2136
					if ($_supportsOrInQuery && $criteria=='QUICKWITHCC')
2137
					{
2138
						$imapFilter3 = new Horde_Imap_Client_Search_Query();
2139
						$imapFilter3->charset('UTF-8');
2140
						$imapFilter3->headerText('CC', $_criterias['string'], $not=false);
2141
						$imapSearchFilter->orSearch($imapFilter3);
2142
					}
2143
					$queryValid = true;
2144
					break;
2145
				case 'LARGER':
2146
				case 'SMALLER':
2147
					if (strlen(trim($_criterias['string'])) != strlen((float) trim($_criterias['string'])))
2148
					{
2149
						//examine string to evaluate size
2150
						$unit = strtoupper(trim(substr(trim($_criterias['string']),strlen((float) trim($_criterias['string'])))));
2151
						$multipleBy = array('KB'=>1024,'K'=>1024,
2152
											'MB'=>1024*1000,'M'=>1024*1000,
2153
											'GB'=>1024*1000*1000,'G'=>1024*1000*1000,
2154
											'TB'=>1024*1000*1000*1000,'T'=>1024*1000*1000*1000);
2155
						$numberinBytes=(float)$_criterias['string'];
2156
						if (isset($multipleBy[$unit])) $numberinBytes=(float)$_criterias['string']*$multipleBy[$unit];
2157
						//error_log(__METHOD__.__LINE__.'#'.$_criterias['string'].'->'.(float)$_criterias['string'].'#'.$unit.' ='.$numberinBytes);
2158
						$_criterias['string']=$numberinBytes;
2159
					}
2160
					$imapSearchFilter->size( $_criterias['string'], ($criteria=='LARGER'?true:false), $not=false);
2161
					//$imapSearchFilter->charset('UTF-8');
2162
					$queryValid = true;
2163
					break;
2164
				case 'FROM':
2165
				case 'TO':
2166
				case 'CC':
2167
				case 'BCC':
2168
				case 'SUBJECT':
2169
					$imapSearchFilter->headerText($criteria, $_criterias['string'], $not=false);
2170
					//$imapSearchFilter->charset('UTF-8');
2171
					$queryValid = true;
2172
					break;
2173
				case 'BODY':
2174
				case 'TEXT':
2175
					$imapSearchFilter->text($_criterias['string'],($criteria=='BODY'?true:false), $not=false);
2176
					//$imapSearchFilter->charset('UTF-8');
2177
					$queryValid = true;
2178
					break;
2179
				case 'SINCE':
2180
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2181
					$queryValid = true;
2182
					break;
2183
				case 'BEFORE':
2184
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2185
					$queryValid = true;
2186
					break;
2187 View Code Duplication
				case 'ON':
2188
					$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2189
					$queryValid = true;
2190
					break;
2191
			}
2192
		}
2193
		if ($statusQueryValid && !$queryValid) $queryValid=true;
2194
		if ($queryValid) $imapFilter->andSearch($imapSearchFilter);
2195
2196
		if (isset($_criterias['range']) && !empty($_criterias['range']))
2197
		{
2198
			$rangeValid = false;
2199
			$imapRangeFilter = new Horde_Imap_Client_Search_Query();
2200
			$imapRangeFilter->charset('UTF-8');
2201
			$criteria = strtoupper($_criterias['range']);
2202
			if ($_criterias['range'] == "BETWEEN" && isset($_criterias['since']) && isset($_criterias['before']) && $_criterias['since']==$_criterias['before'])
2203
			{
2204
				$_criterias['date']=$_criterias['since'];
2205
				unset($_criterias['since']);
2206
				unset($_criterias['before']);
2207
				$criteria=$_criterias['range']='ON';
2208
			}
2209
			switch ($criteria) {
2210
				case 'BETWEEN':
2211
					//try to be smart about missing
2212
					//enddate
2213
					if ($_criterias['since'])
2214
					{
2215
						$imapRangeFilter->dateSearch(new DateTime($_criterias['since']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2216
						$rangeValid = true;
2217
					}
2218
					//startdate
2219
					if ($_criterias['before'])
2220
					{
2221
						$imapRangeFilter2 = new Horde_Imap_Client_Search_Query();
2222
						$imapRangeFilter2->charset('UTF-8');
2223
						//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2224
						$_criterias['before'] = date("d-M-Y",DateTime::to($_criterias['before'],'ts')+(3600*24));
2225
						$imapRangeFilter2->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2226
						$imapRangeFilter->andSearch($imapRangeFilter2);
2227
						$rangeValid = true;
2228
					}
2229
					break;
2230
				case 'SINCE'://enddate
2231
					$imapRangeFilter->dateSearch(new DateTime(($_criterias['since']?$_criterias['since']:$_criterias['date'])), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
2232
					$rangeValid = true;
2233
					break;
2234
				case 'BEFORE'://startdate
2235
					//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
2236
					$_criterias['before'] = date("d-M-Y",DateTime::to(($_criterias['before']?$_criterias['before']:$_criterias['date']),'ts')+(3600*24));
2237
					$imapRangeFilter->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
2238
					$rangeValid = true;
2239
					break;
2240 View Code Duplication
				case 'ON':
2241
					$imapRangeFilter->dateSearch(new DateTime($_criterias['date']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
2242
					$rangeValid = true;
2243
					break;
2244
			}
2245
			if ($rangeValid && !$queryValid) $queryValid=true;
2246
			if ($rangeValid) $imapFilter->andSearch($imapRangeFilter);
2247
		}
2248
		if (self::$debug)
2249
		{
2250
			//$imapFilter->charset('UTF-8');
2251
			$query_str = $imapFilter->build();
2252
			//error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query'].' created by Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
2253
		}
2254
		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...
2255
			$imapFilter->flag('DELETED', $set=false);
2256
			return $imapFilter;
2257
		} else {
2258
			return $imapFilter;
2259
		}
2260
	}
2261
2262
	/**
2263
	 * decode header (or envelope information)
2264
	 * if array given, note that only values will be converted
2265
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2266
	 * @param  mixed/boolean $_tryIDNConversion (true/false AND FORCE): try IDN Conversion on domainparts of emailADRESSES
2267
	 * @return mixed - based on the input type
2268
	 */
2269
	static function decode_header($_string, $_tryIDNConversion=false)
2270
	{
2271
		if (is_array($_string))
2272
		{
2273
			foreach($_string as $k=>$v)
2274
			{
2275
				$_string[$k] = self::decode_header($v, $_tryIDNConversion);
2276
			}
2277
			return $_string;
2278
		}
2279
		else
2280
		{
2281
			$_string = Mail\Html::decodeMailHeader($_string,self::$displayCharset);
2282
			$test = @json_encode($_string);
2283
			//error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#');
2284
			if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2285
			{
2286
				// try to fix broken utf8
2287
				$x = utf8_encode($_string);
2288
				$test = @json_encode($x);
2289
				if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2290
				{
2291
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
2292
					$_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));
2293
				}
2294
				else
2295
				{
2296
					$_string = $x;
2297
				}
2298
			}
2299
2300
			if ($_tryIDNConversion===true && stripos($_string,'@')!==false)
2301
			{
2302
				$rfcAddr = self::parseAddressList($_string);
2303
				$stringA = array();
2304
				//$_string = str_replace($rfcAddr[0]->host,Horde_Idna::decode($rfcAddr[0]->host),$_string);
2305
				foreach ($rfcAddr as $_rfcAddr)
2306
				{
2307
					if (!$_rfcAddr->valid)
2308
					{
2309
						$stringA = array();
2310
						break; // skip idna conversion if we encounter an error here
2311
					}
2312
					$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox,Horde_Idna::decode($_rfcAddr->host),$_rfcAddr->personal);
2313
				}
2314
				if (!empty($stringA)) $_string = implode(',',$stringA);
2315
			}
2316
			if ($_tryIDNConversion==='FORCE')
2317
			{
2318
				//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_string.'='.Horde_Idna::decode($_string));
2319
				$_string = Horde_Idna::decode($_string);
2320
			}
2321
			return $_string;
2322
		}
2323
	}
2324
2325
	/**
2326
	 * decode subject
2327
	 * if array given, note that only values will be converted
2328
	 * @param  mixed $_string input to be converted, if array call decode_header recursively on each value
2329
	 * @param  boolean $decode try decoding
2330
	 * @return mixed - based on the input type
2331
	 */
2332
	function decode_subject($_string,$decode=true)
2333
	{
2334
		#$string = $_string;
2335
		if($_string=='NIL')
2336
		{
2337
			return 'No Subject';
2338
		}
2339
		if ($decode) $_string = self::decode_header($_string);
2340
		// make sure its utf-8
2341
		$test = @json_encode($_string);
2342
		if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
2343
		{
2344
			$_string = utf8_encode($_string);
2345
		}
2346
		return $_string;
2347
2348
	}
2349
2350
	/**
2351
	 * decodeEntityFolderName - remove html entities
2352
	 * @param string _folderName the foldername
2353
	 * @return string the converted string
2354
	 */
2355
	function decodeEntityFolderName($_folderName)
2356
	{
2357
		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...
2358
	}
2359
2360
	/**
2361
	 * convert a mailboxname from utf7-imap to displaycharset
2362
	 *
2363
	 * @param string _folderName the foldername
2364
	 * @return string the converted string
2365
	 */
2366
	function encodeFolderName($_folderName)
2367
	{
2368
		return Translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset);
2369
	}
2370
2371
	/**
2372
	 * convert the foldername from display charset to UTF-7
2373
	 *
2374
	 * @param string _parent the parent foldername
2375
	 * @return ISO-8859-1 / UTF7-IMAP encoded string
2376
	 */
2377
	function _encodeFolderName($_folderName) {
2378
		return Translation::convert($_folderName, self::$displayCharset, 'ISO-8859-1');
2379
		#return Translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
2380
	}
2381
2382
	/**
2383
	 * create a new folder under given parent folder
2384
	 *
2385
	 * @param string _parent the parent foldername
2386
	 * @param string _folderName the new foldername
2387
	 * @param string _error pass possible error back to caller
2388
	 *
2389
	 * @return mixed name of the newly created folder or false on error
2390
	 */
2391
	function createFolder($_parent, $_folderName, &$_error)
2392
	{
2393 View Code Duplication
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."->"."$_parent, $_folderName called from:".function_backtrace());
2394
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2395
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2396
2397 View Code Duplication
		if(empty($parent)) {
2398
			$newFolderName = $folderName;
2399
		} else {
2400
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2401
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2402
		}
2403
		if (empty($newFolderName)) return false;
2404
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.'->'.$newFolderName);
2405
		if ($this->folderExists($newFolderName,true))
2406
		{
2407
			if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." Folder $newFolderName already exists.");
2408
			return $newFolderName;
2409
		}
2410
		try
2411
		{
2412
			$opts = array();
2413
			// if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too
2414
			if (isset(self::$specialUseFolders[$newFolderName]))
2415
			{
2416
				$opts['special_use'] = self::$specialUseFolders[$newFolderName];
2417
			}
2418
			$this->icServer->createMailbox($newFolderName, $opts);
2419
		}
2420
		catch (\Exception $e)
2421
		{
2422
			$_error = lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());
2423
			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...
2424
			return false;
2425
		}
2426
		try
2427
		{
2428
			$this->icServer->subscribeMailbox($newFolderName);
2429
		}
2430
		catch (\Exception $e)
2431
		{
2432
			error_log(__METHOD__.' ('.__LINE__.') '.' subscribe to new folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details);
2433
			return false;
2434
		}
2435
2436
		return $newFolderName;
2437
	}
2438
2439
	/**
2440
	 * rename a folder
2441
	 *
2442
	 * @param string _oldFolderName the old foldername
2443
	 * @param string _parent the parent foldername
2444
	 * @param string _folderName the new foldername
2445
	 *
2446
	 * @return mixed name of the newly created folder or false on error
2447
	 * @throws Exception
2448
	 */
2449
	function renameFolder($_oldFolderName, $_parent, $_folderName)
2450
	{
2451
		$oldFolderName	= $_oldFolderName;//$this->_encodeFolderName($_oldFolderName);
2452
		$parent		= $_parent;//$this->_encodeFolderName($_parent);
2453
		$folderName	= $_folderName;//$this->_encodeFolderName($_folderName);
2454
2455 View Code Duplication
		if(empty($parent)) {
2456
			$newFolderName = $folderName;
2457
		} else {
2458
			$HierarchyDelimiter = $this->getHierarchyDelimiter();
2459
			$newFolderName = $parent . $HierarchyDelimiter . $folderName;
2460
		}
2461
		if (self::$debug) error_log("create folder: $newFolderName");
2462
		try
2463
		{
2464
			$this->icServer->renameMailbox($oldFolderName, $newFolderName);
2465
		}
2466
		catch (\Exception $e)
2467
		{
2468
			throw new Exception(__METHOD__." failed for $oldFolderName (rename to: $newFolderName) with error:".$e->getMessage());;
2469
		}
2470
		// clear FolderExistsInfoCache
2471
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2472
2473
		return $newFolderName;
2474
2475
	}
2476
2477
	/**
2478
	 * delete an existing folder
2479
	 *
2480
	 * @param string _folderName the name of the folder to be deleted
2481
	 *
2482
	 * @return bool true on success, PEAR Error on failure
2483
	 * @throws Exception
2484
	 */
2485
	function deleteFolder($_folderName)
2486
	{
2487
		//$folderName = $this->_encodeFolderName($_folderName);
2488
		try
2489
		{
2490
			$this->icServer->subscribeMailbox($_folderName,false);
2491
			$this->icServer->deleteMailbox($_folderName);
2492
		}
2493
		catch (\Exception $e)
2494
		{
2495
			throw new Exception("Deleting Folder $_folderName failed! Error:".$e->getMessage());;
2496
		}
2497
		// clear FolderExistsInfoCache
2498
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
2499
2500
		return true;
2501
	}
2502
2503
	/**
2504
	 * fetchUnSubscribedFolders: get unsubscribed IMAP folder list
2505
	 *
2506
	 * returns an array of unsubscribed IMAP folder names.
2507
	 *
2508
	 * @return array with folder names. eg.: 1 => INBOX/TEST
2509
	 */
2510
	function fetchUnSubscribedFolders()
2511
	{
2512
		$unSubscribedMailboxes = $this->icServer->listUnSubscribedMailboxes();
2513
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($unSubscribedMailboxes));
2514
		return $unSubscribedMailboxes;
2515
	}
2516
2517
	/**
2518
	 * get IMAP folder objects
2519
	 *
2520
	 * returns an array of IMAP folder objects. Put INBOX folder in first
2521
	 * position. Preserves the folder seperator for later use. The returned
2522
	 * array is indexed using the foldername. Use cachedObjects when retrieving subscribedFolders
2523
	 *
2524
	 * @param boolean _subscribedOnly  get subscribed or all folders
2525
	 * @param boolean _getCounters   get get messages counters
2526
	 * @param boolean _alwaysGetDefaultFolders  this triggers to ignore the possible notavailableautofolders - preference
2527
	 *			as activeSync needs all folders like sent, trash, drafts, templates and outbox - if not present devices may crash
2528
	 *			-> autoFolders should be created if needed / accessed (if possible and configured)
2529
	 * @param boolean _useCacheIfPossible  - if set to false cache will be ignored and reinitialized
2530
	 *
2531
	 * @return array with folder objects. eg.: INBOX => {inbox object}
2532
	 */
2533
	function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false,$_useCacheIfPossible=true)
2534
	{
2535
		if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' ServerID:'.$this->icServer->ImapServerId.", subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders, _useCacheIfPossible:$_useCacheIfPossible");
2536
		if (self::$debugTimes) $starttime = microtime (true);
2537
		static $folders2return;
2538
		//$_subscribedOnly=false;
2539
		// always use static on single request if info is available;
2540
		// so if you require subscribed/unsubscribed results on a single request you MUST
2541
		// set $_useCacheIfPossible to false !
2542 View Code Duplication
		if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2543
		{
2544
			if (self::$debugTimes) self::logRunTimes($starttime,null,'using static',__METHOD__.' ('.__LINE__.') ');
2545
			return $folders2return[$this->icServer->ImapServerId];
2546
		}
2547
2548
		if ($_subscribedOnly && $_getCounters===false)
2549
		{
2550
			if (is_null($folders2return)) $folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
2551 View Code Duplication
			if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
2552
			{
2553
				//error_log(__METHOD__.' ('.__LINE__.') '.' using Cached folderObjects'.array2string($folders2return[$this->icServer->ImapServerId]));
2554
				if (self::$debugTimes) self::logRunTimes($starttime,null,'from Cache',__METHOD__.' ('.__LINE__.') ');
2555
				return $folders2return[$this->icServer->ImapServerId];
2556
			}
2557
		}
2558
		// use $folderBasicInfo for holding attributes and other basic folderinfo $folderBasicInfo[$this->icServer->ImapServerId]
2559
		static $folderBasicInfo;
2560 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);
2561
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($folderBasicInfo[$this->icServer->ImapServerId])));
2562
2563
		$delimiter = $this->getHierarchyDelimiter();
2564
2565
		$inboxData = new \stdClass;
2566
		$inboxData->name 		= 'INBOX';
2567
		$inboxData->folderName		= 'INBOX';
2568
		$inboxData->displayName		= lang('INBOX');
2569
		$inboxData->delimiter 		= $delimiter;
2570
		$inboxData->shortFolderName	= 'INBOX';
2571
		$inboxData->shortDisplayName	= lang('INBOX');
2572
		$inboxData->subscribed = true;
2573
		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...
2574
			$inboxData->counter = $this->getMailBoxCounters('INBOX');
2575
		}
2576
		// force unsubscribed by preference showAllFoldersInFolderPane
2577
		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...
2578
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2579
			$this->mailPreferences['showAllFoldersInFolderPane']==1)
2580
		{
2581
			$_subscribedOnly = false;
2582
		}
2583
		$inboxFolderObject = array('INBOX' => $inboxData);
2584
2585
		//$nameSpace = $this->icServer->getNameSpaces();
2586
		$nameSpace = $this->_getNameSpaces();
2587
		$fetchedAllInOneGo = false;
2588
		$subscribedFoldersForCache = $foldersNameSpace = array();
2589
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2590
		if (is_array($nameSpace))
2591
		{
2592
			foreach($nameSpace as $k => $singleNameSpace) {
2593
				$type = $singleNameSpace['type'];
2594
				// the following line (assumption that for the same namespace the delimiter should be equal) may be wrong
2595
				$foldersNameSpace[$type]['delimiter']  = $singleNameSpace['delimiter'];
2596
2597
				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...
2598
					// fetch and sort the subscribed folders
2599
					// we alway fetch the subscribed, as this provides the only way to tell
2600
					// if a folder is subscribed or not
2601
					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...
2602
					{
2603
						try
2604
						{
2605
							$subscribedMailboxes = $this->icServer->listSubscribedMailboxes('',0,true);
2606
							if (!empty($subscribedMailboxes))
2607
							{
2608
								$fetchedAllInOneGo = true;
2609
							}
2610
							else
2611
							{
2612
								$subscribedMailboxes = $this->icServer->listSubscribedMailboxes($singleNameSpace['prefix'],0,true);
2613
							}
2614
						}
2615
						catch(Exception $e)
2616
						{
2617
							continue;
2618
						}
2619
						//echo "subscribedMailboxes";_debug_array($subscribedMailboxes);
2620
						$subscribedFoldersPerNS = (!empty($subscribedMailboxes)?array_keys($subscribedMailboxes):array());
2621
						//if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']);
2622
						//_debug_array($foldersNameSpace);
2623
						//error_log(__METHOD__.__LINE__.array2string($singleNameSpace).':#:'.array2string($subscribedFoldersPerNS));
2624
						if (!empty($subscribedFoldersPerNS) && !empty($subscribedMailboxes))
2625
						{
2626
							//error_log(__METHOD__.' ('.__LINE__.') '." $type / subscribed:". array2string($subscribedMailboxes));
2627
							foreach ($subscribedMailboxes as $k => $finfo)
2628
							{
2629
								//error_log(__METHOD__.__LINE__.$k.':#:'.array2string($finfo));
2630
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$k]=
2631
								$folderBasicInfo[$this->icServer->ImapServerId][$k]=array(
2632
									'MAILBOX'=>$finfo['MAILBOX'],
2633
									'ATTRIBUTES'=>$finfo['ATTRIBUTES'],
2634
									'delimiter'=>$finfo['delimiter'],//lowercase for some reason???
2635
									'SUBSCRIBED'=>$finfo['SUBSCRIBED'],//seeded by getMailboxes
2636
								);
2637 View Code Duplication
								if (empty($foldersNameSpace[$type]['subscribed']) || !in_array($k,$foldersNameSpace[$type]['subscribed']))
2638
								{
2639
									$foldersNameSpace[$type]['subscribed'][] = $k;
2640
								}
2641 View Code Duplication
								if (empty($foldersNameSpace[$type]['all']) || !in_array($k,$foldersNameSpace[$type]['all']))
2642
								{
2643
									$foldersNameSpace[$type]['all'][] = $k;
2644
								}
2645
							}
2646
						}
2647
						//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($foldersNameSpace[$type]['subscribed']));
2648
						if (!is_array($foldersNameSpace[$type]['all'])) $foldersNameSpace[$type]['all'] = array();
2649
						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...
2650
							continue;
2651
						}
2652
2653
					}
2654
2655
					// fetch and sort all folders
2656
					//echo $type.'->'.$singleNameSpace['prefix'].'->'.($type=='shared'?0:2)."<br>";
2657
					try
2658
					{
2659
						// calling with 2 lists all mailboxes on that level with fetches all
2660
						// we switch to all, to avoid further calls for subsequent levels
2661
						// that may produce problems, when encountering recursions probably
2662
						// horde is handling that, so we do not; keep that in mind!
2663
						//$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],2,true);
2664
						$allMailboxesExt = $this->icServer->getMailboxes($singleNameSpace['prefix'],0,true);
2665
					}
2666
					catch (\Exception $e)
2667
					{
2668
						error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve all Boxes:'.$e->getMessage());
2669
						$allMailboxesExt = array();
2670
					}
2671
					if (!is_array($allMailboxesExt))
2672
					{
2673
						//error_log(__METHOD__.' ('.__LINE__.') '.' Expected Array but got:'.array2string($allMailboxesExt). 'Type:'.$type.' Prefix:'.$singleNameSpace['prefix']);
2674
						continue;
2675
						//$allMailboxesExt=array();
2676
					}
2677
2678
					//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($allMailboxesExt));
2679
					foreach ($allMailboxesExt as $mbx) {
2680
						if (!isset($folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2681
						{
2682
							$folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']]=array(
2683
								'MAILBOX'=>$mbx['MAILBOX'],
2684
								'ATTRIBUTES'=>$mbx['ATTRIBUTES'],
2685
								'delimiter'=>$mbx['delimiter'],//lowercase for some reason???
2686
								'SUBSCRIBED'=>$mbx['SUBSCRIBED'],//seeded by getMailboxes
2687
							);
2688
							if ($mbx['SUBSCRIBED'] && !isset($subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']]))
2689
							{
2690
								$subscribedFoldersForCache[$this->icServer->ImapServerId][$mbx['MAILBOX']] = $folderBasicInfo[$this->icServer->ImapServerId][$mbx['MAILBOX']];
2691
							}
2692
						}
2693
						if ($mbx['SUBSCRIBED'] && (empty($foldersNameSpace[$type]['subscribed']) || !in_array($mbx['MAILBOX'],$foldersNameSpace[$type]['subscribed'])))
2694
						{
2695
							$foldersNameSpace[$type]['subscribed'][] = $mbx['MAILBOX'];
2696
						}
2697
						//echo __METHOD__;_debug_array($mbx);
2698
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
2699
						if (isset($allMailBoxesExtSorted[$mbx['MAILBOX']])||
2700
							isset($allMailBoxesExtSorted[$mbx['MAILBOX'].$foldersNameSpace[$type]['delimiter']])||
2701
							(substr($mbx['MAILBOX'],-1)==$foldersNameSpace[$type]['delimiter'] && isset($allMailBoxesExtSorted[substr($mbx['MAILBOX'],0,-1)]))
2702
						) continue;
2703
2704
						//echo '#'.$mbx['MAILBOX'].':'.array2string($mbx)."#<br>";
2705
						$allMailBoxesExtSorted[$mbx['MAILBOX']] = $mbx;
2706
					}
2707
					if (is_array($allMailBoxesExtSorted)) ksort($allMailBoxesExtSorted);
2708
					//_debug_array(array_keys($allMailBoxesExtSorted));
2709
					$allMailboxes = array();
2710
					foreach ((array)$allMailBoxesExtSorted as $mbx) {
2711
						if (!in_array($mbx['MAILBOX'],$allMailboxes)) $allMailboxes[] = $mbx['MAILBOX'];
2712
						//echo "Result:";_debug_array($allMailboxes);
2713
					}
2714
					$foldersNameSpace[$type]['all'] = $allMailboxes;
2715
					if (is_array($foldersNameSpace[$type]['all'])) sort($foldersNameSpace[$type]['all']);
2716
				}
2717
			}
2718
		}
2719
		//subscribed folders may be used in getFolderStatus
2720
		Cache::setCache(Cache::INSTANCE,'email','subscribedFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$subscribedFoldersForCache,$expiration=60*60*1);
2721
		//echo "<br>FolderNameSpace To Process:";_debug_array($foldersNameSpace);
2722
		$autoFolderObjects = $folders = array();
2723
		$autofolder_exists = array();
2724
		foreach( array('personal', 'others', 'shared') as $type) {
2725
			if(isset($foldersNameSpace[$type])) {
2726
				if($_subscribedOnly) {
2727
					if( !empty($foldersNameSpace[$type]['subscribed']) ) $listOfFolders = $foldersNameSpace[$type]['subscribed'];
2728
				} else {
2729
					if( !empty($foldersNameSpace[$type]['all'])) $listOfFolders = $foldersNameSpace[$type]['all'];
2730
				}
2731
				foreach((array)$listOfFolders as $folderName) {
2732
					//echo "<br>FolderToCheck:$folderName<br>";
2733
					//error_log(__METHOD__.__LINE__.'#Delimiter:'.$delimiter.':#'.$folderName);
2734
					if ($_subscribedOnly && empty($foldersNameSpace[$type]['all'])) continue;//when subscribedonly, we fetch all folders in one go.
2735
					if($_subscribedOnly && !(in_array($folderName, $foldersNameSpace[$type]['all'])||in_array($folderName.$foldersNameSpace[$type]['delimiter'], $foldersNameSpace[$type]['all']))) {
2736
						#echo "$folderName failed to be here <br>";
2737
						continue;
2738
					}
2739
					if (isset($folders[$folderName])) continue;
2740
					if (isset($autoFolderObjects[$folderName])) continue;
2741
					if (empty($delimiter)||$delimiter != $foldersNameSpace[$type]['delimiter']) $delimiter = $foldersNameSpace[$type]['delimiter'];
2742
					$folderParts = explode($delimiter, $folderName);
2743
					$shortName = array_pop($folderParts);
2744
2745
					$folderObject = new \stdClass;
2746
					$folderObject->delimiter	= $delimiter;
2747
					$folderObject->folderName	= $folderName;
2748
					$folderObject->shortFolderName	= $shortName;
2749
					if(!$_subscribedOnly) {
2750
						#echo $folderName."->".$type."<br>";
2751
						#_debug_array($foldersNameSpace[$type]['subscribed']);
2752
						$folderObject->subscribed = in_array($folderName, (array)$foldersNameSpace[$type]['subscribed']);
2753
					}
2754
2755
					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...
2756
						//error_log(__METHOD__.' ('.__LINE__.') '.' getCounter forFolder:'.$folderName);
2757
						$folderObject->counter = $this->getMailBoxCounters($folderName);
2758
					}
2759
					if(strtoupper($folderName) == 'INBOX') {
2760
						$folderName = 'INBOX';
2761
						$folderObject->folderName	= 'INBOX';
2762
						$folderObject->shortFolderName	= 'INBOX';
2763
						$folderObject->displayName	= lang('INBOX');
2764
						$folderObject->shortDisplayName = lang('INBOX');
2765
						$folderObject->subscribed	= true;
2766
					// translate the automatic Folders (Sent, Drafts, ...) like the INBOX
2767
					} elseif (in_array($shortName,self::$autoFolders)) {
2768
						$tmpfolderparts = explode($delimiter,$folderObject->folderName);
2769
						array_pop($tmpfolderparts);
2770
						$folderObject->displayName = implode($delimiter,$tmpfolderparts).$delimiter.lang($shortName);
2771
						$folderObject->shortDisplayName = lang($shortName);
2772
						unset($tmpfolderparts);
2773
					} else {
2774
						$folderObject->displayName = $folderObject->folderName;
2775
						$folderObject->shortDisplayName = $shortName;
2776
					}
2777
					//$folderName = $folderName;
2778
					if (in_array($shortName,self::$autoFolders)&&self::searchValueInFolderObjects($shortName,$autoFolderObjects)===false) {
2779
						$autoFolderObjects[$folderName] = $folderObject;
2780
					} else {
2781
						$folders[$folderName] = $folderObject;
2782
					}
2783
					//error_log(__METHOD__.' ('.__LINE__.') '.':'.$folderObject->folderName);
2784
					if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders ();
2785
					if (isset(self::$specialUseFolders[$folderName]))
2786
					{
2787
						$autofolder_exists[$folderName] = self::$specialUseFolders[$folderName];
2788
					}
2789
				}
2790
			}
2791
		}
2792
		if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) {
2793
			uasort($autoFolderObjects,array($this,"sortByAutoFolderPos"));
2794
		}
2795
		// check if some standard folders are missing and need to be created
2796
		if (count($autofolder_exists) < count(self::$autoFolders) && $this->check_create_autofolders($autofolder_exists))
2797
		{
2798
			// if new folders have been created, re-read folders ignoring the cache
2799
			return $this->getFolderObjects($_subscribedOnly, $_getCounters, $_alwaysGetDefaultFolders, false);	// false = do NOT use cache
2800
		}
2801
		if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName"));
2802
		//$folders2return = array_merge($autoFolderObjects,$folders);
2803
		//_debug_array($folders2return); #exit;
2804
		$folders2return[$this->icServer->ImapServerId] = array_merge((array)$inboxFolderObject,(array)$autoFolderObjects,(array)$folders);
2805
		if (($_subscribedOnly && $_getCounters===false) ||
2806
			($_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...
2807
			isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
2808
			$this->mailPreferences['showAllFoldersInFolderPane']==1))
2809
		{
2810
			Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return,$expiration=60*60*1);
2811
		}
2812
		Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1);
2813
		if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') ');
2814
		return $folders2return[$this->icServer->ImapServerId];
2815
	}
2816
2817
	/**
2818
	 * Get IMAP folders for a mailbox
2819
	 *
2820
	 * @param string $_nodePath = null folder name to fetch from IMAP,
2821
	 *			null means all folders
2822
	 * @param boolean $_onlyTopLevel if set to true only top level objects
2823
	 *			will be return and nodePath would be ignored
2824
	 * @param int $_search = 2 search restriction in given mailbox
2825
	 *	0:All folders recursively from the $_nodePath
2826
	 *  1:Only folder of specified $_nodePath
2827
	 *	2:All folders of $_nodePath in the same heirachy level
2828
	 *
2829
	 * @param boolean $_subscribedOnly = false Command to fetch only the subscribed folders
2830
	 * @param boolean $_getCounter = false Command to fetch mailbox counter
2831
	 *
2832
	 * @return array arrays of folders
2833
	 */
2834
	function getFolderArrays ($_nodePath = null, $_onlyTopLevel = false, $_search= 2, $_subscribedOnly = false, $_getCounter = false)
2835
	{
2836
		// delimiter
2837
		$delimiter = $this->getHierarchyDelimiter();
2838
2839
		$folders = $nameSpace =  array();
2840
		$nameSpaceTmp = $this->_getNameSpaces();
2841
		foreach($nameSpaceTmp as $k => $singleNameSpace) {
2842
			$nameSpace[$singleNameSpace['type']]=$singleNameSpace;
2843
		}
2844
		unset($nameSpaceTmp);
2845
2846
		//error_log(__METHOD__.__LINE__.array2string($nameSpace));
2847
		// Get special use folders
2848
		if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders (); // Set self::$specialUseFolders
2849
		// topLevelQueries generally ignore the $_search param. Except for Config::examineNamespace
2850
		if ($_onlyTopLevel) // top level leaves
2851
		{
2852
			// Get top mailboxes of icServer
2853
			$topFolders = $this->icServer->getMailboxes("", 2, true);
2854
			// Trigger examination of namespace to retrieve
2855
			// folders located in other and shared; needed only for some servers
2856
			if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
2857
			if (self::$mailConfig['examineNamespace'])
2858
			{
2859
				$prefixes=array();
2860
				if (is_array($nameSpace))
2861
				{
2862
					foreach($nameSpace as $k => $singleNameSpace) {
2863
						$type = $singleNameSpace['type'];
2864
2865
						if(is_array($singleNameSpace) && $singleNameSpace['prefix']){
2866
							$prefixes[$type] = $singleNameSpace['prefix'];
2867
							//regard extra care for nameSpacequeries when configured AND respect $_search
2868
							$result = $this->icServer->getMailboxes($singleNameSpace['prefix'], $_search==0?0:2, true);
2869
							if (is_array($result))
2870
							{
2871
								ksort($result);
2872
								$topFolders = array_merge($topFolders,$result);
2873
							}
2874
						}
2875
					}
2876
				}
2877
			}
2878
2879
			$autofolders = array();
2880
2881
			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...
2882
			{
2883
				if ($this->folderExists($path))
2884
				{
2885
					$autofolders[$folder] = $folder;
2886
				}
2887
			}
2888
			// Check if the special use folders are there, otherwise try to create them
2889
			if (count($autofolders) < count(self::$autoFolders) && $this->check_create_autofolders ($autofolders))
2890
			{
2891
				return $this->getFolderArrays ($_nodePath, $_onlyTopLevel, $_search, $_subscribedOnly, $_getCounter);
2892
			}
2893
2894
			// now process topFolders for next level
2895
			foreach ($topFolders as &$node)
2896
			{
2897
				$pattern = "/\\".$delimiter."/";
2898
				$reference = preg_replace($pattern, '', $node['MAILBOX']);
2899
				if(!empty($prefixes))
2900
				{
2901
					$reference = '';
2902
					$tmpArray = explode($delimiter,$node['MAILBOX']);
2903
					foreach($tmpArray as $p)
2904
					{
2905
						$reference = empty($reference)?$p:$reference.$delimiter.$p;
2906
					}
2907
				}
2908
				$mainFolder = $subFolders = array();
2909
2910
				if ($_subscribedOnly)
2911
				{
2912
					$mainFolder = $this->icServer->listSubscribedMailboxes($reference, 1, true);
2913
					$subFolders = $this->icServer->listSubscribedMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2914
				}
2915
				else
2916
				{
2917
					$mainFolder = $this->icServer->getMailboxes($reference, 1, true);
2918
					$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
2919
				}
2920
2921
				if (is_array($mainFolder['INBOX']))
2922
				{
2923
					// Array container of auto folders
2924
					$aFolders = array();
2925
2926
					// Array container of non auto folders
2927
					$nFolders = array();
2928
2929
					foreach ((array)$subFolders as $path => $folder)
2930
					{
2931
						$folderInfo = self::pathToFolderData($folder['MAILBOX'], $folder['delimiter']);
2932
						if (in_array(trim($folderInfo['name']), $autofolders) || in_array(trim($folderInfo['name']), self::$autoFolders))
2933
						{
2934
							$aFolders [$path] = $folder;
2935
						}
2936
						else
2937
						{
2938
							$nFolders [$path] = $folder;
2939
						}
2940
					}
2941
					if (is_array($aFolders)) uasort ($aFolders, array($this,'sortByAutofolder'));
2942
					//ksort($aFolders);
2943
2944
					// Sort none auto folders base on mailbox name
2945
					uasort($nFolders,array($this,'sortByMailbox'));
2946
2947
					$subFolders = array_merge($aFolders,$nFolders);
2948
				}
2949
				else
2950
				{
2951
					if (is_array($subFolders)) ksort($subFolders);
2952
				}
2953
				$folders = array_merge($folders,(array)$mainFolder, (array)$subFolders);
2954
			}
2955
		}
2956
		elseif ($_nodePath) // single node
1 ignored issue
show
Bug Best Practice introduced by
The expression $_nodePath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

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

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2983
		{
2984
			if ($_subscribedOnly)
2985
			{
2986
				$folders = $this->icServer->listSubscribedMailboxes('', 0, true);
2987
			}
2988
			else
2989
			{
2990
				$folders = $this->icServer->getMailboxes('', 0, true);
2991
			}
2992
		}
2993
		// only sort (autofolders, shared, others ...) when retrieving all folders or toplevelquery
2994
		if ($_onlyTopLevel || !$_nodePath)
1 ignored issue
show
Bug Best Practice introduced by
The expression $_nodePath of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2995
		{
2996
			// SORTING FOLDERS
2997
			//self::$debugTimes=true;
2998
			if (self::$debugTimes) $starttime = microtime (true);
2999
			// Merge of all auto folders and specialusefolders
3000
			$autoFoldersTmp = array_unique((array_merge(self::$autoFolders, array_values(self::$specialUseFolders))));
3001
			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
3002
			$tmpFolders = $folders;
3003
			$inboxFolderObject=$inboxSubFolderObjects=$autoFolderObjects=$typeFolderObject=$mySpecialUseFolders=array();
3004
			$googleMailFolderObject=$googleAutoFolderObjects=$googleSubFolderObjects=array();
3005
			$isGoogleMail=false;
3006
			foreach($autoFoldersTmp as $afk=>$aF)
3007
			{
3008
				if (!isset($mySpecialUseFolders[$aF]) && $aF) $mySpecialUseFolders[$aF]=$this->getFolderByType($aF,false);
3009
				//error_log($afk.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3010
			}
3011
			//error_log(array2string($mySpecialUseFolders));
3012
			foreach ($tmpFolders as $k => $f) {
3013
				$sorted=false;
3014
				if (strtoupper(substr($k,0,5))=='INBOX') {
3015
					if (strtoupper($k)=='INBOX') {
3016
						//error_log(__METHOD__.__LINE__.':'.strtoupper(substr($k,0,5)).':'.$k);
3017
						$inboxFolderObject[$k]=$f;
3018
						unset($folders[$k]);
3019
						$sorted=true;
3020 View Code Duplication
					} else {
3021
						$isAutoFolder=false;
3022
						foreach($autoFoldersTmp as $afk=>$aF)
3023
						{
3024
							//error_log(__METHOD__.__LINE__.$k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3025
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3026
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter || //k may be child of an autofolder
3027
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3028
							{
3029
								//error_log(__METHOD__.__LINE__.$k.'->'.$mySpecialUseFolders[$aF]);
3030
								$isAutoFolder=true;
3031
								$autoFolderObjects[$k]=$f;
3032
								break;
3033
							}
3034
						}
3035
						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...
3036
						unset($folders[$k]);
3037
						$sorted=true;
3038
					}
3039
				} elseif (strtoupper(substr($k,0,13))=='[GOOGLE MAIL]') {
3040
					$isGoogleMail=true;
3041
					if (strtoupper($k)=='[GOOGLE MAIL]') {
3042
						$googleMailFolderObject[$k]=$f;
3043
						unset($folders[$k]);
3044
						$sorted=true;
3045 View Code Duplication
					} else {
3046
						$isAutoFolder=false;
3047
						foreach($autoFoldersTmp as $afk=>$aF)
3048
						{
3049
							//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3050
							if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3051
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3052
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3053
							{
3054
								//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3055
								$isAutoFolder=true;
3056
								$googleAutoFolderObjects[$k]=$f;
3057
								break;
3058
							}
3059
						}
3060
						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...
3061
						unset($folders[$k]);
3062
						$sorted=true;
3063
					}
3064
				} else {
3065
					$isAutoFolder=false;
3066
					foreach($autoFoldersTmp as $afk=>$aF)
3067
					{
3068
						//error_log($k.':'.$aF.'->'.$mySpecialUseFolders[$aF]);
3069
						if($aF && strlen($mySpecialUseFolders[$aF])&&/*strlen($k)>=strlen($mySpecialUseFolders[$aF])&&*/
3070
								($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter|| //k may be child of an autofolder
3071
								stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false)) // k is parent of an autofolder
3072
						{
3073
							//error_log($k.'->'.$mySpecialUseFolders[$aF]);
3074
							$isAutoFolder=true;
3075
							$autoFolderObjects[$k]=$f;
3076
							unset($folders[$k]);
3077
							$sorted=true;
3078
							break;
3079
						}
3080
					}
3081
				}
3082
3083
				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...
3084
				{
3085
					foreach(array('others','shared') as $type)
3086
					{
3087
						if ($nameSpace[$type]['prefix_present']&&$nameSpace[$type]['prefix'])
3088
						{
3089
							if (substr($k,0,strlen($nameSpace[$type]['prefix']))==$nameSpace[$type]['prefix']||
3090
								substr($k,0,strlen($nameSpace[$type]['prefix'])-strlen($nameSpace[$type]['delimiter']))==substr($nameSpace[$type]['prefix'],0,strlen($nameSpace[$type]['delimiter'])*-1)) {
3091
								//error_log(__METHOD__.__LINE__.':'.substr($k,0,strlen($nameSpace[$type]['prefix'])).':'.$k);
3092
								$typeFolderObject[$type][$k]=$f;
3093
								unset($folders[$k]);
3094
							}
3095
						}
3096
					}
3097
				}
3098
			}
3099
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3100
			// avoid calling sortByAutoFolder as it is not regarding subfolders
3101
			$autoFolderObjectsTmp = $autoFolderObjects;
3102
			unset($autoFolderObjects);
3103
			uasort($autoFolderObjectsTmp, array($this,'sortByMailbox'));
3104
			foreach($autoFoldersTmp as $afk=>$aF)
3105
			{
3106
				foreach($autoFolderObjectsTmp as $k => $f)
3107
				{
3108
					if($aF && ($mySpecialUseFolders[$aF]==$k ||
3109
						substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter ||
3110
						stristr($mySpecialUseFolders[$aF],$k.$delimiter)!==false))
3111
					{
3112
						$autoFolderObjects[$k]=$f;
3113
					}
3114
				}
3115
			}
3116
			//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
3117
			if (!$isGoogleMail) {
3118
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3119
			} else {
3120
				// avoid calling sortByAutoFolder as it is not regarding subfolders
3121
				$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
3122
				unset($googleAutoFolderObjects);
3123
				uasort($gAutoFolderObjectsTmp, array($this,'sortByMailbox'));
3124
				foreach($autoFoldersTmp as $afk=>$aF)
3125
				{
3126
					foreach($gAutoFolderObjectsTmp as $k => $f)
3127
					{
3128
						if($aF && ($mySpecialUseFolders[$aF]==$k || substr($k,0,strlen($mySpecialUseFolders[$aF].$delimiter))==$mySpecialUseFolders[$aF].$delimiter))
3129
						{
3130
							$googleAutoFolderObjects[$k]=$f;
3131
						}
3132
					}
3133
				}
3134
				$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$folders,(array)$googleMailFolderObject,$googleAutoFolderObjects,$googleSubFolderObjects,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
3135
			}
3136
			if (self::$debugTimes) self::logRunTimes($starttime,null,function_backtrace(),__METHOD__.' ('.__LINE__.') Sorting:');
3137
			//self::$debugTimes=false;
3138
		}
3139
		// Get counter information and add them to each fetched folders array
3140
		// TODO:  do not fetch counters for user .... as in shared / others
3141
		if ($_getCounter)
3142
		{
3143
			foreach ($folders as &$folder)
3144
			{
3145
				$folder['counter'] = $this->icServer->getMailboxCounters($folder['MAILBOX']);
3146
			}
3147
		}
3148
		return $folders;
3149
	}
3150
3151
3152
	/**
3153
	 * Check if all automatic folders exist and create them if not
3154
	 *
3155
	 * @param array $autofolders_exists existing folders, no need to check their existance again
3156
	 * @return int number of new folders created
3157
	 */
3158
	function check_create_autofolders(array $autofolders_exists=array())
3159
	{
3160
		$num_created = 0;
3161
		foreach(self::$autoFolders as $folder)
3162
		{
3163
			$created = false;
3164
			if (!in_array($folder, $autofolders_exists) && $this->_getSpecialUseFolder($folder, true, $created) &&
3165
				$created && $folder != 'Outbox')
3166
			{
3167
				$num_created++;
3168
			}
3169
		}
3170
		return $num_created;
3171
	}
3172
3173
	/**
3174
	 * search Value In FolderObjects
3175
	 *
3176
	 * Helper function to search for a specific value within the foldertree objects
3177
	 * @param string $needle
3178
	 * @param array $haystack array of folderobjects
3179
	 * @return MIXED false or key
3180
	 */
3181
	static function searchValueInFolderObjects($needle, $haystack)
3182
	{
3183
		$rv = false;
3184
		foreach ($haystack as $k => $v)
3185
		{
3186
			foreach($v as &$sv) {if (trim($sv)==trim($needle)) return $k;}
3187
		}
3188
		return $rv;
3189
	}
3190
3191
	/**
3192
	 * sortByMailbox
3193
	 *
3194
	 * Helper function to sort folders array by mailbox
3195
	 * @param array $a
3196
	 * @param array $b array of folders
3197
	 * @return int expect values (0, 1 or -1)
3198
	 */
3199
	function sortByMailbox($a,$b)
3200
	{
3201
		return strcasecmp($a['MAILBOX'],$b['MAILBOX']);
3202
	}
3203
3204
	/**
3205
	 * Get folder data from path
3206
	 *
3207
	 * @param string $_path a node path
3208
	 * @param string $_hDelimiter hierarchy delimiter
3209
	 * @return array returns an array of data extracted from given node path
3210
	 */
3211
	static function pathToFolderData ($_path, $_hDelimiter)
3212
	{
3213
		if (!strpos($_path, self::DELIMITER)) $_path = self::DELIMITER.$_path;
3214
		list(,$path) = explode(self::DELIMITER, $_path);
3215
		$path_chain = $parts = explode($_hDelimiter, $path);
3216
		$name = array_pop($parts);
3217
		return array (
3218
			'name' => $name,
3219
			'mailbox' => $path,
3220
			'parent' => implode($_hDelimiter, $parts),
3221
			'text' => $name,
3222
			'tooltip' => $name,
3223
			'path' => $path_chain
3224
		);
3225
	}
3226
3227
	/**
3228
	 * sortByAutoFolder
3229
	 *
3230
	 * Helper function to sort folder-objects by auto Folder Position
3231
	 * @param array $_a
3232
	 * @param array $_b
3233
	 * @return int expect values (0, 1 or -1)
3234
	 */
3235
	function sortByAutoFolder($_a, $_b)
3236
	{
3237
		// 0, 1 und -1
3238
		$a = self::pathToFolderData($_a['MAILBOX'], $_a['delimiter']);
3239
		$b = self::pathToFolderData($_b['MAILBOX'], $_b['delimiter']);
3240
		$pos1 = array_search(trim($a['name']),self::$autoFolders);
3241
		$pos2 = array_search(trim($b['name']),self::$autoFolders);
3242
		if ($pos1 == $pos2) return 0;
3243
		return ($pos1 < $pos2) ? -1 : 1;
3244
	}
3245
3246
	/**
3247
	 * sortByDisplayName
3248
	 *
3249
	 * Helper function to sort folder-objects by displayname
3250
	 * @param object $a
3251
	 * @param object $b array of folderobjects
3252
	 * @return int expect values (0, 1 or -1)
3253
	 */
3254
	function sortByDisplayName($a,$b)
3255
	{
3256
		// 0, 1 und -1
3257
		return strcasecmp($a->displayName,$b->displayName);
3258
	}
3259
3260
	/**
3261
	 * sortByAutoFolderPos
3262
	 *
3263
	 * Helper function to sort folder-objects by auto Folder Position
3264
	 * @param object $a
3265
	 * @param object $b array of folderobjects
3266
	 * @return int expect values (0, 1 or -1)
3267
	 */
3268
	function sortByAutoFolderPos($a,$b)
3269
	{
3270
		// 0, 1 und -1
3271
		$pos1 = array_search(trim($a->shortFolderName),self::$autoFolders);
3272
		$pos2 = array_search(trim($b->shortFolderName),self::$autoFolders);
3273
		if ($pos1 == $pos2) return 0;
3274
		return ($pos1 < $pos2) ? -1 : 1;
3275
	}
3276
3277
	/**
3278
	 * getMailBoxCounters
3279
	 *
3280
	 * function to retrieve the counters for a given folder
3281
	 * @param string $folderName
3282
	 * @param boolean $_returnObject return the counters as object rather than an array
3283
	 * @return mixed false or array of counters array(MESSAGES,UNSEEN,RECENT,UIDNEXT,UIDVALIDITY) or object
3284
	 */
3285
	function getMailBoxCounters($folderName,$_returnObject=true)
3286
	{
3287
		try
3288
		{
3289
			$folderStatus = $this->icServer->getMailboxCounters($folderName);
3290
			//error_log(__METHOD__.' ('.__LINE__.') '.$folderName.": FolderStatus:".array2string($folderStatus).function_backtrace());
3291
		}
3292
		catch (\Exception $e)
3293
		{
3294
			if (self::$debug) error_log(__METHOD__." returned FolderStatus for Folder $folderName:".$e->getMessage());
3295
			return false;
3296
		}
3297
		if(is_array($folderStatus)) {
3298
			if ($_returnObject===false) return $folderStatus;
3299
			$status =  new \stdClass;
3300
			$status->messages   = $folderStatus['MESSAGES'];
3301
			$status->unseen     = $folderStatus['UNSEEN'];
3302
			$status->recent     = $folderStatus['RECENT'];
3303
			$status->uidnext        = $folderStatus['UIDNEXT'];
3304
			$status->uidvalidity    = $folderStatus['UIDVALIDITY'];
3305
3306
			return $status;
3307
		}
3308
		return false;
3309
	}
3310
3311
	/**
3312
	 * getMailBoxesRecursive
3313
	 *
3314
	 * function to retrieve mailboxes recursively from given mailbox
3315
	 * @param string $_mailbox
3316
	 * @param string $delimiter
3317
	 * @param string $prefix
3318
	 * @param string $reclevel 0, counter to keep track of the current recursionlevel
3319
	 * @return array of mailboxes
3320
	 */
3321 View Code Duplication
	function getMailBoxesRecursive($_mailbox, $delimiter, $prefix, $reclevel=0)
3322
	{
3323
		#echo __METHOD__." retrieve SubFolders for $_mailbox$delimiter <br>";
3324
		$maxreclevel=25;
3325
		if ($reclevel > $maxreclevel) {
3326
			error_log( __METHOD__." Recursion Level Exeeded ($reclevel) while looking up $_mailbox$delimiter ");
3327
			return array();
3328
		}
3329
		$reclevel++;
3330
		// clean up double delimiters
3331
		$_mailbox = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$_mailbox);
3332
		//get that mailbox in question
3333
		$mbx = $this->icServer->getMailboxes($_mailbox,1,true);
3334
		$mbxkeys = array_keys($mbx);
3335
		#_debug_array($mbx);
3336
//error_log(__METHOD__.' ('.__LINE__.') '.' Delimiter:'.array2string($delimiter));
3337
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbx));
3338
		// Example: Array([INBOX/GaGa] => Array([MAILBOX] => INBOX/GaGa[ATTRIBUTES] => Array([0] => \\unmarked)[delimiter] => /))
3339
		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"]))) {
3340
			// if there are children fetch them
3341
			//echo $mbx[$mbxkeys[0]]['MAILBOX']."<br>";
3342
3343
			$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'].($mbx[$mbxkeys[0]]['MAILBOX'] == $prefix ? '':$delimiter),2,false);
3344
			//$buff = $this->icServer->getMailboxes($mbx[$mbxkeys[0]]['MAILBOX'],2,false);
3345
			//_debug_array($buff);
3346
			$allMailboxes = array();
3347
			foreach ($buff as $mbxname) {
3348
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mbxname));
3349
				$mbxname = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$mbxname['MAILBOX']);
3350
				#echo "About to recur in level $reclevel:".$mbxname."<br>";
3351
				if ( $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'] && $mbxname != $prefix  && $mbxname != $mbx[$mbxkeys[0]]['MAILBOX'].$delimiter)
3352
				{
3353
					$allMailboxes = array_merge($allMailboxes, self::getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel));
3354
				}
3355
			}
3356
			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'];
3357
			return $allMailboxes;
3358
		} else {
3359
			return array($_mailbox);
3360
		}
3361
	}
3362
3363
	/**
3364
	 * _getSpecialUseFolder
3365
	 * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder
3366
	 * @param string $_type the type to fetch (Drafts|Template|Trash|Sent)
3367
	 * @param boolean $_checkexistance trigger check for existance
3368
	 * @param boolean& $created =null on return true: if folder was just created, false if not
3369
	 * @return mixed string or false
3370
	 */
3371
	function _getSpecialUseFolder($_type, $_checkexistance=TRUE, &$created=null)
3372
	{
3373
		static $types = array(
3374
			'Drafts'   => array('profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'),
3375
			'Template' => array('profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'),
3376
			'Trash'    => array('profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'),
3377
			'Sent'     => array('profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'),
3378
			'Junk'     => array('profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'),
3379
			'Outbox'   => array('profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'),
3380
			'Archive'   => array('profileKey'=>'acc_folder_archive','autoFolderName'=>'Archive'),
3381
		);
3382
		if ($_type == 'Templates') $_type = 'Template';	// for some reason self::$autofolders uses 'Templates'!
3383
		$created = false;
3384
		if (!isset($types[$_type]))
3385
		{
3386
			error_log(__METHOD__.' ('.__LINE__.') '.' '.$_type.' not supported for '.__METHOD__);
3387
			return false;
3388
		}
3389
		if (is_null(self::$specialUseFolders) || empty(self::$specialUseFolders)) self::$specialUseFolders = $this->getSpecialUseFolders();
3390
3391
		//highest precedence
3392
		try
3393
		{
3394
			$_folderName = $this->icServer->$types[$_type]['profileKey'];
3395
		}
3396
		catch (\Exception $e)
3397
		{
3398
			// we know that outbox is not supported, but we use this here, as we autocreate expected SpecialUseFolders in this function
3399
			if ($_type != 'Outbox') error_log(__METHOD__.' ('.__LINE__.') '.' Failed to retrieve Folder'.$_folderName." for ".array2string($types[$_type]).":".$e->getMessage());
3400
			$_folderName = false;
3401
		}
3402
		// do not try to autocreate configured Archive-Folder. Return false if configured folder does not exist
3403
		if ($_type == 'Archive') {
3404
			if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3405
				return false;
3406
			} else {
3407
				return $_folderName;
3408
			}
3409
3410
		}
3411
		// does the folder exist??? (is configured/preset, but non-existent)
3412
		if ($_folderName && $_checkexistance && strtolower($_folderName) !='none' && !$this->folderExists($_folderName,true)) {
3413
			try
3414
			{
3415
				$error = null;
3416
				if (($_folderName = $this->createFolder('', $_folderName, $error))) $created = true;
3417 View Code Duplication
				if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3418
			}
3419
			catch(Exception $e)
3420
			{
3421
				error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage().':'.function_backtrace());
3422
				$_folderName = false;
3423
			}
3424
		}
3425
		// not sure yet if false is the correct behavior on none
3426
		if ($_folderName =='none') return 'none' ; //false;
3427
		//no (valid) folder found yet; try specialUseFolders
3428
		if (empty($_folderName) && is_array(self::$specialUseFolders) && ($f = array_search($_type,self::$specialUseFolders))) $_folderName = $f;
3429
		//no specialUseFolder; try some Defaults
3430
		if (empty($_folderName) && isset($types[$_type]))
3431
		{
3432
			$nameSpace = $this->_getNameSpaces();
3433
			$prefix='';
3434
			foreach ($nameSpace as $nSp)
3435
			{
3436
				if ($nSp['type']=='personal')
3437
				{
3438
					//error_log(__METHOD__.__LINE__.array2string($nSp));
3439
					$prefix = $nSp['prefix'];
3440
					break;
3441
				}
3442
			}
3443
			if ($this->folderExists($prefix.$types[$_type]['autoFolderName'],true))
3444
			{
3445
				$_folderName = $prefix.$types[$_type]['autoFolderName'];
3446
			}
3447
			else
3448
			{
3449
				try
3450
				{
3451
					$error = null;
3452
					$this->createFolder('', $prefix.$types[$_type]['autoFolderName'],$error);
3453
					$_folderName = $prefix.$types[$_type]['autoFolderName'];
3454 View Code Duplication
					if ($error) error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$error);
3455
				}
3456
				catch(Exception $e)
3457
				{
3458
					error_log(__METHOD__.' ('.__LINE__.') '.' Failed to create Folder '.$_folderName." for $_type:".$e->getMessage());
3459
					$_folderName = false;
3460
				}
3461
			}
3462
		}
3463
		return $_folderName;
3464
	}
3465
3466
	/**
3467
	 * getFolderByType wrapper for _getSpecialUseFolder Type as param
3468
	 * @param string $type foldertype to look for
3469
	 * @param boolean $_checkexistance trigger check for existance
3470
	 * @return mixed string or false
3471
	 */
3472
	function getFolderByType($type, $_checkexistance=false)
3473
	{
3474
		return $this->_getSpecialUseFolder($type, $_checkexistance);
3475
	}
3476
3477
	/**
3478
	 * getJunkFolder wrapper for _getSpecialUseFolder Type Junk
3479
	 * @param boolean $_checkexistance trigger check for existance
3480
	 * @return mixed string or false
3481
	 */
3482
	function getJunkFolder($_checkexistance=TRUE)
3483
	{
3484
		return $this->_getSpecialUseFolder('Junk', $_checkexistance);
3485
	}
3486
3487
	/**
3488
	 * getDraftFolder wrapper for _getSpecialUseFolder Type Drafts
3489
	 * @param boolean $_checkexistance trigger check for existance
3490
	 * @return mixed string or false
3491
	 */
3492
	function getDraftFolder($_checkexistance=TRUE)
3493
	{
3494
		return $this->_getSpecialUseFolder('Drafts', $_checkexistance);
3495
	}
3496
3497
	/**
3498
	 * getTemplateFolder wrapper for _getSpecialUseFolder Type Template
3499
	 * @param boolean $_checkexistance trigger check for existance
3500
	 * @return mixed string or false
3501
	 */
3502
	function getTemplateFolder($_checkexistance=TRUE)
3503
	{
3504
		return $this->_getSpecialUseFolder('Template', $_checkexistance);
3505
	}
3506
3507
	/**
3508
	 * getTrashFolder wrapper for _getSpecialUseFolder Type Trash
3509
	 * @param boolean $_checkexistance trigger check for existance
3510
	 * @return mixed string or false
3511
	 */
3512
	function getTrashFolder($_checkexistance=TRUE)
3513
	{
3514
		return $this->_getSpecialUseFolder('Trash', $_checkexistance);
3515
	}
3516
3517
	/**
3518
	 * getSentFolder wrapper for _getSpecialUseFolder Type Sent
3519
	 * @param boolean $_checkexistance trigger check for existance
3520
	 * @return mixed string or false
3521
	 */
3522
	function getSentFolder($_checkexistance=TRUE)
3523
	{
3524
		return $this->_getSpecialUseFolder('Sent', $_checkexistance);
3525
	}
3526
3527
	/**
3528
	 * getOutboxFolder wrapper for _getSpecialUseFolder Type Outbox
3529
	 * @param boolean $_checkexistance trigger check for existance
3530
	 * @return mixed string or false
3531
	 */
3532
	function getOutboxFolder($_checkexistance=TRUE)
3533
	{
3534
		return $this->_getSpecialUseFolder('Outbox', $_checkexistance);
3535
	}
3536
3537
	/**
3538
	 * getArchiveFolder wrapper for _getSpecialUseFolder Type Archive
3539
	 * @param boolean $_checkexistance trigger check for existance . We do no autocreation for configured Archive folder
3540
	 * @return mixed string or false
3541
	 */
3542
	function getArchiveFolder($_checkexistance=TRUE)
3543
	{
3544
		return $this->_getSpecialUseFolder('Archive', $_checkexistance);
3545
	}
3546
3547
	/**
3548
	 * isSentFolder is the given folder the sent folder or at least a subfolder of it
3549
	 * @param string $_folderName folder to perform the check on
3550
	 * @param boolean $_checkexistance trigger check for existance
3551
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3552
	 * @return boolean
3553
	 */
3554 View Code Duplication
	function isSentFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3555
	{
3556
		$sentFolder = $this->getSentFolder($_checkexistance);
3557
		if(empty($sentFolder)) {
3558
			return false;
3559
		}
3560
		// does the folder exist???
3561
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3562
			return false;
3563
		}
3564
3565
		if ($_exactMatch)
3566
		{
3567
			if(false !== stripos($_folderName, $sentFolder)&& strlen($_folderName)==strlen($sentFolder)) {
3568
				return true;
3569
			} else {
3570
				return false;
3571
			}
3572
		} else {
3573
			if(false !== stripos($_folderName, $sentFolder)) {
3574
				return true;
3575
			} else {
3576
				return false;
3577
			}
3578
		}
3579
	}
3580
3581
	/**
3582
	 * checks if the Outbox folder exists and is part of the foldername to be checked
3583
	 * @param string $_folderName folder to perform the check on
3584
	 * @param boolean $_checkexistance trigger check for existance
3585
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3586
	 * @return boolean
3587
	 */
3588
	function isOutbox($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3589
	{
3590
		if (stripos($_folderName, 'Outbox')===false) {
3591
			return false;
3592
		}
3593
		// does the folder exist???
3594
		if ($_checkexistance && $GLOBALS['egw_info']['user']['apps']['activesync'] && !$this->folderExists($_folderName)) {
3595
			$outboxFolder = $this->getOutboxFolder($_checkexistance);
3596
			if ($_exactMatch)
3597
			{
3598
				if(false !== stripos($_folderName, $outboxFolder)&& strlen($_folderName)==strlen($outboxFolder)) {
3599
					return true;
3600
				} else {
3601
					return false;
3602
				}
3603
			} else {
3604
				if(false !== stripos($_folderName, $outboxFolder)) {
3605
					return true;
3606
				} else {
3607
					return false;
3608
				}
3609
			}
3610
		}
3611
		return true;
3612
	}
3613
3614
	/**
3615
	 * isDraftFolder is the given folder the sent folder or at least a subfolder of it
3616
	 * @param string $_folderName folder to perform the check on
3617
	 * @param boolean $_checkexistance trigger check for existance
3618
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3619
	 * @return boolean
3620
	 */
3621
	function isDraftFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3622
	{
3623
		$draftFolder = $this->getDraftFolder($_checkexistance);
3624
		if(empty($draftFolder)) {
3625
			return false;
3626
		}
3627
		// does the folder exist???
3628
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3629
			return false;
3630
		}
3631
		if (is_a($_folderName,"Horde_Imap_Client_Mailbox")) $_folderName = $_folderName->utf8;
3632
		if ($_exactMatch)
3633
		{
3634
			if(false !== stripos($_folderName, $draftFolder)&& strlen($_folderName)==strlen($draftFolder)) {
3635
				return true;
3636
			} else {
3637
				return false;
3638
			}
3639
		} else {
3640
			if(false !== stripos($_folderName, $draftFolder)) {
3641
				return true;
3642
			} else {
3643
				return false;
3644
			}
3645
		}
3646
	}
3647
3648
	/**
3649
	 * isTrashFolder is the given folder the sent folder or at least a subfolder of it
3650
	 * @param string $_folderName folder to perform the check on
3651
	 * @param boolean $_checkexistance trigger check for existance
3652
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3653
	 * @return boolean
3654
	 */
3655 View Code Duplication
	function isTrashFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3656
	{
3657
		$trashFolder = $this->getTrashFolder($_checkexistance);
3658
		if(empty($trashFolder)) {
3659
			return false;
3660
		}
3661
		// does the folder exist???
3662
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3663
			return false;
3664
		}
3665
3666
		if ($_exactMatch)
3667
		{
3668
			if(false !== stripos($_folderName, $trashFolder)&& strlen($_folderName)==strlen($trashFolder)) {
3669
				return true;
3670
			} else {
3671
				return false;
3672
			}
3673
		} else {
3674
			if(false !== stripos($_folderName, $trashFolder)) {
3675
				return true;
3676
			} else {
3677
				return false;
3678
			}
3679
		}
3680
	}
3681
3682
	/**
3683
	 * isTemplateFolder is the given folder the sent folder or at least a subfolder of it
3684
	 * @param string $_folderName folder to perform the check on
3685
	 * @param boolean $_checkexistance trigger check for existance
3686
	 * @param boolean $_exactMatch make the check more strict. return false if folder is subfolder only
3687
	 * @return boolean
3688
	 */
3689 View Code Duplication
	function isTemplateFolder($_folderName, $_checkexistance=TRUE, $_exactMatch=false)
3690
	{
3691
		$templateFolder = $this->getTemplateFolder($_checkexistance);
3692
		if(empty($templateFolder)) {
3693
			return false;
3694
		}
3695
		// does the folder exist???
3696
		if ($_checkexistance && !$this->folderExists($_folderName)) {
3697
			return false;
3698
		}
3699
		if ($_exactMatch)
3700
		{
3701
			if(false !== stripos($_folderName, $templateFolder)&& strlen($_folderName)==strlen($templateFolder)) {
3702
				return true;
3703
			} else {
3704
				return false;
3705
			}
3706
		} else {
3707
			if(false !== stripos($_folderName, $templateFolder)) {
3708
				return true;
3709
			} else {
3710
				return false;
3711
			}
3712
		}
3713
	}
3714
3715
	/**
3716
	 * folderExists checks for existance of a given folder
3717
	 * @param string $_folder folder to perform the check on
3718
	 * @param boolean $_forceCheck trigger check for existance on icServer
3719
	 * @return mixed string or false
3720
	 */
3721
	function folderExists($_folder, $_forceCheck=false)
3722
	{
3723
		static $folderInfo;
3724
		$forceCheck = $_forceCheck;
3725
		if (empty($_folder))
3726
		{
3727
			// this error is more or less without significance, unless we force the check
3728
			if ($_forceCheck===true) error_log(__METHOD__.' ('.__LINE__.') '.' Called with empty Folder:'.$_folder.function_backtrace());
3729
			return false;
3730
		}
3731
		// when check is not enforced , we assume a folder represented as Horde_Imap_Client_Mailbox as existing folder
3732
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")&&$_forceCheck===false) return true;
3733
		if (is_a($_folder,"Horde_Imap_Client_Mailbox")) $_folder =  $_folder->utf8;
3734
		// reduce traffic within the Instance per User; Expire every 5 hours
3735
		//error_log(__METHOD__.' ('.__LINE__.') '.' Called with Folder:'.$_folder.function_backtrace());
3736
		if (is_null($folderInfo)) $folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*5);
3737
		//error_log(__METHOD__.' ('.__LINE__.') '.'Cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.($forceCheck?'(forcedCheck)':'').':'.array2string($folderInfo));
3738
		if (!empty($folderInfo) && isset($folderInfo[$this->profileID]) && isset($folderInfo[$this->profileID][$_folder]) && $forceCheck===false)
3739
		{
3740
			//error_log(__METHOD__.' ('.__LINE__.') '.' Using cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID);
3741
			return $folderInfo[$this->profileID][$_folder];
3742
		}
3743
		else
3744
		{
3745
			if ($forceCheck === false)
3746
			{
3747
				//error_log(__METHOD__.' ('.__LINE__.') '.' No cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.' FolderExistsInfoCache:'.array2string($folderInfo[$this->profileID]));
3748
				$forceCheck = true; // try to force the check, in case there is no connection, we may need that
3749
			}
3750
		}
3751
3752
		// does the folder exist???
3753
		//error_log(__METHOD__."->Connected?".$this->icServer->_connected.", ".$_folder.", ".($forceCheck?' forceCheck activated':'dont check on server'));
3754
		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...
3755
			//error_log(__METHOD__."->NotConnected and forceCheck with profile:".$this->profileID);
3756
			//return false;
3757
			//try to connect
3758
		}
3759
		try
3760
		{
3761
			$folderInfo[$this->profileID][$_folder] = $this->icServer->mailboxExist($_folder);
3762
		}
3763
		catch (\Exception $e)
3764
		{
3765
			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...
3766
			self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
3767
			$folderInfo[$this->profileID][$_folder] = false;
3768
		}
3769
		//error_log(__METHOD__.' ('.__LINE__.') '.' Folder Exists:'.$folderInfo[$this->profileID][$_folder].function_backtrace());
3770
3771
		if(!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) &&
3772
			$folderInfo[$this->profileID][$_folder] !== true)
3773
		{
3774
			$folderInfo[$this->profileID][$_folder] = false; // set to false, whatever it was (to have a valid returnvalue for the static return)
3775
		}
3776
		Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*60*5);
3777
		return (!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) ? $folderInfo[$this->profileID][$_folder] : false);
3778
	}
3779
3780
	/**
3781
	 * remove any messages which are marked as deleted or
3782
	 * remove any messages from the trashfolder
3783
	 *
3784
	 * @param string _folderName the foldername
3785
	 * @return nothing
3786
	 */
3787
	function compressFolder($_folderName = false)
3788
	{
3789
		$folderName	= ($_folderName ? $_folderName : $this->sessionData['mailbox']);
3790
		$deleteOptions	= $GLOBALS['egw_info']['user']['preferences']['mail']['deleteOptions'];
3791
		$trashFolder	= $this->getTrashFolder();
3792
3793
		$this->icServer->openMailbox($folderName);
3794
3795
		if(strtolower($folderName) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") {
3796
			$this->deleteMessages('all',$folderName,'remove_immediately');
3797
		} else {
3798
			$this->icServer->expunge($folderName);
3799
		}
3800
	}
3801
3802
	/**
3803
	 * delete a Message
3804
	 *
3805
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
3806
	 * @param string _folder foldername
3807
	 * @param string _forceDeleteMethod - "no", or deleteMethod like 'move_to_trash',"mark_as_deleted","remove_immediately"
3808
	 *
3809
	 * @return bool true, as we do not handle return values yet
3810
	 * @throws Exception
3811
	 */
3812
	function deleteMessages($_messageUID, $_folder=NULL, $_forceDeleteMethod='no')
3813
	{
3814
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.array2string($_folder).', '.$_forceDeleteMethod);
3815
		$oldMailbox = '';
3816
		if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
3817 View Code Duplication
		if (empty($_messageUID))
3818
		{
3819
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3820
			return false;
3821
		}
3822
		elseif ($_messageUID==='all')
3823
		{
3824
			$_messageUID= null;
3825
		}
3826
		else
3827
		{
3828
			$uidsToDelete = new Horde_Imap_Client_Ids();
3829
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3830
			$uidsToDelete->add($_messageUID);
3831
		}
3832
		$deleteOptions = $_forceDeleteMethod; // use forceDeleteMethod if not "no", or unknown method
3833
		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");
3834
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3835
		$trashFolder    = $this->getTrashFolder();
3836
		$draftFolder	= $this->getDraftFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['draftFolder'];
3837
		$templateFolder = $this->getTemplateFolder(); //$GLOBALS['egw_info']['user']['preferences']['mail']['templateFolder'];
3838
		if((strtolower($_folder) == strtolower($trashFolder) && $deleteOptions == "move_to_trash") ||
3839
		   (strtolower($_folder) == strtolower($draftFolder))) {
3840
			$deleteOptions = "remove_immediately";
3841
		}
3842
		if($this->icServer->getCurrentMailbox() != $_folder) {
3843
			$oldMailbox = $this->icServer->getCurrentMailbox();
3844
			$this->icServer->openMailbox($_folder);
3845
		}
3846
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
3847
		$updateCache = false;
3848
		switch($deleteOptions) {
3849
			case "move_to_trash":
3850
				//error_log(__METHOD__.' ('.__LINE__.') ');
3851
				$updateCache = true;
3852
				if(!empty($trashFolder)) {
3853
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.implode(' : ', $_messageUID));
3854
					if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."$trashFolder <= $_folder / ". $this->sessionData['mailbox']);
3855
					// copy messages
3856
					try
3857
					{
3858
						$this->icServer->copy($_folder, $trashFolder, array('ids'=>$uidsToDelete,'move'=>true));
3859
					}
3860
					catch (\Exception $e)
3861
					{
3862
						throw new Exception("Failed to move Messages (".array2string($uidsToDelete).") from Folder $_folder to $trashFolder Error:".$e->getMessage());
3863
					}
3864
				}
3865
				break;
3866
3867
			case "mark_as_deleted":
3868
				//error_log(__METHOD__.' ('.__LINE__.') ');
3869
				// mark messages as deleted
3870
				if (is_null($_messageUID)) $_messageUID='all';
3871
				foreach((array)$_messageUID as $key =>$uid)
3872
				{
3873
					//flag messages, that are flagged for deletion as seen too
3874
					$this->flagMessages('read', $uid, $_folder);
3875
					$flags = $this->getFlags($uid);
3876
					$this->flagMessages('delete', $uid, $_folder);
3877
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
3878
					if (strpos( array2string($flags),'Deleted')!==false) $undelete[] = $uid;
3879
					unset($flags);
3880
				}
3881
				foreach((array)$undelete as $key =>$uid)
3882
				{
3883
					$this->flagMessages('undelete', $uid, $_folder);
3884
				}
3885
				break;
3886
3887
			case "remove_immediately":
3888
				//error_log(__METHOD__.' ('.__LINE__.') ');
3889
				$updateCache = true;
3890
				if (is_null($_messageUID)) $_messageUID='all';
3891
				if (is_object($_messageUID))
3892
				{
3893
					$this->flagMessages('delete', $_messageUID, $_folder);
3894
				}
3895
				else
3896
				{
3897
					foreach((array)$_messageUID as $key =>$uid)
3898
					{
3899
						//flag messages, that are flagged for deletion as seen too
3900
						$this->flagMessages('delete', $uid, $_folder);
3901
					}
3902
				}
3903
				// delete the messages finaly
3904
				$this->icServer->expunge($_folder);
3905
				break;
3906
		}
3907
		if($oldMailbox != '') {
3908
			$this->icServer->openMailbox($oldMailbox);
3909
		}
3910
3911
		return true;
3912
	}
3913
3914
	/**
3915
	 * get flags for a Message
3916
	 *
3917
	 * @param mixed string _messageUID array of id to retrieve the flags for
3918
	 *
3919
	 * @return null/array flags
3920
	 */
3921
	function getFlags ($_messageUID) {
3922
		try
3923
		{
3924
			$uidsToFetch = new Horde_Imap_Client_Ids();
3925
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
3926
			$uidsToFetch->add($_messageUID);
3927
			$_folderName = $this->icServer->getCurrentMailbox();
3928
			$fquery = new Horde_Imap_Client_Fetch_Query();
3929
			$fquery->flags();
3930
			$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
3931
				'ids' => $uidsToFetch,
3932
			));
3933
			if (is_object($headersNew)) {
3934
				foreach($headersNew->ids() as $id) {
3935
					$_headerObject = $headersNew->get($id);
3936
					$flags = $_headerObject->getFlags();
3937
				}
3938
			}
3939
		}
3940
		catch (\Exception $e)
3941
		{
3942
			error_log(__METHOD__.' ('.__LINE__.') '."Failed to fetch flags for ".array2string($_messageUID)." Error:".$e->getMessage());
3943
			return null;
3944
			//throw new Exception("Failed to fetch flags for ".array2string($_messageUID)" Error:".$e->getMessage());
3945
		}
3946
		return $flags;
3947
	}
3948
3949
	/**
3950
	 * get and parse the flags response for the Notifyflag for a Message
3951
	 *
3952
	 * @param string _messageUID array of id to retrieve the flags for
3953
	 * @param array flags - to avoid additional server call
3954
	 *
3955
	 * @return null/boolean
3956
	 */
3957
	function getNotifyFlags ($_messageUID, $flags=null)
3958
	{
3959
		if (self::$debug) error_log(__METHOD__.$_messageUID.' Flags:'.array2string($flags));
3960
		try
3961
		{
3962
			if($flags===null) $flags =  $this->getFlags($_messageUID);
3963
		}
3964
		catch (\Exception $e)
3965
		{
3966
			return null;
3967
		}
3968
3969
		if ( stripos( array2string($flags),'MDNSent')!==false)
3970
			return true;
3971
3972
		if ( stripos( array2string($flags),'MDNnotSent')!==false)
3973
			return false;
3974
3975
		return null;
3976
	}
3977
3978
	/**
3979
	 * flag a Message
3980
	 *
3981
	 * @param string _flag (readable name)
3982
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
3983
	 * @param string _folder foldername
3984
	 *
3985
	 * @todo handle handle icserver->setFlags returnValue
3986
	 *
3987
	 * @return bool true, as we do not handle icserver->setFlags returnValue
3988
	 */
3989
	function flagMessages($_flag, $_messageUID,$_folder=NULL)
3990
	{
3991
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']);
3992
		if (empty($_messageUID))
3993
		{
3994
			if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
3995
			return false;
3996
		}
3997
		$this->icServer->openMailbox(($_folder?$_folder:$this->sessionData['mailbox']));
3998
		$folder = $this->icServer->getCurrentMailbox();
3999
		if (is_array($_messageUID)&& count($_messageUID)>50)
4000
		{
4001
			$count = $this->getMailBoxCounters($folder,true);
4002
			if ($count->messages == count($_messageUID)) $_messageUID='all';
4003
		}
4004
4005
		if ($_messageUID==='all')
4006
		{
4007
			$messageUIDs = array('all');
4008
		}
4009
		else
4010
		{
4011
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4012
			$messageUIDs = array_chunk($_messageUID,50,true);
4013
		}
4014
		try
4015
		{
4016
			foreach($messageUIDs as &$uids)
4017
			{
4018
				if ($uids==='all')
4019
				{
4020
					$uidsToModify=null;
4021
				}
4022
				else
4023
				{
4024
					$uidsToModify = new Horde_Imap_Client_Ids();
4025
					$uidsToModify->add($uids);
4026
				}
4027
				switch($_flag) {
4028 View Code Duplication
					case "delete":
4029
						$ret = $this->icServer->store($folder, array('add'=>array('\\Deleted'), 'ids'=> $uidsToModify));
4030
						break;
4031 View Code Duplication
					case "undelete":
4032
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Deleted'), 'ids'=> $uidsToModify));
4033
						break;
4034 View Code Duplication
					case "flagged":
4035
						$ret = $this->icServer->store($folder, array('add'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4036
						break;
4037
					case "read":
4038 View Code Duplication
					case "seen":
4039
						$ret = $this->icServer->store($folder, array('add'=>array('\\Seen'), 'ids'=> $uidsToModify));
4040
						break;
4041 View Code Duplication
					case "forwarded":
4042
						$ret = $this->icServer->store($folder, array('add'=>array('$Forwarded'), 'ids'=> $uidsToModify));
4043 View Code Duplication
					case "answered":
4044
						$ret = $this->icServer->store($folder, array('add'=>array('\\Answered'), 'ids'=> $uidsToModify));
4045
						break;
4046 View Code Duplication
					case "unflagged":
4047
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Flagged'), 'ids'=> $uidsToModify));
4048
						break;
4049
					case "unread":
4050 View Code Duplication
					case "unseen":
4051
						$ret = $this->icServer->store($folder, array('remove'=>array('\\Seen','\\Answered','$Forwarded'), 'ids'=> $uidsToModify));
4052
						break;
4053 View Code Duplication
					case "mdnsent":
4054
						$ret = $this->icServer->store($folder, array('add'=>array('MDNSent'), 'ids'=> $uidsToModify));
4055
						break;
4056 View Code Duplication
					case "mdnnotsent":
4057
						$ret = $this->icServer->store($folder, array('add'=>array('MDNnotSent'), 'ids'=> $uidsToModify));
4058
						break;
4059
					case "label1":
4060 View Code Duplication
					case "labelone":
4061
						$ret = $this->icServer->store($folder, array('add'=>array('$label1'), 'ids'=> $uidsToModify));
4062
						break;
4063
					case "unlabel1":
4064 View Code Duplication
					case "unlabelone":
4065
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4066
						break;
4067
					case "label2":
4068 View Code Duplication
					case "labeltwo":
4069
						$ret = $this->icServer->store($folder, array('add'=>array('$label2'), 'ids'=> $uidsToModify));
4070
						break;
4071
					case "unlabel2":
4072 View Code Duplication
					case "unlabeltwo":
4073
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4074
						break;
4075
					case "label3":
4076 View Code Duplication
					case "labelthree":
4077
						$ret = $this->icServer->store($folder, array('add'=>array('$label3'), 'ids'=> $uidsToModify));
4078
						break;
4079
					case "unlabel3":
4080 View Code Duplication
					case "unlabelthree":
4081
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4082
						break;
4083
					case "label4":
4084 View Code Duplication
					case "labelfour":
4085
						$ret = $this->icServer->store($folder, array('add'=>array('$label4'), 'ids'=> $uidsToModify));
4086
						break;
4087
					case "unlabel4":
4088 View Code Duplication
					case "unlabelfour":
4089
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4090
						break;
4091
					case "label5":
4092 View Code Duplication
					case "labelfive":
4093
						$ret = $this->icServer->store($folder, array('add'=>array('$label5'), 'ids'=> $uidsToModify));
4094
						break;
4095
					case "unlabel5":
4096 View Code Duplication
					case "unlabelfive":
4097
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4098
						break;
4099
					case "unlabel":
4100
						$ret = $this->icServer->store($folder, array('remove'=>array('$label1'), 'ids'=> $uidsToModify));
4101
						$ret = $this->icServer->store($folder, array('remove'=>array('$label2'), 'ids'=> $uidsToModify));
4102
						$ret = $this->icServer->store($folder, array('remove'=>array('$label3'), 'ids'=> $uidsToModify));
4103
						$ret = $this->icServer->store($folder, array('remove'=>array('$label4'), 'ids'=> $uidsToModify));
4104
						$ret = $this->icServer->store($folder, array('remove'=>array('$label5'), 'ids'=> $uidsToModify));
4105
						break;
4106
				}
4107
			}
4108
		}
4109
		catch(Exception $e)
4110
		{
4111
			error_log(__METHOD__.__LINE__.' Error, could not flag messages in folder '.$folder.' Reason:'.$e->getMessage());
4112
		}
4113
		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...
4114
		//error_log(__METHOD__.__LINE__.'#'.$this->icServer->ImapServerId.'#'.array2string($_folder).'#');
4115
		self::$folderStatusCache[$this->icServer->ImapServerId][(!empty($_folder)?$_folder: $this->sessionData['mailbox'])]['uidValidity'] = 0;
4116
4117
		//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
4118
		return true; // as we do not catch/examine setFlags returnValue
4119
	}
4120
4121
	/**
4122
	 * move Message(s)
4123
	 *
4124
	 * @param string _foldername target folder
4125
	 * @param mixed array/string _messageUID array of ids to flag, or 'all'
4126
	 * @param boolean $deleteAfterMove - decides if a mail is moved (true) or copied (false)
4127
	 * @param string $currentFolder
4128
	 * @param boolean $returnUIDs - control wether or not the action called should return the new uids
4129
	 *						caveat: not all servers do support that
4130
	 * @param int $_sourceProfileID - source profile ID, should be handed over, if not $this->icServer->ImapServerId is used
4131
	 * @param int $_targetProfileID - target profile ID, should only be handed over when target server is different from source
4132
	 *
4133
	 * @return mixed/bool true,false or new uid
4134
	 * @throws Exception
4135
	 */
4136
	function moveMessages($_foldername, $_messageUID, $deleteAfterMove=true, $currentFolder = Null, $returnUIDs = false, $_sourceProfileID = Null, $_targetProfileID = Null)
4137
	{
4138
		$source = Mail\Account::read(($_sourceProfileID?$_sourceProfileID:$this->icServer->ImapServerId))->imapServer();
4139
		//$deleteOptions  = $GLOBALS['egw_info']["user"]["preferences"]["mail"]["deleteOptions"];
4140 View Code Duplication
		if (empty($_messageUID))
4141
		{
4142
			if (self::$debug) error_log(__METHOD__." no Message(s): ".implode(',',$_messageUID));
4143
			return false;
4144
		}
4145
		elseif ($_messageUID==='all')
4146
		{
4147
			//error_log(__METHOD__." all Message(s): ".implode(',',$_messageUID));
4148
			$uidsToMove= null;
4149
		}
4150
		else
4151
		{
4152
			//error_log(__METHOD__." Message(s): ".implode(',',$_messageUID));
4153
			$uidsToMove = new Horde_Imap_Client_Ids();
4154
			if (!(is_object($_messageUID) || is_array($_messageUID))) $_messageUID = (array)$_messageUID;
4155
			$uidsToMove->add($_messageUID);
4156
		}
4157
		$sourceFolder = (!empty($currentFolder)?$currentFolder: $this->sessionData['mailbox']);
4158
		//error_log(__METHOD__.__LINE__."$_targetProfileID !== ".array2string($source->ImapServerId));
4159
		if (!is_null($_targetProfileID) && $_targetProfileID !== $source->ImapServerId)
4160
		{
4161
			$sourceFolder = $source->getMailbox($sourceFolder);
4162
			$source->openMailbox($sourceFolder);
4163
			$uidsToFetch = new Horde_Imap_Client_Ids();
4164
			$uidsToFetch->add($_messageUID);
4165
			$fquery = new Horde_Imap_Client_Fetch_Query();
4166
			$fquery->flags();
4167
			$fquery->headerText(array('peek'=>true));
4168
			$fquery->fullText(array('peek'=>true));
4169
			$fquery->imapDate();
4170
			$headersNew = $source->fetch($sourceFolder, $fquery, array(
4171
				'ids' => $uidsToFetch,
4172
			));
4173
4174
			//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' mailheaders:'.array2string($headersNew));
4175
4176
			if (is_object($headersNew)) {
4177
				$c=0;
4178
				$retUid = new Horde_Imap_Client_Ids();
4179
				// we copy chunks of 5 to avoid too much memory and/or server stress
4180
				// some servers seem not to allow/support the appendig of multiple messages. so we are down to one
4181
				foreach($headersNew as &$_headerObject) {
4182
					$c++;
4183
					$flags = $_headerObject->getFlags(); //unseen status seems to be lost when retrieving the full message
4184
					$date = $_headerObject->getImapDate();
4185
					$currentDate =  new Horde_Imap_Client_DateTime();
4186
					// if the internal Date of the message equals the current date; try using the header date
4187
					if ($date==$currentDate)
4188
					{
4189
						$headerForPrio = array_change_key_case($_headerObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray(), CASE_UPPER);
4190
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#'.$headerForPrio['DATE']);
4191
						$date = new Horde_Imap_Client_DateTime($headerForPrio['DATE']);
4192
						//error_log(__METHOD__.__LINE__.'#'.array2string($date).'#'.array2string($currentDate).'#');
4193
					}
4194
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject)));
4195
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($flags));
4196
					$body = $_headerObject->getFullMsg();
4197
					$dataNflags[] = array('data'=>$body, 'flags'=>$flags, 'internaldate'=>$date);
4198
					if ($c==1)
4199
					{
4200
						$target = Mail\Account::read($_targetProfileID)->imapServer();
4201
						//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($_foldername));
4202
						$foldername = $target->getMailbox($_foldername);
4203
						// make sure the target folder is open and ready
4204
						$target->openMailbox($foldername);
4205
						$ret = $target->append($foldername,$dataNflags);
4206
						$retUid->add($ret);
4207
						unset($dataNflags);
4208
						// sleep 500 miliseconds; AS some sERVERs seem not to be capable of the load this is
4209
						// inflicting in them. they "reply" with an unspecific IMAP Error
4210
						time_nanosleep(0,500000);
4211
						$c=0;
4212
					}
4213
				}
4214
				if (isset($dataNflags))
4215
				{
4216
					$target = Mail\Account::read($_targetProfileID)->imapServer();
4217
					//error_log(__METHOD__.' ('.__LINE__.') '.' Sourceserver:'.$source->ImapServerId.' TargetServer:'.$_targetProfileID.' TargetFolderObject:'.array2string($foldername));
4218
					$foldername = $target->getMailbox($_foldername);
4219
					// make sure the target folder is open and ready
4220
					$target->openMailbox($foldername);
4221
					$ret = $target->append($foldername,$dataNflags);
4222
					$retUid->add($ret);
4223
					unset($dataNflags);
4224
				}
4225
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4226
				// make sure we are back to source
4227
				$source->openMailbox($sourceFolder);
4228
				if ($deleteAfterMove)
4229
				{
4230
					$remember = $this->icServer;
4231
					$this->icServer = $source;
4232
					$this->deleteMessages($_messageUID, $sourceFolder, $_forceDeleteMethod='remove_immediately');
4233
					$this->icServer = $remember;
4234
				}
4235
			}
4236
		}
4237
		else
4238
		{
4239
			try
4240
			{
4241
				$retUid = $source->copy($sourceFolder, $_foldername, array('ids'=>$uidsToMove,'move'=>$deleteAfterMove));
4242
			}
4243
			catch (exception $e)
4244
			{
4245
				error_log(__METHOD__.' ('.__LINE__.') '."Copying to Folder $_foldername failed! Error:".$e->getMessage());
4246
				throw new Exception("Copying to Folder $_foldername failed! Error:".$e->getMessage());
4247
			}
4248
		}
4249
4250
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retUid));
4251
		return ($returnUIDs ? $retUid : true);
4252
	}
4253
4254
	/**
4255
	 * Parse dates, also handle wrong or unrecognized timezones, falling back to current time
4256
	 *
4257
	 * @param string $_date to be parsed/formatted
4258
	 * @param string $format ='' if none is passed, use user prefs
4259
	 * @return string returns the date as it is parseable by strtotime, or current timestamp if everything fails
4260
	 */
4261
	static function _strtotime($_date='', $format='', $convert2usertime=false)
4262
	{
4263
		try {
4264
			$date = new DateTime($_date);	// parse date & time including timezone (throws exception, if not parsable)
4265
			if ($convert2usertime) $date->setUser();	// convert to user-time
4266
			$date2return = $date->format($format);
4267
		}
4268
		catch(\Exception $e)
4269
		{
4270
			unset($e);	// not used
4271
4272
			// remove last space-separated part and retry
4273
			$parts = explode(' ',$_date);
4274
			if (count($parts) > 1)
4275
			{
4276
				array_pop($parts);
4277
				$date2return = self::_strtotime(implode(' ', $parts), $format, $convert2usertime);
4278
			}
4279
			else	// not last part, use current time
4280
			{
4281
				$date2return = DateTime::to('now', $format);
4282
			}
4283
		}
4284
		return $date2return;
4285
	}
4286
4287
	/**
4288
	 * htmlentities
4289
	 * helperfunction to cope with wrong encoding in strings
4290
	 * @param string $_string  input to be converted
4291
	 * @param mixed $_charset false or string -> Target charset, if false Mail displayCharset will be used
4292
	 * @return string
4293
	 */
4294
	static function htmlentities($_string, $_charset=false)
4295
	{
4296
		//setting the charset (if not given)
4297
		if ($_charset===false) $_charset = self::$displayCharset;
4298
		$string = @htmlentities($_string, ENT_QUOTES, $_charset, false);
4299
		if (empty($string) && !empty($_string)) $string = @htmlentities(Translation::convert($_string,Translation::detect_encoding($_string),$_charset),ENT_QUOTES | ENT_IGNORE,$_charset, false);
4300
		return $string;
4301
	}
4302
4303
	/**
4304
	 * clean a message from elements regarded as potentially harmful
4305
	 * param string/reference $_html is the text to be processed
4306
	 * return nothing
4307
	 */
4308
	static function getCleanHTML(&$_html)
4309
	{
4310
		// remove CRLF and TAB as it is of no use in HTML.
4311
		// but they matter in <pre>, so we rather don't
4312
		//$_html = str_replace("\r\n",' ',$_html);
4313
		//$_html = str_replace("\t",' ',$_html);
4314
		//error_log(__METHOD__.__LINE__.':'.$_html);
4315
		//repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on
4316
		$_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>'),
4317
							 array('&amp;',    '<BR>',           '<BR>',             '<BR>',             '</font></td>','<td>',    '',         '',           '',  ''),$_html);
4318
		//$_html = str_replace(array('&amp;amp;'),array('&amp;'),$_html);
4319
		if (stripos($_html,'style')!==false) Mail\Html::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags
4320
		if (stripos($_html,'head')!==false) Mail\Html::replaceTagsCompletley($_html,'head'); // Strip out stuff in head
4321
		//if (stripos($_html,'![if')!==false && stripos($_html,'<![endif]>')!==false) Mail\Html::replaceTagsCompletley($_html,'!\[if','<!\[endif\]>',false); // Strip out stuff in ifs
4322
		//if (stripos($_html,'!--[if')!==false && stripos($_html,'<![endif]-->')!==false) Mail\Html::replaceTagsCompletley($_html,'!--\[if','<!\[endif\]-->',false); // Strip out stuff in ifs
4323
		//error_log(__METHOD__.' ('.__LINE__.') '.$_html);
4324
4325
		if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html);
4326
		// Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards
4327
		if (stripos($_html,'!doctype')!==false) Mail\Html::replaceTagsCompletley($_html,'!doctype');
4328
		if (stripos($_html,'?xml:namespace')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml:namespace','/>',false);
4329
		if (stripos($_html,'?xml version')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml version','\?>',false);
4330
		if (strpos($_html,'!CURSOR')!==false) Mail\Html::replaceTagsCompletley($_html,'!CURSOR');
4331
		// htmLawed filter only the 'body'
4332
		//preg_match('`(<htm.+?<body[^>]*>)(.+?)(</body>.*?</html>)`ims', $_html, $matches);
4333
		//if ($matches[2])
4334
		//{
4335
		//	$hasOther = true;
4336
		//	$_html = $matches[2];
4337
		//}
4338
		// purify got switched to htmLawed
4339
		// some testcode to test purifying / htmlawed
4340
		//$_html = "<BLOCKQUOTE>hi <div> there </div> kram <br> </blockquote>".$_html;
4341
		$_html = Html\HtmLawed::purify($_html,self::$htmLawed_config,array(),true);
4342
		//if ($hasOther) $_html = $matches[1]. $_html. $matches[3];
4343
		// clean out comments , should not be needed as purify should do the job.
4344
		$search = array(
4345
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
4346
			'@<!--[\s\S]*?[ \t\n\r]*-->@',         // Strip multi-line comments including CDATA
4347
		);
4348
		$_html = preg_replace($search,"",$_html);
4349
		// remove non printable chars
4350
		$_html = preg_replace('/([\000-\011])/','',$_html);
4351
		//error_log(__METHOD__.':'.__LINE__.':'.$_html);
4352
	}
4353
4354
	/**
4355
	 * Header and Bodystructure stuff
4356
	 */
4357
4358
	/**
4359
	 * getMimePartCharset - fetches the charset mimepart if it exists
4360
	 * @param $_mimePartObject structure object
4361
	 * @return mixed mimepart or false if no CHARSET is found, the missing charset has to be handled somewhere else,
4362
	 *		as we cannot safely assume any charset as we did earlier
4363
	 */
4364
	function getMimePartCharset($_mimePartObject)
4365
	{
4366
		//$charSet = 'iso-8859-1';//self::$displayCharset; //'iso-8859-1'; // self::displayCharset seems to be asmarter fallback than iso-8859-1
4367
		$CharsetFound=false;
4368
		//echo "#".$_mimePartObject->encoding.'#<br>';
4369
		if(is_array($_mimePartObject->parameters)) {
4370
			if(isset($_mimePartObject->parameters['CHARSET'])) {
4371
				$charSet = $_mimePartObject->parameters['CHARSET'];
4372
				$CharsetFound=true;
4373
			}
4374
		}
4375
		// this one is dirty, but until I find something that does the trick of detecting the encoding, ....
4376
		//if ($CharsetFound == false && $_mimePartObject->encoding == "QUOTED-PRINTABLE") $charSet = 'iso-8859-1'; //assume quoted-printable to be ISO
4377
		//if ($CharsetFound == false && $_mimePartObject->encoding == "BASE64") $charSet = 'utf-8'; // assume BASE64 to be UTF8
4378
		return ($CharsetFound ? $charSet : $CharsetFound);
4379
	}
4380
4381
	/**
4382
	 * decodeMimePart - fetches the charset mimepart if it exists
4383
	 * @param string $_mimeMessage - the message to be decoded
4384
	 * @param string $_encoding - the encoding used BASE64 and QUOTED-PRINTABLE is supported
4385
	 * @param string $_charset - not used
4386
	 * @return string decoded mimePart
4387
	 */
4388
	function decodeMimePart($_mimeMessage, $_encoding, $_charset = '')
4389
	{
4390
		// decode the part
4391
		if (self::$debug) error_log(__METHOD__."() with $_encoding and $_charset:".print_r($_mimeMessage,true));
4392
		switch (strtoupper($_encoding))
4393
		{
4394
			case 'BASE64':
4395
				// use imap_base64 to decode, not any longer, as it is strict, and fails if it encounters invalid chars
4396
				return base64_decode($_mimeMessage);
4397
4398
			case 'QUOTED-PRINTABLE':
4399
				// use imap_qprint to decode
4400
				return quoted_printable_decode($_mimeMessage);
4401
4402
			case 'WEDONTKNOWTHEENCODING':
4403
				// try base64
4404
				$r = base64_decode($_mimeMessage);
4405
				if (json_encode($r))
4406
				{
4407
					return $r;
4408
				}
4409
				//we do not know the encoding, so we do not decode
4410
			default:
4411
				// it is either not encoded or we don't know about it
4412
				return $_mimeMessage;
4413
		}
4414
	}
4415
4416
	/**
4417
	 * get part of the message, if its stucture is indicating its of multipart alternative style
4418
	 * a wrapper for multipartmixed
4419
	 * @param string/int $_uid the messageuid,
4420
	 * @param Horde_Mime_Part $_structure structure for parsing
4421
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4422
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4423
	 * @return array containing the desired part
4424
	 */
4425
	function getMultipartAlternative($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false)
4426
	{
4427
		// a multipart/alternative has exactly 2 parts (text and html  OR  text and something else)
4428
		// sometimes there are 3 parts, when there is an ics/ical attached/included-> we want to show that
4429
		// as attachment AND as abstracted ical information (we use our notification style here).
4430
		$partText = $partCalendar = $partHTML = null;
4431
		if (self::$debug) _debug_array(array("METHOD"=>__METHOD__,"LINE"=>__LINE__,"STRUCTURE"=>$_structure));
4432
		//error_log(__METHOD__.' ('.__LINE__.') ');
4433
		$ignore_first_part = true;
4434
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4435
		{
4436
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type"." ignoreFirstPart:".$ignore_first_part);
4437
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4438
4439
			if ($ignore_first_part)
4440
			{
4441
				$ignore_first_part = false;
4442
				continue;	// ignore multipart/alternative itself
4443
			}
4444
4445
			$mimePart = $_structure->getPart($mime_id);
4446
4447
			switch($mimePart->getPrimaryType())
4448
			{
4449
				case 'text':
4450
					switch($mimePart->getSubType())
4451
					{
4452
						case 'plain':
4453
							if ($mimePart->getBytes() > 0) $partText = $mimePart;
4454
							break;
4455
4456
						case 'html':
4457
							if ($mimePart->getBytes() > 0)  $partHTML = $mimePart;
4458
							break;
4459
					}
4460
					break;
4461
4462
				case 'multipart':
4463
					switch($mimePart->getSubType())
4464
					{
4465
						case 'related':
4466
						case 'mixed':
4467
							if (count($mimePart->getParts()) > 1)
4468
							{
4469
								// in a multipart alternative we treat the multipart/related as html part
4470
								if (self::$debug) error_log(__METHOD__." process MULTIPART/".$mimePart->getSubType()." with array as subparts");
4471
								$partHTML = $mimePart;
4472
								break 3; // GET OUT OF LOOP, will be processed according to type
4473
							}
4474
							break;
4475
						case 'alternative':
4476
							if (count($mimePart->getParts()) > 1)
4477
							{
4478
								//cascading multipartAlternative structure, assuming only the first one is to be used
4479
								return $this->getMultipartAlternative($_uid, $mimePart, $_htmlMode, $_preserveSeen);
4480
							}
4481
					}
4482
			}
4483
		}
4484
4485
		switch($_htmlMode)
4486
		{
4487
			case 'html_only':
4488
			case 'always_display':
4489
				if ($partHTML)
4490
				{
4491
					switch($partHTML->getSubType())
4492
					{
4493
						case 'related':
4494
							return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4495
4496
						case 'mixed':
4497
							return $this->getMultipartMixed($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4498
4499
						default:
4500
							return $this->getTextPart($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4501
					}
4502
				}
4503
				elseif ($partText && $_htmlMode=='always_display')
4504
				{
4505
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4506
				}
4507
				break;
4508
4509
			case 'only_if_no_text':
4510
				if ($partText)
4511
				{
4512
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4513
				}
4514
				if ($partHTML)
4515
				{
4516
					if ($partHTML->getPrimaryType())
4517
					{
4518
						return $this->getMultipartRelated($_uid, $partHTML, $_htmlMode, $_preserveSeen);
4519
					}
4520
					return $this->getTextPart($_uid, $partHTML, 'always_display', $_preserveSeen);
4521
				}
4522
				break;
4523
4524
			default:
4525
				if ($partText)
4526
				{
4527
					return $this->getTextPart($_uid, $partText, $_htmlMode, $_preserveSeen);
4528
				}
4529
				$bodyPart = array(
4530
					'body'		=> lang("no plain text part found"),
4531
					'mimeType'	=> 'text/plain',
4532
					'charSet'	=> self::$displayCharset,
4533
				);
4534
				break;
4535
		}
4536
		return $bodyPart;
4537
	}
4538
4539
	/**
4540
	 * Get part of the message, if its stucture is indicating its of multipart mixed style
4541
	 *
4542
	 * @param int $_uid the messageuid,
4543
	 * @param Horde_Mime_Part $_structure = '' if given use structure for parsing
4544
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4545
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4546
	 * @param array	&$skipParts - passed by reference to have control/knowledge which parts are already fetched
4547
	 * @return array containing the desired part
4548
	 */
4549
	function getMultipartMixed($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false, &$skipParts=array())
4550
	{
4551
		if (self::$debug) echo __METHOD__."$_uid, $_htmlMode<br>";
4552
		$bodyPart = array();
4553
		if (self::$debug) _debug_array($_structure);
4554
4555
		$ignore_first_part = true;
4556
		//$skipParts = array();
4557
		//error_log(__METHOD__.__LINE__.array2string($_structure->contentTypeMap()));
4558
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
4559
		{
4560
			//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") $mime_id: $mime_type");
4561
			if (self::$debug) echo __METHOD__."($_uid, partID=".$_structure->getMimeId().") $mime_id: $mime_type<br>";
4562
			if ($ignore_first_part)
4563
			{
4564
				$ignore_first_part = false;
4565
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED FirstPart $mime_id: $mime_type");
4566
				continue;	// ignore multipart/mixed itself
4567
			}
4568
			if (array_key_exists($mime_id,$skipParts))
4569
			{
4570
				//error_log(__METHOD__."($_uid, ".$_structure->getMimeId().") SKIPPED $mime_id: $mime_type");
4571
				continue;
4572
			}
4573
4574
			$part = $_structure->getPart($mime_id);
4575
4576
			switch($part->getPrimaryType())
4577
			{
4578
				case 'multipart':
4579
					if ($part->getDisposition() == 'attachment') continue;
4580
					switch($part->getSubType())
4581
					{
4582
						case 'alternative':
4583
							return array($this->getMultipartAlternative($_uid, $part, $_htmlMode, $_preserveSeen));
4584
4585
						case 'mixed':
4586
						case 'signed':
4587
							$bodyPart = array_merge($bodyPart, $this->getMultipartMixed($_uid, $part, $_htmlMode, $_preserveSeen, $skipParts));
4588
							break;
4589
4590
						case 'related':
4591
							$bodyPart = array_merge($bodyPart, $this->getMultipartRelated($_uid, $part, $_htmlMode, $_preserveSeen));
4592
							break;
4593
					}
4594
					break;
4595
				case 'application':
4596
					switch($part->getSubType())
4597
					{
4598
						case 'pgp-encrypted':
4599
							if (($part = $_structure->getPart($mime_id+1)) &&
4600
								$part->getType() == 'application/octet-stream')
4601
							{
4602
								$this->fetchPartContents($_uid, $part);
4603
								$skipParts[$mime_id]=$mime_type;
4604
								$skipParts[$mime_id+1]=$part->getType();
4605
								$bodyPart[] = array(
4606
									'body'		=> $part->getContents(array(
4607
										'stream' => false,
4608
									)),
4609
									'mimeType'  => 'text/plain',
4610
									'charSet'	=> $_structure->getCharset(),
4611
								);
4612
							}
4613
							break;
4614
					}
4615
					break;
4616
4617
				case 'text':
4618
					switch($part->getSubType())
4619
					{
4620
						case 'plain':
4621
						case 'html':
4622
						case 'calendar': // inline ics/ical files
4623
							if($part->getDisposition() != 'attachment')
4624
							{
4625
								$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4626
								$skipParts[$mime_id]=$mime_type;
4627
							}
4628
							//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$part->type."/".$part->subType.' -> BodyPart:'.array2string($bodyPart[count($bodyPart)-1]));
4629
							break;
4630
					}
4631
					break;
4632
4633
				case 'message':
4634
					//skip attachments
4635
					if($part->getSubType() == 'delivery-status' && $part->getDisposition() != 'attachment')
4636
					{
4637
						$bodyPart[] = $this->getTextPart($_uid, $part, $_htmlMode, $_preserveSeen);
4638
						$skipParts[$mime_id]=$mime_type;
4639
					}
4640
					// do not descend into attached Messages
4641
					if($part->getSubType() == 'rfc822' || $part->getDisposition() == 'attachment')
4642
					{
4643
						$skipParts[$mime_id.'.0'] = $mime_type;
4644
						foreach($part->contentTypeMap() as $sub_id => $sub_type){ $skipParts[$sub_id] = $sub_type;}
4645
						//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$_uid.' Part:'.$mime_id.':'.array2string($skipParts));
4646
						//break 2;
4647
					}
4648
					break;
4649
4650
				default:
4651
					// do nothing
4652
					// the part is a attachment
4653
			}
4654
		}
4655
		return $bodyPart;
4656
	}
4657
4658
	/**
4659
	 * getMultipartRelated
4660
	 * get part of the message, if its stucture is indicating its of multipart related style
4661
	 * a wrapper for multipartmixed
4662
	 * @param string/int $_uid the messageuid,
4663
	 * @param Horde_Mime_Part $_structure if given use structure for parsing
4664
	 * @param string $_htmlMode how to display a message, html, plain text, ...
4665
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4666
	 * @return array containing the desired part
4667
	 */
4668
	function getMultipartRelated($_uid, Horde_Mime_Part $_structure, $_htmlMode, $_preserveSeen = false)
4669
	{
4670
		return $this->getMultipartMixed($_uid, $_structure, $_htmlMode, $_preserveSeen);
4671
	}
4672
4673
	/**
4674
	 * Fetch a body part
4675
	 *
4676
	 * @param int $_uid
4677
	 * @param string $_partID = null
4678
	 * @param string $_folder = null
4679
	 * @param boolean $_preserveSeen = false
4680
	 * @param boolean $_stream = false true return a stream, false return string
4681
	 * @param string &$_encoding = null on return: transfer encoding of returned part
4682
	 * @param boolean $_tryDecodingServerside = true; wether to try to fetch Data with BINARY instead of BODY
4683
	 * @return string|resource
4684
	 */
4685
	function getBodyPart($_uid, $_partID=null, $_folder=null, $_preserveSeen=false, $_stream=false, &$_encoding=null, $_tryDecodingServerside=true)
4686
	{
4687
		if (self::$debug) error_log( __METHOD__.__LINE__."(".array2string($_uid).", $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, $_tryDecodingServerside)");
4688
4689 View Code Duplication
		if (empty($_folder))
4690
		{
4691
			$_folder = (isset($this->sessionData['mailbox'])&&$this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
4692
		}
4693
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_folder).'/'.$this->icServer->getCurrentMailbox().'/'. $this->sessionData['mailbox']);
4694
		// querying contents of body part
4695
		$uidsToFetch = new Horde_Imap_Client_Ids();
4696
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
4697
		$uidsToFetch->add($_uid);
4698
4699
		$fquery = new Horde_Imap_Client_Fetch_Query();
4700
		$fetchParams = array(
4701
			'peek' => $_preserveSeen,
4702
			'decode' => true,	// try decode on server, does NOT neccessary work
4703
		);
4704
		if ($_tryDecodingServerside===false)// || ($_tryDecodingServerside&&$this->isDraftFolder($_folder)))
4705
		{
4706
			$_tryDecodingServerside=false;
4707
			$fetchParams = array(
4708
				'peek' => $_preserveSeen,
4709
			);
4710
		}
4711
		$fquery->bodyPart($_partID, $fetchParams);
4712
4713
		$part = $this->icServer->fetch($_folder, $fquery, array(
4714
			'ids' => $uidsToFetch,
4715
		))->first();
4716
		$partToReturn = null;
4717
		if ($part)
4718
		{
4719
			$_encoding = $part->getBodyPartDecode($_partID);
4720
			//error_log(__METHOD__.__LINE__.':'.$_encoding.'#');
4721
			$partToReturn = $part->getBodyPart($_partID, $_stream);
4722
			//error_log(__METHOD__.__LINE__.':'.$partToReturn.'#');
4723
		}
4724
		// if we get an empty result, server may have trouble fetching data with UID FETCH $_uid (BINARY.PEEK[$_partID])
4725
		// thus we trigger a second go with UID FETCH $_uid (BODY.PEEK[$_partID])
4726
		if (empty($partToReturn)&&$_tryDecodingServerside===true)
4727
		{
4728
			error_log(__METHOD__.__LINE__.' failed to fetch bodyPart in  BINARY. Try BODY');
4729
			$partToReturn = $this->getBodyPart($_uid, $_partID, $_folder, $_preserveSeen, $_stream, $_encoding, false);
4730
		}
4731
		return ($partToReturn?$partToReturn:null);
4732
	}
4733
4734
	/**
4735
	 * Get Body from message
4736
	 *
4737
	 * @param int $_uid the messageuid
4738
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4739
	 * @param string $_htmlMode how to display a message: 'html_only', 'always_display', 'only_if_no_text' or ''
4740
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4741
	 * @param boolean $_stream = false true return a stream, false return string
4742
	 * @return array containing the desired text part, mimeType and charset
4743
	 */
4744
	function getTextPart($_uid, Horde_Mime_Part $_structure, $_htmlMode='', $_preserveSeen=false, $_stream=false)
4745
	{
4746
		//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_uid.':'.array2string($_structure).' '.function_backtrace());
4747
		$bodyPart = array();
4748
		if (self::$debug) _debug_array(array($_structure,function_backtrace()));
4749
4750
		if($_structure->getSubType() == 'html' && !in_array($_htmlMode, array('html_only', 'always_display', 'only_if_no_text')))
4751
		{
4752
			$bodyPart = array(
4753
				'error'		=> 1,
4754
				'body'		=> lang("displaying html messages is disabled"),
4755
				'mimeType'	=> 'text/html',
4756
				'charSet'	=> self::$displayCharset,
4757
			);
4758
		}
4759
		elseif ($_structure->getSubType() == 'plain' && $_htmlMode == 'html_only')
4760
		{
4761
			$bodyPart = array(
4762
				'error'		=> 1,
4763
				'body'      => lang("displaying plain messages is disabled"),
4764
				'mimeType'  => 'text/plain', // make sure we do not return mimeType text/html
4765
				'charSet'   => self::$displayCharset,
4766
			);
4767
		}
4768
		else
4769
		{
4770
			// some Servers append PropertyFile___ ; strip that here for display
4771
			// RB: not sure what this is: preg_replace('/PropertyFile___$/','',$this->decodeMimePart($mimePartBody, $_structure->encoding, $this->getMimePartCharset($_structure))),
4772
			$this->fetchPartContents($_uid, $_structure, $_stream, $_preserveSeen);
4773
4774
			$bodyPart = array(
4775
				'body'		=> $_structure->getContents(array(
4776
					'stream' => $_stream,
4777
				)),
4778
				'mimeType'  => $_structure->getType() == 'text/html' ? 'text/html' : 'text/plain',
4779
				'charSet'	=> $_structure->getCharset(),
4780
			);
4781
		}
4782
		return $bodyPart;
4783
	}
4784
4785
	/**
4786
	 * Get Body of message
4787
	 *
4788
	 * @param int $_uid the messageuid,
4789
	 * @param string $_htmlOptions how to display a message, html, plain text, ...
4790
	 * @param string $_partID = null the partID, may be omitted
4791
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
4792
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
4793
	 * @param string $_folder folder to work on
4794
	 * @return array containing the message body, mimeType and charset
4795
	 */
4796
	function getMessageBody($_uid, $_htmlOptions='', $_partID=null, Horde_Mime_Part $_structure=null, $_preserveSeen = false, $_folder = '')
4797
	{
4798
		if (self::$debug) echo __METHOD__."$_uid, $_htmlOptions, $_partID<br>";
4799
		if($_htmlOptions != '') {
4800
			$this->htmlOptions = $_htmlOptions;
4801
		}
4802
		if (empty($_folder))
4803
		{
4804
			$_folder = $this->sessionData['mailbox'];
4805
		}
4806
		if (empty($this->sessionData['mailbox']) && !empty($_folder))
4807
		{
4808
			$this->sessionData['mailbox'] = $_folder;
4809
		}
4810
4811
		if (!isset($_structure))
4812
		{
4813
			$_structure = $this->getStructure($_uid, $_partID, $_folder, $_preserveSeen);
4814
		}
4815
		if (!is_object($_structure))
4816
		{
4817
			return array(
4818
				array(
4819
					'error'		=> 1,
4820
					'body'		=> 'Error: Could not fetch structure on mail:'.$_uid." as $_htmlOptions". 'for Mailprofile'.$this->icServer->ImapServerId.' User:'.$GLOBALS['egw_info']['user']['account_lid'],
4821
					'mimeType'	=> 'text/plain',
4822
					'charSet'	=> self::$displayCharset,
4823
				)
4824
			);
4825
		}
4826
		if (!empty($_partID))
4827
		{
4828
			$_structure->contentTypeMap();
4829
			$_structure = $_structure->getPart($_partID);
4830
			//_debug_array($_structure->getMimeId()); exit;
4831
		}
4832
4833
		switch($_structure->getPrimaryType())
4834
		{
4835
			case 'application':
4836
				return array(
4837
					array(
4838
						'body'		=> '',
4839
						'mimeType'	=> 'text/plain',
4840
						'charSet'	=> 'iso-8859-1',
4841
					)
4842
				);
4843
4844
			case 'multipart':
4845
				switch($_structure->getSubType())
4846
				{
4847
					case 'alternative':
4848
						$bodyParts = array($this->getMultipartAlternative($_uid, $_structure, $this->htmlOptions, $_preserveSeen));
0 ignored issues
show
Bug introduced by
It seems like $this->htmlOptions can also be of type array; however, EGroupware\Api\Mail::getMultipartAlternative() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4849
						break;
4850
4851
					case 'nil': // multipart with no Alternative
4852
					case 'mixed':
4853
					case 'report':
4854
					case 'signed':
4855
					case 'encrypted':
4856
						$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...
4857
						break;
4858
4859
					case 'related':
4860
						$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...
4861
						break;
4862
				}
4863
				return self::normalizeBodyParts($bodyParts);
4864
4865
			case 'video':
4866
			case 'audio': // some servers send audiofiles and imagesfiles directly, without any stuff surround it
4867
			case 'image': // they are displayed as Attachment NOT INLINE
4868
				return array(
4869
					array(
4870
						'body'      => '',
4871
						'mimeType'  => $_structure->getSubType(),
4872
					),
4873
				);
4874
4875
			case 'text':
4876
				$bodyPart = array();
4877
				if ($_structure->getDisposition() != 'attachment')
4878
				{
4879
					switch($_structure->getSubType())
4880
					{
4881
						case 'calendar':
4882
							// this is handeled in getTextPart
4883
						case 'html':
4884
						case 'plain':
4885
						default:
4886
							$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...
4887
					}
4888
				} 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...
4889
					// what if the structure->disposition is attachment ,...
4890
				}
4891
				return self::normalizeBodyParts($bodyPart);
4892
4893
			case 'attachment':
4894
			case 'message':
4895
				switch($_structure->getSubType())
4896
				{
4897
					case 'rfc822':
4898
						$newStructure = $_structure->getParts();
4899
						if (self::$debug) {echo __METHOD__." Message -> RFC -> NewStructure:"; _debug_array($newStructure[0]);}
4900
						return self::normalizeBodyParts($this->getMessageBody($_uid, $_htmlOptions, $newStructure[0]->getMimeId(), $newStructure[0], $_preserveSeen, $_folder));
4901
				}
4902
				break;
4903
4904
			default:
4905
				if (self::$debug) _debug_array($_structure);
4906
				return array(
4907
					array(
4908
						'body'		=> lang('The mimeparser can not parse this message.').$_structure->getType(),
4909
						'mimeType'	=> 'text/plain',
4910
						'charSet'	=> self::$displayCharset,
4911
					)
4912
				);
4913
		}
4914
	}
4915
4916
	/**
4917
	 * normalizeBodyParts - function to gather and normalize all body Information
4918
	 * as we may recieve a bodyParts structure from within getMessageBody nested deeper than expected
4919
	 * so this is used to normalize the output, so we are able to rely on our expectation
4920
	 * @param _bodyParts - Body Array
4921
	 * @return array - a normalized Bodyarray
4922
	 */
4923
	static function normalizeBodyParts($_bodyParts)
4924
	{
4925
		if (is_array($_bodyParts))
4926
		{
4927
			foreach($_bodyParts as $singleBodyPart)
4928
			{
4929
				if (!isset($singleBodyPart['body'])) {
4930
					$buff = self::normalizeBodyParts($singleBodyPart);
4931
					foreach ((array)$buff as $val) { $body2return[] = $val;}
4932
					continue;
4933
				}
4934
				$body2return[] = $singleBodyPart;
4935
			}
4936
		}
4937
		else
4938
		{
4939
			$body2return = $_bodyParts;
4940
		}
4941
		return $body2return;
4942
	}
4943
4944
	/**
4945
	 * getdisplayableBody - creates the bodypart of the email as textual representation
4946
	 * @param object $mailClass the mailClass object to be used
4947
	 * @param array $bodyParts  with the bodyparts
4948
	 * @param boolean $preserveHTML  switch to preserve HTML
4949
	 * @param boolean $useTidy  switch to use tidy
4950
	 * @return string a preformatted string with the mails converted to text
4951
	 */
4952
	static function &getdisplayableBody(&$mailClass, $bodyParts, $preserveHTML = false,  $useTidy = true)
4953
	{
4954
		$message='';
4955
		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...
4956
		{
4957
			if (!isset($bodyParts[$i]['body'])) {
4958
				$bodyParts[$i]['body'] = self::getdisplayableBody($mailClass, $bodyParts[$i], $preserveHTML, $useTidy);
4959
				$message .= empty($bodyParts[$i]['body'])?'':$bodyParts[$i]['body'];
4960
				continue;
4961
			}
4962
			if (isset($bodyParts[$i]['error'])) continue;
4963
			if (empty($bodyParts[$i]['body'])) continue;
4964
			// some characterreplacements, as they fail to translate
4965
			$sar = array(
4966
				'@(\x84|\x93|\x94)@',
4967
				'@(\x96|\x97|\x1a)@',
4968
				'@(\x82|\x91|\x92)@',
4969
				'@(\x85)@',
4970
				'@(\x86)@',
4971
				'@(\x99)@',
4972
				'@(\xae)@',
4973
			);
4974
			$rar = array(
4975
				'"',
4976
				'-',
4977
				'\'',
4978
				'...',
4979
				'&',
4980
				'(TM)',
4981
				'(R)',
4982
			);
4983
4984
			if(($bodyParts[$i]['mimeType'] == 'text/html' || $bodyParts[$i]['mimeType'] == 'text/plain') &&
4985
				strtoupper($bodyParts[$i]['charSet']) != 'UTF-8')
4986
			{
4987
				$bodyParts[$i]['body'] = preg_replace($sar,$rar,$bodyParts[$i]['body']);
4988
			}
4989
4990 View Code Duplication
			if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Translation::detect_encoding($bodyParts[$i]['body']);
4991
			// add line breaks to $bodyParts
4992
			//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
4993
			$newBody  = Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
4994
			//error_log(__METHOD__.' ('.__LINE__.') '.' MimeType:'.$bodyParts[$i]['mimeType'].'->'.$newBody);
4995
			$mailClass->activeMimeType = 'text/plain';
4996
			if ($bodyParts[$i]['mimeType'] == 'text/html') {
4997
				$mailClass->activeMimeType = $bodyParts[$i]['mimeType'];
4998
				if (!$preserveHTML)
4999
				{
5000
					$alreadyHtmlLawed=false;
5001
					// as Translation::convert reduces \r\n to \n and purifier eats \n -> peplace it with a single space
5002
					$newBody = str_replace("\n"," ",$newBody);
5003
					// convert HTML to text, as we dont want HTML in infologs
5004 View Code Duplication
					if ($useTidy && extension_loaded('tidy'))
5005
					{
5006
						$tidy = new tidy();
5007
						$cleaned = $tidy->repairString($newBody, self::$tidy_config,'utf8');
5008
						// Found errors. Strip it all so there's some output
5009
						if($tidy->getStatus() == 2)
5010
						{
5011
							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...
5012
						}
5013
						else
5014
						{
5015
							$newBody = $cleaned;
5016
						}
5017
						if (!$preserveHTML)
5018
						{
5019
							// filter only the 'body', as we only want that part, if we throw away the html
5020
							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...
5021
							if ($matches[2])
5022
							{
5023
								$hasOther = true;
5024
								$newBody = $matches[2];
5025
							}
5026
						}
5027
					}
5028
					else
5029
					{
5030
						// htmLawed filter only the 'body'
5031
						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...
5032
						if ($matches[2])
5033
						{
5034
							$hasOther = true;
5035
							$newBody = $matches[2];
5036
						}
5037
						$htmLawed = new Html\HtmLawed();
5038
						// the next line should not be needed, but produces better results on HTML 2 Text conversion,
5039
						// as we switched off HTMLaweds tidy functionality
5040
						$newBody = str_replace(array('&amp;amp;','<DIV><BR></DIV>',"<DIV>&nbsp;</DIV>",'<div>&nbsp;</div>'),array('&amp;','<BR>','<BR>','<BR>'),$newBody);
5041
						$newBody = $htmLawed->run($newBody,self::$htmLawed_config);
5042
						if ($hasOther && $preserveHTML) $newBody = $matches[1]. $newBody. $matches[3];
5043
						$alreadyHtmlLawed=true;
5044
					}
5045
					//error_log(__METHOD__.' ('.__LINE__.') '.' after purify:'.$newBody);
5046
					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...
5047
					//error_log(__METHOD__.' ('.__LINE__.') '.' after convertHTMLToText:'.$newBody);
5048
					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...
5049
					/*if (!$alreadyHtmlLawed) */ $mailClass->getCleanHTML($newBody); // remove stuff we regard as unwanted
5050
					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...
5051
					//error_log(__METHOD__.' ('.__LINE__.') '.' after getClean:'.$newBody);
5052
				}
5053
				$message .= $newBody;
5054
				continue;
5055
			}
5056
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after specialchars):'.$newBody);
5057
			//use Mail\Html::convertHTMLToText instead of strip_tags, (even message is plain text) as strip_tags eats away too much
5058
			//$newBody = strip_tags($newBody); //we need to fix broken tags (or just stuff like "<800 USD/p" )
5059
			$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...
5060
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body(after strip tags):'.$newBody);
5061
			$newBody = htmlspecialchars_decode($newBody,ENT_QUOTES);
5062
			//error_log(__METHOD__.' ('.__LINE__.') '.' Body (after hmlspc_decode):'.$newBody);
5063
			$message .= $newBody;
5064
			//continue;
5065
		}
5066
		return $message;
5067
	}
5068
5069
	static function wordwrap($str, $cols, $cut, $dontbreaklinesstartingwith=false)
5070
	{
5071
		$lines = explode("\n", $str);
5072
		$newStr = '';
5073
		foreach($lines as $line)
5074
		{
5075
			// replace tabs by 8 space chars, or any tab only counts one char
5076
			//$line = str_replace("\t","        ",$line);
5077
			//$newStr .= wordwrap($line, $cols, $cut);
5078
			$allowedLength = $cols-strlen($cut);
5079
			//dont try to break lines with links, chance is we mess up the text is way too big
5080
			if (strlen($line) > $allowedLength && stripos($line,'href=')===false &&
5081
				($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...
5082
				 ($dontbreaklinesstartingwith &&
5083
				  strlen($dontbreaklinesstartingwith)>=1 &&
5084
				  substr($line,0,strlen($dontbreaklinesstartingwith)) != $dontbreaklinesstartingwith
5085
				 )
5086
				)
5087
			   )
5088
			{
5089
				$s=explode(" ", $line);
5090
				$line = "";
5091
				$linecnt = 0;
5092
				foreach ($s as &$v) {
5093
					$cnt = strlen($v);
5094
					// only break long words within the wordboundaries,
5095
					// but it may destroy links, so we check for href and dont do it if we find one
5096
					// we check for any html within the word, because we do not want to break html by accident
5097
					if($cnt > $allowedLength && stripos($v,'href=')===false && stripos($v,'onclick=')===false && $cnt == strlen(html_entity_decode($v)))
5098
					{
5099
						$v=wordwrap($v, $allowedLength, $cut, true);
5100
					}
5101
					// the rest should be broken at the start of the new word that exceeds the limit
5102
					if ($linecnt+$cnt > $allowedLength) {
5103
						$v=$cut.$v;
5104
						#$linecnt = 0;
5105
						$linecnt =strlen($v)-strlen($cut);
5106
					} else {
5107
						$linecnt += $cnt;
5108
					}
5109
					if (strlen($v)) $line .= (strlen($line) ? " " : "").$v;
5110
				}
5111
			}
5112
			$newStr .= $line . "\n";
5113
		}
5114
		return $newStr;
5115
	}
5116
5117
	/**
5118
	 * getMessageEnvelope
5119
	 * get parsed headers from message
5120
	 * @param string/int $_uid the messageuid,
5121
	 * @param string/int $_partID = '' , the partID, may be omitted
5122
	 * @param boolean $decode flag to do the decoding on the fly
5123
	 * @param string $_folder folder to work on
5124
	 * @param boolean $_useHeaderInsteadOfEnvelope - force getMessageHeader method to be used for fetching Envelope Information
5125
	 * @return array the message header
5126
	 */
5127
	function getMessageEnvelope($_uid, $_partID = '',$decode=false, $_folder='', $_useHeaderInsteadOfEnvelope=false)
5128
	{
5129
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder".function_backtrace());
5130 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5131
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid,$_partID,$decode,$_folder");
5132
		if((empty($_partID)||$_partID=='null')&&$_useHeaderInsteadOfEnvelope===false) {
5133
			$uidsToFetch = new Horde_Imap_Client_Ids();
5134
			if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5135
			$uidsToFetch->add($_uid);
5136
5137
			$fquery = new Horde_Imap_Client_Fetch_Query();
5138
			$envFields = new Horde_Mime_Headers();
5139
			$fquery->envelope();
5140
			$fquery->size();
5141
			$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5142
				'ids' => $uidsToFetch,
5143
			));
5144
			if (is_object($headersNew)) {
5145
				foreach($headersNew as &$_headerObject) {
5146
					$env = $_headerObject->getEnvelope();
5147
					//_debug_array($envFields->singleFields());
5148
					$singleFields = $envFields->singleFields();
5149
					foreach ($singleFields as &$v)
5150
					{
5151
						switch ($v)
5152
						{
5153
							case 'to':
5154
							case 'reply-to':
5155
							case 'from':
5156
							case 'cc':
5157
							case 'bcc':
5158
							case 'sender':
5159
								//error_log(__METHOD__.' ('.__LINE__.') '.$v.'->'.array2string($env->$v->addresses));
5160
								$envelope[$v]=$env->$v->addresses;
5161
								$address = array();
5162
								if (!is_array($envelope[$v])) break;
5163
								foreach ($envelope[$v] as $k => $ad)
5164
								{
5165
									if (stripos($ad,'@')===false)
5166
									{
5167
										$remember=$k;
5168
									}
5169
									else
5170
									{
5171
										$address[] = (!is_null($remember)?$envelope[$v][$remember].' ':'').$ad;
5172
										$remember=null;
5173
									}
5174
								}
5175
								$envelope[$v] = $address;
5176
								break;
5177
							case 'date':
5178
								$envelope[$v]=DateTime::to($env->$v);
5179
								break;
5180
							default:
5181
								$envelope[$v]=$env->$v;
5182
						}
5183
					}
5184
					$envelope['size']=$_headerObject->getSize();
5185
				}
5186
			}
5187
			$envelope = array_change_key_case($envelope,CASE_UPPER);
5188
			//if ($decode) _debug_array($envelope);
5189
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($envelope));
5190 View Code Duplication
			if ($decode)
5191
			{
5192
				foreach ($envelope as $key => $rvV)
5193
				{
5194
					//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5195
					$envelope[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5196
				}
5197
			}
5198
			return $envelope;
5199
		} else {
5200
5201
			$headers = $this->getMessageHeader($_uid, $_partID, true,true,$_folder);
5202
5203
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($headers));
5204
			//_debug_array($headers);
5205
			$newData = array(
5206
				'DATE'		=> $headers['DATE'],
5207
				'SUBJECT'	=> ($decode ? self::decode_header($headers['SUBJECT']):$headers['SUBJECT']),
5208
				'MESSAGE_ID'	=> $headers['MESSAGE-ID']
5209
			);
5210
			if (isset($headers['IN-REPLY-TO'])) $newData['IN-REPLY-TO'] = $headers['IN-REPLY-TO'];
5211
			if (isset($headers['REFERENCES'])) $newData['REFERENCES'] = $headers['REFERENCES'];
5212
			if (isset($headers['THREAD-TOPIC'])) $newData['THREAD-TOPIC'] = $headers['THREAD-TOPIC'];
5213
			if (isset($headers['THREAD-INDEX'])) $newData['THREAD-INDEX'] = $headers['THREAD-INDEX'];
5214
			if (isset($headers['LIST-ID'])) $newData['LIST-ID'] = $headers['LIST-ID'];
5215
			if (isset($headers['SIZE'])) $newData['SIZE'] = $headers['SIZE'];
5216
			//_debug_array($newData);
5217
			$recepientList = array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO');
5218
			foreach($recepientList as $recepientType) {
5219
				if(isset($headers[$recepientType])) {
5220
					if ($decode) $headers[$recepientType] =  self::decode_header($headers[$recepientType],true);
5221
					//error_log(__METHOD__.__LINE__." ".$recepientType."->".array2string($headers[$recepientType]));
5222
					foreach(self::parseAddressList($headers[$recepientType]) as $singleAddress) {
5223
						$addressData = array(
5224
							'PERSONAL_NAME'		=> $singleAddress->personal ? $singleAddress->personal : 'NIL',
5225
							'AT_DOMAIN_LIST'	=> $singleAddress->adl ? $singleAddress->adl : 'NIL',
5226
							'MAILBOX_NAME'		=> $singleAddress->mailbox ? $singleAddress->mailbox : 'NIL',
5227
							'HOST_NAME'		=> $singleAddress->host ? $singleAddress->host : 'NIL',
5228
							'EMAIL'			=> $singleAddress->host ? $singleAddress->mailbox.'@'.$singleAddress->host : $singleAddress->mailbox,
5229
						);
5230
						if($addressData['PERSONAL_NAME'] != 'NIL') {
5231
							$addressData['RFC822_EMAIL'] = imap_rfc822_write_address($singleAddress->mailbox, $singleAddress->host, $singleAddress->personal);
5232
						} else {
5233
							$addressData['RFC822_EMAIL'] = 'NIL';
5234
						}
5235
						$newData[$recepientType][] = ($addressData['RFC822_EMAIL']!='NIL'?$addressData['RFC822_EMAIL']:$addressData['EMAIL']);//$addressData;
5236
					}
5237
				} else {
5238
					if($recepientType == 'SENDER' || $recepientType == 'REPLY-TO') {
5239
						$newData[$recepientType] = $newData['FROM'];
5240
					} else {
5241
						$newData[$recepientType] = array();
5242
					}
5243
				}
5244
			}
5245
			//if ($decode) _debug_array($newData);
5246
			return $newData;
5247
		}
5248
	}
5249
5250
	/**
5251
	 * Get parsed headers from message
5252
	 *
5253
	 * @param string/int $_uid the messageuid,
5254
	 * @param string/int $_partID ='' , the partID, may be omitted
5255
	 * @param boolean|string $decode flag to do the decoding on the fly or "object"
5256
	 * @param boolean $preserveUnSeen flag to preserve the seen flag where applicable
5257
	 * @param string $_folder folder to work on
5258
	 * @return array|Horde_Mime_Headers message header as array or object
5259
	 */
5260
	function getMessageHeader($_uid, $_partID = '',$decode=false, $preserveUnSeen=false, $_folder='')
5261
	{
5262
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.$_uid.', '.$_partID.', '.$decode.', '.$preserveUnSeen.', '.$_folder);
5263 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5264
		$uidsToFetch = new Horde_Imap_Client_Ids();
5265
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5266
		$uidsToFetch->add($_uid);
5267
5268
		$fquery = new Horde_Imap_Client_Fetch_Query();
5269
		if ($_partID != '')
5270
		{
5271
			$fquery->headerText(array('id'=>$_partID,'peek'=>$preserveUnSeen));
5272
			$fquery->structure();
5273
		}
5274
		else
5275
		{
5276
			$fquery->headerText(array('peek'=>$preserveUnSeen));
5277
		}
5278
		$fquery->size();
5279
5280
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5281
			'ids' => $uidsToFetch,
5282
		));
5283
		if (is_object($headersNew)) {
5284
			foreach($headersNew as $_fetchObject)
5285
			{
5286
				$headers = $_fetchObject->getHeaderText(0,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5287
				if ($_partID != '')
5288
				{
5289
					$mailStructureObject = $_fetchObject->getStructure();
5290
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5291
					{
5292
						if ($mime_id==$_partID)
5293
						{
5294
							//error_log(__METHOD__.' ('.__LINE__.') '."$mime_id == $_partID".array2string($_headerObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray()));
5295
							$headers = $_fetchObject->getHeaderText($mime_id,Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
5296
							break;
5297
						}
5298
					}
5299
				}
5300
				$size = $_fetchObject->getSize();
5301
				//error_log(__METHOD__.__LINE__.'#'.$size);
5302
			}
5303
			if ($decode === 'object')
5304
			{
5305
				if (is_object($headers)) $headers->setUserAgent('EGroupware API '.$GLOBALS['egw_info']['server']['versions']['phpgwapi']);
5306
				return $headers;
5307
			}
5308
			$retValue = is_object($headers) ? $headers->toArray():array();
5309
			if ($size) $retValue['size'] = $size;
5310
		}
5311
		$retValue = array_change_key_case($retValue,CASE_UPPER);
5312
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue));
5313
		// if SUBJECT is an array, use thelast one, as we assume something with the unfolding for the subject did not work
5314
		if (is_array($retValue['SUBJECT']))
5315
		{
5316
			$retValue['SUBJECT'] = $retValue['SUBJECT'][count($retValue['SUBJECT'])-1];
5317
		}
5318
		//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($decode ? self::decode_header($retValue,true):$retValue));
5319 View Code Duplication
		if ($decode)
5320
		{
5321
			foreach ($retValue as $key => $rvV)
5322
			{
5323
				//try idn conversion only on 'FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO'
5324
				$retValue[$key]=self::decode_header($rvV,in_array($key,array('FROM', 'TO', 'CC', 'BCC', 'SENDER', 'REPLY-TO')));
5325
			}
5326
		}
5327
		return $retValue;
5328
	}
5329
5330
	/**
5331
	 * getMessageRawHeader
5332
	 * get messages raw header data
5333
	 * @param string/int $_uid the messageuid,
5334
	 * @param string/int $_partID = '' , the partID, may be omitted
5335
	 * @param string $_folder folder to work on
5336
	 * @return string the message header
5337
	 */
5338
	function getMessageRawHeader($_uid, $_partID = '', $_folder = '')
5339
	{
5340
		static $rawHeaders;
5341 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5342
		//error_log(__METHOD__.' ('.__LINE__.') '." Try Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5343
5344 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);
5345 View Code Duplication
		if (isset($rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5346
		{
5347
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Header $_uid, $_partID in Folder $_folder");
5348
			return $rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5349
		}
5350
		$uidsToFetch = new Horde_Imap_Client_Ids();
5351
		$uid = $_uid;
5352
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5353
		$uidsToFetch->add($uid);
5354
5355
		$fquery = new Horde_Imap_Client_Fetch_Query();
5356
		if ($_partID != '')
5357
		{
5358
			$fquery->headerText(array('id'=>$_partID,'peek'=>true));
5359
			$fquery->structure();
5360
		}
5361
		else
5362
		{
5363
			$fquery->headerText(array('peek'=>true));
5364
		}
5365
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5366
			'ids' => $uidsToFetch,
5367
		));
5368
		if (is_object($headersNew)) {
5369 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5370
				$retValue = $_headerObject->getHeaderText();
5371
				if ($_partID != '')
5372
				{
5373
					$mailStructureObject = $_headerObject->getStructure();
5374
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5375
					{
5376
						if ($mime_id==$_partID)
5377
						{
5378
							$retValue = $_headerObject->getHeaderText($mime_id);
5379
						}
5380
					}
5381
				}
5382
			}
5383
		}
5384
		$rawHeaders[$this->icServer->ImapServerId][(string)$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]=$retValue;
5385
		Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($GLOBALS['egw_info']['user']['account_id']),$rawHeaders,60*60*1);
5386
		return $retValue;
5387
	}
5388
5389
	/**
5390
	 * getStyles - extracts the styles from the given bodyparts
5391
	 * @param array $_bodyParts  with the bodyparts
5392
	 * @return string a preformatted string with the mails converted to text
5393
	 */
5394
	static function &getStyles($_bodyParts)
5395
	{
5396
		$style = '';
5397
		if (empty($_bodyParts)) return "";
5398
		foreach((array)$_bodyParts as $singleBodyPart) {
5399
			if (!isset($singleBodyPart['body'])) {
5400
				$singleBodyPart['body'] = self::getStyles($singleBodyPart);
5401
				$style .= $singleBodyPart['body'];
5402
				continue;
5403
			}
5404
5405
			if ($singleBodyPart['charSet']===false) $singleBodyPart['charSet'] = Translation::detect_encoding($singleBodyPart['body']);
5406
			$singleBodyPart['body'] = Translation::convert(
5407
				$singleBodyPart['body'],
5408
				strtolower($singleBodyPart['charSet'])
5409
			);
5410
			$ct = 0;
5411
			$newStyle=array();
5412
			if (stripos($singleBodyPart['body'],'<style')!==false)  $ct = preg_match_all('#<style(?:\s.*)?>(.+)</style>#isU', $singleBodyPart['body'], $newStyle);
5413
			if ($ct>0)
5414
			{
5415
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$ct.'#'.array2string($newStyle));
5416
				$style2buffer = implode('',$newStyle[0]);
5417
			}
5418
			if ($style2buffer && strtoupper(self::$displayCharset) == 'UTF-8')
5419
			{
5420
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($style2buffer));
5421
				$test = json_encode($style2buffer);
5422
				//error_log(__METHOD__.' ('.__LINE__.') '.'#'.$test.'# ->'.strlen($style2buffer).' Error:'.json_last_error());
5423
				//if (json_last_error() != JSON_ERROR_NONE && strlen($style2buffer)>0)
5424
				if ($test=="null" && strlen($style2buffer)>0)
5425
				{
5426
					// this should not be needed, unless something fails with charset detection/ wrong charset passed
5427
					error_log(__METHOD__.' ('.__LINE__.') '.' Found Invalid sequence for utf-8 in CSS:'.$style2buffer.' Charset Reported:'.$singleBodyPart['charSet'].' Carset Detected:'.Translation::detect_encoding($style2buffer));
5428
					$style2buffer = utf8_encode($style2buffer);
5429
				}
5430
			}
5431
			$style .= $style2buffer;
5432
		}
5433
		// clean out comments and stuff
5434
		$search = array(
5435
			'@url\(http:\/\/[^\)].*?\)@si',  // url calls e.g. in style definitions
5436
//			'@<!--[\s\S]*?[ \t\n\r]*-->@',   // Strip multi-line comments including CDATA
5437
//			'@<!--[\s\S]*?[ \t\n\r]*--@',    // Strip broken multi-line comments including CDATA
5438
		);
5439
		$style = preg_replace($search,"",$style);
5440
5441
		// CSS Security
5442
		// http://code.google.com/p/browsersec/wiki/Part1#Cascading_stylesheets
5443
		$css = preg_replace('/(javascript|expression|-moz-binding)/i','',$style);
5444
		if (stripos($css,'script')!==false) Mail\Html::replaceTagsCompletley($css,'script'); // Strip out script that may be included
5445
		// 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
5446
		// as the comments as <!-- styledefinition --> in stylesheet are outdated, and ck-editor does not understand it, we remove it
5447
		$css = str_replace(array(':','<!--','-->'),array(': ','',''),$css);
5448
		//error_log(__METHOD__.' ('.__LINE__.') '.$css);
5449
		// TODO: we may have to strip urls and maybe comments and ifs
5450
		return $css;
5451
	}
5452
5453
	/**
5454
	 * getMessageRawBody
5455
	 * get the message raw body
5456
	 * @param string/int $_uid the messageuid,
5457
	 * @param string/int $_partID = '' , the partID, may be omitted
5458
	 * @param string $_folder folder to work on
5459
	 * @return string the message body
5460
	 */
5461
	function getMessageRawBody($_uid, $_partID = '', $_folder='')
5462
	{
5463
		//TODO: caching einbauen static!
5464
		static $rawBody;
5465
		if (is_null($rawBody)) $rawBody = array();
5466 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5467 View Code Duplication
		if (isset($rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)]))
5468
		{
5469
			//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder");
5470
			return $rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)];
5471
		}
5472
5473
		$uidsToFetch = new Horde_Imap_Client_Ids();
5474
		$uid = $_uid;
5475
		if (!(is_object($_uid) || is_array($_uid))) $uid = (array)$_uid;
5476
		$uidsToFetch->add($uid);
5477
5478
		$fquery = new Horde_Imap_Client_Fetch_Query();
5479
		$fquery->fullText(array('peek'=>true));
5480
		if ($_partID != '')
5481
		{
5482
			$fquery->structure();
5483
			$fquery->bodyPart($_partID,array('peek'=>true));
5484
		}
5485
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5486
			'ids' => $uidsToFetch,
5487
		));
5488
		if (is_object($headersNew)) {
5489 View Code Duplication
			foreach($headersNew as &$_headerObject) {
5490
				$body = $_headerObject->getFullMsg();
5491
				if ($_partID != '')
5492
				{
5493
					$mailStructureObject = $_headerObject->getStructure();
5494
					//_debug_array($mailStructureObject->contentTypeMap());
5495
					foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
5496
					{
5497
						if ($mime_id==$_partID)
5498
						{
5499
							$body = $_headerObject->getBodyPart($mime_id);
5500
						}
5501
					}
5502
				}
5503
			}
5504
		}
5505
		//error_log(__METHOD__.' ('.__LINE__.') '."[$this->icServer->ImapServerId][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]");
5506
		$rawBody[$this->icServer->ImapServerId][$_folder][$_uid][(empty($_partID)?'NIL':$_partID)] = $body;
5507
		return $body;
5508
	}
5509
5510
	/**
5511
	 * Get structure of a mail or part of a mail
5512
	 *
5513
	 * @param int $_uid
5514
	 * @param string $_partID = null
5515
	 * @param string $_folder = null
5516
	 * @param boolean $_preserveSeen = false flag to preserve the seenflag by using body.peek
5517
	 * @param Horde_Imap_Client_Fetch_Query $fquery=null default query just structure
5518
	 * @return Horde_Mime_Part
5519
	 */
5520
	function getStructure($_uid, $_partID=null, $_folder=null, $_preserveSeen=false)
5521
	{
5522
		if (self::$debug) error_log( __METHOD__.' ('.__LINE__.') '.":$_uid, $_partID");
5523
5524 View Code Duplication
		if (empty($_folder))
5525
		{
5526
			$_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5527
		}
5528
		$uidsToFetch = new Horde_Imap_Client_Ids();
5529
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5530
		$uidsToFetch->add($_uid);
5531
		try
5532
		{
5533
			$_fquery = new Horde_Imap_Client_Fetch_Query();
5534
	// not sure why Klaus add these, seem not necessary
5535
	//		$fquery->envelope();
5536
	//		$fquery->size();
5537
			$_fquery->structure();
5538
			if ($_partID) $_fquery->bodyPart($_partID, array('peek' => $_preserveSeen));
1 ignored issue
show
Bug Best Practice introduced by
The expression $_partID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
5539
5540
			$mail = $this->icServer->fetch($_folder, $_fquery, array(
5541
				'ids' => $uidsToFetch,
5542
			))->first();
5543
5544
			return is_object($mail)?$mail->getStructure():null;
5545
		}
5546
		catch (\Exception $e)
5547
		{
5548
			error_log(__METHOD__.' ('.__LINE__.') '.' Could not fetch structure on mail:'.$_uid.' Serverprofile->'.$this->icServer->ImapServerId.' Message:'.$e->getMessage().' Stack:'.function_backtrace());
5549
			return null;
5550
		}
5551
	}
5552
5553
	/**
5554
	 * Parse the structure for attachments
5555
	 *
5556
	 * Returns not the attachments itself, but an array of information about the attachment
5557
	 *
5558
	 * @param int $_uid the messageuid,
5559
	 * @param string $_partID = null , the partID, may be omitted
5560
	 * @param Horde_Mime_Part $_structure = null if given use structure for parsing
5561
	 * @param boolean $fetchEmbeddedImages = true,
5562
	 * @param boolean $fetchTextCalendar = false,
5563
	 * @param boolean $resolveTNEF = true
5564
	 * @param string $_folder folder to work on
5565
	 * @return array  an array of information about the attachment: array of array(name, size, mimeType, partID, encoding)
5566
	 */
5567
	function getMessageAttachments($_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folder='')
5568
	{
5569
		if (self::$debug) error_log( __METHOD__.":$_uid, $_partID");
5570 View Code Duplication
		if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5571
		$attachments = array();
5572
		if (!isset($_structure))
5573
		{
5574
			$_structure = $this->getStructure($_uid, $_partID,$_folder,true);
5575
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.print_r($_structure->contentTypeMap(),true));
5576
		}
5577
		if (!$_structure || !$_structure->contentTypeMap()) return array();
5578
		if (!empty($_partID)) $_structure = $_structure->getPart($_partID);
5579
		$skipParts = array();
5580
		$tnefParts = array();
5581
		$skip = 0;
5582
		foreach($_structure->contentTypeMap() as $mime_id => $mime_type)
5583
		{
5584
			// skip multipart/encrypted incl. its two sub-parts, as we show 2. sub-part as body to be decrypted client-side
5585
			if ($mime_type == 'multipart/encrypted')
5586
			{
5587
				$skip = 2;
5588
				continue;
5589
			}
5590
			elseif($skip)
5591
			{
5592
				$skip--;
5593
				continue;
5594
			}
5595
			$part = $_structure->getPart($mime_id);
5596
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getMimeId()));
5597
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.$part->getPrimaryType().'/'.$part->getSubType().'->'.$part->getDisposition());
5598
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllDispositionParameters()));
5599
			//error_log(__METHOD__.' ('.__LINE__.') '.':'.' Uid:'.$uid.' Part:'.$_partID.'->'.array2string($part->getAllContentTypeParameters()));
5600
			$partDisposition = $part->getDisposition();
5601
			$partPrimaryType = $part->getPrimaryType();
5602
			// we only want to retrieve the attachments of the current mail, not those of possible
5603
			// attached mails
5604 View Code Duplication
			if ($mime_type=='message/rfc822' && $_partID!=$mime_id)
5605
			{
5606
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
5607
				foreach($part->contentTypeMap() as $sub_id => $sub_type) {if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
5608
			}
5609
			if (empty($partDisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
5610
			{
5611
				// the absence of an partDisposition does not necessarily indicate there is no attachment. it may be an
5612
				// attachment with no link to show the attachment inline.
5613
				// Considering this: we "list" everything that matches the above criteria
5614
				// as attachment in order to not loose/miss information on our data
5615
				$partDisposition='attachment';
5616
			}
5617
			//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($skipParts));
5618
			if (array_key_exists($mime_id,$skipParts)) continue;
5619
5620
			if ($partDisposition == 'attachment' ||
5621
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image' && $part->getContentId()=='') ||
5622
				(($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType != 'image' && $partPrimaryType != 'text' && $partPrimaryType != 'multipart') ||
5623
				($mime_type=='image/tiff') || //always fetch. even if $fetchEmbeddedImages is false. as we cannot display tiffs
5624
				($fetchEmbeddedImages && ($partDisposition == 'inline' || empty($partDisposition)) && $partPrimaryType == 'image') ||
5625
				($fetchTextCalendar && $partPrimaryType == 'text' && $part->getSubType() == 'calendar'))
5626
			{
5627
				// if type is message/rfc822 and _partID is given, and MimeID equals partID
5628
				// we attempt to fetch "ourselves"
5629
				if ($_partID==$part->getMimeId() && $part->getPrimaryType()=='message') continue;
5630
				$attachment = $part->getAllDispositionParameters();
5631
				$attachment['disposition'] = $part->getDisposition();
5632
				$attachment['mimeType'] = $mime_type;
5633
				$attachment['uid'] = $_uid;
5634
				$attachment['partID'] = $mime_id;
5635 View Code Duplication
				if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5636
				if ($fetchTextCalendar)
5637
				{
5638
					//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part->getAllContentTypeParameters()));
5639
					$method = $part->getContentTypeParameter('method');
5640
					if ($method) $attachment['method'] = $method;
5641
					if (!isset($attachment['name'])) $attachment['name'] = 'event.ics';
5642
				}
5643
				$attachment['size'] = $part->getBytes();
5644
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5645 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);
5646
				//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.' Part:'.$_partID.'->'.$mime_id.':'.array2string($attachment));
5647
				//typical winmail.dat attachment is
5648
				//Array([size] => 1462762[filename] => winmail.dat[mimeType] => application/ms-tnef[uid] => 100[partID] => 2[name] => winmail.dat)
5649
				if ($resolveTNEF && ($attachment['mimeType']=='application/ms-tnef' || !strcasecmp($attachment['name'],'winmail.dat')))
5650
				{
5651
					$tnefParts[] = $attachment;
5652
				}
5653
				else
5654
				{
5655
					$attachments[] = $attachment;
5656
				}
5657
			}
5658
		}
5659
		if ($resolveTNEF && !empty($tnefParts))
5660
		{
5661
			//error_log(__METHOD__.__LINE__.array2string($tnefParts));
5662
			foreach ($tnefParts as $k => $tnp)
5663
			{
5664
				$tnefResolved=false;
5665
				$tnef_data = $this->getAttachment($tnp['uid'],$tnp['partID'],$k,false);
5666
				$myTnef = $this->tnef_decoder($tnef_data['attachment']);
5667
				//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5668
				// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5669
				// we need to build Something that meets the needs
5670
				if ($myTnef)
5671
				{
5672
					foreach($myTnef->getParts() as $mime_id => $part)
5673
					{
5674
						$tnefResolved=true;
5675
						$attachment = $part->getAllDispositionParameters();
5676
						$attachment['disposition'] = $part->getDisposition();
5677
						$attachment['mimeType'] = $part->getType();
5678
						$attachment['uid'] = $tnp['uid'];
5679
						$attachment['partID'] = $tnp['partID'];
5680
						$attachment['is_winmail'] = $tnp['uid'].'@'.$tnp['partID'].'@'.$mime_id;
5681 View Code Duplication
						if (!isset($attachment['name'])||empty($attachment['name'])) $attachment['name'] = $part->getName();
5682
						$attachment['size'] = $part->getBytes();
5683
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5684 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']);
5685
						$attachments[] = $attachment;
5686
					}
5687
				}
5688
				if ($tnefResolved===false) $attachments[]=$tnp;
5689
			}
5690
		}
5691
		//error_log(__METHOD__.__LINE__.array2string($attachments));
5692
		return $attachments;
5693
	}
5694
5695
	/**
5696
	 * Decode TNEF type attachment into Multipart/mixed attachment
5697
	 *
5698
	 * @param MIME object $data Mime part object
5699
	 *
5700
	 * @return boolean|Horde_Mime_part Multipart/Mixed part decoded attachments |
5701
	 *	return false if there's no attachments or failure
5702
	 */
5703
	public function tnef_decoder( $data )
5704
	{
5705
		foreach(array('Horde_Compress', 'Horde_Icalendar', 'Horde_Mapi') as $class)
5706
		{
5707
			if (!class_exists($class))
5708
			{
5709
				error_log(__METHOD__."() missing required PEAR package $class --> aborting");
5710
				return false;
5711
			}
5712
		}
5713
		$parts_obj = new Horde_Mime_part;
5714
		$parts_obj->setType('multipart/mixed');
5715
5716
		$tnef_object = Horde_Compress::factory('tnef');
5717
		try
5718
		{
5719
			$tnef_data = $tnef_object->decompress($data);
5720
		}
5721
		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...
5722
		{
5723
			error_log(__METHOD__."() ".$ex->getMessage().' --> aborting');
5724
			_egw_log_exception($ex);
5725
			return false;
5726
		}
5727
		if (is_array($tnef_data))
5728
		{
5729
			foreach ($tnef_data as &$data)
5730
			{
5731
				$tmp_part = new Horde_Mime_part;
5732
5733
				$tmp_part->setName($data['name']);
5734
				$tmp_part->setContents($data['stream']);
5735
				$tmp_part->setDescription($data['name']);
5736
5737
				$type = $data['type'] . '/' . $data['subtype'];
5738
				if (in_array($type, array('application/octet-stream', 'application/base64')))
5739
				{
5740
					$type = Horde_Mime_Magic::filenameToMIME($data['name']);
5741
				}
5742
				$tmp_part->setType($type);
5743
				//error_log(__METHOD__.__LINE__.array2string($tmp_part));
5744
				$parts_obj->addPart($tmp_part);
5745
			}
5746
			$parts_obj->buildMimeIds();
5747
			return $parts_obj;
5748
		}
5749
		return false;
5750
	}
5751
5752
	/**
5753
	 * Get attachment data as string, to be used with Link::(get|set)_data()
5754
	 *
5755
	 * @param int $acc_id
5756
	 * @param string $_mailbox
5757
	 * @param int $_uid
5758
	 * @param string $_partID
5759
	 * @param int $_winmail_nr
5760
	 * @return resource stream with attachment content
5761
	 */
5762
	public static function getAttachmentAccount($acc_id, $_mailbox, $_uid, $_partID, $_winmail_nr)
5763
	{
5764
		$bo = self::getInstance(false, $acc_id);
5765
5766
		$attachment = $bo->getAttachment($_uid, $_partID, $_winmail_nr, false, true, $_mailbox);
5767
5768
		return $attachment['attachment'];
5769
	}
5770
5771
	/**
5772
	 * Retrieve tnef attachments
5773
	 *
5774
	 * @param int $_uid the uid of the message
5775
	 * @param string $_partID the id of the part, which holds the attachment
5776
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5777
	 *
5778
	 * @return array returns an array of all resolved embeded attachments from winmail.dat
5779
	 */
5780
	function getTnefAttachments ($_uid, $_partID, $_stream=false)
5781
	{
5782
		$tnef_data = $this->getAttachment($_uid, $_partID,0,false);
5783
		$tnef_parts = $this->tnef_decoder($tnef_data['attachment']);
5784
		$attachments = array();
5785
		if ($tnef_parts)
5786
		{
5787
			foreach($tnef_parts->getParts() as $mime_id => $part)
5788
			{
5789
5790
				$attachment = $part->getAllDispositionParameters();
5791
				$attachment['mimeType'] = $part->getType();
5792 View Code Duplication
				if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5793
				if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5794 View Code Duplication
				if (empty($attachment['filename']))
5795
				{
5796
					$attachment['filename'] = (isset($attachment['cid'])&&!empty($attachment['cid'])?
5797
						$attachment['cid']:lang("unknown").'_Uid'.$_uid.'_Part'.$mime_id).'.'.MimeMagic::mime2ext($attachment['mimeType']);
5798
				}
5799
5800
				$attachment['attachment'] = $part->getContents(array('stream'=>$_stream));
5801
5802
				$attachments[$_uid.'@'.$_partID.'@'.$mime_id] = $attachment;
5803
			}
5804
		}
5805
		if (!is_array($attachments)) return false;
5806
		return $attachments;
5807
	}
5808
5809
	/**
5810
	 * Retrieve a attachment
5811
	 *
5812
	 * @param int $_uid the uid of the message
5813
	 * @param string $_partID the id of the part, which holds the attachment
5814
	 * @param int $_winmail_nr = 0 winmail.dat attachment nr.
5815
	 * @param boolean $_returnPart =true flag to indicate if the attachment is to be returned as horde mime part object
5816
	 * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer
5817
	 * @param string $_folder =null folder to use if not current folder
5818
	 *
5819
	 * @return array
5820
	 */
5821
	function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false, $_folder=null)
5822
	{
5823
		//error_log(__METHOD__.__LINE__."Uid:$_uid, PartId:$_partID, WinMailNr:$_winmail_nr, ReturnPart:$_returnPart, Stream:$_stream, Folder:$_folder".function_backtrace());
5824 View Code Duplication
		if (!isset($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
5825
5826
		$uidsToFetch = new Horde_Imap_Client_Ids();
5827
		if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid;
5828
		$uidsToFetch->add($_uid);
5829
5830
		$fquery = new Horde_Imap_Client_Fetch_Query();
5831
		$fquery->structure();
5832
		$fquery->bodyPart($_partID, array('peek'=>true));
5833
		$headersNew = $this->icServer->fetch($_folder, $fquery, array(
5834
			'ids' => $uidsToFetch,
5835
		));
5836
		if (is_object($headersNew)) {
5837
			foreach($headersNew as $id=>$_headerObject) {
5838
				$body = $_headerObject->getFullMsg();
5839
				if ($_partID != '')
5840
				{
5841
					$mailStructureObject = $_headerObject->getStructure();
5842
					$mailStructureObject->contentTypeMap();
5843
					$part = $mailStructureObject->getPart($_partID);
5844
					$partDisposition = ($part?$part->getDisposition():'failed');
5845
					if ($partDisposition=='failed')
5846
					{
5847
						error_log(__METHOD__.'('.__LINE__.'):'.array2string($_uid).','.$_partID.' ID:'.$id.' HObject:'.array2string($_headerObject).' StructureObject:'.array2string($mailStructureObject->contentTypeMap()).'->'.function_backtrace());
5848
					}
5849
					// if $partDisposition is empty, we assume attachment, and hope that the function
5850
					// itself is only triggered to fetch attachments
5851
					if (empty($partDisposition)) $partDisposition='attachment';
5852
					if ($part && ($partDisposition=='attachment' || $partDisposition=='inline' || ($part->getPrimaryType() == 'text' && $part->getSubType() == 'calendar')))
5853
					{
5854
						//$headerObject=$part->getAllDispositionParameters();//not used anywhere around here
5855
						$structure_mime = $part->getType();
5856
						$filename = $part->getName();
5857
						$charset = $part->getContentTypeParameter('charset');
5858
						//$structure_bytes = $part->getBytes(); $structure_partID=$part->getMimeId(); error_log(__METHOD__.__LINE__." fetchPartContents(".array2string($_uid).", $structure_partID, $_stream, $_preserveSeen,$structure_mime)" );
5859
						$this->fetchPartContents($_uid, $part, $_stream, $_preserveSeen=true,$structure_mime);
5860
						if ($_returnPart) return $part;
5861
					}
5862
				}
5863
			}
5864
		}
5865
		$ext = MimeMagic::mime2ext($structure_mime);
5866
		if ($ext && stripos($filename,'.')===false && stripos($filename,$ext)===false) $filename = trim($filename).'.'.$ext;
5867
		if (!$part)
5868
		{
5869
			throw new Exception\WrongParameter("Error: Could not fetch attachment for Uid=".array2string($_uid).", PartId=$_partID, WinMailNr=$_winmail_nr, folder=$_folder");
5870
		}
5871
		$attachmentData = array(
5872
			'type'		=> $structure_mime,
5873
			'charset' => $charset,
5874
			'filename'	=> $filename,
5875
			'attachment'	=> $part->getContents(array(
5876
				// tnef_decode needs strings not a stream
5877
				'stream' => $_stream && !($filename == 'winmail.dat' && $_winmail_nr)
5878
			)),
5879
		);
5880
5881
		// try guessing the mimetype, if we get the application/octet-stream
5882
		if (strtolower($attachmentData['type']) == 'application/octet-stream') $attachmentData['type'] = MimeMagic::filename2mime($attachmentData['filename']);
5883
		# if the attachment holds a winmail number and is a winmail.dat then we have to handle that.
5884
		if ( $filename == 'winmail.dat' && $_winmail_nr)
5885
		{
5886
			//by now _uid is of type array
5887
			$tnefResolved=false;
5888
			$wantedPart=$_uid[0].'@'.$_partID;
5889
			$myTnef = $this->tnef_decoder($attachmentData['attachment']);
5890
			//error_log(__METHOD__.__LINE__.array2string($myTnef->getParts()));
5891
			// Note: MimeId starts with 0, almost always, we cannot use that as winmail_id
5892
			// we need to build Something that meets the needs
5893
			if ($myTnef)
5894
			{
5895
				foreach($myTnef->getParts() as $mime_id => $part)
5896
				{
5897
					$tnefResolved=true;
5898
					$attachment = $part->getAllDispositionParameters();
5899
					$attachment['mimeType'] = $part->getType();
5900
					//error_log(__METHOD__.__LINE__.'#'.$mime_id.'#'.$filename.'#'.array2string($attachment));
5901
					//error_log(__METHOD__.__LINE__." $_winmail_nr == $wantedPart@$mime_id");
5902
					if ($_winmail_nr == $wantedPart.'@'.$mime_id)
5903
					{
5904
						//error_log(__METHOD__.__LINE__.'#'.$structure_mime.'#'.$filename.'#'.array2string($attachment));
5905 View Code Duplication
						if (!isset($attachment['filename'])||empty($attachment['filename'])) $attachment['filename'] = $part->getName();
5906
						if (($cid = $part->getContentId())) $attachment['cid'] = $cid;
5907 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']);
5908
						$wmattach = $attachment;
5909
						$wmattach['attachment'] = $part->getContents(array('stream'=>$_stream));
5910
5911
					}
5912
				}
5913
			}
5914
			if ($tnefResolved)
5915
			{
5916
				$ext = MimeMagic::mime2ext($wmattach['mimeType']);
5917
				if ($ext && stripos($wmattach['filename'],'.')===false && stripos($wmattach['filename'],$ext)===false) $wmattach['filename'] = trim($wmattach['filename']).'.'.$ext;
5918
				$attachmentData = array(
5919
					'type'       => $wmattach['mimeType'],
5920
					'filename'   => $wmattach['filename'],
5921
					'attachment' => $wmattach['attachment'],
5922
				);
5923
			}
5924
		}
5925
		return $attachmentData;
5926
	}
5927
5928
	/**
5929
	 * Fetch a specific attachment from a message by it's cid
5930
	 *
5931
	 * this function is based on a on "Building A PHP-Based Mail Client"
5932
	 * http://www.devshed.com
5933
	 *
5934
	 * @param string|int $_uid
5935
	 * @param string $_cid
5936
	 * @param string $_part
5937
	 * @param boolean $_stream = null null do NOT fetch content, use fetchPartContents later
5938
	 *	true:
5939
	 * @return Horde_Mime_Part
5940
	 */
5941
	function getAttachmentByCID($_uid, $_cid, $_part, $_stream=null)
5942
	{
5943
		// some static variables to avoid fetching the same mail multiple times
5944
		static $uid=null, $part=null, $structure=null;
5945
		//error_log(__METHOD__.' ('.__LINE__.') '.":$_uid, $_cid, $_part");
5946
5947
		if(empty($_cid)) return false;
5948
5949
		if ($_uid != $uid || $_part != $part)
5950
		{
5951
			$structure = $this->getStructure($uid=$_uid, $part=$_part);
5952
		}
5953
		/** @var Horde_Mime_Part */
5954
		$attachment = null;
5955
		foreach($structure->contentTypeMap() as $mime_id => $mime_type)
5956
		{
5957
			$part = $structure->getPart($mime_id);
5958
5959
			if ($part->getPrimaryType() == 'image' &&
5960
				(($cid = $part->getContentId()) &&
5961
				// RB: seem a bit fague to search for inclusion in both ways
5962
				(strpos($cid, $_cid) !== false || strpos($_cid, $cid) !== false)) ||
5963
				(($name = $part->getName()) &&
5964
				(strpos($name, $_cid) !== false || strpos($_cid, $name) !== false)))
5965
			{
5966
				// if we have a direct match, dont search any further
5967
				if ($cid == $_cid)
5968
				{
5969
					$attachment = $part;
5970
				}
5971
				// everything else we only consider after we checked all
5972
				if (!isset($attachment)) $attachment = $part;
5973
				// do we want content fetched, can be done later, if not needed
5974
				if (isset($_stream))
5975
				{
5976
					$this->fetchPartContents($_uid, $attachment, $_stream);
5977
				}
5978
				if (isset($attachment)) break;
5979
			}
5980
		}
5981
		// set name as filename, if not set
5982
		if ($attachment && !$attachment->getDispositionParameter('filename'))
5983
		{
5984
			$attachment->setDispositionParameter('filename', $attachment->getName());
5985
		}
5986
		// guess type, if not set
5987
		if ($attachment && $attachment->getType() == 'application/octet-stream')
5988
		{
5989
			$attachment->setType(MimeMagic::filename2mime($attachment->getDispositionParameter('filename')));
5990
		}
5991
		//error_log(__METHOD__."($_uid, '$_cid', '$_part') returning ".array2string($attachment));
5992
		return $attachment;
5993
	}
5994
5995
	/**
5996
	 * Fetch and add contents to a part
5997
	 *
5998
	 * To get contents you use $part->getContents();
5999
	 *
6000
	 * @param int $_uid
6001
	 * @param Horde_Mime_Part $part
6002
	 * @param boolean $_stream = false true return a stream, false a string
6003
	 * @param boolean $_preserveSeen flag to preserve the seenflag by using body.peek
6004
	 * @param string  $_mimetype to decide wether to try to fetch part as binary or not
6005
	 * @return Horde_Mime_Part
6006
	 */
6007
	public function fetchPartContents($_uid, Horde_Mime_Part $part=null, $_stream=false, $_preserveSeen=false, $_mimetype=null)
6008
	{
6009
		if (is_null($part)) return null;//new Horde_Mime_Part;
6010
		$encoding = null;
6011
		$fetchAsBinary = true;
6012
		if ($_mimetype && strtolower($_mimetype)=='message/rfc822') $fetchAsBinary = false;
1 ignored issue
show
Bug Best Practice introduced by
The expression $_mimetype of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
6013
		// we need to set content on structure to decode transfer encoding
6014
		$part->setContents(
6015
			$this->getBodyPart($_uid, $part->getMimeId(), null, $_preserveSeen, $_stream, $encoding, $fetchAsBinary),
6016
			array('encoding' => (!$fetchAsBinary&&!$encoding?'8bit':$encoding)));
6017
6018
		return $part;
6019
	}
6020
6021
	/**
6022
	 * save a message in folder
6023
	 *	throws exception on failure
6024
	 * @todo set flags again
6025
	 *
6026
	 * @param string _folderName the foldername
6027
	 * @param string|resource _header header part of message or resource with hole message
6028
	 * @param string _body body part of message, only used if _header is NO resource
6029
	 * @param string _flags = '\\Recent'the imap flags to set for the saved message
6030
	 *
6031
	 * @return the id of the message appended or exception
6032
	 * @throws Exception\WrongUserinput
6033
	 */
6034
	function appendMessage($_folderName, $_header, $_body, $_flags='\\Recent')
6035
	{
6036
		if (!is_resource($_header))
6037
		{
6038
			if (stripos($_header,'message-id:')===false)
6039
			{
6040
				$_header = 'Message-ID: <'.self::getRandomString().'@localhost>'."\n".$_header;
6041
			}
6042
			//error_log(__METHOD__.' ('.__LINE__.') '."$_folderName, $_header, $_body, $_flags");
6043
			$_header = ltrim(str_replace("\n","\r\n",$_header));
6044
			$_header .= str_replace("\n","\r\n",$_body);
6045
		}
6046
		// the recent flag is the default enforced here ; as we assume the _flags is always set,
6047
		// we default it to hordes default (Recent) (, other wise we should not pass the parameter
6048
		// for flags at all)
6049
		if (empty($_flags)) $_flags = '\\Recent';
6050
		//if (!is_array($_flags) && stripos($_flags,',')!==false) $_flags=explode(',',$_flags);
6051
		//if (!is_array($_flags)) $_flags = (array) $_flags;
6052
		try
6053
		{
6054
			$dataNflags = array();
6055
			// both methods below are valid for appending a message to a mailbox.
6056
			// the commented version fails in retrieving the uid of the created message if the server
6057
			// is not returning the uid upon creation, as the method in append for detecting the uid
6058
			// expects data to be a string. this string is parsed for message-id, and the mailbox
6059
			// searched for the message-id then returning the uid found
6060
			//$dataNflags[] = array('data'=>array(array('t'=>'text','v'=>"$header"."$body")), 'flags'=>array($_flags));
6061
			$dataNflags[] = array('data' => $_header, 'flags'=>array($_flags));
6062
			$messageid = $this->icServer->append($_folderName,$dataNflags);
6063
		}
6064
		catch (\Exception $e)
6065
		{
6066
			if (self::$debug) error_log("Could not append Message: ".$e->getMessage());
6067
			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...
6068
			//return false;
6069
		}
6070
		//error_log(__METHOD__.' ('.__LINE__.') '.' appended UID:'.$messageid);
6071
		//$messageid = true; // for debug reasons only
6072
		if ($messageid === true || empty($messageid)) // try to figure out the message uid
6073
		{
6074
			$list = $this->getHeaders($_folderName, $_startMessage=1, 1, 'INTERNALDATE', true, array(),null, false);
6075
			if ($list)
6076
			{
6077 View Code Duplication
				if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' MessageUid:'.$messageid.' but found:'.array2string($list));
6078
				$messageid = $list['header'][0]['uid'];
6079
			}
6080
		}
6081
		return $messageid;
6082
	}
6083
6084
	/**
6085
	 * Get a random string of 32 chars
6086
	 *
6087
	 * @return string
6088
	 */
6089
	static function getRandomString()
6090
	{
6091
		return Auth::randomstring(32);
6092
	}
6093
6094
	/**
6095
	 * functions to allow access to mails through other apps to fetch content
6096
	 * used in infolog, tracker
6097
	 */
6098
6099
	/**
6100
	 * get_mailcontent - fetches the actual mailcontent, and returns it as well defined array
6101
	 * @param object mailClass the mailClassobject to be used
6102
	 * @param uid the uid of the email to be processed
6103
	 * @param partid the partid of the email
6104
	 * @param mailbox the mailbox, that holds the message
6105
	 * @param preserveHTML flag to pass through to getdisplayableBody
6106
	 * @param addHeaderSection flag to be able to supress headersection
6107
	 * @param includeAttachments flag to be able to supress possible attachments
6108
	 * @return array/bool with 'mailaddress'=>$mailaddress,
6109
	 *				'subject'=>$subject,
6110
	 *				'message'=>$message,
6111
	 *				'attachments'=>$attachments,
6112
	 *				'headers'=>$headers,; boolean false on failure
6113
	 */
6114
	static function get_mailcontent(&$mailClass,$uid,$partid='',$mailbox='', $preserveHTML = false, $addHeaderSection=true, $includeAttachments=true)
6115
	{
6116
			//echo __METHOD__." called for $uid,$partid <br>";
6117
			$headers = $mailClass->getMessageHeader($uid,$partid,true,false,$mailbox);
6118
			if (empty($headers)) return false;
6119
			// dont force retrieval of the textpart, let mailClass preferences decide
6120
			$bodyParts = $mailClass->getMessageBody($uid,($preserveHTML?'always_display':'only_if_no_text'),$partid,null,false,$mailbox);
6121
			// if we do not want HTML but there is no TextRepresentation with the message itself, try converting
6122
			if ( !$preserveHTML && $bodyParts[0]['mimeType']=='text/html')
6123
			{
6124
				foreach($bodyParts as $i => $part)
6125
				{
6126
					if ($bodyParts[$i]['mimeType']=='text/html')
6127
					{
6128
						$bodyParts[$i]['body'] = Mail\Html::convertHTMLToText($bodyParts[$i]['body'],$bodyParts[$i]['charSet'],true,$stripalltags=true);
6129
						$bodyParts[$i]['mimeType']='text/plain';
6130
					}
6131
				}
6132
			}
6133
			//error_log(array2string($bodyParts));
6134
			$attachments = $includeAttachments?$mailClass->getMessageAttachments($uid,$partid,null,true,false,true,$mailbox):array();
6135
6136
			if ($mailClass->isSentFolder($mailbox)) $mailaddress = $headers['TO'];
6137
			elseif (isset($headers['FROM'])) $mailaddress = $headers['FROM'];
6138
			elseif (isset($headers['SENDER'])) $mailaddress = $headers['SENDER'];
6139
			if (isset($headers['CC'])) $mailaddress .= ','.$headers['CC'];
6140
			//_debug_array(array($headers,$mailaddress));
6141
			$subject = $headers['SUBJECT'];
6142
6143
			$message = self::getdisplayableBody($mailClass, $bodyParts, $preserveHTML);
6144
			if ($preserveHTML && $mailClass->activeMimeType == 'text/plain') $message = '<pre>'.$message.'</pre>';
6145
			$headdata = ($addHeaderSection ? self::createHeaderInfoSection($headers, '',$preserveHTML) : '');
6146
			$message = $headdata.$message;
6147
			//echo __METHOD__.'<br>';
6148
			//_debug_array($attachments);
6149
			if (is_array($attachments))
6150
			{
6151
				// For dealing with multiple files of the same name
6152
				$dupe_count = $file_list = array();
6153
6154
				foreach ($attachments as $num => $attachment)
6155
				{
6156
					if ($attachment['mimeType'] == 'MESSAGE/RFC822')
6157
					{
6158
						//_debug_array($mailClass->getMessageHeader($uid, $attachment['partID']));
6159
						//_debug_array($mailClass->getMessageBody($uid,'', $attachment['partID']));
6160
						//_debug_array($mailClass->getMessageAttachments($uid, $attachment['partID']));
6161
						$mailcontent = self::get_mailcontent($mailClass,$uid,$attachment['partID'],$mailbox);
6162
						$headdata ='';
6163
						if ($mailcontent['headers'])
6164
						{
6165
							$headdata = self::createHeaderInfoSection($mailcontent['headers'],'',$preserveHTML);
6166
						}
6167
						if ($mailcontent['message'])
6168
						{
6169
							$tempname =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6170
							$attachedMessages[] = array(
6171
								'type' => 'TEXT/PLAIN',
6172
								'name' => $mailcontent['subject'].'.txt',
6173
								'tmp_name' => $tempname,
6174
							);
6175
							$tmpfile = fopen($tempname,'w');
6176
							fwrite($tmpfile,$headdata.$mailcontent['message']);
6177
							fclose($tmpfile);
6178
						}
6179
						foreach($mailcontent['attachments'] as &$tmpval)
6180
						{
6181
							$attachedMessages[] = $tmpval;
6182
						}
6183
						unset($attachments[$num]);
6184
					}
6185
					else
6186
					{
6187
						$attachments[$num] = array_merge($attachments[$num],$mailClass->getAttachment($uid, $attachment['partID'],0,false,false));
6188
6189
						if (empty($attachments[$num]['attachment'])&&$attachments[$num]['cid'])
6190
						{
6191
							$c = $mailClass->getAttachmentByCID($uid, $attachment['cid'], $attachment['partID'],true);
6192
							$attachments[$num]['attachment'] = $c->getContents();
6193
						}
6194
						// no attempt to convert, if we dont know about the charset
6195
						if (isset($attachments[$num]['charset'])&&!empty($attachments[$num]['charset'])) {
6196
							// we do not try guessing the charset, if it is not set
6197
							//if ($attachments[$num]['charset']===false) $attachments[$num]['charset'] = Translation::detect_encoding($attachments[$num]['attachment']);
6198
							Translation::convert($attachments[$num]['attachment'],$attachments[$num]['charset']);
6199
						}
6200
						if(in_array($attachments[$num]['name'], $file_list))
6201
						{
6202
							$dupe_count[$attachments[$num]['name']]++;
6203
							$attachments[$num]['name'] = pathinfo($attachments[$num]['name'], PATHINFO_FILENAME) .
6204
								' ('.($dupe_count[$attachments[$num]['name']] + 1).')' . '.' .
6205
								pathinfo($attachments[$num]['name'], PATHINFO_EXTENSION);
6206
						}
6207
						$attachments[$num]['type'] = $attachments[$num]['mimeType'];
6208
						$attachments[$num]['tmp_name'] = tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6209
						$tmpfile = fopen($attachments[$num]['tmp_name'],'w');
6210
						fwrite($tmpfile,$attachments[$num]['attachment']);
6211
						fclose($tmpfile);
6212
						$file_list[] = $attachments[$num]['name'];
6213
						unset($attachments[$num]['attachment']);
6214
					}
6215
				}
6216
				if (is_array($attachedMessages)) $attachments = array_merge($attachments,$attachedMessages);
6217
			}
6218
			return array(
6219
					'mailaddress'=>$mailaddress,
6220
					'subject'=>$subject,
6221
					'message'=>$message,
6222
					'attachments'=>$attachments,
6223
					'headers'=>$headers,
6224
					);
6225
	}
6226
6227
	/**
6228
	 * getStandardIdentityForProfile
6229
	 * get either the first identity out of the given identities or the one matching the profile_id
6230
	 * @param object/array $_identities identity iterator object or array with identities from Mail\Account
6231
	 * @param integer $_profile_id the acc_id/profileID the identity with the matching key is the standard one
6232
	 * @return array the identity
6233
	 */
6234
	static function getStandardIdentityForProfile($_identities, $_profile_id)
6235
	{
6236
		$c = 0;
6237
		// use the standardIdentity
6238
		foreach($_identities as $key => $acc) {
6239
			if ($c==0) $identity = $acc;
6240
			//error_log(__METHOD__.__LINE__." $key == $_profile_id ");
6241
			if ($key==$_profile_id) $identity = $acc;
6242
			$c++;
6243
		}
6244
		return $identity;
6245
	}
6246
	/**
6247
	 * createHeaderInfoSection - creates a textual headersection from headerobject
6248
	 * @param array header headerarray may contain SUBJECT,FROM,SENDER,TO,CC,BCC,DATE,PRIORITY,IMPORTANCE
6249
	 * @param string headline Text tom use for headline, if SUPPRESS, supress headline and footerline
6250
	 * @param bool createHTML do it with HTML breaks
6251
	 * @return string a preformatted string with the information of the header worked into it
6252
	 */
6253
	static function createHeaderInfoSection($header,$headline='', $createHTML = false)
6254
	{
6255
		$headdata = null;
6256
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($header).function_backtrace());
6257
		if ($header['SUBJECT']) $headdata = lang('subject').': '.$header['SUBJECT'].($createHTML?"<br />":"\n");
6258 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...
6259 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...
6260 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...
6261 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...
6262 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...
6263 View Code Duplication
		if ($header['DATE']) $headdata .= lang('date').': '.$header['DATE'].($createHTML?"<br />":"\n");
6264 View Code Duplication
		if ($header['PRIORITY'] && $header['PRIORITY'] != 'normal') $headdata .= lang('priority').': '.$header['PRIORITY'].($createHTML?"<br />":"\n");
6265 View Code Duplication
		if ($header['IMPORTANCE'] && $header['IMPORTANCE'] !='normal') $headdata .= lang('importance').': '.$header['IMPORTANCE'].($createHTML?"<br />":"\n");
6266
		//if ($mailcontent['headers']['ORGANIZATION']) $headdata .= lang('organization').': '.$mailcontent['headers']['ORGANIZATION']."\
6267
		if (!empty($headdata))
6268
		{
6269 View Code Duplication
			if (!empty($headline) && $headline != 'SUPPRESS') $headdata = "---------------------------- $headline ----------------------------".($createHTML?"<br />":"\n").$headdata;
6270 View Code Duplication
			if (empty($headline)) $headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'').$headdata;
6271
			$headdata .= ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6272
		}
6273
		else
6274
		{
6275
			$headdata = ($headline != 'SUPPRESS'?"--------------------------------------------------------".($createHTML?"<br />":"\n"):'');
6276
		}
6277
		return $headdata;
6278
	}
6279
6280
	/**
6281
	 * adaptSubjectForImport - strips subject from unwanted Characters, and does some normalization
6282
	 * to meet expectations
6283
	 * @param string $subject string to process
6284
	 * @return string
6285
	 */
6286
	static function adaptSubjectForImport($subject)
6287
	{
6288
		$subject = str_replace('$$','__',($subject?$subject:lang('(no subject)')));
6289
		$subject = str_ireplace(array('[FWD]','[',']','{','}','<','>'),array('Fwd:',' ',' ',' ',' ',' ',' '),trim($subject));
6290
		return $subject;
6291
	}
6292
6293
	/**
6294
	 * convertAddressArrayToString - converts an mail envelope Address Array To String
6295
	 * @param array $rfcAddressArray  an addressarray as provided by mail retieved via egw_pear....
6296
	 * @return string a comma separated string with the mailaddress(es) converted to text
6297
	 */
6298
	static function convertAddressArrayToString($rfcAddressArray)
6299
	{
6300
		//error_log(__METHOD__.' ('.__LINE__.') '.array2string($rfcAddressArray));
6301
		$returnAddr ='';
6302
		if (is_array($rfcAddressArray))
6303
		{
6304
			foreach((array)$rfcAddressArray as $addressData) {
6305
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressData));
6306
				if($addressData['MAILBOX_NAME'] == 'NIL') {
6307
					continue;
6308
				}
6309
				if(strtolower($addressData['MAILBOX_NAME']) == 'undisclosed-recipients') {
6310
					continue;
6311
				}
6312
				if ($addressData['RFC822_EMAIL'])
6313
				{
6314
					$addressObjectA = self::parseAddressList($addressData['RFC822_EMAIL']);
6315
				}
6316
				else
6317
				{
6318
					$emailaddress = ($addressData['PERSONAL_NAME']?$addressData['PERSONAL_NAME'].' <'.$addressData['EMAIL'].'>':$addressData['EMAIL']);
6319
					$addressObjectA = self::parseAddressList($emailaddress);
6320
				}
6321
				$addressObject = $addressObjectA[0];
6322
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($addressObject));
6323
				if (!$addressObject->valid) continue;
6324
				//$mb =(string)$addressObject->mailbox;
6325
				//$h = (string)$addressObject->host;
6326
				//$p = (string)$addressObject->personal;
6327
				$returnAddr .= (strlen($returnAddr)>0?',':'');
6328
				//error_log(__METHOD__.' ('.__LINE__.') '.$p.' <'.$mb.'@'.$h.'>');
6329
				$buff = imap_rfc822_write_address($addressObject->mailbox, Horde_Idna::decode($addressObject->host), $addressObject->personal);
6330
				$buff = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$buff);
6331
				//error_log(__METHOD__.' ('.__LINE__.') '.' Address: '.$returnAddr);
6332
				$returnAddr .= $buff;
6333
			}
6334
		}
6335
		else
6336
		{
6337
			// do not mess with strings, return them untouched /* ToDo: validate string as Address */
6338
			$rfcAddressArray = self::decode_header($rfcAddressArray,true);
6339
			$rfcAddressArray = str_replace(array('<','>','"\'','\'"'),array('[',']','"','"'),$rfcAddressArray);
6340
			if (is_string($rfcAddressArray)) return $rfcAddressArray;
6341
		}
6342
		return $returnAddr;
6343
	}
6344
6345
	/**
6346
	 * Merges a given content with contact data
6347
	 *
6348
	 * @param string $content
6349
	 * @param array $ids array with contact id(s)
6350
	 * @param string &$err error-message on error
6351
	 * @return string/boolean merged content or false on error
6352
	 */
6353
	static function merge($content,$ids,$mimetype='')
6354
	{
6355
		$mergeobj = new Contacts\Merge();
6356
6357
		if (empty($mimetype)) $mimetype = (strlen(strip_tags($content)) == strlen($content) ?'text/plain':'text/html');
6358
		$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...
6359
		if (empty($rv) && !empty($content) && !empty($err)) $rv = $content;
6360
		if (!empty($err) && !empty($content) && !empty($ids)) error_log(__METHOD__.' ('.__LINE__.') '.' Merge failed for Ids:'.array2string($ids).' ContentType:'.$mimetype.' Content:'.$content.' Reason:'.array2string($err));
6361
		return $rv;
6362
	}
6363
6364
	/**
6365
	 * Returns a string showing the size of the message/attachment
6366
	 *
6367
	 * @param integer $bytes
6368
	 * @return string formatted string
6369
	 */
6370
	static function show_readable_size($bytes)
6371
	{
6372
		$bytes /= 1024;
6373
		$type = 'k';
6374
6375
		if ($bytes / 1024 > 1)
6376
		{
6377
			$bytes /= 1024;
6378
			$type = 'M';
6379
6380
			if ($bytes / 1024 > 1)
6381
			{
6382
				$bytes *= 10;
6383
				settype($bytes, 'integer');
6384
				$bytes /= 10;
6385
				$bytes /= 1024;
6386
				$type = 'G';
6387
			}
6388
6389
		}
6390
6391
		if ($bytes < 10)
6392
		{
6393
			$bytes *= 10;
6394
			settype($bytes, 'integer');
6395
			$bytes /= 10;
6396
		}
6397
		else
6398
			settype($bytes, 'integer');
6399
6400
		return $bytes . ' ' . $type ;
6401
	}
6402
6403
	static function detect_qp(&$sting) {
6404
		$needle = '/(=[0-9][A-F])|(=[A-F][0-9])|(=[A-F][A-F])|(=[0-9][0-9])/';
6405
		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...
6406
	}
6407
6408
	/**
6409
	 * logRunTimes
6410
	 *	logs to the error log all parameters given; output only if self::$debugTimes is true
6411
	 *
6412
	 * @param int $_starttime starttime of the action measured based on microtime(true)
6413
	 * @param int $_endtime endtime of the action measured, if not given microtime(true) is used
6414
	 * @param string $_message message to output details or params, whatever seems neccesary
6415
	 * @param string $_methodNline - Information where the log was taken
6416
	 * @return void
6417
	 */
6418
	static function logRunTimes($_starttime,$_endtime=null,$_message='',$_methodNline='')
6419
	{
6420
		if (is_null($_endtime)) $_endtime = microtime(true);
6421
		$usagetime = microtime(true) - $_starttime;
6422
		if (self::$debugTimes) error_log($_methodNline.' took:'.number_format($usagetime,5).'(s) '.($_message?'Details:'.$_message:''));
6423
	}
6424
6425
	/**
6426
	 * check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6427
	 *
6428
	 * @param array $_formData passed by reference Array with information of name, type, file and size, mimetype may be adapted
6429
	 * @param string $IDtoAddToFileName id to enrich the returned tmpfilename
6430
	 * @param string $reqMimeType /(default message/rfc822, if set to false, mimetype check will not be performed
6431
	 * @return mixed $fullPathtoFile or exception
6432
	 *
6433
	 * @throws Exception\WrongUserinput
6434
	 */
6435
	static function checkFileBasics(&$_formData, $IDtoAddToFileName='', $reqMimeType='message/rfc822')
6436
	{
6437
		if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'egw-data') return $_formData['file'];
6438
6439
		//error_log(__METHOD__.__FILE__.array2string($_formData).' Id:'.$IDtoAddToFileName.' ReqMimeType:'.$reqMimeType);
6440
		$importfailed = $tmpFileName = false;
6441
		// ignore empty files, but allow to share vfs directories (which can have 0 size)
6442
		if ($_formData['size'] == 0 && parse_url($_formData['file'], PHP_URL_SCHEME) != 'vfs' && is_dir($_formData['file']))
6443
		{
6444
			$importfailed = true;
6445
			$alert_msg .= lang("Empty file %1 ignored.", $_formData['name']);
6446
		}
6447
		elseif (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs' || is_uploaded_file($_formData['file']) ||
6448
			realpath(dirname($_formData['file'])) == realpath($GLOBALS['egw_info']['server']['temp_dir']))
6449
		{
6450
			// ensure existance of eGW temp dir
6451
			// note: this is different from apache temp dir,
6452
			// and different from any other temp file location set in php.ini
6453
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6454
			{
6455
				@mkdir($GLOBALS['egw_info']['server']['temp_dir'],0700);
6456
			}
6457
6458
			// if we were NOT able to create this temp directory, then make an ERROR report
6459
			if (!file_exists($GLOBALS['egw_info']['server']['temp_dir']))
6460
			{
6461
				$alert_msg .= 'Error:'.'<br>'
6462
					.'Server is unable to access EGroupware tmp directory'.'<br>'
6463
					.$GLOBALS['egw_info']['server']['temp_dir'].'<br>'
6464
					.'Please check your configuration'.'<br>'
6465
					.'<br>';
6466
			}
6467
6468
			// sometimes PHP is very clue-less about MIME types, and gives NO file_type
6469
			// rfc default for unknown MIME type is:
6470
			if ($reqMimeType == 'message/rfc822')
6471
			{
6472
				$mime_type_default = 'message/rfc';
6473
			}
6474
			else
6475
			{
6476
				$mime_type_default = $reqMimeType;
6477
			}
6478
			// check the mimetype by extension. as browsers seem to report crap
6479
			// maybe its application/octet-stream -> this may mean that we could not determine the type
6480
			// so we check for the suffix too
6481
			// trust vfs mime-types, trust the mimetype if it contains a method
6482
			if ((substr($_formData['file'],0,6) !== 'vfs://' || $_formData['type'] == 'application/octet-stream') && stripos($_formData['type'],'method=')===false)
6483
			{
6484
				$buff = explode('.',$_formData['name']);
6485
				$suffix = '';
6486
				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
6487
				if (!empty($suffix)) $sfxMimeType = MimeMagic::ext2mime($suffix);
6488
				if (!empty($suffix) && !empty($sfxMimeType) &&
6489
					(strlen(trim($_formData['type']))==0 || (strtolower(trim($_formData['type'])) != $sfxMimeType)))
6490
				{
6491
					error_log(__METHOD__.' ('.__LINE__.') '.' Data:'.array2string($_formData));
6492
					error_log(__METHOD__.' ('.__LINE__.') '.' Form reported Mimetype:'.$_formData['type'].' but seems to be:'.$sfxMimeType);
6493
					$_formData['type'] = $sfxMimeType;
6494
				}
6495
			}
6496
			if (trim($_formData['type']) == '')
6497
			{
6498
				$_formData['type'] = 'application/octet-stream';
6499
			}
6500
			// if reqMimeType is set to false do not test for that
6501
			if ($reqMimeType)
6502
			{
6503
				// so if PHP did not pass any file_type info, then substitute the rfc default value
6504
				if (substr(strtolower(trim($_formData['type'])),0,strlen($mime_type_default)) != $mime_type_default)
6505
				{
6506
					if (!(strtolower(trim($_formData['type'])) == "application/octet-stream" && $sfxMimeType == $reqMimeType))
6507
					{
6508
						//error_log("Message rejected, no message/rfc. Is:".$_formData['type']);
6509
						$importfailed = true;
6510
						$alert_msg .= lang("File rejected, no %2. Is:%1",$_formData['type'],$reqMimeType);
6511
					}
6512
					if ((strtolower(trim($_formData['type'])) != $reqMimeType && $sfxMimeType == $reqMimeType))
6513
					{
6514
						$_formData['type'] = MimeMagic::ext2mime($suffix);
6515
					}
6516
				}
6517
			}
6518
			// as FreeBSD seems to have problems with the generated temp names we append some more random stuff
6519
			$randomString = chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90)).chr(rand(48,57)).chr(rand(65,90));
6520
			$tmpFileName = $GLOBALS['egw_info']['user']['account_id'].
6521
				trim($IDtoAddToFileName).basename($_formData['file']).'_'.$randomString;
6522
6523
			if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'vfs')
6524
			{
6525
				$tmpFileName = $_formData['file'];	// no need to store it somewhere
6526
			}
6527 View Code Duplication
			elseif (is_uploaded_file($_formData['file']))
6528
			{
6529
				move_uploaded_file($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);	// requirement for safe_mode!
6530
			}
6531
			else
6532
			{
6533
				rename($_formData['file'], $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmpFileName);
6534
			}
6535
		} else {
6536
			//error_log("Import of message ".$_formData['file']." failes to meet basic restrictions");
6537
			$importfailed = true;
6538
			$alert_msg .= lang("Processing of file %1 failed. Failed to meet basic restrictions.",$_formData['name']);
6539
		}
6540
		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...
6541
		{
6542
			throw new Exception\WrongUserinput($alert_msg);
6543
		}
6544
		else
6545
		{
6546
			if (parse_url($tmpFileName,PHP_URL_SCHEME) == 'vfs')
6547
			{
6548
				Vfs::load_wrapper('vfs');
6549
			}
6550
			return $tmpFileName;
6551
		}
6552
	}
6553
6554
	/**
6555
	 * Parses a html text for images, and adds them as inline attachment
6556
	 *
6557
	 * Images can be data-urls, own VFS webdav.php urls or absolute path.
6558
	 *
6559
	 * @param Mailer $_mailObject instance of the Mailer Object to be used
6560
	 * @param string $_html2parse the html to parse and to be altered, if conditions meet
6561
	 * @param $mail_bo mail bo object
6562
	 * @return void
6563
	 */
6564
	static function processURL2InlineImages(Mailer $_mailObject, &$_html2parse, $mail_bo)
6565
	{
6566
		//error_log(__METHOD__."()");
6567
		$imageC = 0;
6568
		$images = null;
6569
		if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
6570
		{
6571
			foreach($images[2] as $i => $url)
6572
			{
6573
				//$isData = false;
6574
				$basedir = $data = '';
6575
				$needTempFile = true;
6576
6577
				// do not change urls for absolute images (thanks to corvuscorax)
6578
				if (substr($url, 0, 5) !== 'data:')
6579
				{
6580
					$filename = basename($url);
6581
					if (($directory = dirname($url)) == '.') $directory = '';
6582
					$ext = pathinfo($filename, PATHINFO_EXTENSION);
6583
					$mimeType  = MimeMagic::ext2mime($ext);
6584
					if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; }
6585
					$myUrl = $directory.$filename;
6586
					if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
6587
					{
6588
						$basedir = ($_SERVER['HTTPS']?'https://':'http://'.$_SERVER['HTTP_HOST']);
6589
					}
6590
					// use vfs instead of url containing webdav.php
6591
					// ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign
6592
					// webdav.php urls as vfs
6593
					if (strpos($myUrl,'/webdav.php') !== false) // we have a webdav link, so we build a vfs/sqlfs link of it.
6594
					{
6595
						Vfs::load_wrapper('vfs');
6596
						list(,$myUrl) = explode('/webdav.php',$myUrl,2);
6597
						$basedir = 'vfs://default';
6598
						$needTempFile = false;
6599
					}
6600
6601
					// If it is an inline image url, we need to fetch the actuall attachment
6602
					// content and later on to be able to store its content as temp file
6603
					if (strpos($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage') !== false)
6604
					{
6605
						$URI_params = array();
6606
						// Strips the url and store it into a temp for further procss
6607
						$tmp_url = html_entity_decode($myUrl);
6608
6609
						parse_str(parse_url($tmp_url, PHP_URL_QUERY),$URI_params);
6610
						if ($URI_params['mailbox'] && $URI_params['uid'] && $URI_params['cid'])
6611
						{
6612
							$mail_bo->reopen(base64_decode($URI_params['mailbox']));
6613
							$attachment = $mail_bo->getAttachmentByCID($URI_params['uid'], base64_decode($URI_params['cid']),base64_decode($URI_params['partID']),true);
6614
							$mail_bo->closeConnection();
6615
							if ($attachment)
6616
							{
6617
								$data = $attachment->getContents();
6618
								$mimeType = $attachment->getType();
6619
								$filename = $attachment->getDispositionParameter('filename');
6620
							}
6621
						}
6622
					}
6623
6624
					if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; }
6625
					if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl));
6626
				}
6627
				if (substr($url,0,strlen('data:'))=='data:')
6628
				{
6629
					//error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i]));
6630
					// we only support base64 encoded data
6631
					$tmp = substr($url,strlen('data:'));
6632
					list($mimeType,$data_base64) = explode(';base64,',$tmp);
6633
					$data = base64_decode($data_base64);
6634
					// FF currently does NOT add any mime-type
6635
					if (strtolower(substr($mimeType, 0, 6)) != 'image/')
6636
					{
6637
						$mimeType = MimeMagic::analyze_data($data);
6638
					}
6639
					list($what,$exactly) = explode('/',$mimeType);
6640
					$needTempFile = true;
6641
					$filename = ($what?$what:'data').$imageC++.'.'.$exactly;
6642
				}
6643
				if ($data || $needTempFile === false)
6644
				{
6645
					if ($needTempFile)
6646
					{
6647
						$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
6648
						$tmpfile = fopen($attachment_file,'w');
6649
						fwrite($tmpfile,$data);
6650
						fclose($tmpfile);
6651
					}
6652
					else
6653
					{
6654
						$attachment_file = $basedir.urldecode($myUrl);
6655
					}
6656
					// we use $attachment_file as base for cid instead of filename, as it may be image.png
6657
					// (or similar) in all cases (when cut&paste). This may lead to more attached files, in case
6658
					// we use the same image multiple times, but, if we do this, we should try to detect that
6659
					// on upload. filename itself is not sufficient to determine the sameness of images
6660
					$cid = 'cid:' . md5($attachment_file);
6661
					if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null)
6662
					{
6663
						//$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse);
6664
						$_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse);
6665
					}
6666
				}
6667
			}
6668
		}
6669
	}
6670
6671
	/**
6672
	 * importMessageToMergeAndSend
6673
	 *
6674
	 * @param Storage\Merge Storage\Merge bo_merge object
6675
	 * @param string $document the full filename
6676
	 * @param array $SendAndMergeTocontacts array of contact ids
6677
	 * @param string& $_folder (passed by reference) will set the folder used. must be set with a folder, but will hold modifications if
6678
	 *					folder is modified
6679
	 * @param string& $importID ID for the imported message, used by attachments to identify them unambiguously
6680
	 * @return mixed array of messages with success and failed messages or exception
6681
	 */
6682
	function importMessageToMergeAndSend(Storage\Merge $bo_merge, $document, $SendAndMergeTocontacts, &$_folder, &$importID='')
6683
	{
6684
		$importfailed = false;
6685
		$processStats = array('success'=>array(),'failed'=>array());
6686
		if (empty($SendAndMergeTocontacts))
6687
		{
6688
			$importfailed = true;
6689
			$alert_msg .= lang("Import of message %1 failed. No Contacts to merge and send to specified.", '');
6690
		}
6691
6692
		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
6693
		/* as the file is provided by Storage\Merge, we do not check
6694
		try
6695
		{
6696
			$tmpFileName = Mail::checkFileBasics($_formData,$importID);
6697
		}
6698
		catch (\Exception\WrongUserinput $e)
6699
		{
6700
			$importfailed = true;
6701
			$alert_msg .= $e->getMessage();
6702
		}
6703
		*/
6704
		$tmpFileName = $document;
6705
		// -----------------------------------------------------------------------
6706
		if ($importfailed === false)
6707
		{
6708
			$mailObject = new Mailer($this->profileID);
6709
			try
6710
			{
6711
				$this->parseFileIntoMailObject($mailObject, $tmpFileName);
6712
			}
6713
			catch (Exception\AssertionFailed $e)
6714
			{
6715
				$importfailed = true;
6716
				$alert_msg .= $e->getMessage();
6717
			}
6718
6719
			//_debug_array($Body);
6720
			$this->openConnection();
6721
			if (empty($_folder))
6722
			{
6723
				$_folder = $this->getSentFolder();
6724
			}
6725
			$delimiter = $this->getHierarchyDelimiter();
6726
			if($_folder=='INBOX'.$delimiter) $_folder='INBOX';
6727
			if ($importfailed === false)
6728
			{
6729
				$Subject = $mailObject->getHeader('Subject');
6730
				//error_log(__METHOD__.' ('.__LINE__.') '.' Subject:'.$Subject);
6731
				$Body = ($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null;
6732
				//error_log(__METHOD__.' ('.__LINE__.') '.' Body:'.$Body);
6733
				//error_log(__METHOD__.' ('.__LINE__.') '.' BodyContentType:'.$mailObject->BodyContentType);
6734
				$AltBody = ($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null;
6735
				//error_log(__METHOD__.' ('.__LINE__.') '.' AltBody:'.$AltBody);
6736
				//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject->GetReplyTo()));
6737
6738
				// Fetch ReplyTo - Address if existing to check if we are to replace it
6739
				$replyTo = $mailObject->getReplyTo();
6740
				if (isset($replyTo['[email protected]']))
6741
				{
6742
					$mailObject->clearReplyTos();
6743
					$activeMailProfiles = $this->mail->getAccountIdentities($this->profileID);
6744
					$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6745
6746
					$mailObject->addReplyTo(Horde_Idna::encode($activeMailProfile['ident_email']),Mail::generateIdentityString($activeMailProfile,false));
6747
				}
6748
				foreach ($SendAndMergeTocontacts as $k => $val)
6749
				{
6750
					$errorInfo = $email = '';
6751
					$sendOK = $openComposeWindow = $openAsDraft = null;
6752
					//error_log(__METHOD__.' ('.__LINE__.') '.' Id To Merge:'.$val);
6753
					if (/*$GLOBALS['egw_info']['flags']['currentapp'] == 'addressbook' &&*/
6754
						count($SendAndMergeTocontacts) > 1 && $val &&
6755
						(is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val))) // do the merge
6756
					{
6757
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6758
6759
						// Parse destinations for placeholders
6760
						foreach(Mailer::$type2header as $type => $h)
6761
						{
6762
							//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));
6763
							$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...
6764
							$mailObject->addAddress($merged,'',$type);
6765
							if($type == 'to')
6766
							{
6767
								$email = $merged;
6768
							}
6769
						}
6770
6771
						// No addresses from placeholders?  Treat it as just a contact ID
6772
						if (!$email)
6773
						{
6774
							$contact = $bo_merge->contacts->read($val);
6775
							//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
6776
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6777
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6778
							if($email)
6779
							{
6780
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6781
							}
6782
						}
6783
6784
						$activeMailProfiles = $this->getAccountIdentities($this->profileID);
6785
						$activeMailProfile = self::getStandardIdentityForProfile($activeMailProfiles,$this->profileID);
6786
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($activeMailProfile));
6787
						$mailObject->setFrom($activeMailProfile['ident_email'],
6788
							self::generateIdentityString($activeMailProfile,false));
6789
6790
						$mailObject->removeHeader('Message-ID');
6791
						$mailObject->removeHeader('Date');
6792
						$mailObject->clearCustomHeaders();
6793
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6794
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6795 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));
6796
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6797 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));
6798
6799
						//error_log(__METHOD__.' ('.__LINE__.') '.array2string($mailObject));
6800
						// set a higher timeout for big messages
6801
						@set_time_limit(120);
6802
						$sendOK = true;
6803
						try {
6804
							$mailObject->send();
6805
						}
6806
						catch(Exception $e) {
6807
							$sendOK = false;
6808
							$errorInfo = $e->getMessage();
6809
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($errorInfo));
6810
						}
6811
					}
6812
					elseif (!$k)	// 1. entry, further entries will fail for apps other then addressbook
6813
					{
6814
						$openAsDraft = true;
6815
						$mailObject->removeHeader('Message-ID');
6816
						$mailObject->removeHeader('Date');
6817
						$mailObject->clearCustomHeaders();
6818
6819
						// Parse destinations for placeholders
6820
						foreach(Mailer::$type2header as $type => $h)
6821
						{
6822
							$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...
6823
							//error_log($type . ': ' . $mailObject->getHeader(Mailer::$type2header[$type]) . ' -> ' .$merged);
6824
							$mailObject->addAddress(trim($merged,'"'),'',$type);
6825
						}
6826
6827
						// No addresses from placeholders?  Treat it as just a contact ID
6828
						if (count($mailObject->getAddresses('to',true)) == 0 &&
6829
							is_numeric($val) || $GLOBALS['egw']->accounts->name2id($val)) // do the merge
6830
						{
6831
							$contact = $bo_merge->contacts->read($val);
6832
							//error_log(__METHOD__.' ('.__LINE__.') '.array2string($contact));
6833
							$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
6834
							$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
6835
							if($email)
6836
							{
6837
								$mailObject->addAddress(Horde_Idna::encode($email), $nfn);
6838
							}
6839
						}
6840
						$mailObject->addHeader('Subject', $bo_merge->merge_string($Subject, $val, $e, 'text/plain', array(), self::$displayCharset));
6841
						//error_log(__METHOD__.' ('.__LINE__.') '.' ContentType:'.$mailObject->BodyContentType);
6842 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));
6843
						//error_log(__METHOD__.' ('.__LINE__.') '.' Result:'.$mailObject->Body.' error:'.array2string($e));
6844 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));
6845
						$_folder = $this->getDraftFolder();
6846
					}
6847
					if ($sendOK || $openAsDraft)
6848
					{
6849
						if ($this->folderExists($_folder,true))
6850
						{
6851
						    if($this->isSentFolder($_folder))
6852
							{
6853
						        $flags = '\\Seen';
6854
						    } elseif($this->isDraftFolder($_folder)) {
6855
						        $flags = '\\Draft';
6856
						    } else {
6857
						        $flags = '';
6858
						    }
6859
							$savefailed = false;
6860
							try
6861
							{
6862
								$messageUid =$this->appendMessage($_folder,
6863
									$mailObject->getRaw(),
6864
									null,
6865
									$flags);
6866
							}
6867
							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...
6868
							{
6869
								$savefailed = true;
6870
								$alert_msg .= lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",$Subject,$_folder,$e->getMessage());
6871
							}
6872
							// no send, save successful, and message_uid present
6873
							if ($savefailed===false && $messageUid && is_null($sendOK))
6874
							{
6875
								$importID = $messageUid;
6876
								$openComposeWindow = true;
6877
							}
6878
						}
6879
						else
6880
						{
6881
							$savefailed = true;
6882
							$alert_msg .= lang("Saving of message %1 failed. Destination Folder %2 does not exist.",$Subject,$_folder);
6883
						}
6884
						if ($sendOK)
6885
						{
6886
							$processStats['success'][$val] = 'Send succeeded to '.$nfn.'<'.$email.'>'.($savefailed?' but failed to store to Folder:'.$_folder:'');
6887
						}
6888
						else
6889
						{
6890
							if (!$openComposeWindow) $processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
1 ignored issue
show
Bug Best Practice introduced by
The expression $openComposeWindow of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

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

$a = canBeFalseAndNull();

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

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
6891
						}
6892
					}
6893
					if (!is_null($sendOK) && $sendOK===false && is_null($openComposeWindow))
6894
					{
6895
						$processStats['failed'][$val] = $errorInfo?$errorInfo:'Send failed to '.$nfn.'<'.$email.'> See error_log for details';
6896
					}
6897
				}
6898
			}
6899
			unset($mailObject);
6900
		}
6901
		// set the url to open when refreshing
6902
		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...
6903
		{
6904
			throw new Exception\WrongUserinput($alert_msg);
6905
		}
6906
		else
6907
		{
6908
			//error_log(__METHOD__.' ('.__LINE__.') '.array2string($processStats));
6909
			return $processStats;
6910
		}
6911
	}
6912
6913
	/**
6914
	 * functions to allow the parsing of message/rfc files
6915
	 * used in felamimail to import mails, or parsev a message from file enrich it with addressdata (merge) and send it right away.
6916
	 */
6917
6918
	/**
6919
	 * Parses a message/rfc mail from file to the mailobject
6920
	 *
6921
	 * @param object $mailer instance of the SMTP Mailer Object
6922
	 * @param string $tmpFileName string that points/leads to the file to be imported
6923
	 * @throws Exception\NotFound if $fle is not found
6924
	 */
6925
	function parseFileIntoMailObject(Mailer $mailer, $tmpFileName)
6926
	{
6927
		switch (parse_url($tmpFileName, PHP_URL_SCHEME))
6928
		{
6929
			case 'vfs':
6930
				break;
6931
			case 'egw-data':
6932
				$message = Link::get_data(parse_url($tmpFileName, PHP_URL_HOST), true);
0 ignored issues
show
Security Bug introduced by
It seems like parse_url($tmpFileName, PHP_URL_HOST) targeting parse_url() can also be of type false; however, EGroupware\Api\Link::get_data() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
6933
				break;
6934
			default:
6935
				$tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($tmpFileName);
6936
				break;
6937
		}
6938
		if (!isset($message)) $message = fopen($tmpFileName, 'r');
6939
6940
		if (!$message)
6941
		{
6942
			throw new Exception\NotFound("File '$tmpFileName' not found!");
6943
		}
6944
		$this->parseRawMessageIntoMailObject($mailer, $message);
6945
6946
		fclose($message);
6947
	}
6948
6949
	/**
6950
	 * Parses a message/rfc mail from file to the mailobject
6951
	 *
6952
	 * @param Mailer $mailer instance of SMTP Mailer object
6953
	 * @param string|ressource|Horde_Mime_Part $message string or resource containing the RawMessage / object Mail_mimeDecoded message (part))
6954
	 * @throws Exception\WrongParameter when the required Horde_Mail_Part not found
6955
	 */
6956
	function parseRawMessageIntoMailObject(Mailer $mailer, $message)
6957
	{
6958
		if (is_string($message) || is_resource($message))
6959
		{
6960
			$structure = Horde_Mime_Part::parseMessage($message);
6961
			$mailer->setBasePart($structure);
6962
			//error_log(__METHOD__.__LINE__.':'.array2string($structure));
6963
6964
			// unfortunately parseMessage does NOT return parsed headers (we assume header is shorter then 8k)
6965
			$start = is_string($message) ? substr($message, 0, 8192) :
6966
				(fseek($message, 0, SEEK_SET) == -1 ? '' : fread($message, 8192));
6967
6968
			$length = strpos($start, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
6969
			if ($length===false) $length = strlen($start);
6970
			$headers = Horde_Mime_Headers::parseHeaders(substr($start, 0,$length));
6971
6972
			foreach($headers->toArray(array('nowrap' => true)) as $header => $value)
6973
			{
6974
				foreach((array)$value as $n => $val)
6975
				{
6976
					$overwrite = !$n;
6977
					switch($header)
6978
					{
6979
						case 'Content-Transfer-Encoding':
6980
							//as we parse the message and this sets the part with a Content-Transfer-Encoding, we
6981
							//should not overwrite it with the header-values of the source-message as the encoding
6982
							//may be altered when retrieving the message e.g. from server
6983
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val.'<->'.$mailer->getHeader('Content-Transfer-Encoding'));
6984
							break;
6985
						default:
6986
							//error_log(__METHOD__.__LINE__.':'.$header.'->'.$val);
6987
							$mailer->addHeader($header, $val, $overwrite);
6988
					}
6989
				}
6990
			}
6991
		}
6992
		elseif (is_a($message, 'Horde_Mime_Part'))
6993
		{
6994
			$mailer->setBasePart($message);
6995
		}
6996
		else
6997
		{
6998
			if (($type = gettype($message)) == 'object') $type = get_class ($message);
6999
			throw new Exception\WrongParameter('Wrong parameter type for message: '.$type);
7000
		}
7001
	}
7002
7003
	/**
7004
	 * Parse an address-list
7005
	 *
7006
	 * Replaces imap_rfc822_parse_adrlist, which fails for utf-8, if not our replacement in common_functions is used!
7007
	 *
7008
	 * @param string $addresses
7009
	 * @param string $default_domain
7010
	 * @return Horde_Mail_Rfc822_List iteratable Horde_Mail_Rfc822_Address objects with attributes mailbox, host, personal and valid
7011
	 */
7012
	public static function parseAddressList($addresses, $default_domain=null)
7013
	{
7014
		$rfc822 = new Horde_Mail_Rfc822();
7015
		$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7016
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count.function_backtrace());
7017
		if ((empty($ret) || $ret->count()==0)&& is_string($addresses) && strlen($addresses)>0)
7018
		{
7019
			$matches = array();
7020
			preg_match_all("/[\w\.,-.,_.,0-9.]+@[\w\.,-.,_.,0-9.]+/",$addresses,$matches);
7021
			//error_log(__METHOD__.__LINE__.array2string($matches));
7022
			foreach ($matches[0] as &$match) {$match = trim($match,', ');}
7023
			$addresses = implode(',',$matches[0]);
7024
			//error_log(__METHOD__.__LINE__.array2string($addresses));
7025
			$ret = $rfc822->parseAddressList($addresses, $default_domain ? array('default_domain' => $default_domain) : array());
7026
			//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret).'#'.$ret->count().'#'.$ret->count);
7027
		}
7028
		$previousFailed=false;
7029
		$ret2 = new Horde_Mail_Rfc822_List();
7030
		// handle known problems on emailaddresses
7031
		foreach($ret as $i => $adr)
7032
		{
7033
			//mailaddresses enclosed in single quotes like '[email protected]' show up as 'me as mailbox and you.com' as host
7034
			if ($adr->mailbox && stripos($adr->mailbox,"'")== 0 &&
7035
					$adr->host && stripos($adr->host,"'")== (strlen($adr->host) -1))
7036
			{
7037
				$adr->mailbox = str_replace("'","",$adr->mailbox);
7038
				$adr->host = str_replace("'","",$adr->host);
7039
			}
7040
			// no mailbox or host part as 'Xr\xc3\xa4hlyz, User <[email protected]>' is parsed as 2 addresses separated by ','
7041
			//#'Xr\xc3\xa4hlyz, User <[email protected]>'
7042
			//#Horde_Mail_Rfc822_List Object([_data:protected] => Array(
7043
			//[0] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => Xr\xc3\xa4hlyz[_host:protected] => [_personal:protected] => )
7044
			//[1] => Horde_Mail_Rfc822_Address Object([comment] => Array()[mailbox] => mailboxpart1.mailboxpart2[_host:protected] => youthost.com[_personal:protected] => User))[_filter:protected] => Array()[_ptr:protected] => )#2#,
7045
			if (strlen($adr->mailbox)==0||strlen($adr->host)==0)
7046
			{
7047
				$remember = ($adr->mailbox?$adr->mailbox:($adr->host?$adr->host:''));
7048
				$previousFailed=true;
7049
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7050
			}
7051
			else
7052
			{
7053
				if ($previousFailed && $remember) $adr->personal = $remember. ' ' . $adr->personal;
7054
				$remember = '';
7055
				$previousFailed=false;
7056
				//error_log(__METHOD__.__LINE__."('$addresses', $default_domain) parsed $i: mailbox=$adr->mailbox, host=$adr->host, personal=$adr->personal");
7057
				$ret2->add($adr);
7058
			}
7059
		}
7060
		//error_log(__METHOD__.__LINE__.'#'.array2string($addresses).'#'.array2string($ret2).'#'.$ret2->count().'#'.$ret2->count);
7061
		return $ret2;
7062
	}
7063
7064
	/**
7065
	 * Send a read notification
7066
	 *
7067
	 * @param string $uid
7068
	 * @param string $_folder
7069
	 * @return boolean
7070
	 */
7071
	function sendMDN($uid,$_folder)
7072
	{
7073
		$acc = Mail\Account::read($this->profileID);
7074
		$identity = Mail\Account::read_identity($acc['ident_id'], true, null, $acc);
7075
		if (self::$debug) error_log(__METHOD__.__LINE__.array2string($identity));
7076
		$headers = $this->getMessageHeader($uid, '', 'object', true, $_folder);
7077
7078
		$mdn = new Horde_Mime_Mdn($headers);
7079
		$mdn->generate(true, true, 'displayed', php_uname('n'), $acc->smtpTransport(), array(
7080
			'charset' => 'utf-8',
7081
			'from_addr' => self::generateIdentityString($identity),
7082
		));
7083
7084
		return true;
7085
	}
7086
7087
	/**
7088
	 * Hook stuff
7089
	 */
7090
7091
	/**
7092
	 * hook to add account
7093
	 *
7094
	 * this function is a wrapper function for emailadmin
7095
	 *
7096
	 * @param _hookValues contains the hook values as array
7097
	 * @return nothing
7098
	 */
7099
	function addAccount($_hookValues)
7100
	{
7101
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7102
7103
	}
7104
7105
	/**
7106
	 * hook to delete account
7107
	 *
7108
	 * this function is a wrapper function for emailadmin
7109
	 *
7110
	 * @param _hookValues contains the hook values as array
7111
	 * @return nothing
7112
	 */
7113
	function deleteAccount($_hookValues)
7114
	{
7115
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7116
7117
	}
7118
7119
	/**
7120
	 * hook to update account
7121
	 *
7122
	 * this function is a wrapper function for emailadmin
7123
	 *
7124
	 * @param _hookValues contains the hook values as array
7125
	 * @return nothing
7126
	 */
7127
	function updateAccount($_hookValues)
7128
	{
7129
		error_log(__METHOD__.' ('.__LINE__.') '.' NOT DONE YET!' . ' hookValue = '. $_hookValues);
7130
7131
	}
7132
}
7133