Completed
Push — master ( 648b6f...910b92 )
by Klaus
33:59 queued 13:24
created

mail_zpush::GetMessageList()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 24
Code Lines 14

Duplication

Lines 4
Ratio 16.67 %

Importance

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

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

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

Loading history...
1405
			return array();
1406
		}
1407
	}
1408
1409
	/**
1410
	 * Fetch headers for one or all mail of a folder using optional cutoffdate
1411
	 *
1412
	 * Headers of last fetchMessage call of complate folder are cached in static $headers,
1413
	 * to allow to use them without fetching them again.
1414
	 * Next call clears cache
1415
	 *
1416
	 * @param int $folderid
1417
	 * @param int $cutoffdate timestamp with cutoffdate
1418
	 * @param string $_id =null uid of single message to fetch
1419
	 * @param boolean $return_all_headers =false true: additinal contain all headers eg. "subject"
1420
	 * @return array uid => array StatMessage($folderid, $_id)
1421
	 */
1422
	private function fetchMessages($folderid, $cutoffdate=NULL, $_id=NULL, $return_all_headers=false)
1423
	{
1424
		static $headers = array();
1425
1426
		if ($this->debugLevel>1) $gstarttime = microtime (true);
1427
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
1428
		$rv_messages = array();
1429
		// if the message is still available within the class, we use it instead of fetching it again
1430
		if ($_id && isset($headers[$_id]) && is_array($headers[$_id]))
1431
		{
1432
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." the message ".$_id[0]." is still available within the class, we use it instead of fetching it again");
1433
			$rv_messages = array('header'=>array($headers[$_id]));
1434
		}
1435
		else
1436
		{
1437
			$headers = array();	// clear cache to not use too much memory
1438
1439
			if ($this->debugLevel>1) $starttime = microtime (true);
1440
			$this->_connect($this->account);
1441 View Code Duplication
			if ($this->debugLevel>1)
1442
			{
1443
				$endtime = microtime(true) - $starttime;
1444
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " connect took : ".$endtime.' for account:'.$this->account);
1445
			}
1446
			$messagelist = $_filter = array();
1447
			// if not connected, any further action must fail
1448
			if (!empty($cutoffdate)) $_filter = array('status'=>array('UNDELETED'),'range'=>"SINCE",'date'=> date("d-M-Y", $cutoffdate));
1449
			if ($this->debugLevel>1) $starttime = microtime (true);
1450
			$account = $_folderName = $id = null;
1451
			$this->splitID($folderid,$account,$_folderName,$id);
1452 View Code Duplication
			if ($this->debugLevel>1)
1453
			{
1454
				$endtime = microtime(true) - $starttime;
1455
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " splitID took : ".$endtime.' for FolderID:'.$folderid);
1456
			}
1457
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1458
			if ($this->debugLevel>1) $starttime = microtime (true);
1459
			$_numberOfMessages = (empty($cutoffdate)?250:99999);
1460
			$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages, $_sort=0, $_reverse=false, $_filter, $_id);
1461 View Code Duplication
			if ($this->debugLevel>1)
1462
			{
1463
				$endtime = microtime(true) - $starttime;
1464
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " getHeaders call took : ".$endtime.' for FolderID:'.$_folderName);
1465
			}
1466
		}
1467
		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...
1468
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Result:'.array2string($rv_messages));
1469
		$messagelist = array();
1470
		if (!isset($rv_messages['header'])||empty($rv_messages['header'])) return $messagelist;
1471
		//if ($_returnModHash) $messageFolderHash = array();
1472
		foreach ((array)$rv_messages['header'] as $k => $vars)
1473
		{
1474 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to process:'.$vars['uid'].' Subject:'.$vars['subject']);
1475
			$headers[$vars['uid']] = $vars;
1476 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' MailID:'.$k.'->'.array2string($vars));
1477
			if (!empty($vars['deleted'])) continue; // cut of deleted messages
1478
			if ($cutoffdate && $vars['date'] < $cutoffdate) continue; // message is out of range for cutoffdate, ignore it
1479 View Code Duplication
			if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to report:'.$vars['uid'].' Subject:'.$vars['subject']);
1480
			$mess = $return_all_headers ? $vars : array();
1481
			$mess["mod"] = self::doFlagsMod($vars).$vars['date'];
