Completed
Push — master ( ef25b8...955ba3 )
by Klaus
24:18
created

mail_zpush::SendMail()   F

Complexity

Conditions 177
Paths 0

Size

Total Lines 473
Code Lines 274

Duplication

Lines 86
Ratio 18.18 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 177
eloc 274
c 2
b 1
f 0
nc 0
nop 1
dl 86
loc 473
rs 2

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * EGroupware - Mail - interface class for activesync implementation
4
 *
5
 * @link http://www.egroupware.org
6
 * @package mail
7
 * @author Stylite AG [[email protected]]
8
 * @author Ralf Becker <[email protected]>
9
 * @author Philip Herbert <[email protected]>
10
 * @copyright (c) 2014-16 by Stylite AG <info-AT-stylite.de>
11
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
12
 * @version $Id$
13
 */
14
15
use EGroupware\Api;
16
use EGroupware\Api\Mail;
17
18
/**
19
 * mail eSync plugin
20
 *
21
 * Plugin creates a device specific file to map alphanumeric folder names to nummeric id's.
22
 */
23
class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail, activesync_plugin_meeting_response, activesync_plugin_search_mailbox
24
{
25
	/**
26
	 * var activesync_backend
27
	 */
28
	private $backend;
29
30
	/**
31
	 * Instance of Mail
32
	 *
33
	 * @var Mail
34
	 */
35
	private $mail;
36
37
	/**
38
	 * Provides the ability to change the line ending
39
	 * @var string
40
	 */
41
	public static $LE = "\n";
42
43
	/**
44
	 * Integer id of trash folder
45
	 *
46
	 * @var mixed
47
	 */
48
	private $_wasteID = false;
49
50
	/**
51
	 * Integer id of sent folder
52
	 *
53
	 * @var mixed
54
	 */
55
	private $_sentID = false;
56
57
	/**
58
	 * Integer id of current mail account / connection
59
	 *
60
	 * @var int
61
	 */
62
	private $account;
63
64
	private $folders;
65
66
	private $messages;
0 ignored issues
show
Unused Code introduced by
The property $messages is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
67
68
	static $profileID;
69
70
	// to control how deep one may dive into the past
71
	const PAST_LIMIT = 178;
72
73
	/**
74
	 * debugLevel - enables more debug
75
	 *
76
	 * @var int
77
	 */
78
	private $debugLevel = 0;
79
80
	/**
81
	 * Constructor
82
	 *
83
	 * @param activesync_backend $backend
84
	 */
85
	public function __construct(activesync_backend $backend)
86
	{
87
		if ($GLOBALS['egw_setup']) return;
88
89
		//$this->debugLevel=2;
90
		$this->backend = $backend;
91
		if (!isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
92
		{
93
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Noprefs set: using 0 as default');
94
			// globals preferences add appname varname value
95
			$GLOBALS['egw']->preferences->add('activesync','mail-ActiveSyncProfileID',0,'user');
96
			// save prefs
97
			$GLOBALS['egw']->preferences->save_repository(true);
98
		}
99 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID:'.array2string(self::$profileID));
100
101
		if (is_null(self::$profileID))
102
		{
103 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' self::ProfileID isNUll:'.array2string(self::$profileID));
104
			self::$profileID =& Api\Cache::getSession('mail','activeSyncProfileID');
105 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID (after reading Cache):'.array2string(self::$profileID));
106
		}
107
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
108
		{
109
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Pref for ProfileID:'.array2string($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']));
110
			if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] == 'G')
111
			{
112
				self::$profileID = 'G'; // this should trigger the fetch of the first negative profile (or if no negative profile is available the firstb there is)
113
			}
114
			else
115
			{
116
				self::$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'];
117
			}
118
		}
119 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Profile Selected (after reading Prefs):'.array2string(self::$profileID));
120
121
		// verify we are on an existing profile, if not running in setup (settings can not be static according to interface!)
122
		if (!isset($GLOBALS['egw_setup']))
123
		{
124
			try {
125
				Mail\Account::read(self::$profileID);
126
			}
127
			catch(Exception $e) {
128
				unset($e);
129
				self::$profileID = Mail\Account::get_default_acc_id();
130
			}
131
		}
132
		if ($this->debugLevel>0) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.self::$profileID);
133
		//$this->debugLevel=0;
134
	}
135
136
	/**
137
	 * Populates $settings for the preferences
138
	 *
139
	 * @param array|string $hook_data
140
	 * @return array
141
	 */
142
	function egw_settings($hook_data)
143
	{
144
		//error_log(__METHOD__.__LINE__.array2string($hook_data));
145
		$identities = array();
146
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group')))
147
		{
148
			$identities = iterator_to_array(Mail\Account::search((int)$hook_data['account_id']));
149
		}
150
		$identities += array(
151
			'G' => lang('Primary Profile'),
152
		);
153
154
		$settings['mail-ActiveSyncProfileID'] = array(
155
			'type'   => 'select',
156
			'label'  => 'eMail Account to sync',
157
			'name'   => 'mail-ActiveSyncProfileID',
158
			'help'   => 'eMail Account to sync ',
159
			'values' => $identities,
160
			'default'=> 'G',
161
			'xmlrpc' => True,
162
			'admin'  => False,
163
		);
164
		$settings['mail-allowSendingInvitations'] = array(
165
			'type'   => 'select',
166
			'label'  => 'allow sending of calendar invitations using this profile?',
167
			'name'   => 'mail-allowSendingInvitations',
168
			'help'   => 'control the sending of calendar invitations while using this profile',
169
			'values' => array(
170
				'sendifnocalnotif'=>'only send if there is no notification in calendar',
171
				'send'=>'yes, always send',
172
				'nosend'=>'no, do not send',
173
			),
174
			'xmlrpc' => True,
175
			'default' => 'sendifnocalnotif',
176
			'admin'  => False,
177
		);
178
		$settings['mail-maximumSyncRange'] = array(
179
			'type'   => 'integer',
180
			'label'  => lang('How many days to sync in the past when client does not specify a date-range (default %1)', self::PAST_LIMIT),
181
			'name'   => 'mail-maximumSyncRange',
182
			'help'   => 'if the client sets no sync range, you may override the setting (preventing client crash that may be caused by too many mails/too much data). If you want to sync way-back into the past: set a large number',
183
			'xmlrpc' => True,
184
			'admin'  => False,
185
		);
186
187
/*
188
		$sigOptions = array(
189
				'send'=>'yes, always add EGroupware signatures to outgoing mails',
190
				'nosend'=>'no, never add EGroupware signatures to outgoing mails',
191
			);
192
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group'))&&$hook_data['account_id'])
193
		{
194
			$pID=self::$profileID;
195
			if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']=='G')
196
			{
197
				$pID=Mail\Account::get_default_acc_id();
198
			}
199
			$acc = Mail\Account::read($pID);
200
			error_log(__METHOD__.__LINE__.':'.$pID.'->'.array2string($acc));
201
			$Identities = Mail\Account::identities($pID);
202
			foreach($Identities as &$identity)
203
			{
204
				$Identity = self::identity_name($identity);
205
			}
206
			error_log(__METHOD__.__LINE__.array2string($Identities));
207
		}
208
		$settings['mail-useSignature'] = array(
209
			'type'   => 'select',
210
			'label'  => 'control if and which available signature is added to outgoing mails',
211
			'name'   => 'mail-useSignature',
212
			'help'   => 'control the use of signatures',
213
			'values' => $sigOptions,
214
			'xmlrpc' => True,
215
			'default' => 'nosend',
216
			'admin'  => False,
217
		);
218
*/
219
		return $settings;
220
	}
221
222
	/**
223
	 * Verify preferences
224
	 *
225
	 * @param array|string $hook_data
226
	 * @return array with error-messages from all plugins
227
	 */
228
	function verify_settings($hook_data)
229
	{
230
		$errors = array();
231
232
		// check if an eSync eMail profile is set (might not be set as default or forced!)
233
		if (isset($hook_data['prefs']['mail-ActiveSyncProfileID']) || $hook_data['type'] == 'user')
234
		{
235
			// eSync and eMail translations are not (yet) loaded
236
			Api\Translation::add_app('activesync');
237
			Api\Translation::add_app('mail');
238
239
			// inject preference to verify and call constructor
240
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] =
241
				$hook_data['prefs']['mail-ActiveSyncProfileID'];
242
			$this->__construct($this->backend);
243
244
			try {
245
				$this->_connect(0,true);
246
				$this->_disconnect();
247
248
				if (!$this->_wasteID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('trash').'</b>');
249
				if (!$this->_sentID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('send').'</b>');
250
			}
251
			catch(Exception $e) {
252
				$errors[] = lang('Can not open IMAP connection').': '.$e->getMessage();
253
			}
254
			if ($errors)
255
			{
256
				$errors[] = '<b>'.lang('eSync will FAIL without a working eMail configuration!').'</b>';
257
			}
258
		}
259
		//error_log(__METHOD__.'('.array2string($hook_data).') returning '.array2string($errors));
260
		return $errors;
261
	}
262
263
	/**
264
	 * Open IMAP connection
265
	 *
266
	 * @param int $account integer id of account to use
267
	 * @todo support different accounts
268
	 */
269
	private function _connect($account=0)
270
	{
271
		if (!$account) $account = self::$profileID ? self::$profileID : 0;
272
		if ($this->mail && $this->account != $account) $this->_disconnect();
273
274
		$this->_wasteID = false;
275
		$this->_sentID = false;
276
277
		if (!$this->mail)
278
		{
279
			$this->account = $account;
280
			// todo: tell mail which account to use
281
			//error_log(__METHOD__.__LINE__.' create object with ProfileID:'.array2string(self::$profileID));
282
			$this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
283 View Code Duplication
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
284
		}
285 View Code Duplication
		else
286
		{
287
			//error_log(__METHOD__.__LINE__." connect with profileID: ".self::$profileID);
288
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
289
		}
290
		$this->mail->openConnection(self::$profileID,false);
0 ignored issues
show
Unused Code introduced by
The call to Mail::openConnection() has too many arguments starting with false.

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...
291
292
		$this->_wasteID = $this->mail->getTrashFolder(false);
293
		//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
294
		$this->_sentID = $this->mail->getSentFolder(false);
295
		$this->mail->getOutboxFolder(true);
296
		//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
297
		//error_log(__METHOD__.__LINE__.' Connection Status for ProfileID:'.self::$profileID.'->'.$this->mail->icServer->_connected);
298
	}
299
300
	/**
301
	 * Close IMAP connection
302
	 */
303
	private function _disconnect()
304
	{
305
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__);
306
		if ($this->mail) $this->mail->closeConnection();
307
308
		unset($this->mail);
309
		unset($this->account);
310
		unset($this->folders);
311
	}
312
313
	/**
314
	 *  GetFolderList
315
	 *
316
	 *  @ToDo loop over available email accounts
317
	 */
318
	public function GetFolderList()
319
	{
320
		$folderlist = array();
321
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
322
		/*foreach($available_accounts as $account)*/ $account = 0;
323
		{
324
			$this->_connect($account);
325
			if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false,$_alwaysGetDefaultFolders=true);
326
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($this->folders));
327
328
			foreach ($this->folders as $folder => $folderObj) {
329
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' folder='.$folder);
330
				$folderlist[] = $f = array(
331
					'id'     => $this->createID($account,$folder),
332
					'mod'    => $folderObj->shortDisplayName,
333
					'parent' => $this->getParentID($account,$folder),
334
				);
335
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($f));
336
			}
337
		}
338
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($folderlist));
339
340
		return $folderlist;
341
	}
