Completed
Push — 16.1 ( 641f26...0f474b )
by Klaus
251:34 queued 233:02
created

mail_zpush::MoveMessage()   D

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 15

Duplication

Lines 1
Ratio 5 %

Importance

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

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

Loading history...
67
68
	static $profileID;
69
70
	// to control how deep one may dive into the past
71
	const PAST_LIMIT = 178;
72
73
	/**
74
	 * debugLevel - enables more debug
75
	 *
76
	 * @var int
77
	 */
78
	private $debugLevel = 0;
79
80
	/**
81
	 * Constructor
82
	 *
83
	 * @param activesync_backend $backend
84
	 */
85
	public function __construct(activesync_backend $backend)
86
	{
87
		if ($GLOBALS['egw_setup']) return;
88
89
		//$this->debugLevel=2;
90
		$this->backend = $backend;
91
		if (!isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
92
		{
93
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Noprefs set: using 0 as default');
94
			// globals preferences add appname varname value
95
			$GLOBALS['egw']->preferences->add('activesync','mail-ActiveSyncProfileID',0,'user');
96
			// save prefs
97
			$GLOBALS['egw']->preferences->save_repository(true);
98
		}
99 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID:'.array2string(self::$profileID));
100
101
		if (is_null(self::$profileID))
102
		{
103 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' self::ProfileID isNUll:'.array2string(self::$profileID));
104
			self::$profileID =& Api\Cache::getSession('mail','activeSyncProfileID');
105 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID (after reading Cache):'.array2string(self::$profileID));
106
		}
107
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
108
		{
109
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Pref for ProfileID:'.array2string($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']));
110
			if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] == 'G')
111
			{
112
				self::$profileID = 'G'; // this should trigger the fetch of the first negative profile (or if no negative profile is available the firstb there is)
113
			}
114
			else
115
			{
116
				self::$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'];
117
			}
118
		}
119 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Profile Selected (after reading Prefs):'.array2string(self::$profileID));
120
121
		// verify we are on an existing profile, if not running in setup (settings can not be static according to interface!)
122
		if (!isset($GLOBALS['egw_setup']))
123
		{
124
			try {
125
				Mail\Account::read(self::$profileID);
126
			}
127
			catch(Exception $e) {
128
				unset($e);
129
				self::$profileID = Mail\Account::get_default_acc_id();
130
			}
131
		}
132
		if ($this->debugLevel>0) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.self::$profileID);
133
		//$this->debugLevel=0;
134
	}
135
136
	/**
137
	 * Populates $settings for the preferences
138
	 *
139
	 * @param array|string $hook_data
140
	 * @return array
141
	 */
142
	function egw_settings($hook_data)