1482
			$mess["id"] = $vars['uid'];
1483
			// 'seen' aka 'read' is the only flag we want to know about
1484
			$mess["flags"] = 0;
1485
			// outlook supports additional flags, set them to 0
1486
			if($vars["seen"]) $mess["flags"] = 1;
1487 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($mess));
1488
			$messagelist[$vars['uid']] = $mess;
1489
			unset($mess);
1490
		}
1491
		if ($this->debugLevel>1)
1492
		{
1493
			$endtime = microtime(true) - $gstarttime;
1494
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " total time used : ".$endtime.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1495
		}
1496
		return $messagelist;
1497
	}
1498
1499
	/**
1500
	 * Prepare headeinfo on a message to return some standardized string to tell which flags are set for a message
1501
	 *
1502
	 * AS currently only supports flagged, answered/replied and forwarded flags.
1503
	 * Seen/read is in under flags key of stat!
1504
	 *
1505
	 * @param array $headerFlags  - array to process, a full return array from getHeaders
1506
	 * @link https://sourceforge.net/p/zimbrabackend/code/HEAD/tree/zimbra-backend/branches/z-push-2/zimbra.php#l11652
1507
	 * @return string string of a representation of supported flags
1508
	 */
1509
	static function doFlagsMod($headerFlags)
1510
	{
1511
		$flags = 'nnn';
1512
		if ($headerFlags['flagged']) $flags[0] = 'f';
1513
		if ($headerFlags['answered']) $flags[1] = 'a';
1514
		if ($headerFlags['forwarded']) $flags[2] = 'f';
1515
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.'('.array2string($headerFlags).') returning '.array2string($flags));
1516
		return $flags;
1517
	}
1518
1519
	/**
1520
	 * Search mailbox for a given pattern
1521
	 *
1522
	 * @param object $_searchquery holds information specifying the query with GetDataArray it holds
1523
	 * 		[searchname] => MAILBOX
1524
	 * 		[searchfolderid] => 101000000000
1525
	 * 		[searchfreetext] => somesearchtexgt
1526
	 * 		[searchdatereceivedgreater] => 1
1527
	 * 		[searchvaluegreater] => 2015-07-06T22:00:00.000Z
1528
	 * 		[searchdatereceivedless] => 1
1529
	 * 		[searchvalueless] => 2015-07-14T15:11:00.000Z
1530
	 * 		[searchrebuildresults] => 1
1531
	 * 		[searchrange] => 0-99
1532
	 * 		[bodypref] => Array([1] => BodyPreference Object([unsetdata:protected] => Array([truncationsize] => [allornone] => [preview] => )[SO_internalid:StateObject:private] => [data:protected] =>
1533
	 * 			 Array([truncationsize] => 2147483647)[changed:protected] => 1))
1534
	 * 				[mimesupport] => 2)
1535
	 * @return array(["range"] = $_searchquery->GetSearchRange(), ['searchtotal'] = count of results,
1536
	 *			array("class" => "Email",
1537
	 *					"longid" => folderid.':'.uid',
1538
	 *					"folderid"	=> folderid,
1539
	 *					), ....
1540
	 *		)
1541
	 */
1542
	public function getSearchResultsMailbox($_searchquery)
1543
	{
1544
		//$this->debugLevel=1;
1545
		$searchquery=$_searchquery->GetDataArray();
1546
		if (!is_array($searchquery)) return array();
1547 View Code Duplication
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($searchquery));
1548
1549
		if (isset($searchquery['searchrebuildresults'])) {
1550
			$rebuildresults = $searchquery['searchrebuildresults'];
1551
		} else {
1552
			$rebuildresults = false;
1553
		}
1554
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'RebuildResults ['.$rebuildresults.']' );
1555
1556
		if (isset($searchquery['deeptraversal'])) {
1557
			$deeptraversal = $searchquery['deeptraversal'];
1558
		} else {
1559
			$deeptraversal = false;
1560
		}
1561
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'DeepTraversal ['.$deeptraversal.']' );
1562
1563
		if (isset($searchquery['searchrange'])) {
1564
			$range = explode("-",$_searchquery->GetSearchRange());
1565
			$start =$range[0] + 1;
1566
			$limit = $range[1] - $range[0] + 1;
1567
		} else {
1568
			$range = false;
1569
		}