342
343
    /**
344
     * Sends a message which is passed as rfc822. You basically can do two things
345
     * 1) Send the message to an SMTP server as-is
346
     * 2) Parse the message yourself, and send it some other way
347
     * It is up to you whether you want to put the message in the sent items folder. If you
348
     * want it in 'sent items', then the next sync on the 'sent items' folder should return
349
     * the new message as any other new message in a folder.
350
     *
351
     * @param array $smartdata = IMAP-SendMail: SyncSendMail (
352
     *        (S) clientid => SendMail-30722448149304
353
     *        (S) saveinsent => empty
354
     *        (S) replacemime => null
355
     *        (S) accountid => null
356
     *        (S) source => SyncSendMailSource (
357
     *                                (S) folderid => 101000000000
358
     *                                (S) itemid => 33776
359
     *                                (S) longid => null
360
     *                                (S) instanceid => null
361
     *                                unsetVars(Array) size: 0
362
     *                                flags => false
363
     *                                content => null
364
     *                        )
365
     *        (S) mime => Date: Tue, 23 Jun 2015 14:13:23 +0200
366
     *Subject: AW: Blauer himmel
367
     *....
368
     *        (S) replyflag => true
369
     *        (S) forwardflag => null
370
     *        unsetVars(Array) size: 0
371
     *        flags => false
372
     *        content => null
373
     *)
374
	 *
375
     * @return boolean true on success, false on error
376
     *
377
     * @see eg. BackendIMAP::SendMail()
378
     * @todo implement either here or in mail backend
379
     * 	(maybe sending here and storing to sent folder in plugin, as sending is supposed to always work in EGroupware)
380
     */
381
	public function SendMail($smartdata)
382
	{
383
		//$this->debugLevel=2;
384
		$ClientSideMeetingRequest = false;
385
		$allowSendingInvitations = 'sendifnocalnotif';
386
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
387
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']=='nosend')
388
		{
389
			$allowSendingInvitations = false;
390
		}
391
		elseif (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
392
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']!='nosend')
393
		{
394
			$allowSendingInvitations = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations'];
395
		}
396
		$smartdata_task = ($smartdata->replyflag?'reply':($smartdata->forwardflag?'forward':'new'));
397
398
   		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__ . (isset($smartdata->mime) ? $smartdata->mime : ""). "task: ".(isset($smartdata_task) ? $smartdata_task : "")." itemid: ".(isset($smartdata->source->itemid) ? $smartdata->source->itemid : "")." folder: ".(isset($smartdata->source->folderid) ? $smartdata->source->folderid : ""));
399
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Smartdata = ".array2string($smartdata));
400
		//error_log("IMAP-Sendmail: Smartdata = ".array2string($smartdata));
401
402
		// initialize our Mail
403
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
404
		$activeMailProfiles = $this->mail->getAccountIdentities(self::$profileID);
405
		// use the standardIdentity
406
		$activeMailProfile = Mail::getStandardIdentityForProfile($activeMailProfiles,self::$profileID);
407
408
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.")".' ProfileID:'.self::$profileID.' ActiveMailProfile:'.array2string($activeMailProfile));
409
		// collect identity / signature for later usage, and to determine if we may have to manipulate TransferEncoding and Charset
410
		try
411
		{
412
			$acc = Mail\Account::read($this->mail->icServer->ImapServerId);
413
			//error_log(__METHOD__.__LINE__.array2string($acc));
414
			$_signature = Mail\Account::read_identity($acc['ident_id'],true);
415
		}
416
		catch (Exception $e)
417
		{
418
			$_signature=array();
419
		}
420
		$signature = $_signature['ident_signature'];
421
		if ((isset($preferencesArray['disableRulerForSignatureSeparation']) &&
0 ignored issues
show
Bug introduced by
The variable $preferencesArray seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
422
			$preferencesArray['disableRulerForSignatureSeparation']) ||
0 ignored issues
show
Bug introduced by
The variable $preferencesArray seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
423
			empty($signature) || trim(Api\Mail\Html::convertHTMLToText($signature)) =='')
424
		{
425
			$disableRuler = true;
426
		}
427
		$beforePlain = $beforeHtml = "";
428
		$beforeHtml = ($disableRuler ?'&nbsp;<br>':'&nbsp;<br><hr style="border:dotted 1px silver; width:90%; border:dotted 1px silver;">');
429
		$beforePlain = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
430
		$sigText = Mail::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
431
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Signature to use:'.$sigText);
432
		$sigTextHtml = $beforeHtml.$sigText;
433
		$sigTextPlain = $beforePlain.Api\Mail\Html::convertHTMLToText($sigText);
434
435
		$force8bit=false;
436
		if (Api\Translation::detect_encoding($sigTextPlain)!='ascii') $force8bit=true;
437
		// initialize the new Api\Mailer object for sending
438
		$mailObject = new Api\Mailer(self::$profileID);
439
440
		$this->mail->parseRawMessageIntoMailObject($mailObject,$smartdata->mime,$force8bit);
441
		// Horde SMTP Class uses utf-8 by default. as we set charset always to utf-8
442
		$mailObject->Sender  = $activeMailProfile['ident_email'];
443
		$mailObject->setFrom($activeMailProfile['ident_email'],Mail::generateIdentityString($activeMailProfile,false));
444
		$mailObject->addHeader('X-Mailer', 'mail-Activesync');
445
446
447
		// prepare addressee list; moved the adding of addresses to the mailobject down
448
		// to
449
450 View Code Duplication
		foreach(Mail::parseAddressList($mailObject->getHeader("To")) as $addressObject) {
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader('To') targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Mail::parseAddressList() 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...
451
			if (!$addressObject->valid) continue;
452
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail To: ".array2string($addressObject) );
453
			//$mailObject->AddAddress($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
454
			$toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
455
		}
456
		// CC
457 View Code Duplication
		foreach(Mail::parseAddressList($mailObject->getHeader("Cc")) as $addressObject) {
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader('Cc') targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Mail::parseAddressList() 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...
458
			if (!$addressObject->valid) continue;
459
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail CC: ".array2string($addressObject) );
460
			//$mailObject->AddCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
461
			$ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
462
		}
463
		// BCC
464 View Code Duplication
		foreach(Mail::parseAddressList($mailObject->getHeader("Bcc")) as $addressObject) {
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader('Bcc') targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Mail::parseAddressList() 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...
465
			if (!$addressObject->valid) continue;
466
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail BCC: ".array2string($addressObject) );
467
			//$mailObject->AddBCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
468
			$bccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
469
		}
470
		$mailObject->clearAllRecipients();
471
472
		$use_orgbody = false;
473
474
		$k = 'Content-Type';
475
		$ContentType =$mailObject->getHeader('Content-Type');
476
		//error_log(__METHOD__.__LINE__." Header Sentmail original Header (filtered): " . $k.  " = ".trim($ContentType));
477
		// if the message is a multipart message, then we should use the sent body
478
		if (preg_match("/multipart/i", $ContentType)) {
479
			$use_orgbody = true;
480
		}
481
482
		// save the original content-type header for the body part when forwarding
483
		if ($smartdata_task == 'forward' && $smartdata->source->itemid && !$use_orgbody) {
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...
484
			//continue; // ignore
485
		}
486
		// horde/egw_ mailer does everything as utf-8, the following should not be needed
487
		//$org_charset = $ContentType;
488
		//$ContentType = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $ContentType);
489
		// if the message is a multipart message, then we should use the sent body
490
		if (($smartdata_task == 'new' || $smartdata_task == 'reply' || $smartdata_task == 'forward') &&
491
			((isset($smartdata->replacemime) && $smartdata->replacemime == true) ||
492
			$k == "Content-Type" && preg_match("/multipart/i", $ContentType))) {
493
			$use_orgbody = true;
494
		}
495
		$Body =  $AltBody = "";
496
		// get body of the transmitted message
497
		// if this is a simple message, no structure at all
498
		if (preg_match("/text/i", $ContentType))
499
		{
500
			$simpleBodyType = (preg_match("/html/i", $ContentType)?'text/html':'text/plain');
501
			$bodyObj = $mailObject->findBody(preg_match("/html/i", $ContentType) ? 'html' : 'plain');
502
			$body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]", $bodyObj ?$bodyObj->getContents() : null);
503
			if  ($simpleBodyType == "text/plain")
504
			{
505
				$Body = $body;
506
				$AltBody = "<pre>".nl2br($body)."</pre>";
507
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created AltBody');
508
			}
509
			else
510
			{
511
				$AltBody = $body;
512
				$Body =  trim(Api\Mail\Html::convertHTMLToText($body));
513
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created Body');
514
			}
515
		}
516
		else
517
		{
518
			// if this is a structured message
519
			// prefer plain over html
520
			$Body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
521
				($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null);
522
			$AltBody = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
523
				($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null);
524
		}
525
		if ($this->debugLevel>1 && $Body) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as with MessageContentType:". $ContentType.'=>'.$Body);
526
		if ($this->debugLevel>1 && $AltBody) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched AltBody as with MessageContentType:". $ContentType.'=>'.$AltBody);
527
		//error_log(__METHOD__.__LINE__.array2string($mailObject));
528
		// if this is a multipart message with a boundary, we must use the original body
529
		//if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' mailObject after Inital Parse:'.array2string($mailObject));
530
        if ($use_orgbody) {
531
    	    ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") use_orgbody = true ContentType:".$ContentType);
532
 			// if it is a ClientSideMeetingRequest, we report it as send at all times
533
			if (($cal_body = $mailObject->findBody('calendar')) &&
534
				($cSMRMethod = $cal_body->getContentTypeParameter('method')))
535
			{
536
				if ($cSMRMethod == 'REPLY' && class_exists('calendar_ical'))
537
				{
538
					$organizer = calendar_ical::getIcalOrganizer($cal_body->getContents());
539
				}
540
				if ($this->debugLevel) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") we have a Client Side Meeting Request from organizer=$organizer");
541
				$ClientSideMeetingRequest = true;
542
			}
543
        }
544
		// now handle the addressee list
545
		$toCount = 0;
546
		//error_log(__METHOD__.__LINE__.array2string($toMailAddr));
547
		foreach((array)$toMailAddr as $address) {
548
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
549
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
550
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' &&
551
					calendar_boupdate::email_update_requested($emailAddress, isset($cSMRMethod) ? $cSMRMethod : 'REQUEST',
552
						$organizer && !strcasecmp($emailAddress, $organizer) ? 'CHAIR' : ''))
553
				{
554
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") skiping mail to organizer '$organizer', as it will be send by calendar app");
555
					continue;
556
				}
557
				$mailObject->AddAddress($emailAddress, $addressObject->personal);
558
				$toCount++;
559
			}
560
		}
561
		$ccCount = 0;
562 View Code Duplication
		foreach((array)$ccMailAddr as $address) {
563
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
564
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
565
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
566
				$mailObject->AddCC($emailAddress, $addressObject->personal);
567
				$ccCount++;
568
			}
569
		}
570
		$bccCount = 0;
571 View Code Duplication
		foreach((array)$bccMailAddr as $address) {
572
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
573
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
574
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
575
				$mailObject->AddBCC($emailAddress, $addressObject->personal);
576
				$bccCount++;
577
			}
578
		}
579
		// typical organizer reply will end here with nothing send --> return true, because we suppressed the send above
580
		if ($toCount+$ccCount+$bccCount == 0)
581
		{
582
			return $ClientSideMeetingRequest && $allowSendingInvitations === 'sendifnocalnotif' && $organizer ? true : 0; // noone to send mail to
583
		}
584
		if ($ClientSideMeetingRequest === true && $allowSendingInvitations===false) return true;
585
		// as we use our mailer (horde mailer) it is detecting / setting the mimetype by itself while creating the mail