143
	{
144
		//error_log(__METHOD__.__LINE__.array2string($hook_data));
145
		$identities = array();
146
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group')))
147
		{
148
			$identities = iterator_to_array(Mail\Account::search((int)$hook_data['account_id']));
149
		}
150
		$identities += array(
151
			'G' => lang('Primary Profile'),
152
		);
153
154
		$settings['mail-ActiveSyncProfileID'] = array(
155
			'type'   => 'select',
156
			'label'  => 'eMail Account to sync',
157
			'name'   => 'mail-ActiveSyncProfileID',
158
			'help'   => 'eMail Account to sync ',
159
			'values' => $identities,
160
			'default'=> 'G',
161
			'xmlrpc' => True,
162
			'admin'  => False,
163
		);
164
		$settings['mail-allowSendingInvitations'] = array(
165
			'type'   => 'select',
166
			'label'  => 'allow sending of calendar invitations using this profile?',
167
			'name'   => 'mail-allowSendingInvitations',
168
			'help'   => 'control the sending of calendar invitations while using this profile',
169
			'values' => array(
170
				'sendifnocalnotif'=>'only send if there is no notification in calendar',
171
				'send'=>'yes, always send',
172
				'nosend'=>'no, do not send',
173
			),
174
			'xmlrpc' => True,
175
			'default' => 'sendifnocalnotif',
176
			'admin'  => False,
177
		);
178
		$settings['mail-maximumSyncRange'] = array(
179
			'type'   => 'input',
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
		$settings['mail-useSignature'] = array(
189
			'type'   => 'select',
190
			'label'  => 'control if and which available signature is added to outgoing mails',
191
			'name'   => 'mail-useSignature',
192
			'help'   => 'control the use of signatures',
193
			'values' => array(
194
				'sendifnocalnotif'=>'only send if there is no notification in calendar',
195
				'send'=>'yes, always add EGroupware signatures to outgoing mails',
196
				'nosend'=>'no, never add EGroupware signatures to outgoing mails',
197
			),
198
			'xmlrpc' => True,
199
			'default' => 'nosend',
200
			'admin'  => False,
201
		);
202
*/
203
		return $settings;
204
	}
205
206
	/**
207
	 * Verify preferences
208
	 *
209
	 * @param array|string $hook_data
210
	 * @return array with error-messages from all plugins
211
	 */
212
	function verify_settings($hook_data)
213
	{
214
		$errors = array();
215
216
		// check if an eSync eMail profile is set (might not be set as default or forced!)
217
		if (isset($hook_data['prefs']['mail-ActiveSyncProfileID']) || $hook_data['type'] == 'user')
218
		{
219
			// eSync and eMail translations are not (yet) loaded
220
			Api\Translation::add_app('activesync');
221
			Api\Translation::add_app('mail');
222
223
			// inject preference to verify and call constructor
224
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] =
225
				$hook_data['prefs']['mail-ActiveSyncProfileID'];
226
			$this->__construct($this->backend);
227
228
			try {
229
				$this->_connect(0,true);
230
				$this->_disconnect();
231
232
				if (!$this->_wasteID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('trash').'</b>');
233
				if (!$this->_sentID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('send').'</b>');
234
			}
235
			catch(Exception $e) {
236
				$errors[] = lang('Can not open IMAP connection').': '.$e->getMessage();
237
			}
238
			if ($errors)
239
			{
240
				$errors[] = '<b>'.lang('eSync will FAIL without a working eMail configuration!').'</b>';
241
			}
242
		}
243
		//error_log(__METHOD__.'('.array2string($hook_data).') returning '.array2string($errors));
244
		return $errors;
245
	}
246
247
	/**
248
	 * Open IMAP connection
249
	 *
250
	 * @param int $account integer id of account to use
251
	 * @todo support different accounts
252
	 */
253
	private function _connect($account=0)
254
	{
255
		if (!$account) $account = self::$profileID ? self::$profileID : 0;
256
		if ($this->mail && $this->account != $account) $this->_disconnect();
257
258
		$this->_wasteID = false;
259
		$this->_sentID = false;
260
261
		if (!$this->mail)
262
		{
263
			$this->account = $account;
264
			// todo: tell mail which account to use
265
			//error_log(__METHOD__.__LINE__.' create object with ProfileID:'.array2string(self::$profileID));
266
			$this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
267 View Code Duplication
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
268
		}
269 View Code Duplication
		else
270
		{
271
			//error_log(__METHOD__.__LINE__." connect with profileID: ".self::$profileID);
272
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
273
		}
274
		$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...
275
276
		$this->_wasteID = $this->mail->getTrashFolder(false);
277
		//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
278
		$this->_sentID = $this->mail->getSentFolder(false);
279
		$this->mail->getOutboxFolder(true);
280
		//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
281
		//error_log(__METHOD__.__LINE__.' Connection Status for ProfileID:'.self::$profileID.'->'.$this->mail->icServer->_connected);
282
	}
283
284
	/**
285
	 * Close IMAP connection
286
	 */
287
	private function _disconnect()
288
	{
289
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__);
290
		if ($this->mail) $this->mail->closeConnection();
291
292
		unset($this->mail);
293
		unset($this->account);