1570
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,  'Range ['.print_r($range, true).']' );
1571
1572
		//foreach($searchquery['query'] as $k => $value) {
1573
		//	$query = $value;
1574
		//}
1575
		if (isset($searchquery['searchfolderid']))
1576
		{
1577
			$folderid = $searchquery['searchfolderid'];
1578
		}
1579
/*
1580
		// other types may be possible - we support quicksearch first (freeText in subject and from (or TO in Sent Folder))
1581
		if (is_null(Mail::$supportsORinQuery) || !isset(Mail::$supportsORinQuery[self::$profileID]))
1582
		{
1583
			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);
1584
			if (!isset(Mail::$supportsORinQuery[self::$profileID])) Mail::$supportsORinQuery[self::$profileID]=true;
1585
		}
1586
*/
1587
		if (isset($searchquery['searchfreetext']))
1588
		{
1589
			$searchText = $searchquery['searchfreetext'];
1590
		}
1591
		if (!$folderid)
1592
		{
1593
			$_folderName = ($this->mail->sessionData['mailbox']?$this->mail->sessionData['mailbox']:'INBOX');
1594
			$folderid = $this->createID($account=0,$_folderName);
1595
		}
1596
		$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...
1597
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ProfileID:'.self::$profileID.' FolderID:'.$folderid.' Foldername:'.$_folderName);
1598
		$this->_connect($account);
1599
		// this should not be needed ???
1600
		Mail::$supportsORinQuery[self::$profileID]=true; // trigger quicksearch (if possible)
1601
		$_filter = array('type'=> (Mail::$supportsORinQuery[self::$profileID]?'quick':'subject'),
1602
						 'string'=> $searchText,
1603
						 'status'=>'any'
1604
						);
1605
1606
		if (isset($searchquery['searchdatereceivedgreater']) || isset($searchquery['searchdatereceivedless']))
1607
		{
1608
		/*
1609
		 *	We respect only the DATEPART of the RANGE specified
1610
		 * 		[searchdatereceivedgreater] => 1
1611
		 * 		[searchvaluegreater] => 2015-07-06T22:00:00.000Z , SINCE
1612
		 * 		[searchdatereceivedless] => 1
1613
		 * 		[searchvalueless] => 2015-07-14T15:11:00.000Z , BEFORE
1614
		 */
1615
			$_filter['range'] = "BETWEEN";
1616
			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...
1617
			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...
1618
			$_filter['before'] = date("d-M-Y", Api\DateTime::to($beforedate,'ts'));
1619
			$_filter['since'] = date("d-M-Y", Api\DateTime::to($sincedate,'ts'));
1620
		}
1621
		//$_filter[] = array('type'=>"SINCE",'string'=> date("d-M-Y", $cutoffdate));
1622
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter));
1623
		$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=($range?$start:1), $_numberOfMessages=($limit?$limit:9999999), $_sort=0, $_reverse=false, $_filter, $_id=NULL);
1624
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($rv_messages));
1625
		$list=array();
1626
1627
		$cnt = count($rv_messages['header']);
1628
		//$list['status'] = 1;
1629
		$list['searchtotal'] = $cnt;
1630
		$list["range"] = $_searchquery->GetSearchRange();
1631
		foreach((array)$rv_messages['header'] as $i => $vars)
1632
		{
1633
			$list[] = array(
1634
				"class" => "Email",
1635
				"longid" => $folderid.':'.$vars['uid'],
1636
				"folderid"	=> $folderid,
1637
			);
1638
		}
1639
		//error_log(__METHOD__.__LINE__.array2string($list));
1640
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($list));
1641
		return $list;
1642
	}
1643
1644
	/**
1645
	 * Get ID of parent Folder or '0' for folders in root
1646
	 *
1647
	 * @param int $account
1648
	 * @param string $folder
1649
	 * @return string
1650
	 */
1651
	private function getParentID($account,$folder)