586
/*
587
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body:'.$body);
588
		$body = str_replace("\r",((preg_match("^text/html^i", $ContentType))?'<br>':""),$body); // what is this for?
589
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body (modified):'.$body);
590
*/
591
		// actually use prepared signature --------------------collected earlier--------------------------
592
		$isreply = $isforward = false;
593
		// reply ---------------------------------------------------------------------------
594
		if ($smartdata_task == 'reply' && isset($smartdata->source->itemid) &&
595
			isset($smartdata->source->folderid) && $smartdata->source->itemid && $smartdata->source->folderid &&
596
			(!isset($smartdata->replacemime) ||
597
			(isset($smartdata->replacemime) && $smartdata->replacemime == false)))
598
		{
599
			// now get on, and fetch the original mail
600
			$uid = $smartdata->source->itemid;
601
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP Smartreply is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
602
			$this->splitID($smartdata->source->folderid, $account, $folder);
603
604
			$this->mail->reopen($folder);
605
			$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
606
			$bodyBUFFHtml = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
607
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$bodyBUFFHtml);
608 View Code Duplication
		    if ($bodyBUFFHtml != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
609
				// may be html
610
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:html (fetched with html_only):'.$bodyBUFFHtml);
611
				$AltBody = $AltBody."</br>".$bodyBUFFHtml.$sigTextHtml;
612
				$isreply = true;
613
			}
614
			// plain text Message part
615
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain, fetch text:');
616
			// if the new part of the message is html, we must preserve it, and handle that the original mail is text/plain
617
			$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
618
			$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
619 View Code Duplication
			if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/plain')) {
620
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain (fetched with never_display):'.$bodyBUFF);
621
				$Body = $Body."\r\n".$bodyBUFF.$sigTextPlain;
622
				$isreply = true;
623
			}
624 View Code Duplication
			if (!empty($bodyBUFF) && empty($bodyBUFFHtml) && !empty($AltBody))
625
			{
626
				$isreply = true;
627
				$AltBody = $AltBody."</br><pre>".nl2br($bodyBUFF).'</pre>'.$sigTextHtml;
628
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." no Api\Html Body found use modified plaintext body for txt/html: ".$AltBody);
629
			}
630
		}
631
632
		// how to forward and other prefs
633
		$preferencesArray =& $GLOBALS['egw_info']['user']['preferences']['mail'];
634
635
		// forward -------------------------------------------------------------------------
636
		if ($smartdata_task == 'forward' && isset($smartdata->source->itemid) &&
637
			isset($smartdata->source->folderid) && $smartdata->source->itemid && $smartdata->source->folderid &&
638
			(!isset($smartdata->replacemime) ||
639
			(isset($smartdata->replacemime) && $smartdata->replacemime == false)))
640
		{
641
			//force the default for the forwarding -> asmail
642
			if (is_array($preferencesArray)) {
643
				if (!array_key_exists('message_forwarding',$preferencesArray)
644
					|| !isset($preferencesArray['message_forwarding'])
645
					|| empty($preferencesArray['message_forwarding'])) $preferencesArray['message_forwarding'] = 'asmail';
646
			} else {
647
				$preferencesArray['message_forwarding'] = 'asmail';
648
			}
649
			// construct the uid of the message out of the itemid - seems to be the uid, no construction needed
650
			$uid = $smartdata->source->itemid;
651
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.")IMAP Smartfordward is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
652
			$this->splitID($smartdata->source->folderid, $account, $folder);
653
654
			$this->mail->reopen($folder);
655
            // receive entire mail (header + body)
656
			// get message headers for specified message
657
			$headers	= $this->mail->getMessageEnvelope($uid, $_partID, true, $folder);
658
			// build a new mime message, forward entire old mail as file
659
			if ($preferencesArray['message_forwarding'] == 'asmail')
660
			{
661
				$rawHeader      = $this->mail->getMessageRawHeader($smartdata->source->itemid, $_partID,$folder);
662
				$rawBody        = $this->mail->getMessageRawBody($smartdata->source->itemid, $_partID,$folder);
663
				$mailObject->AddStringAttachment($rawHeader.$rawBody, $headers['SUBJECT'].'.eml', 'message/rfc822');
664
				$AltBody = $AltBody."</br>".lang("See Attachments for Content of the Orignial Mail").$sigTextHtml;
665
				$Body = $Body."\r\n".lang("See Attachments for Content of the Orignial Mail").$sigTextPlain;
666
				$isforward = true;
667
			}
668
			else
669
			{
670
				// now get on, and fetch the original mail
671
				$uid = $smartdata->source->itemid;
672
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP Smartreply is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
673
				$this->splitID($smartdata->source->folderid, $account, $folder);
674
675
				$this->mail->reopen($folder);
676
				$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
677
				$bodyBUFFHtml = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
678
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$bodyBUFFHtml);
679 View Code Duplication
				if ($bodyBUFFHtml != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
680
					// may be html
681
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:html (fetched with html_only):'.$bodyBUFFHtml);
682
					$AltBody = $AltBody."</br>".$bodyBUFFHtml.$sigTextHtml;
683
					$isforward = true;
684
				}
685
				// plain text Message part
686
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain, fetch text:');
687
				// if the new part of the message is html, we must preserve it, and handle that the original mail is text/plain
688
				$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
689
				$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
690 View Code Duplication
				if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/plain')) {
691
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain (fetched with never_display):'.$bodyBUFF);
692
					$Body = $Body."\r\n".$bodyBUFF.$sigTextPlain;
693
					$isforward = true;
694
				}
695 View Code Duplication
				if (!empty($bodyBUFF) && empty($bodyBUFFHtml) && !empty($AltBody))
696
				{
697
					$AltBody = $AltBody."</br><pre>".nl2br($bodyBUFF).'</pre>'.$sigTextHtml;
698
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." no html Body found use modified plaintext body for txt/html: ".$AltBody);
699
					$isforward = true;
700
				}
701
				// get all the attachments and add them too.
702
				// start handle Attachments
703
				//												$_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folderName=''
704
				$attachments = $this->mail->getMessageAttachments($uid, null,          null,								true,						false,				 true			, $folder);
705
				$attachmentNames = false;
706
				if (is_array($attachments) && count($attachments)>0)
707
				{
708
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Attachments for BodyCreation of/for MessageID:'.$uid.' found:'.count($attachments));
709
					foreach((array)$attachments as $key => $attachment)
710
					{
711 View Code Duplication
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attachment));
712
						$attachmentNames .= $attachment['name']."\n";
713
						$attachmentData	= $this->mail->getAttachment($uid, $attachment['partID'],0,false,false,$folder);
714
						/*$x =*/ $mailObject->AddStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['mimeType']);
715
						ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' added part with number:'.$x);
716
					}
717
				}
718
			}
719
		} // end forward
720
		// add signature, in case its not already added in forward or reply
721
		if (!$isreply && !$isforward)
722
		{
723
			//error_log(__METHOD__.__LINE__.'adding Signature');
724
			$Body = $Body.$sigTextPlain;
725
			$AltBody = $AltBody.$sigTextHtml;
726
		}
727
		// now set the body
728 View Code Duplication
		if ($AltBody && ($html_body = $mailObject->findBody('html')))
729
		{
730
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$AltBody);
731
			//error_log(__METHOD__.__LINE__.' html:'.$AltBody);
732
			$html_body->setContents($AltBody,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
733
		}
734 View Code Duplication
		if ($Body && ($text_body = $mailObject->findBody('plain')))
735
		{
736
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$Body);
737
			//error_log(__METHOD__.__LINE__.' text:'.$Body);
738
			$text_body->setContents($Body,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
739
		}
740
		//advanced debugging
741
		// Horde SMTP Class uses utf-8 by default.
742
        //ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SendMail: parsed message: ". print_r($message,1));
743
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): MailObject:".array2string($mailObject));
744
745
		// set a higher timeout for big messages
746
		@set_time_limit(120);
747
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.' about to send ....');
748
		// send
749
		$send = true;
750
		try {
751
			$mailObject->Send();
752
		}
753
		catch(Exception $e) {
754
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") The email could not be sent. Last-SMTP-error: ". $e->getMessage());
755
			$send = false;
756
		}
757
758
		if (( $smartdata_task == 'reply' || $smartdata_task == 'forward') && $send == 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...
759
		{
760
			$uid = $smartdata->source->itemid;
761
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' tASK:'.$smartdata_task." FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
762
			$this->splitID($smartdata->source->folderid, $account, $folder);
763
			//error_log(__METHOD__.__LINE__.' Folder:'.$folder.' Uid:'.$uid);
764
			$this->mail->reopen($folder);
765
			// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
766
			// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
767
			if ($this->mail->isDraftFolder($folder) && !$this->mail->isTemplateFolder($folder))
768
			{
769
				$this->mail->deleteMessages(array($uid),$folder);
770
			} else {
771
				$this->mail->flagMessages("answered", array($uid),$folder);
772
				if ($smartdata_task== "forward")
773
				{
774
					$this->mail->flagMessages("forwarded", array($uid),$folder);
775
				}
776
			}
777
		}
778
779
		$asf = ($send ? true:false); // initalize accordingly
780
		if (/*($smartdata->saveinsent==1 || !isset($smartdata->saveinsent)) && */  $send==true && $this->mail->mailPreferences['sendOptions'] != 'send_only')
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...
781
		{
782
			$asf = false;
783
			$sentFolder = $this->mail->getSentFolder();
784
			if ($this->_sentID) {
785
				$folderArray[] = $this->_sentID;
786
			}
787
			else if(isset($sentFolder) && $sentFolder != 'none')
788
			{
789
				$folderArray[] = $sentFolder;
790
			}
791
			// No Sent folder set, try defaults
792
			else
793
			{
794
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP-SendMail: No Sent mailbox set");
795
				// we dont try guessing
796
				$asf = true;
797
			}
798
			if (count($folderArray) > 0) {
799
				foreach((array)$bccMailAddr as $address) {
800
					foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
801
						$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
802
						$mailAddr[] = array($emailAddress, $addressObject->personal);
803
					}
804
				}
805
				$BCCmail='';
806
				if (count($mailAddr)>0) $BCCmail = $mailObject->AddrAppend("Bcc",$mailAddr);
807
				foreach($folderArray as $folderName) {
808 View Code Duplication
					if($this->mail->isSentFolder($folderName)) {
809
						$flags = '\\Seen';
810
					} elseif($this->mail->isDraftFolder($folderName)) {
811
						$flags = '\\Draft';
812
					} else {
813
						$flags = '';
814
					}
815
					$asf = true;
816
					//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.array2string($this->mail->icServer));
817
					$this->mail->openConnection(self::$profileID,false);
818
					if ($this->mail->folderExists($folderName)) {
819
						try
820
						{
821
							$this->mail->appendMessage($folderName,$mailObject->getRaw(), null,
822
									$flags);
823
						}
824
						catch (Api\Exception\WrongUserinput $e)
825
						{
826
							//$asf = false;
827
							ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$mailObject->getHeader('Subject'),$folderName,$e->getMessage()));
828
						}
829
					}
830
					else
831
					{
832
						//$asf = false;
833
						ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$mailObject->getHeader('Subject'),$folderName));
834
					}
835
			        ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Outgoing mail saved in configured 'Sent' folder '".$folderName."': ". (($asf)?"success":"failed"));
836
				}
837
				//$this->mail->closeConnection();
838
			}
839
		}
840
841
		$this->debugLevel=0;
842
843
		if ($send && $asf)
844
		{
845
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> send successfully');
846
			return true;
847
		}
848
		else