294
		unset($this->folders);
295
	}
296
297
	/**
298
	 *  GetFolderList
299
	 *
300
	 *  @ToDo loop over available email accounts
301
	 */
302
	public function GetFolderList()
303
	{
304
		$folderlist = array();
305
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
306
		/*foreach($available_accounts as $account)*/ $account = 0;
307
		{
308
			$this->_connect($account);
309
			if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false,$_alwaysGetDefaultFolders=true);
310
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($this->folders));
311
312
			foreach ($this->folders as $folder => $folderObj) {
313
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' folder='.$folder);
314
				$folderlist[] = $f = array(
315
					'id'     => $this->createID($account,$folder),
316
					'mod'    => $folderObj->shortDisplayName,
317
					'parent' => $this->getParentID($account,$folder),
318
				);
319
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($f));
320
			}
321
		}
322
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($folderlist));
323
324
		return $folderlist;
325
	}
326
327
    /**
328
     * Sends a message which is passed as rfc822. You basically can do two things
329
     * 1) Send the message to an SMTP server as-is
330
     * 2) Parse the message yourself, and send it some other way
331
     * It is up to you whether you want to put the message in the sent items folder. If you
332
     * want it in 'sent items', then the next sync on the 'sent items' folder should return
333
     * the new message as any other new message in a folder.
334
     *
335
     * @param array $smartdata = IMAP-SendMail: SyncSendMail (
336
     *        (S) clientid => SendMail-30722448149304
337
     *        (S) saveinsent => empty
338
     *        (S) replacemime => null
339
     *        (S) accountid => null
340
     *        (S) source => SyncSendMailSource (
341
     *                                (S) folderid => 101000000000
342
     *                                (S) itemid => 33776
343
     *                                (S) longid => null
344
     *                                (S) instanceid => null
345
     *                                unsetVars(Array) size: 0
346
     *                                flags => false
347
     *                                content => null
348
     *                        )
349
     *        (S) mime => Date: Tue, 23 Jun 2015 14:13:23 +0200
350
     *Subject: AW: Blauer himmel
351
     *....
352
     *        (S) replyflag => true
353
     *        (S) forwardflag => null
354
     *        unsetVars(Array) size: 0
355
     *        flags => false
356
     *        content => null
357
     *)
358
	 *
359
     * @return boolean true on success, false on error
360
     *
361
     * @see eg. BackendIMAP::SendMail()
362
     * @todo implement either here or in mail backend
363
     * 	(maybe sending here and storing to sent folder in plugin, as sending is supposed to always work in EGroupware)
364
     */
365
	public function SendMail($smartdata)
366
	{
367
		//$this->debugLevel=2;
368
		$ClientSideMeetingRequest = false;
369
		$allowSendingInvitations = 'sendifnocalnotif';
370
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
371
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']=='nosend')
372
		{
373
			$allowSendingInvitations = false;
374
		}
375
		elseif (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
376
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']!='nosend')
377
		{
378
			$allowSendingInvitations = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations'];
379
		}
380
		$smartdata_task = ($smartdata->replyflag?'reply':($smartdata->forwardflag?'forward':'new'));
381
382
   		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 : ""));
383
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Smartdata = ".array2string($smartdata));
384
		//error_log("IMAP-Sendmail: Smartdata = ".array2string($smartdata));
385
386
		// initialize our Mail
387 View Code Duplication
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
388
		$activeMailProfiles = $this->mail->getAccountIdentities(self::$profileID);
389
		// use the standardIdentity
390
		$activeMailProfile = Mail::getStandardIdentityForProfile($activeMailProfiles,self::$profileID);
391
392
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.")".' ProfileID:'.self::$profileID.' ActiveMailProfile:'.array2string($activeMailProfile));
393
394
		// initialize the new Api\Mailer object for sending
395
		$mailObject = new Api\Mailer(self::$profileID);
396
		$this->mail->parseRawMessageIntoMailObject($mailObject,$smartdata->mime);