1652
	{
1653
		$this->_connect($account);
1654
		if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
1655
1656
		$mailFolder = $this->folders[$folder];
1657
		if (!isset($mailFolder)) return false;
1658
		$delimiter = (isset($mailFolder->delimiter)?$mailFolder->delimiter:$this->mail->getHierarchyDelimiter());
1659
		$parent = explode($delimiter,$folder);
1660
		array_pop($parent);
1661
		$parent = implode($delimiter,$parent);
1662
1663
		$id = $parent && $this->folders[$parent] ? $this->createID($account, $parent) : '0';
1664
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$folder') --> parent=$parent --> $id");
1665
		return $id;
1666
	}
1667
1668
	/**
1669
	 * Get Information about a folder
1670
	 *
1671
	 * @param string $id
1672
	 * @return SyncFolder|boolean false on error
1673
	 */
1674
	public function GetFolder($id)
1675
	{
1676
		static $last_id = null;
1677
		static $folderObj = null;
1678
		if (isset($last_id) && $last_id === $id) return $folderObj;
1679
1680
		try {
1681
			$account = $folder = null;
1682
			$this->splitID($id, $account, $folder);
1683
		}
1684
		catch(Exception $e) {
1685
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' failed for '.$e->getMessage());
1686
			return $folderObj=false;
1687
		}
1688
		$this->_connect($account);
1689
		if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
1690
1691
		$mailFolder = $this->folders[$folder];
1692
		if (!isset($mailFolder)) return $folderObj=false;
1693
1694
		$folderObj = new SyncFolder();
1695
		$folderObj->serverid = $id;
1696
		$folderObj->parentid = $this->getParentID($account,$folder);
1697
		$folderObj->displayname = $mailFolder->shortDisplayName;
1698
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." ID: $id, Account:$account, Folder:$folder");
1699
		// get folder-type
1700
		foreach($this->folders as $inbox => $mailFolder) break;
1701
		if ($folder == $inbox)
0 ignored issues
show
Bug introduced by
The variable $inbox seems to be defined by a foreach iteration on line 1700. 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...
1702
		{
1703
			$folderObj->type = SYNC_FOLDER_TYPE_INBOX;
1704
		}
1705
		elseif($this->mail->isDraftFolder($folder, false, true))
1706
		{
1707
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isDraft');
1708
			$folderObj->type = SYNC_FOLDER_TYPE_DRAFTS;
1709
			$folderObj->parentid = 0; // required by devices
1710
		}
1711
		elseif($this->mail->isTrashFolder($folder, false, true))
1712
		{
1713
			$folderObj->type = SYNC_FOLDER_TYPE_WASTEBASKET;
1714
			$this->_wasteID = $folder;
1715
			//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
1716
			$folderObj->parentid = 0; // required by devices
1717
		}
1718
		elseif($this->mail->isSentFolder($folder, false, true))
1719
		{
1720
			$folderObj->type = SYNC_FOLDER_TYPE_SENTMAIL;
1721
			$folderObj->parentid = 0; // required by devices
1722
			$this->_sentID = $folder;
1723
			//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
1724
		}
1725
		elseif($this->mail->isOutbox($folder, false, true))
1726
		{
1727
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isOutbox');
1728
			$folderObj->type = SYNC_FOLDER_TYPE_OUTBOX;
1729
			$folderObj->parentid = 0; // required by devices
1730
		}
1731
		else
1732
		{
1733
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' isOther Folder'.$folder);
1734
			$folderObj->type = SYNC_FOLDER_TYPE_USER_MAIL;
1735
		}
1736
1737
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($id) --> $folder --> type=$folderObj->type, parentID=$folderObj->parentid, displayname=$folderObj->displayname");
1738
		return $folderObj;
1739
	}
1740
1741
	/**
1742
	 * Return folder stats. This means you must return an associative array with the
1743
	 * following properties:
1744
	 *
1745
	 * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
1746
	 *		 How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
1747
	 * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
1748
	 * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
1749
	 *		  the folder has not changed. In practice this means that 'mod' can be equal to the folder name
1750
	 *		  as this is the only thing that ever changes in folders. (the type is normally constant)
1751
	 *
1752
	 * @return array with values for keys 'id', 'mod' and 'parent'
1753
	 */
1754
	public function StatFolder($id)
1755
	{
1756
		$folder = $this->GetFolder($id);
1757
1758
		$stat = array(
1759
			'id'     => $id,
1760
			'mod'    => $folder->displayname,
1761
			'parent' => $folder->parentid,
1762
		);
1763
1764
		return $stat;
1765
	}