849
		{
850
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." returning ".($ClientSideMeetingRequest ? true : 120)." (MailSubmissionFailed)".($ClientSideMeetingRequest ?" is ClientSideMeetingRequest (we ignore the failure)":""));
851
			return ($ClientSideMeetingRequest ? true : 120);   //MAIL Submission failed, see MS-ASCMD
852
		}
853
	}
854
855
	/**
856
	 * For meeting requests (iCal attachments with method='request') we call calendar plugin with iCal to get SyncMeetingRequest object,
857
	 * and do NOT return the attachment itself!
858
	 *
859
	 * @param string $folderid
860
	 * @param string $id
861
	 * @param ContentParameters $contentparameters  parameters of the requested message (truncation, mimesupport etc)
862
	 *  object with attributes foldertype, truncation, rtftruncation, conflict, filtertype, bodypref, deletesasmoves, filtertype, contentclass, mimesupport, conversationmode
863
	 *  bodypref object with attributes: ]truncationsize, allornone, preview
864
	 * @return $messageobject|boolean false on error
865
	 */
866
	public function GetMessage($folderid, $id, $contentparameters)
867
	{
868
		//$this->debugLevel=4;
869
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' ContentParams='.array2string($contentparameters));
870
		$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
871
		$mimesupport = $contentparameters->GetMimeSupport();
872
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() truncsize=$truncsize, mimeSupport=".array2string($mimesupport));
873
		$bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */
874
875
		// fix for z-push bug returning additional bodypreference type 4, even if only 1 is requested and mimessupport = 0
876
		if (!$mimesupport && ($key = array_search('4', $bodypreference))) unset($bodypreference[$key]);
877
878
		//$this->debugLevel=4;
879
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
880
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' TruncSize:'.$truncsize.' Bodypreference: '.array2string($bodypreference));
881
		$account = $_folderName = $xid = null;
882
		$this->splitID($folderid,$account,$_folderName,$xid);
883
		$this->mail->reopen($_folderName);
884
		$messages = $this->fetchMessages($folderid, NULL, $id, true);	// true: return all headers
885
		$headers = $messages[$id];
886
		if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($headers));
887
		// StatMessage should reopen the folder in question, so we dont need folderids in the following statements.
888
		if ($headers)
889
		{
890
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." Message $id with stat ".array2string($headers));
891
			// initialize the object
892
			$output = new SyncMail();
893
			//$rawHeaders = $this->mail->getMessageRawHeader($id);
894
			// simple style
895
			// start AS12 Stuff (bodypreference === false) case = old behaviour
896 View Code Duplication
			if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__. ' for message with ID:'.$id.' with headers:'.array2string($headers));
897
898
			if ($bodypreference === false) {
899
				$bodyStruct = $this->mail->getMessageBody($id, 'only_if_no_text', '', null, true,$_folderName);
900
				$raw_body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
901
				//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
902
				if (stripos($raw_body,'<style')!==false) $body = preg_replace("/<style.*?<\/style>/is", "", $raw_body); // in case there is only a html part
903
				// remove all other html
904
				$body = strip_tags($raw_body);
905
				if(strlen($body) > $truncsize) {
906
					$body = Utils::Utf8_truncate($body, $truncsize);
907
					$output->bodytruncated = 1;
908
				}
909
				else
910
				{
911
					$output->bodytruncated = 0;
912
				}
913
				$output->bodysize = strlen($body);
914
				$output->body = $body;
915
			}
916
			else // style with bodypreferences
917
			{
918
				//Select body type preference
919
				$bpReturnType = 1;//SYNC_BODYPREFERENCE_PLAIN;
920
				if ($bodypreference !== false) {
921
					// bodypreference can occur multiple times
922
					// usually we would use Utils::GetBodyPreferenceBestMatch($bodypreference);
923
					$bpReturnType = Utils::GetBodyPreferenceBestMatch($bodypreference);
924
/*
925
					foreach($bodypreference as $bpv)
926
					{
927
						// we use the last, or MIMEMESSAGE when present
928
						$bpReturnType = $bpv;
929
						if ($bpReturnType==SYNC_BODYPREFERENCE_MIME) break;
930
					}
931
*/
932
				}
933
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." getBodyPreferenceBestMatch: ".array2string($bpReturnType));
934
				// set the protocoll class
935
				$output->asbody = new SyncBaseBody();
936
937
				// return full mime-message without any (charset) conversation directly as stream
938
				if ($bpReturnType==SYNC_BODYPREFERENCE_MIME)
939
				{
940
					//SYNC_BODYPREFERENCE_MIME
941
					$output->asbody->type = SYNC_BODYPREFERENCE_MIME;
942
					$stream = $this->mail->getMessageRawBody($id, '', $_folderName, true);
943
					$fstat = fstat($stream);
944
					fseek($stream, 0, SEEK_SET);
945
					$output->asbody->data = $stream;
946
					$output->asbody->estimatedDataSize = $fstat['size'];
947
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." bodypreference 4=SYNC_BODYPREFERENCE_MIME=full mime message requested, size=$fstat[size]");
948
				}
949
				else
950
				{
951
					// fetch the body (try to gather data only once)
952
					$css ='';
953
					$bodyStruct = $this->mail->getMessageBody($id, 'html_only', '', null, true,$_folderName);
954
					if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only Struct:'.array2string($bodyStruct));
955
					$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
956
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$body);
957
					if ($body != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
958
						// may be html
959
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:html (fetched with html_only)');
960
						$css = $this->mail->getStyles($bodyStruct);
961
						$output->nativebodytype=2;
962
					} else {
963
						// plain text Message
964
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:plain, fetch text (HTML, if no text available)');
965
						$output->nativebodytype=1;
966
						$bodyStruct = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
967
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' plain text Struct:'.array2string($bodyStruct));
968
						$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
969
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' never display html(plain text only):'.$body);
970
					}
971
					// whatever format decode (using the correct encoding)
972 View Code Duplication
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__."MIME Body".' Type:'.($output->nativebodytype==2?' html ':' plain ').$body);
973
					//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
974
					// prepare plaintextbody
975
					$plainBody='';
976
					if ($output->nativebodytype == 2)
977
					{
978
						$bodyStructplain = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
979
						if(isset($bodyStructplain[0])&&isset($bodyStructplain[0]['error'])&&$bodyStructplain[0]['error']==1)
980
						{
981
							$plainBody = Api\Mail\Html::convertHTMLToText($body); // always display with preserved HTML
982
						}
983
						else
984
						{
985
							$plainBody = $this->mail->getdisplayableBody($this->mail,$bodyStructplain,false,false);
986
						}
987
					}
988
					//if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".$body);
989
					$plainBody = preg_replace("/<style.*?<\/style>/is", "", (strlen($plainBody)?$plainBody:$body));
990
					// remove all other html
991
					$plainBody = preg_replace("/<br.*>/is","\r\n",$plainBody);
992
					$plainBody = strip_tags($plainBody);
993
					if ($this->debugLevel>3 && $output->nativebodytype==1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Plain Text:'.$plainBody);
994
					//$body = str_replace("\n","\r\n", str_replace("\r","",$body)); // do we need that?
995
996
					if ($bpReturnType==2) //SYNC_BODYPREFERENCE_HTML
997
					{
998
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "HTML Body with requested pref 2");
999
						// Send HTML if requested and native type was html
1000
						$output->asbody->type = 2;
1001
						$htmlbody = '<html>'.
1002
							'<head>'.
1003
							'<meta name="Generator" content="Z-Push">'.
1004
							'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'.
1005
							$css.
1006
							'</head>'.
1007
							'<body>';
1008
						if ($output->nativebodytype==2)
1009
						{
1010
							if ($css) Api\Mail\Html::replaceTagsCompletley($body,'style');
1011
							// as we fetch html, and body is HTML, we may not need to handle this
1012
							$htmlbody .= $body;
1013
						}
1014
						else
1015
						{
1016
							// html requested but got only plaintext, so fake html
1017
							$htmlbody .= str_replace("\n","<BR>",str_replace("\r","<BR>", str_replace("\r\n","<BR>",$plainBody)));
1018
						}
1019
						$htmlbody .= '</body>'.
1020
								'</html>';
1021
1022 View Code Duplication
						if(isset($truncsize) && strlen($htmlbody) > $truncsize)
1023
						{
1024
							$htmlbody = Utils::Utf8_truncate($htmlbody,$truncsize);
1025
							$output->asbody->truncated = 1;
1026
						}
1027
						// output->nativebodytype is used as marker that the original message was of type ... but is now converted to, as type 2 is requested.
1028
						$output->nativebodytype = 2;
1029
						$output->asbody->data = StringStreamWrapper::Open($htmlbody);
1030
						$output->asbody->estimatedDataSize = strlen($htmlbody);
1031
					}
1032
					else
1033
					{
1034
						// Send Plaintext as Fallback or if original body is plainttext
1035
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "Plaintext Body:".$plainBody);
1036
						/* we use plainBody (set above) instead
1037
						$bodyStruct = $this->mail->getMessageBody($id,'only_if_no_text'); //'never_display');
1038
						$plain = $this->mail->getdisplayableBody($this->mail,$bodyStruct);
1039
						$plain = html_entity_decode($plain,ENT_QUOTES,$this->mail->detect_encoding($plain));
1040
						$plain = strip_tags($plain);
1041
						//$plain = str_replace("\n","\r\n",str_replace("\r","",$plain));
1042
						*/
1043
						$output->asbody->type = 1;
1044
						$output->nativebodytype = 1;
1045 View Code Duplication
						if(isset($truncsize) &&
1046
							strlen($plainBody) > $truncsize)
1047
						{
1048
							$plainBody = Utils::Utf8_truncate($plainBody, $truncsize);
1049
							$output->asbody->truncated = 1;
1050
						}
1051
						$output->asbody->data = StringStreamWrapper::Open((string)$plainBody !== '' ? $plainBody : ' ');
1052
						$output->asbody->estimatedDataSize = strlen($plainBody);
1053
					}
1054
					// In case we have nothing for the body, send at least a blank...
1055
					// dw2412 but only in case the body is not rtf!
1056
					if ($output->asbody->type != 3 && !isset($output->asbody->data))
1057
					{
1058
						$output->asbody->data = StringStreamWrapper::Open(" ");
1059
						$output->asbody->estimatedDataSize = 1;
1060
					}
1061
				}
1062
			}
1063
			// end AS12 Stuff
1064
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Header info:'.$headers['subject'].' from:'.$headers['date']);
1065
			$output->read = $headers["flags"];
1066
1067
			$output->flag = new SyncMailFlags();
1068
			if ($headers['flagged'] == 1)
1069
			{
1070
				$output->flag->flagstatus = 2;
1071
				//$output->flag->flagtype = "Flag for Follow up";
1072
			} else {
1073
				$output->flag->flagstatus = 0;
1074
			}
1075
			if ($headers['answered'])
1076
			{
1077
				$output->lastverexecuted = AS_REPLYTOSENDER;
1078
			}
1079
			elseif ($headers['forwarded'])
1080
			{
1081
				$output->lastverexecuted = AS_FORWARD;
1082
			}
1083
			$output->subject = $headers['subject'];
1084
			$output->importance = $headers['priority'] > 3 ? 0 :
1085
				($headers['priority'] < 3 ? 2 : 1) ;
1086
			$output->datereceived = $this->mail->_strtotime($headers['date'],'ts',true);
1087
			$output->to = $headers['to_address'];
1088
			if ($headers['to']) $output->displayto = $headers['to_address']; //$headers['FETCHED_HEADER']['to_name']
1089
			$output->from = $headers['sender_address'];
1090
			if (isset($headers['cc_addresses']) && $headers['cc_addresses']) $output->cc = $headers['cc_addresses'];