397
		// Horde SMTP Class uses utf-8 by default. as we set charset always to utf-8
398
		$mailObject->Sender  = $activeMailProfile['ident_email'];
399
		$mailObject->setFrom($activeMailProfile['ident_email'],Mail::generateIdentityString($activeMailProfile,false));
400
		$mailObject->addHeader('X-Mailer', 'mail-Activesync');
401
402
403
		// prepare addressee list; moved the adding of addresses to the mailobject down
404
		// to
405
406 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...
407
			if (!$addressObject->valid) continue;
408
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail To: ".array2string($addressObject) );
409
			//$mailObject->AddAddress($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
410
			$toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
411
		}
412
		// CC
413 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...
414
			if (!$addressObject->valid) continue;
415
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail CC: ".array2string($addressObject) );
416
			//$mailObject->AddCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
417
			$ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
418
		}
419
		// BCC
420 View Code Duplication
		foreach(Mail::parseAddressList($mailObject->getHeader("Bcc")) as $addressObject) {
0 ignored issues
show
Bug introduced by
It seems like $mailObject->getHeader('Bcc') targeting EGroupware\Api\Mailer::getHeader() can also be of type array; however, EGroupware\Api\Mail::parseAddressList() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

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

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1404
		{
1405
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." the message ".$_id[0]." is still available within the class, we use it instead of fetching it again");
1406
			$rv_messages = array('header'=>array($headers[$_id]));
1407
		}
1408
		else
1409
		{
1410
			$headers = array();	// clear cache to not use too much memory
1411
1412
			if ($this->debugLevel>1) $starttime = microtime (true);
1413
			$this->_connect($this->account);
1414 View Code Duplication
			if ($this->debugLevel>1)
1415
			{
1416
				$endtime = microtime(true) - $starttime;
1417
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " connect took : ".$endtime.' for account:'.$this->account);
1418
			}
1419
			$messagelist = $_filter = array();
1420
			// if not connected, any further action must fail
1421
			if (!empty($cutoffdate)) $_filter = array('status'=>array('UNDELETED'),'range'=>"SINCE",'date'=> date("d-M-Y", $cutoffdate));
1422
			if ($this->debugLevel>1) $starttime = microtime (true);
1423
			$account = $_folderName = $id = null;
1424
			$this->splitID($folderid,$account,$_folderName,$id);
1425 View Code Duplication
			if ($this->debugLevel>1)
1426
			{
1427
				$endtime = microtime(true) - $starttime;
1428
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " splitID took : ".$endtime.' for FolderID:'.$folderid);
1429
			}
1430
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1431
			if ($this->debugLevel>1) $starttime = microtime (true);
1432
			$_numberOfMessages = (empty($cutoffdate)?250:99999);
1433
			$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages, $_sort=0, $_reverse=false, $_filter, $_id);
1434 View Code Duplication
			if ($this->debugLevel>1)
1435
			{
1436
				$endtime = microtime(true) - $starttime;
1437
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " getHeaders call took : ".$endtime.' for FolderID:'.$_folderName);
1438
			}
1439
		}
1440
		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...
1441
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Result:'.array2string($rv_messages));
1442
		$messagelist = array();
1443
		if (!isset($rv_messages['header'])||empty($rv_messages['header'])) return $messagelist;
1444
		//if ($_returnModHash) $messageFolderHash = array();
1445
		foreach ((array)$rv_messages['header'] as $k => $vars)
1446
		{
1447 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to process:'.$vars['uid'].' Subject:'.$vars['subject']);
1448
			$headers[$vars['uid']] = $vars;
1449 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' MailID:'.$k.'->'.array2string($vars));
1450
			if (!empty($vars['deleted'])) continue; // cut of deleted messages
1451
			if ($cutoffdate && $vars['date'] < $cutoffdate) continue; // message is out of range for cutoffdate, ignore it
1 ignored issue
show
Bug Best Practice introduced by
The expression $cutoffdate of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

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

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