1766
1767
1768
	/**
1769
	 * Return a changes array
1770
	 *
1771
	 * if changes occurr default diff engine computes the actual changes
1772
	 *
1773
	 * @param string $folderid
1774
	 * @param string &$syncstate on call old syncstate, on return new syncstate
1775
	 * @return array|boolean false if $folderid not found, array() if no changes or array(array("type" => "fakeChange"))
1776
	 */
1777
	function AlterPingChanges($folderid, &$syncstate)
1778
	{
1779
		$account = $folder = null;
1780
		$this->splitID($folderid, $account, $folder);
1781
		if (is_numeric($account)) $type = 'mail';
1782
1783
		if ($type != 'mail') return false;
1784
1785
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1786
1787
        $this->mail->reopen($folder);
1788
1789
		if (!($status = $this->mail->getFolderStatus($folder,$ignoreStatusCache=true)))
1790
		{
1791
            ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": could not stat folder $folder ");
1792
            return false;
1793
        }
1794
		$syncstate = "M:". $status['messages'] ."-R:". $status['recent'] ."-U:". $status['unseen']."-NUID:".$status['uidnext']."-UIDV:".$status['uidvalidity'];
1795
1796
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($folderid, ...) $folder ($account) returning ".array2string($syncstate));
1797
		return array();
1798
	}
1799
1800
	/**
1801
	 * Should return a wastebasket folder if there is one. This is used when deleting
1802
	 * items; if this function returns a valid folder ID, then all deletes are handled
1803
	 * as moves and are sent to your backend as a move. If it returns FALSE, then deletes
1804
	 * are always handled as real deletes and will be sent to your importer as a DELETE
1805
	 */
1806
	function GetWasteBasket()
1807
	{
1808
		$this->_connect($this->account);
1809
		$id = $this->createID($account=0, $this->_wasteID);
1810
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__."() account=$this->account returned $id for folder $this->_wasteID");
1811
		return $id;
1812
	}
1813
1814
    /**
1815
     * Called when the user has requested to delete (really delete) a message. Usually
1816
     * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
1817
     * GetMessageList() should no longer list the message. If it does, the message will be re-sent to the mobile
1818
     * as it will be seen as a 'new' item. This means that if this method is not implemented, it's possible to
1819
     * delete messages on the PDA, but as soon as a sync is done, the item will be resynched to the mobile
1820
     *
1821
     * @param string              $folderid             id of the folder
1822
     * @param string              $id                   id of the message
1823
     * @param ContentParameters   $contentParameters
1824
     *
1825
     * @access public
1826
     * @return boolean                      status of the operation
1827
     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1828
     */
1829
    public function DeleteMessage($folderid, $id, $contentParameters)
1830
	{
1831
		unset($contentParameters);	// not used, but required by function signature
1832
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-DeleteMessage: (fid: '$folderid'  id: '$id' )");
1833
		/*
1834
		$this->imap_reopenFolder($folderid);
1835
		$s1 = @imap_delete ($this->_mbox, $id, FT_UID);
1836
		$s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID);
1837
		$s2 = @imap_expunge($this->_mbox);
1838
		*/
1839
		// we may have to split folderid
1840
		$account = $folder = null;
1841
		$this->splitID($folderid, $account, $folder);
1842
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' '.$folderid.'->'.$folder);
1843
		$_messageUID = (array)$id;
1844
1845
		$this->_connect($this->account);
1846
		$this->mail->reopen($folder);
1847
		try
1848
		{
1849
			$rv = $this->mail->deleteMessages($_messageUID, $folder);
1850
		}
1851
		catch (Api\Exception $e)
1852
		{
1853
			$error = $e->getMessage();
1854
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." $_messageUID, $folder ->".$error);
1855
			// if the server thinks the message does not exist report deletion as success
1856
			if (stripos($error,'[NONEXISTENT]')!==false) return true;
1857
			return false;
1858
		}
1859
1860
		// this may be a bit rude, it may be sufficient that GetMessageList does not list messages flagged as deleted
1861
		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...
1862
		{
1863
			// ignore mark as deleted -> Expunge!
1864
			//$this->mail->icServer->expunge(); // do not expunge as GetMessageList does not List messages flagged as deleted
1865
		}
1866
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-DeleteMessage: $rv");
1867
1868
		return $rv;