1091
			if (isset($headers['reply_to_address']) && $headers['reply_to_address']) $output->reply_to = $headers['reply_to_address'];
1092
1093
			$output->messageclass = "IPM.Note";
1094
			if (stripos($headers['mimetype'],'multipart')!== false &&
1095
				stripos($headers['mimetype'],'signed')!== false)
1096
			{
1097
				$output->messageclass = "IPM.Note.SMIME.MultipartSigned";
1098
			}
1099
			if (Request::GetProtocolVersion() >= 12.0) {
1100
				$output->contentclass = "urn:content-classes:message";
1101
			}
1102
1103
			// start handle Attachments (include text/calendar multipart alternative)
1104
			$attachments = $this->mail->getMessageAttachments($id, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true, true, $_folderName);
1105
			// Attachments should not needed for MIME messages, so skip this part if bpReturnType==4
1106
			if (/*$bpReturnType != SYNC_BODYPREFERENCE_MIME &&*/ is_array($attachments) && count($attachments)>0)
1107
			{
1108
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Attachments for MessageID:'.$id.' found:'.count($attachments));
1109
				//error_log(__METHOD__.__LINE__.array2string($attachments));
1110
				foreach ($attachments as $key => $attach)
1111
				{
1112 View Code Duplication
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attach));
1113
1114
					// pass meeting requests to calendar plugin
1115
					if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1116
						isset($GLOBALS['egw_info']['user']['apps']['calendar']) &&
1117
						($attachment = $this->mail->getAttachment($id, $attach['partID'],0,false,false,$_folderName)) &&
1118
						($output->meetingrequest = calendar_zpush::meetingRequest($attachment['attachment'])))
1119
					{
1120
						//overwrite the globalobjId from calendar object, as: if you delete the mail, that is
1121
						//the meeting-request its using the globalobjid as reference and deletes both:
1122
						//mail AND meeting. we dont want this. accepting meeting requests with the mobile does nothing
1123
						$output->meetingrequest->globalobjid = activesync_backend::uid2globalObjId($id);
1124
						$output->messageclass = "IPM.Schedule.Meeting.Request";
1125
						//$output->messageclass = "IPM.Schedule.Meeting";
1126
						unset($attachment);
1127
						continue;	// do NOT add attachment as attachment
1128
					}
1129
					if (Request::GetProtocolVersion() >= 12.0) {
1130
						$attachment = new SyncBaseAttachment();
1131
						if (!isset($output->asattachments) || !is_array($output->asattachments))
1132
							$output->asattachments = array();
1133
						$attachment->estimatedDataSize = $attach['size'];
1134
						$attachment->method = 1;
1135
						$attachment->filereference = $folderid . ":" . $id . ":" . $attach['partID'];
1136
					} else {
1137
						$attachment = new SyncAttachment();
1138
						if (!isset($output->attachments) || !is_array($output->attachments))
1139
							$output->attachments = array();
1140
						$attachment->attsize = $attach['size'];
1141
						$attachment->attmethod = 1;
1142
						$attachment->attname = $folderid . ":" . $id . ":" . $attach['partID'];//$key;
1143
					}
1144
1145
					$attachment->displayname = $attach['name'];
1146
					//error_log(__METHOD__.__LINE__.'->'.$folderid . ":" . $id . ":" . $attach['partID']);
1147
1148
					$attachment->attoid = "";//isset($part->headers['content-id']) ? trim($part->headers['content-id']) : "";
1149
					//$attachment->isinline=0; // if not inline, do not use isinline
1150
					if (!empty($attach['cid']) && $attach['cid'] <> 'NIL' )
1151
					{
1152
						if ($bpReturnType != 4 && $attach['disposition'] == 'inline')
1153
						{
1154
							$attachment->isinline = true;
1155
						}
1156
						if (Request::GetProtocolVersion() >= 12.0) {
1157
							$attachment->method=1;
1158
							$attachment->contentid= str_replace(array("<",">"), "",$attach['cid']);
1159
						} else {
1160
							$attachment->attmethod=6;
1161
							$attachment->attoid = str_replace(array("<",">"), "",$attach['cid']);
1162
						}
1163
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-id']."'  ".$attachment->contentid);
1164
						$attachment->contenttype = trim($attach['mimeType']);
1165
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-type']."'  ".$attachment->contentid);
1166
					}
1167
					if (Request::GetProtocolVersion() >= 12.0) {
1168
						array_push($output->asattachments, $attachment);
1169
					} else {
1170
						array_push($output->attachments, $attachment);
1171
					}
1172
					unset($attachment);
1173
				}
1174
			}
1175
			//$this->debugLevel=0;
1176
			// end handle Attachments
1177
			unset($attachments);
1178
1179
            // Language Code Page ID: http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx
1180
            $output->internetcpid = INTERNET_CPID_UTF8;
1181
1182 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($output));
1183
//$this->debugLevel=0;
1184
			return $output;
1185
		}
1186
//$this->debugLevel=0;
1187
		return false;
1188
	}
1189
1190
	/**
1191
	 * Process response to meeting request
1192
	 *
1193
	 * mail plugin only extracts the iCal attachment and let's calendar plugin deal with adding it
1194
	 *
1195
	 * @see BackendDiff::MeetingResponse()
1196
	 * @param string $folderid folder of meeting request mail
1197
	 * @param int|string $requestid uid of mail with meeting request
1198
	 * @param int $response 1=accepted, 2=tentative, 3=decline
1199
	 * @return int|boolean id of calendar item, false on error
1200
	 */
1201
	function MeetingResponse($folderid, $requestid, $response)
1202
	{
1203
		if (!class_exists('calendar_zpush'))
1204
		{
1205
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(...) no EGroupware calendar installed!");
1206
			return null;
1207
		}
1208
		if (!($stat = $this->StatMessage($folderid, $requestid)))
1209
		{
1210
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning FALSE (can NOT stat message)");
1211
			return false;
1212
		}
1213
		$ret = false;
1214
		foreach($this->mail->getMessageAttachments($requestid, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true) as $key => $attach)
1215
		{
1216
			if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1217
				($attachment = $this->mail->getAttachment($requestid, $attach['partID'],0,false)))
1218
			{
1219
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) iCal found, calling now backend->MeetingResponse('$attachment[attachment]')");
1220
1221
				// calling backend again with iCal attachment, to let calendar add the event
1222
				$ret = $this->backend->MeetingResponse($attachment['attachment'],
1223
					$this->backend->createID('calendar',$GLOBALS['egw_info']['user']['account_id']),
1224
					$response);
1225
				break;
1226
			}
1227
		}
1228
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning ".array2string($ret));
1229
		return $ret;
1230
	}
1231
1232
	/**
1233
	 * GetAttachmentData
1234
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1235
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1236
	 * encode any information you need to find the attachment in that 'attname' property.
1237
	 *
1238
     * @param string $fid - id
1239
     * @param string $attname - should contain (folder)id
1240
	 * @return SyncItemOperationsAttachment-object
1241
	 */
1242
	function GetAttachmentData($fid,$attname) {
1243
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1244
		return $this->_GetAttachmentData($fid,$attname);
1245
	}
1246
1247
	/**
1248
	 * ItemOperationsGetAttachmentData
1249
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1250
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1251
	 * encode any information you need to find the attachment in that 'attname' property.
1252
	 *
1253
     * @param string $fid - id
1254
     * @param string $attname - should contain (folder)id
1255
	 * @return SyncItemOperationsAttachment-object
1256
	 */
1257
	function ItemOperationsGetAttachmentData($fid,$attname) {
1258
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1259
		return $this->_GetAttachmentData($fid,$attname);
1260
	}
1261
1262
	/**
1263
	 * _GetAttachmentData implements
1264
	 * -ItemOperationsGetAttachmentData
1265
	 * -GetAttachmentData
1266
	 *
1267
     * @param string $fid - id
1268
     * @param string $attname - should contain (folder)id
1269
	 * @return SyncItemOperationsAttachment-object
1270
	 */
1271
	private function _GetAttachmentData($fid,$attname)
1272
	{
1273
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')".function_backtrace());
1274
		//error_log(__METHOD__.__LINE__." Fid: $fid (attname: '$attname')");
1275
		list($folderid, $id, $part) = explode(":", $attname);
1276
1277
		$this->splitID($folderid, $account, $folder);
1278
1279
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1280
1281
		$this->mail->reopen($folder);
1282
		$attachment = $this->mail->getAttachment($id,$part,0,false,true,$folder);
1283
		$SIOattachment = new SyncItemOperationsAttachment();
1284
		fseek($attachment['attachment'], 0, SEEK_SET);	// z-push requires stream seeked to start
1285
		$SIOattachment->data = $attachment['attachment'];
1286
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname') Data:".$attachment['attachment']);
1287
		if (isset($attachment['type']) )
1288
			$SIOattachment->contenttype = $attachment['type'];
1289
1290
		unset($attachment);
1291
1292
		return $SIOattachment;
1293
	}
1294
1295
	/**
1296
	 * StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
1297
	 *
1298
	 * 'id'	 => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
1299
	 * 'flags'	 => simply '0' for unread, '1' for read
1300
	 * 'mod'	=> modification signature. As soon as this signature changes, the item is assumed to be completely
1301
	 *			 changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
1302
	 *			 time for this field, which will change as soon as the contents have changed.
1303
	 *
1304
	 * @param string $folderid
1305
	 * @param int $id id (uid) of message
1306
	 * @return array
1307
	 */
1308
	public function StatMessage($folderid, $id)
1309
	{
1310
		$messages = $this->fetchMessages($folderid, NULL, $id);
1311
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid','$id') returning ".array2string($messages[$id]));
1312
		return $messages[$id];
1313
	}
1314
1315
	/**
1316
	 * Called when a message has been changed on the mobile.
1317
	 * Added support for FollowUp flag
1318
	 *
1319
	 * @param string              $folderid            id of the folder
1320
	 * @param string              $id                  id of the message
1321
	 * @param SyncXXX             $message             the SyncObject containing a message
1322
	 * @param ContentParameters   $contentParameters
1323
	 *
1324
	 * @access public
1325
	 * @return array                        same return value as StatMessage()
1326
	 * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1327
	 */
1328
	function ChangeMessage($folderid, $id, $message, $contentParameters)
1329
	{
1330
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." $folderid, $id,".array2string($message).",".array2string($contentParameters));
1331
		//unset($folderid, $id, $message, $contentParameters);
1332
		$account = $folder = null;
1333
		$this->splitID($folderid, $account, $folder);
1334
		if (isset($message->flag)) {
1335
			if (isset($message->flag->flagstatus) && $message->flag->flagstatus == 2) {
1336
				$rv = $this->mail->flagMessages((($message->flag->flagstatus == 2) ? "flagged" : "unflagged"), $id,$folder);
1337
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . (($message->flag->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
1338
			} else {
1339
				$rv = $this->mail->flagMessages("unflagged", $id,$folder);
1340
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . "unflagged" . "-->". $rv);
1341
			}
1342
		}
1343
		return $this->StatMessage($folderid, $id);
1344
	}
1345
1346
	/**
1347
	 * This function is called when the user moves an item on the PDA. You should do whatever is needed
1348
	 * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
1349
	 * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
1350
	 * at all on the source folder, and the destination folder will show the new message
1351
	 *
1352
	 * @param string              $folderid            id of the source folder
1353
	 * @param string              $id                  id of the message
1354
	 * @param string              $newfolderid         id of the destination folder
1355
	 * @param ContentParameters   $contentParameters
1356
	 *
1357
	 * @return boolean                      status of the operation
1358
	 * @throws StatusException              could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
1359
	 */
1360
	public function MoveMessage($folderid, $id, $newfolderid, $contentParameters)