1869
	}
1870
1871
    /**
1872
     * Changes the 'read' flag of a message on disk. The $flags
1873
     * parameter can only be '1' (read) or '0' (unread). After a call to
1874
     * SetReadFlag(), GetMessageList() should return the message with the
1875
     * new 'flags' but should not modify the 'mod' parameter. If you do
1876
     * change 'mod', simply setting the message to 'read' on the mobile will trigger
1877
     * a full resync of the item from the server.
1878
     *
1879
     * @param string              $folderid            id of the folder
1880
     * @param string              $id                  id of the message
1881
     * @param int                 $flags               read flag of the message
1882
     * @param ContentParameters   $contentParameters
1883
     *
1884
     * @access public
1885
     * @return boolean                      status of the operation
1886
     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1887
     */
1888
    public function SetReadFlag($folderid, $id, $flags, $contentParameters)
1889
	{
1890
		unset($contentParameters);	// not used, but required by function signature
1891
		// ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetReadFlag: (fid: '$folderid'  id: '$id'  flags: '$flags' )");
1892
		$account = $folder = null;
1893
		$this->splitID($folderid, $account, $folder);
1894
1895
		$_messageUID = (array)$id;
1896
		$this->_connect($this->account);
1897
		$rv = $this->mail->flagMessages((($flags) ? "read" : "unread"), $_messageUID,$folder);
1898
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetReadFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags) ? "read" : "unread") . "-->". $rv);
1899
1900
		return $rv;
1901
	}
1902
1903
	/**
1904
	 *  Creates or modifies a folder
1905
	 *
1906
	 * @param string $id of the parent folder
1907
	 * @param string $oldid => if empty -> new folder created, else folder is to be renamed
1908
	 * @param string $displayname => new folder name (to be created, or to be renamed to)
1909
	 * @param string $type folder type, ignored in IMAP
1910
	 *
1911
	 * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
1912
	 * @return array|boolean stat array or false on error
1913
	 */
1914
	public function ChangeFolder($id, $oldid, $displayname, $type)
1915
	{
1916
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$id', '$oldid', '$displayname', $type)");
1917
		$account = $parent_id = null;
1918
		$this->splitID($id, $account, $parentFolder, $app);
1919
1920
		$parent_id = $this->folder2hash($account, $parentFolder);
1921
		$old_hash = $oldFolder = null;
1922
1923
		if (empty($oldid))
1924
		{
1925
			$action = 'create';
1926
		}
1927
		else
1928
		{
1929
			$action = 'rename';
1930
			$this->splitID($oldid, $account, $oldFolder, $app);
1931
			$old_hash = $this->folder2hash($account, $oldFolder);
1932
		}
1933
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.":{$action}Folder('$id'=>($parentFolder ($parent_id)), '$oldid'".($oldid?"=>($oldFolder ($old_hash))":'').", '$displayname', $type)");
1934
		$this->_connect($this->account);
1935
		try
1936
		{
1937
			if ($action=='rename')
1938
			{
1939
				$newFolderName = $this->mail->renameFolder($oldFolder, $parentFolder, $displayname);
1940
			}
1941
			elseif ($action=='create')
1942
			{
1943
				$error=null;
1944
				$newFolderName = $this->mail->createFolder($parentFolder, $displayname, $error);
1945
			}
1946
		}
1947
		catch (\Exception $e)
1948
		{
1949
			//throw new Exception(__METHOD__." $action failed for $oldFolder ($action: $displayname) with error:".$e->getMessage());
1950
			return false;
1951
		}
1952
		$newHash = $this->rename_folder_hash($account, $old_hash, $newFolderName);
1953
		$newID = $this->createID($account, $newHash);
1954
1955
		ZLog::Write(LOGLEVEL_DEBUG,":{$action}Folder('$id'=>($parentFolder), '$oldid'".($oldid?"=>($oldFolder)":'').", '$displayname' => $newFolderName (ID:$newID))");
1956
		return $this->StatFolder($newID);
1957
	}
1958
1959
	/**
1960
	 * Deletes (really delete) a Folder
1961
	 *
1962
	 * @param string $id of the folder to delete
1963
	 * @param string $parentid (=false) of the folder to delete, may be false/not set
1964
	 *
1965
	 * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
1966
	 * @return boolean true or false on error
1967
	 */