1361
	{
1362
		unset($contentParameters);	// not used, but required by function signature
1363
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (sfid: '$folderid'  id: '$id'  dfid: '$newfolderid' )");
1364
		$account = $srcFolder = $destFolder = null;
1365
		$this->splitID($folderid, $account, $srcFolder);
1366
		$this->splitID($newfolderid, $account, $destFolder);
1367
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (SourceFolder: '$srcFolder'  id: '$id'  DestFolder: '$destFolder' )");
1368
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1369
		$this->mail->reopen($destFolder);
1370
		$status = $this->mail->getFolderStatus($destFolder);
1371
		$uidNext = $status['uidnext'];
1372
		$this->mail->reopen($srcFolder);
1373
1374
		// move message
1375
		$rv = $this->mail->moveMessages($destFolder,(array)$id,true,$srcFolder,true);
1376
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.": New Status of $destFolder :".array2string($status).", ReturnValOf moveMessage".array2string($rv)); // this may be true, so try using the nextUID value by examine
1377
		// return the new id "as string"
1378
		return ($rv===true ? $uidNext : $rv[$id]) . "";
1379
	}
1380
1381
	/**
1382
	 *  Get all messages of a folder with optional cutoffdate
1383
	 *
1384
	 *  @param int $cutoffdate =null timestamp with cutoffdate, default 12 weeks
1385
	 */
1386
	public function GetMessageList($folderid, $cutoffdate=NULL)
1387
	{
1388
		if ($cutoffdate > 0)
1389
		{
1390
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.' for Folder:'.$folderid.' SINCE:'.$cutoffdate.'/'.date("d-M-Y", $cutoffdate));
1391
		}
1392
		else
1393
		{
1394
			$maximumSyncRangeInDays = self::PAST_LIMIT; // corresponds to our default value
1395 View Code Duplication
			if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange']))
1396
			{
1397
				$maximumSyncRangeInDays = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange'];
1398
			}
1399
			$cutoffdate = (is_numeric($maximumSyncRangeInDays) ? Api\DateTime::to('now','ts')-(3600*24*$maximumSyncRangeInDays):null);
1400
			if (is_numeric($maximumSyncRangeInDays)) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' Client set no truncationdate. Using '.$maximumSyncRangeInDays.' days.'.date("d-M-Y", $cutoffdate));
1401
		}
1402
		return $this->fetchMessages($folderid, $cutoffdate);
1403
	}
1404
1405
	/**
1406
	 * Fetch headers for one or all mail of a folder using optional cutoffdate
1407
	 *
1408
	 * Headers of last fetchMessage call of complate folder are cached in static $headers,
1409
	 * to allow to use them without fetching them again.
1410
	 * Next call clears cache
1411
	 *
1412
	 * @param int $folderid
1413
	 * @param int $cutoffdate timestamp with cutoffdate
1414
	 * @param string $_id =null uid of single message to fetch
1415
	 * @param boolean $return_all_headers =false true: additinal contain all headers eg. "subject"
1416
	 * @return array uid => array StatMessage($folderid, $_id)
1417
	 */
1418
	private function fetchMessages($folderid, $cutoffdate=NULL, $_id=NULL, $return_all_headers=false)
1419
	{
1420
		static $headers = array();
1421
1422
		if ($this->debugLevel>1) $gstarttime = microtime (true);
1423
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
1424
		$rv_messages = array();
1425
		// if the message is still available within the class, we use it instead of fetching it again
1426
		if ($_id && isset($headers[$_id]) && is_array($headers[$_id]))
1 ignored issue
show
Bug Best Practice introduced by
The expression $_id 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...
1427
		{
1428
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." the message ".$_id[0]." is still available within the class, we use it instead of fetching it again");
1429
			$rv_messages = array('header'=>array($headers[$_id]));
1430
		}
1431
		else
1432
		{
1433
			$headers = array();	// clear cache to not use too much memory
1434
1435
			if ($this->debugLevel>1) $starttime = microtime (true);
1436
			$this->_connect($this->account);
1437 View Code Duplication
			if ($this->debugLevel>1)
1438
			{
1439
				$endtime = microtime(true) - $starttime;
1440
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " connect took : ".$endtime.' for account:'.$this->account);
1441
			}
1442
			$messagelist = $_filter = array();
1443
			// if not connected, any further action must fail
1444
			if (!empty($cutoffdate)) $_filter = array('status'=>array('UNDELETED'),'range'=>"SINCE",'date'=> date("d-M-Y", $cutoffdate));
1445
			if ($this->debugLevel>1) $starttime = microtime (true);
1446
			$account = $_folderName = $id = null;
1447
			$this->splitID($folderid,$account,$_folderName,$id);
1448 View Code Duplication
			if ($this->debugLevel>1)
1449
			{
1450
				$endtime = microtime(true) - $starttime;
1451
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " splitID took : ".$endtime.' for FolderID:'.$folderid);
1452
			}
1453
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1454
			if ($this->debugLevel>1) $starttime = microtime (true);
1455
			$_numberOfMessages = (empty($cutoffdate)?250:99999);
1456
			$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages, $_sort=0, $_reverse=false, $_filter, $_id);
1457 View Code Duplication
			if ($this->debugLevel>1)
1458
			{
1459
				$endtime = microtime(true) - $starttime;
1460
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " getHeaders call took : ".$endtime.' for FolderID:'.$_folderName);
1461
			}
1462
		}
1463
		if ($_id == NULL && $this->debugLevel>1)  ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." found :". count($rv_messages['header']));
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $_id of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1464
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Result:'.array2string($rv_messages));
1465
		$messagelist = array();
1466
		if (!isset($rv_messages['header'])||empty($rv_messages['header'])) return $messagelist;
1467
		//if ($_returnModHash) $messageFolderHash = array();
1468
		foreach ((array)$rv_messages['header'] as $k => $vars)
1469
		{
1470 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to process:'.$vars['uid'].' Subject:'.$vars['subject']);
1471
			$headers[$vars['uid']] = $vars;
1472 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' MailID:'.$k.'->'.array2string($vars));
1473
			if (!empty($vars['deleted'])) continue; // cut of deleted messages
1474
			if ($cutoffdate && $vars['date'] < $cutoffdate) continue; // message is out of range for cutoffdate, ignore it
1 ignored issue
show
Bug Best Practice introduced by
The expression $cutoffdate of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1475 View Code Duplication
			if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to report:'.$vars['uid'].' Subject:'.$vars['subject']);
1476
			$mess = $return_all_headers ? $vars : array();
1477
			$mess["mod"] = self::doFlagsMod($vars).$vars['date'];
1478
			$mess["id"] = $vars['uid'];
1479
			// 'seen' aka 'read' is the only flag we want to know about
1480
			$mess["flags"] = 0;
1481
			// outlook supports additional flags, set them to 0
1482
			if($vars["seen"]) $mess["flags"] = 1;
1483 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($mess));
1484
			$messagelist[$vars['uid']] = $mess;
1485
			unset($mess);
1486
		}
1487
		if ($this->debugLevel>1)
1488
		{
1489
			$endtime = microtime(true) - $gstarttime;
1490
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " total time used : ".$endtime.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1491
		}
1492
		return $messagelist;
1493
	}
1494
1495
	/**
1496
	 * Prepare headeinfo on a message to return some standardized string to tell which flags are set for a message
1497
	 *
1498
	 * AS currently only supports flagged, answered/replied and forwarded flags.
1499
	 * Seen/read is in under flags key of stat!
1500
	 *
1501
	 * @param array $headerFlags  - array to process, a full return array from getHeaders
1502
	 * @link https://sourceforge.net/p/zimbrabackend/code/HEAD/tree/zimbra-backend/branches/z-push-2/zimbra.php#l11652
1503
	 * @return string string of a representation of supported flags
1504
	 */
1505
	static function doFlagsMod($headerFlags)
1506
	{
1507
		$flags = 'nnn';
1508
		if ($headerFlags['flagged']) $flags[0] = 'f';
1509
		if ($headerFlags['answered']) $flags[1] = 'a';
1510
		if ($headerFlags['forwarded']) $flags[2] = 'f';
1511
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.'('.array2string($headerFlags).') returning '.array2string($flags));
1512
		return $flags;
1513
	}
1514
1515
	/**
1516
	 * Search mailbox for a given pattern
1517
	 *
1518
	 * @param object $_searchquery holds information specifying the query with GetDataArray it holds
1519
	 * 		[searchname] => MAILBOX
1520
	 * 		[searchfolderid] => 101000000000
1521
	 * 		[searchfreetext] => somesearchtexgt
1522
	 * 		[searchdatereceivedgreater] => 1
1523
	 * 		[searchvaluegreater] => 2015-07-06T22:00:00.000Z
1524
	 * 		[searchdatereceivedless] => 1
1525
	 * 		[searchvalueless] => 2015-07-14T15:11:00.000Z
1526
	 * 		[searchrebuildresults] => 1
1527
	 * 		[searchrange] => 0-99
1528
	 * 		[bodypref] => Array([1] => BodyPreference Object([unsetdata:protected] => Array([truncationsize] => [allornone] => [preview] => )[SO_internalid:StateObject:private] => [data:protected] =>
1529
	 * 			 Array([truncationsize] => 2147483647)[changed:protected] => 1))
1530
	 * 				[mimesupport] => 2)
1531
	 * @return array(["range"] = $_searchquery->GetSearchRange(), ['searchtotal'] = count of results,
1532
	 *			array("class" => "Email",
1533
	 *					"longid" => folderid.':'.uid',
1534
	 *					"folderid"	=> folderid,
1535
	 *					), ....
1536
	 *		)
1537
	 */
1538
	public function getSearchResultsMailbox($_searchquery)
1539
	{
1540
		//$this->debugLevel=1;
1541
		$searchquery=$_searchquery->GetDataArray();
1542
		if (!is_array($searchquery)) return array();
1543 View Code Duplication
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($searchquery));
1544
1545
		if (isset($searchquery['searchrebuildresults'])) {
1546
			$rebuildresults = $searchquery['searchrebuildresults'];
1547
		} else {
1548
			$rebuildresults = false;
1549
		}
1550
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'RebuildResults ['.$rebuildresults.']' );
1551
1552
		if (isset($searchquery['deeptraversal'])) {
1553
			$deeptraversal = $searchquery['deeptraversal'];
1554
		} else {
1555
			$deeptraversal = false;
1556
		}
1557
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'DeepTraversal ['.$deeptraversal.']' );
1558
1559
		if (isset($searchquery['searchrange'])) {
1560
			$range = explode("-",$_searchquery->GetSearchRange());
1561
			$start =$range[0] + 1;
1562
			$limit = $range[1] - $range[0] + 1;
1563
		} else {
1564
			$range = false;
1565
		}
1566
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'Range ['.print_r($range, true).']' );
1567
1568
		//foreach($searchquery['query'] as $k => $value) {
1569
		//	$query = $value;
1570
		//}
1571
		if (isset($searchquery['searchfolderid']))
1572
		{
1573
			$folderid = $searchquery['searchfolderid'];
1574
		}
1575
/*
1576
		// other types may be possible - we support quicksearch first (freeText in subject and from (or TO in Sent Folder))
1577
		if (is_null(Mail::$supportsORinQuery) || !isset(Mail::$supportsORinQuery[self::$profileID]))
1578
		{
1579
			Mail::$supportsORinQuery = Api\Cache::getCache(Api\Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*10);
1580
			if (!isset(Mail::$supportsORinQuery[self::$profileID])) Mail::$supportsORinQuery[self::$profileID]=true;
1581
		}
1582
*/
1583
		if (isset($searchquery['searchfreetext']))
1584
		{
1585
			$searchText = $searchquery['searchfreetext'];
1586
		}