1968
	public function DeleteFolder($id, $parentid=false)
1969
	{
1970
		$account = $parent_id = $app = null;
1971
		$this->splitID($id, $account, $folder, $app);
1972
		$old_hash = $this->folder2hash($account, $folder);
1973
		if ($parentid) $this->splitID($parentid, $account, $parentfolder, $app);
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentid of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
1974
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."( '$id (-> $folder)','$parentid ".($parentid?'(->'.$parentfolder.')':'')."') called!");
1975
		$ret = $this->mail->deleteFolder($folder);
1976
		if ($ret) $newHash = $this->rename_folder_hash($account, $old_hash, "##Dele#edFolder#$folder##");
1977
		return $ret;
1978
	}
1979
1980
	/**
1981
	 * modify olflags (outlook style) flag of a message
1982
	 *
1983
	 * @param $folderid
1984
	 * @param $id
1985
	 * @param $flags
1986
	 *
1987
	 *
1988
	 * @DESC The $flags parameter must contains the poommailflag Object
1989
	 */
1990
	function ChangeMessageFlag($folderid, $id, $flags)
1991
	{
1992
		$_messageUID = (array)$id;
1993
		$this->_connect($this->account);
1994
		$account = $folder = null;
1995
		$this->splitID($folderid, $account, $folder);
1996
		$rv = $this->mail->flagMessages((($flags->flagstatus == 2) ? "flagged" : "unflagged"), $_messageUID,$folder);
1997
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SetFlaggedFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
1998
1999
		return $rv;
2000
	}
2001
2002
	/**
2003
	 * Create a max. 32 hex letter ID, current 20 chars are used
2004
	 *
2005
	 * @param int $account mail account id
2006
	 * @param string $folder
2007
	 * @param int $id =0
2008
	 * @return string
2009
	 * @throws Api\Exception\WrongParameter
2010
	 */
2011
	private function createID($account,$folder,$id=0)
2012
	{
2013
		if (!is_numeric($folder))
2014
		{
2015
			// convert string $folder in numeric id
2016
			$folder = $this->folder2hash($account,$f=$folder);
2017
		}
2018
2019
		$str = $this->backend->createID($account, $folder, $id);
2020
2021 View Code Duplication
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($account,'$f',$id) type=$account, folder=$folder --> '$str'");
2022
2023
		return $str;
2024
	}
2025
2026
	/**
2027
	 * Split an ID string into $app, $account $folder and $appid
2028
	 *
2029
	 * @param string $str
2030
	 * @param int &$account mail account id
2031
	 * @param string &$folder
2032
	 * @param int &$appid=null (for mail=mail is to be expected)
2033
	 * @throws Api\Exception\WrongParameter
2034
	 */
2035
	private function splitID($str,&$account,&$folder,&$appid=null)
2036
	{
2037
		$this->backend->splitID($str, $account, $folder, $appid);
2038
2039
		// convert numeric folder-id back to folder name
2040
		$folder = $this->hash2folder($account,$f=$folder);
2041
2042 View Code Duplication
		if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."('$str','$account','$folder',$appid)");
2043
	}
2044
2045
	/**
2046
	 * Methods to convert (hierarchical) folder names to nummerical id's
2047
	 *
2048
	 * This is currently done by storing a serialized array in the device specific
2049
	 * state directory.
2050
	 */
2051
2052
	/**
2053
	 * Convert folder string to nummeric hash
2054
	 *
2055
	 * @param int $account
2056
	 * @param string $folder
2057
	 * @return int
2058
	 */
2059
	private function folder2hash($account,$folder)
2060
	{
2061
		if(!isset($this->folderHashes)) $this->readFolderHashes();
2062
2063
		if (($index = array_search($folder, (array)$this->folderHashes[$account])) === false)
2064
		{
2065
			// new hash
2066
			$this->folderHashes[$account][] = $folder;
2067
			$index = array_search($folder, (array)$this->folderHashes[$account]);
2068
2069
			// maybe later storing in on class destruction only
2070
			$this->storeFolderHashes();
2071
		}
2072
		return $index;
2073
	}
2074
2075
	/**
2076
	 * Convert numeric hash to folder string
2077
	 *
2078
	 * @param int $account
2079
	 * @param int $index
2080
	 * @return string NULL if not used so far
2081
	 */
2082
	private function hash2folder($account,$index)
2083
	{
2084
		if(!isset($this->folderHashes)) $this->readFolderHashes();
2085
2086
		return isset($this->folderHashes[$account]) ? $this->folderHashes[$account][$index] : null;
2087
	}
2088
2089
	/**
2090
	 * Rename or create a folder in hash table
2091
	 *
2092
	 * @param int $account
2093
	 * @param int $index or null to create
2094
	 * @param string $new_name
2095
	 * @return int $index or new hash if $index is not found
2096
	 */
2097
	private function rename_folder_hash($account, $index, $new_name)
2098
	{
2099
		if ((string)$index === '' || !$this->hash2folder($account, $index))
2100
		{
2101
			return $this->folder2hash($account, $new_name);
2102
		}
2103
		$this->folderHashes[$account][$index] = $new_name;
2104
		$this->storeFolderHashes();
2105
		return $index;
2106
	}
2107
2108
	private $folderHashes;
2109
2110
	/**
2111
	 * Statemaschine instance used to store folders
2112
	 *
2113
	 * @var activesync_statemaschine
2114
	 */
2115
	private $fh_state_maschine;
2116
2117
	/**
2118
	 * state_type (and _key) used to store folder hashes
2119
	 */
2120
	const FOLDER_STATE_TYPE = 'folder_hashes';
2121
2122
	/**
2123
	 * Read hashfile from state dir
2124
	 */
2125
	private function readFolderHashes()
2126
	{
2127
		if (!isset($this->fh_state_maschine))
2128
		{
2129
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
2130
		}
2131
		try {
2132
			$this->folderHashes = $this->fh_state_maschine->getState(Request::GetDeviceID(),
2133
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2134
		}
2135
		catch (Exception $e) {
2136
			unset($e);
2137
			if ((file_exists($file = $this->hashFile()) || file_exists($file = $this->hashFile(true))) &&
2138
				($hashes = file_get_contents($file)))
2139
			{
2140
				$this->folderHashes = json_decode($hashes,true);
2141
				// fallback in case hashes have been serialized instead of being json-encoded
2142
				if (json_last_error()!=JSON_ERROR_NONE)
2143
				{
2144
					//error_log(__METHOD__.__LINE__." error decoding with json");
2145
					$this->folderHashes = unserialize($hashes);
2146
				}
2147
				// store folder-hashes to state
2148
				$this->storeFolderHashes();
2149
			}
2150
			else
2151
			{
2152
				$this->folderHashes = array();
2153
			}
2154
		}
2155
	}
2156
2157
	/**
2158
	 * Store hashfile via state-maschine
2159
	 *
2160
	 * return int|boolean false on error
2161
	 */
2162
	private function storeFolderHashes()
2163
	{
2164
		if (!isset($this->fh_state_maschine))
2165
		{
2166
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
2167
		}
2168
		try {
2169
			$this->fh_state_maschine->setState($this->folderHashes, Request::GetDeviceID(),
2170
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2171
		}
2172
		catch (Exception $e) {
2173
			_egw_log_exception($e);
2174
			return false;
2175
		}
2176
		return true;
2177
	}
2178
2179
	/**
2180
	 * Get name of hashfile in state dir
2181
	 *
2182
	 * New z-push 2 directory is in activesync_statemachine::getDeviceDirectory.
2183
	 * On request this function also returns, but never creates (!), old z-push 1 directory.
2184
	 *
2185
	 * @param boolean $old =false true: return old / pre-15 hash-file
2186
	 * @throws Api\Exception\AssertionFailed
2187
	 */
2188
	private function hashFile($old=false)
2189
	{
2190
		if (!($dev_id=Request::GetDeviceID()))
2191
		{
2192
			throw new Api\Exception\AssertionFailed(__METHOD__."() no DeviceID set!");
2193
		}
2194
		if ($old)
2195
		{
2196
			return STATE_DIR.$dev_id.'/'.$dev_id.'.hashes';
2197
		}
2198
		$dir = activesync_statemachine::getDeviceDirectory($dev_id);
2199
2200
		return $dir.'/'.$dev_id.'.hashes';
2201
	}
2202
}
2203