1587
		if (!$folderid)
1588
		{
1589
			$_folderName = ($this->mail->sessionData['mailbox']?$this->mail->sessionData['mailbox']:'INBOX');
1590
			$folderid = $this->createID($account=0,$_folderName);
1591
		}
1592
		$rv = $this->splitID($folderid,$account,$_folderName,$id);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $rv is correct as $this->splitID($folderid...unt, $_folderName, $id) (which targets mail_zpush::splitID()) seems to always return null.

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

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

}

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

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

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

Loading history...
1593
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ProfileID:'.self::$profileID.' FolderID:'.$folderid.' Foldername:'.$_folderName);
1594
		$this->_connect($account);
1595
		// this should not be needed ???
1596
		Mail::$supportsORinQuery[self::$profileID]=true; // trigger quicksearch (if possible)
1597
		$_filter = array('type'=> (Mail::$supportsORinQuery[self::$profileID]?'quick':'subject'),
1598
						 'string'=> $searchText,
1599
						 'status'=>'any'
1600
						);
1601
1602
		if (isset($searchquery['searchdatereceivedgreater']) || isset($searchquery['searchdatereceivedless']))
1603
		{
1604
		/*
1605
		 *	We respect only the DATEPART of the RANGE specified
1606
		 * 		[searchdatereceivedgreater] => 1
1607
		 * 		[searchvaluegreater] => 2015-07-06T22:00:00.000Z , SINCE
1608
		 * 		[searchdatereceivedless] => 1
1609
		 * 		[searchvalueless] => 2015-07-14T15:11:00.000Z , BEFORE
1610
		 */
1611
			$_filter['range'] = "BETWEEN";
1612
			list($sincedate,$crap) = explode('T',$searchquery['searchvaluegreater']);
0 ignored issues
show
Unused Code introduced by
The assignment to $crap is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1613
			list($beforedate,$crap) = explode('T',$searchquery['searchvalueless']);
0 ignored issues
show
Unused Code introduced by
The assignment to $crap is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1614
			$_filter['before'] = date("d-M-Y", Api\DateTime::to($beforedate,'ts'));
1615
			$_filter['since'] = date("d-M-Y", Api\DateTime::to($sincedate,'ts'));
1616
		}
1617
		//$_filter[] = array('type'=>"SINCE",'string'=> date("d-M-Y", $cutoffdate));
1618
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter));
1619
		$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=($range?$start:1), $_numberOfMessages=($limit?$limit:9999999), $_sort=0, $_reverse=false, $_filter, $_id=NULL);
1620
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($rv_messages));
1621
		$list=array();
1622
1623
		$cnt = count($rv_messages['header']);
1624
		//$list['status'] = 1;
1625
		$list['searchtotal'] = $cnt;
1626
		$list["range"] = $_searchquery->GetSearchRange();
1627
		foreach((array)$rv_messages['header'] as $i => $vars)
1628
		{
1629
			$list[] = array(
1630
				"class" => "Email",
1631
				"longid" => $folderid.':'.$vars['uid'],
1632
				"folderid"	=> $folderid,
1633
			);
1634
		}
1635
		//error_log(__METHOD__.__LINE__.array2string($list));
1636
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($list));
1637
		return $list;
1638
	}
1639
1640
	/**
1641
	 * Get ID of parent Folder or '0' for folders in root
1642
	 *
1643
	 * @param int $account
1644
	 * @param string $folder
1645
	 * @return string
1646
	 */
1647
	private function getParentID($account,$folder)
1648
	{
1649
		$this->_connect($account);
1650
		if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
1651
1652
		$mailFolder = $this->folders[$folder];
1653
		if (!isset($mailFolder)) return false;
1654
		$delimiter = (isset($mailFolder->delimiter)?$mailFolder->delimiter:$this->mail->getHierarchyDelimiter());
1655
		$parent = explode($delimiter,$folder);
1656
		array_pop($parent);
1657
		$parent = implode($delimiter,$parent);
1658
1659
		$id = $parent && $this->folders[$parent] ? $this->createID($account, $parent) : '0';
1660
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$folder') --> parent=$parent --> $id");
1661
		return $id;
1662
	}
1663
1664
	/**
1665
	 * Get Information about a folder
1666
	 *
1667
	 * @param string $id
1668
	 * @return SyncFolder|boolean false on error
1669
	 */
1670
	public function GetFolder($id)
1671
	{
1672
		static $last_id = null;
1673
		static $folderObj = null;
1674
		if (isset($last_id) && $last_id === $id) return $folderObj;
1675
1676
		try {
1677
			$account = $folder = null;
1678
			$this->splitID($id, $account, $folder);
1679
		}
1680
		catch(Exception $e) {
1681
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' failed for '.$e->getMessage());
1682
			return $folderObj=false;
1683
		}
1684
		$this->_connect($account);
1685
		if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
1686
1687
		$mailFolder = $this->folders[$folder];
1688
		if (!isset($mailFolder)) return $folderObj=false;
1689
1690
		$folderObj = new SyncFolder();
1691
		$folderObj->serverid = $id;
1692
		$folderObj->parentid = $this->getParentID($account,$folder);
1693
		$folderObj->displayname = $mailFolder->shortDisplayName;
1694
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." ID: $id, Account:$account, Folder:$folder");
1695
		// get folder-type
1696
		foreach($this->folders as $inbox => $mailFolder) break;
1697
		if ($folder == $inbox)
0 ignored issues
show
Bug introduced by
The variable $inbox seems to be defined by a foreach iteration on line 1696. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
1698
		{
1699
			$folderObj->type = SYNC_FOLDER_TYPE_INBOX;
1700
		}
1701
		elseif($this->mail->isDraftFolder($folder, false, true))
1702
		{
1703
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isDraft');
1704
			$folderObj->type = SYNC_FOLDER_TYPE_DRAFTS;
1705
			$folderObj->parentid = 0; // required by devices
1706
		}
1707
		elseif($this->mail->isTrashFolder($folder, false, true))
1708
		{
1709
			$folderObj->type = SYNC_FOLDER_TYPE_WASTEBASKET;
1710
			$this->_wasteID = $folder;
1711
			//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
1712
			$folderObj->parentid = 0; // required by devices
1713
		}
1714
		elseif($this->mail->isSentFolder($folder, false, true))
1715
		{
1716
			$folderObj->type = SYNC_FOLDER_TYPE_SENTMAIL;
1717
			$folderObj->parentid = 0; // required by devices
1718
			$this->_sentID = $folder;
1719
			//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
1720
		}
1721
		elseif($this->mail->isOutbox($folder, false, true))
1722
		{
1723
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isOutbox');
1724
			$folderObj->type = SYNC_FOLDER_TYPE_OUTBOX;
1725
			$folderObj->parentid = 0; // required by devices
1726
		}
1727
		else
1728
		{
1729
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isOther Folder'.$folder);
1730
			$folderObj->type = SYNC_FOLDER_TYPE_USER_MAIL;
1731
		}
1732
1733
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($id) --> $folder --> type=$folderObj->type, parentID=$folderObj->parentid, displayname=$folderObj->displayname");
1734
		return $folderObj;
1735
	}
1736
1737
	/**
1738
	 * Return folder stats. This means you must return an associative array with the
1739
	 * following properties:
1740
	 *
1741
	 * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
1742
	 *		 How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
1743
	 * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
1744
	 * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
1745
	 *		  the folder has not changed. In practice this means that 'mod' can be equal to the folder name
1746
	 *		  as this is the only thing that ever changes in folders. (the type is normally constant)
1747
	 *
1748
	 * @return array with values for keys 'id', 'mod' and 'parent'
1749
	 */
1750
	public function StatFolder($id)
1751
	{
1752
		$folder = $this->GetFolder($id);
1753
1754
		$stat = array(
1755
			'id'     => $id,
1756
			'mod'    => $folder->displayname,
1757
			'parent' => $folder->parentid,
1758
		);
1759
1760
		return $stat;
1761
	}
1762
1763
1764
	/**
1765
	 * Return a changes array
1766
	 *
1767
	 * if changes occurr default diff engine computes the actual changes
1768
	 *
1769
	 * @param string $folderid
1770
	 * @param string &$syncstate on call old syncstate, on return new syncstate
1771
	 * @return array|boolean false if $folderid not found, array() if no changes or array(array("type" => "fakeChange"))
1772
	 */
1773
	function AlterPingChanges($folderid, &$syncstate)
1774
	{
1775
		$account = $folder = null;
1776
		$this->splitID($folderid, $account, $folder);
1777
		if (is_numeric($account)) $type = 'mail';
1778
1779
		if ($type != 'mail') return false;
1780
1781
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1782
1783
        $this->mail->reopen($folder);
1784
1785
		if (!($status = $this->mail->getFolderStatus($folder,$ignoreStatusCache=true)))
1786
		{
1787
            ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": could not stat folder $folder ");
1788
            return false;
1789
        }
1790
		$syncstate = "M:". $status['messages'] ."-R:". $status['recent'] ."-U:". $status['unseen']."-NUID:".$status['uidnext']."-UIDV:".$status['uidvalidity'];
1791
1792
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($folderid, ...) $folder ($account) returning ".array2string($syncstate));
1793
		return array();
1794
	}
1795
1796
	/**
1797
	 * Should return a wastebasket folder if there is one. This is used when deleting
1798
	 * items; if this function returns a valid folder ID, then all deletes are handled
1799
	 * as moves and are sent to your backend as a move. If it returns FALSE, then deletes
1800
	 * are always handled as real deletes and will be sent to your importer as a DELETE
1801
	 */
1802
	function GetWasteBasket()
1803
	{
1804
		$this->_connect($this->account);
1805
		$id = $this->createID($account=0, $this->_wasteID);
1806
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__."() account=$this->account returned $id for folder $this->_wasteID");
1807
		return $id;
1808
	}
1809
1810
    /**
1811
     * Called when the user has requested to delete (really delete) a message. Usually
1812
     * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
1813
     * GetMessageList() should no longer list the message. If it does, the message will be re-sent to the mobile
1814
     * as it will be seen as a 'new' item. This means that if this method is not implemented, it's possible to
1815
     * delete messages on the PDA, but as soon as a sync is done, the item will be resynched to the mobile
1816
     *
1817
     * @param string              $folderid             id of the folder
1818
     * @param string              $id                   id of the message
1819
     * @param ContentParameters   $contentParameters
1820
     *
1821
     * @access public
1822
     * @return boolean                      status of the operation
1823
     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1824
     */
1825
    public function DeleteMessage($folderid, $id, $contentParameters)
1826
	{
1827
		unset($contentParameters);	// not used, but required by function signature
1828
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-DeleteMessage: (fid: '$folderid'  id: '$id' )");
1829
		/*
1830
		$this->imap_reopenFolder($folderid);
1831
		$s1 = @imap_delete ($this->_mbox, $id, FT_UID);
1832
		$s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID);
1833
		$s2 = @imap_expunge($this->_mbox);
1834
		*/
1835
		// we may have to split folderid
1836
		$account = $folder = null;
1837
		$this->splitID($folderid, $account, $folder);
1838
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' '.$folderid.'->'.$folder);
1839
		$_messageUID = (array)$id;
1840
1841
		$this->_connect($this->account);
1842
		$this->mail->reopen($folder);
1843
		try
1844
		{
1845
			$rv = $this->mail->deleteMessages($_messageUID, $folder);
1846
		}
1847
		catch (Api\Exception $e)
1848
		{
1849
			$error = $e->getMessage();
1850
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." $_messageUID, $folder ->".$error);
1851
			// if the server thinks the message does not exist report deletion as success
1852
			if (stripos($error,'[NONEXISTENT]')!==false) return true;
1853
			return false;
1854
		}
1855
1856
		// this may be a bit rude, it may be sufficient that GetMessageList does not list messages flagged as deleted
1857
		if ($this->mail->mailPreferences['deleteOptions'] == 'mark_as_deleted')
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...
1858
		{
1859
			// ignore mark as deleted -> Expunge!
1860
			//$this->mail->icServer->expunge(); // do not expunge as GetMessageList does not List messages flagged as deleted
1861
		}
1862
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-DeleteMessage: $rv");
1863
1864
		return $rv;
1865
	}
1866
1867
    /**
1868
     * Changes the 'read' flag of a message on disk. The $flags
1869
     * parameter can only be '1' (read) or '0' (unread). After a call to
1870
     * SetReadFlag(), GetMessageList() should return the message with the
1871
     * new 'flags' but should not modify the 'mod' parameter. If you do
1872
     * change 'mod', simply setting the message to 'read' on the mobile will trigger
1873
     * a full resync of the item from the server.
1874
     *
1875
     * @param string              $folderid            id of the folder
1876
     * @param string              $id                  id of the message
1877
     * @param int                 $flags               read flag of the message
1878
     * @param ContentParameters   $contentParameters
1879
     *
1880
     * @access public
1881
     * @return boolean                      status of the operation
1882
     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1883
     */
1884
    public function SetReadFlag($folderid, $id, $flags, $contentParameters)
1885
	{
1886
		unset($contentParameters);	// not used, but required by function signature
1887
		// ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetReadFlag: (fid: '$folderid'  id: '$id'  flags: '$flags' )");
1888
		$account = $folder = null;
1889
		$this->splitID($folderid, $account, $folder);
1890
1891
		$_messageUID = (array)$id;
1892
		$this->_connect($this->account);
1893
		$rv = $this->mail->flagMessages((($flags) ? "read" : "unread"), $_messageUID,$folder);
1894
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetReadFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags) ? "read" : "unread") . "-->". $rv);
1895
1896
		return $rv;
1897
	}
1898
1899
	/**
1900
	 *  Creates or modifies a folder
1901
	 *
1902
	 * @param string $id of the parent folder
1903
	 * @param string $oldid => if empty -> new folder created, else folder is to be renamed
1904
	 * @param string $displayname => new folder name (to be created, or to be renamed to)
1905
	 * @param string $type folder type, ignored in IMAP
1906
	 *
1907
	 * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
1908
	 * @return array|boolean stat array or false on error
1909
	 */
1910
	public function ChangeFolder($id, $oldid, $displayname, $type)
1911
	{
1912
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$id', '$oldid', '$displayname', $type)");
1913
		$this->splitID($id, $account, $parentFolder);
1914
		if (empty($oldid))
1915
		{
1916
			$action = 'create';
1917
		} elseif (!empty($oldid)) {
1918
			$action = 'rename';
1919
			$this->splitID($oldid, $account, $oldFolder);
1920
		} else {
1921
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$id'=>($parentFolder), '$oldid'".($oldid?"=>($oldFolder)":'').", '$displayname', $type) NOT supported!");
0 ignored issues
show
Bug introduced by
The variable $oldFolder seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1922
			return false;
1923
		}
1924
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.":{$action}Folder('$id'=>($parentFolder), '$oldid'".($oldid?"=>($oldFolder)":'').", '$displayname', $type)");
1925
		$this->_connect($this->account);
1926
		try
1927
		{
1928
			if ($action=='rename')
1929
			{
1930
				$newFolderName = $this->mail->renameFolder($oldFolder, $parentFolder, $displayname);
1931
			} elseif ($action=='create')
1932
			{
1933
				$error=null;
1934
				$newFolderName = $this->mail->createFolder($parentFolder, $displayname, $error);
1935
			}
1936
		}
1937
		catch (\Exception $e)
1938
		{
1939
			throw new Exception(__METHOD__." $action failed for $oldFolder ($action: $newFolderName) with error:".$e->getMessage());;
1940
		}
1941
		$newID = $this->createID($account,$newFolderName);
1942
		ZLog::Write(LOGLEVEL_DEBUG,":{$action}Folder('$id'=>($parentFolder), '$oldid'".($oldid?"=>($oldFolder)":'').", '$displayname' => $newFolderName (ID:$newID))");
1943
		return (bool)$newFolderName;
1944
	}
1945
1946
	/**
1947
	 * Deletes (really delete) a Folder
1948
	 *
1949
	 * @param string $parentid of the folder to delete
1950
	 * @param string $id of the folder to delete
1951
	 *
1952
	 * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
1953
	 * @return array|boolean stat array or false on error
1954
	 */
1955
	public function DeleteFolder($parentid, $id)
1956
	{
1957
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$parentid', '$id') NOT supported!");
1958
		error_log(__METHOD__."('$parentid', '$id') NOT supported!");
1959
		return false;
1960
	}
1961
1962
	/**
1963
	 * modify olflags (outlook style) flag of a message
1964
	 *
1965
	 * @param $folderid
1966
	 * @param $id
1967
	 * @param $flags
1968
	 *
1969
	 *
1970
	 * @DESC The $flags parameter must contains the poommailflag Object
1971
	 */
1972
	function ChangeMessageFlag($folderid, $id, $flags)
1973
	{
1974
		$_messageUID = (array)$id;
1975
		$this->_connect($this->account);
1976
		$account = $folder = null;
1977
		$this->splitID($folderid, $account, $folder);
1978
		$rv = $this->mail->flagMessages((($flags->flagstatus == 2) ? "flagged" : "unflagged"), $_messageUID,$folder);
1979
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetFlaggedFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
1980
1981
		return $rv;
1982
	}
1983
1984
	/**
1985
	 * Create a max. 32 hex letter ID, current 20 chars are used
1986
	 *
1987
	 * @param int $account mail account id
1988
	 * @param string $folder
1989
	 * @param int $id =0
1990
	 * @return string
1991
	 * @throws Api\Exception\WrongParameter
1992
	 */
1993
	private function createID($account,$folder,$id=0)
1994
	{
1995
		if (!is_numeric($folder))
1996
		{
1997
			// convert string $folder in numeric id
1998
			$folder = $this->folder2hash($account,$f=$folder);
1999
		}
2000
2001
		$str = $this->backend->createID($account, $folder, $id);
2002
2003 View Code Duplication
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($account,'$f',$id) type=$account, folder=$folder --> '$str'");
2004
2005
		return $str;
2006
	}
2007
2008
	/**
2009
	 * Split an ID string into $app, $folder and $id
2010
	 *
2011
	 * @param string $str
2012
	 * @param int &$account mail account id
2013
	 * @param string &$folder
2014
	 * @param int &$id=null
2015
	 * @throws Api\Exception\WrongParameter
2016
	 */
2017
	private function splitID($str,&$account,&$folder,&$id=null)
2018
	{
2019
		$this->backend->splitID($str, $account, $folder, $id);
2020
2021
		// convert numeric folder-id back to folder name
2022
		$folder = $this->hash2folder($account,$f=$folder);
2023
2024 View Code Duplication
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$str','$account','$folder',$id)");
2025
	}
2026
2027
	/**
2028
	 * Methods to convert (hierarchical) folder names to nummerical id's
2029
	 *
2030
	 * This is currently done by storing a serialized array in the device specific
2031
	 * state directory.
2032
	 */
2033
2034
	/**
2035
	 * Convert folder string to nummeric hash
2036
	 *
2037
	 * @param int $account
2038
	 * @param string $folder
2039
	 * @return int
2040
	 */
2041
	private function folder2hash($account,$folder)
2042
	{
2043
		if(!isset($this->folderHashes)) $this->readFolderHashes();
2044
2045
		if (($index = array_search($folder, (array)$this->folderHashes[$account])) === false)
2046
		{
2047
			// new hash
2048
			$this->folderHashes[$account][] = $folder;
2049
			$index = array_search($folder, (array)$this->folderHashes[$account]);
2050
2051
			// maybe later storing in on class destruction only
2052
			$this->storeFolderHashes();
2053
		}
2054
		return $index;
2055
	}
2056
2057
	/**
2058
	 * Convert numeric hash to folder string
2059
	 *
2060
	 * @param int $account
2061
	 * @param int $index
2062
	 * @return string NULL if not used so far
2063
	 */
2064
	private function hash2folder($account,$index)
2065
	{
2066
		if(!isset($this->folderHashes)) $this->readFolderHashes();
2067
2068
		return isset($this->folderHashes[$account]) ? $this->folderHashes[$account][$index] : null;
2069
	}
2070
2071
	private $folderHashes;
2072
2073
	/**
2074
	 * Statemaschine instance used to store folders
2075
	 *
2076
	 * @var activesync_statemaschine
2077
	 */
2078
	private $fh_state_maschine;
2079
2080
	/**
2081
	 * state_type (and _key) used to store folder hashes
2082
	 */
2083
	const FOLDER_STATE_TYPE = 'folder_hashes';
2084
2085
	/**
2086
	 * Read hashfile from state dir
2087
	 */
2088
	private function readFolderHashes()
2089
	{
2090
		if (!isset($this->fh_state_maschine))
2091
		{
2092
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
2093
		}
2094
		try {
2095
			$this->folderHashes = $this->fh_state_maschine->getState(Request::GetDeviceID(),
2096
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2097
		}
2098
		catch (Exception $e) {
2099
			unset($e);
2100
			if ((file_exists($file = $this->hashFile()) || file_exists($file = $this->hashFile(true))) &&
2101
				($hashes = file_get_contents($file)))
2102
			{
2103
				$this->folderHashes = json_decode($hashes,true);
2104
				// fallback in case hashes have been serialized instead of being json-encoded
2105
				if (json_last_error()!=JSON_ERROR_NONE)
2106
				{
2107
					//error_log(__METHOD__.__LINE__." error decoding with json");
2108
					$this->folderHashes = unserialize($hashes);
2109
				}
2110
				// store folder-hashes to state
2111
				$this->storeFolderHashes();
2112
			}
2113
			else
2114
			{
2115
				$this->folderHashes = array();
2116
			}
2117
		}
2118
	}
2119
2120
	/**
2121
	 * Store hashfile via state-maschine
2122
	 *
2123
	 * return int|boolean false on error
2124
	 */
2125
	private function storeFolderHashes()
2126
	{
2127
		if (!isset($this->fh_state_maschine))
2128
		{
2129
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
2130
		}
2131
		try {
2132
			$this->fh_state_maschine->setState($this->folderHashes, Request::GetDeviceID(),
2133
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2134
		}
2135
		catch (Exception $e) {
2136
			_egw_log_exception($e);
2137
			return false;
2138
		}
2139
		return true;
2140
	}
2141
2142
	/**
2143
	 * Get name of hashfile in state dir
2144
	 *
2145
	 * New z-push 2 directory is in activesync_statemachine::getDeviceDirectory.
2146
	 * On request this function also returns, but never creates (!), old z-push 1 directory.
2147
	 *
2148
	 * @param boolean $old =false true: return old / pre-15 hash-file
2149
	 * @throws Api\Exception\AssertionFailed
2150
	 */
2151
	private function hashFile($old=false)
2152
	{
2153
		if (!($dev_id=Request::GetDeviceID()))
2154
		{
2155
			throw new Api\Exception\AssertionFailed(__METHOD__."() no DeviceID set!");
2156
		}
2157
		if ($old)
2158
		{
2159
			return STATE_DIR.$dev_id.'/'.$dev_id.'.hashes';
2160
		}
2161
		$dir = activesync_statemachine::getDeviceDirectory($dev_id);
2162
2163
		return $dir.'/'.$dev_id.'.hashes';
2164
	}
2165
}
2166