Completed
Push — 16.1 ( 39d432...333927 )
by Klaus
17:10
created

mail_zpush::GetMessageList()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 24
Code Lines 14

Duplication

Lines 4
Ratio 16.67 %

Importance

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

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

Loading history...
67
68
	static $profileID;
69
70
	// to control how deep one may dive into the past
71
	const PAST_LIMIT = 178;
72
73
	/**
74
	 * debugLevel - enables more debug
75
	 *
76
	 * @var int
77
	 */
78
	private $debugLevel = 0;
79
80
	/**
81
	 * Constructor
82
	 *
83
	 * @param activesync_backend $backend
84
	 */
85
	public function __construct(activesync_backend $backend)
86
	{
87
		if ($GLOBALS['egw_setup']) return;
88
89
		//$this->debugLevel=2;
90
		$this->backend = $backend;
91
		if (!isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
92
		{
93
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Noprefs set: using 0 as default');
94
			// globals preferences add appname varname value
95
			$GLOBALS['egw']->preferences->add('activesync','mail-ActiveSyncProfileID',0,'user');
96
			// save prefs
97
			$GLOBALS['egw']->preferences->save_repository(true);
98
		}
99 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID:'.array2string(self::$profileID));
100
101
		if (is_null(self::$profileID))
102
		{
103 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' self::ProfileID isNUll:'.array2string(self::$profileID));
104
			self::$profileID =& Api\Cache::getSession('mail','activeSyncProfileID');
105 View Code Duplication
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID (after reading Cache):'.array2string(self::$profileID));
106
		}
107
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
108
		{
109
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Pref for ProfileID:'.array2string($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']));
110
			if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] == 'G')
111
			{
112
				self::$profileID = 'G'; // this should trigger the fetch of the first negative profile (or if no negative profile is available the firstb there is)
113
			}
114
			else
115
			{
116
				self::$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'];
117
			}
118
		}
119 View Code Duplication
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Profile Selected (after reading Prefs):'.array2string(self::$profileID));
120
121
		// verify we are on an existing profile, if not running in setup (settings can not be static according to interface!)
122
		if (!isset($GLOBALS['egw_setup']))
123
		{
124
			try {
125
				Mail\Account::read(self::$profileID);
126
			}
127
			catch(Exception $e) {
128
				unset($e);
129
				self::$profileID = Mail\Account::get_default_acc_id();
130
			}
131
		}
132
		if ($this->debugLevel>0) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.self::$profileID);
133
		//$this->debugLevel=0;
134
	}
135
136
	/**
137
	 * Populates $settings for the preferences
138
	 *
139
	 * @param array|string $hook_data
140
	 * @return array
141
	 */
142
	function egw_settings($hook_data)
143
	{
144
		//error_log(__METHOD__.__LINE__.array2string($hook_data));
145
		$identities = array();
146
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group')))
147
		{
148
			$identities = iterator_to_array(Mail\Account::search((int)$hook_data['account_id']));
149
		}
150
		$identities += array(
151
			'G' => lang('Primary Profile'),
152
		);
153
154
		$settings['mail-ActiveSyncProfileID'] = array(
155
			'type'   => 'select',
156
			'label'  => 'eMail Account to sync',
157
			'name'   => 'mail-ActiveSyncProfileID',
158
			'help'   => 'eMail Account to sync ',
159
			'values' => $identities,
160
			'default'=> 'G',
161
			'xmlrpc' => True,
162
			'admin'  => False,
163
		);
164
		$settings['mail-allowSendingInvitations'] = array(
165
			'type'   => 'select',
166
			'label'  => 'allow sending of calendar invitations using this profile?',
167
			'name'   => 'mail-allowSendingInvitations',
168
			'help'   => 'control the sending of calendar invitations while using this profile',
169
			'values' => array(
170
				'sendifnocalnotif'=>'only send if there is no notification in calendar',
171
				'send'=>'yes, always send',
172
				'nosend'=>'no, do not send',
173
			),
174
			'xmlrpc' => True,
175
			'default' => 'sendifnocalnotif',
176
			'admin'  => False,
177
		);
178
		$settings['mail-maximumSyncRange'] = array(
179
			'type'   => 'integer',
180
			'label'  => lang('How many days to sync in the past when client does not specify a date-range (default %1)', self::PAST_LIMIT),
181
			'name'   => 'mail-maximumSyncRange',
182
			'help'   => 'if the client sets no sync range, you may override the setting (preventing client crash that may be caused by too many mails/too much data). If you want to sync way-back into the past: set a large number',
183
			'xmlrpc' => True,
184
			'admin'  => False,
185
		);
186
187
/*
188
		$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
		// collect identity / signature for later usage, and to determine if we may have to manipulate TransferEncoding and Charset
394
		try
395
		{
396
			$acc = Mail\Account::read($this->mail->icServer->ImapServerId);
397
			//error_log(__METHOD__.__LINE__.array2string($acc));
398
			$_signature = Mail\Account::read_identity($acc['ident_id'],true);
399
		}
400
		catch (Exception $e)
401
		{
402
			$_signature=array();
403
		}
404
		$signature = $_signature['ident_signature'];
405
		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...
406
			$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...
407
			empty($signature) || trim(Api\Mail\Html::convertHTMLToText($signature)) =='')
408
		{
409
			$disableRuler = true;
410
		}
411
		$beforePlain = $beforeHtml = "";
412
		$beforeHtml = ($disableRuler ?'&nbsp;<br>':'&nbsp;<br><hr style="border:dotted 1px silver; width:90%; border:dotted 1px silver;">');
413
		$beforePlain = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
414
		$sigText = Mail::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
415
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Signature to use:'.$sigText);
416
		$sigTextHtml = $beforeHtml.$sigText;
417
		$sigTextPlain = $beforePlain.Api\Mail\Html::convertHTMLToText($sigText);
418
419
		$force8bit=false;
420
		if (Api\Translation::detect_encoding($sigTextPlain)!='ascii') $force8bit=true;
421
		// initialize the new Api\Mailer object for sending
422
		$mailObject = new Api\Mailer(self::$profileID);
423
		$this->mail->parseRawMessageIntoMailObject($mailObject,$smartdata->mime,$force8bit);
424
		// Horde SMTP Class uses utf-8 by default. as we set charset always to utf-8
425
		$mailObject->Sender  = $activeMailProfile['ident_email'];
426
		$mailObject->setFrom($activeMailProfile['ident_email'],Mail::generateIdentityString($activeMailProfile,false));
427
		$mailObject->addHeader('X-Mailer', 'mail-Activesync');
428
429
		// prepare addressee list; moved the adding of addresses to the mailobject down
430
		// to
431 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...
432
			if (!$addressObject->valid) continue;
433
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail To: ".array2string($addressObject) );
434
			//$mailObject->AddAddress($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
435
			$toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
436
		}
437
		// CC
438 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...
439
			if (!$addressObject->valid) continue;
440
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail CC: ".array2string($addressObject) );
441
			//$mailObject->AddCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
442
			$ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
443
		}
444
		// BCC
445 View Code Duplication
		foreach($mailObject->getAddresses('bcc') as $addressObject) {
446
			if (!$addressObject->valid) continue;
447
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail BCC: ".array2string($addressObject) );
448
			//$mailObject->AddBCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
449
			$bccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
450
		}
451
		$mailObject->clearAllRecipients();
452
453
		$use_orgbody = false;
454
455
		$k = 'Content-Type';
456
		$ContentType =$mailObject->getHeader('Content-Type');
457
		//error_log(__METHOD__.__LINE__." Header Sentmail original Header (filtered): " . $k.  " = ".trim($ContentType));
458
		// if the message is a multipart message, then we should use the sent body
459
		if (preg_match("/multipart/i", $ContentType)) {
460
			$use_orgbody = true;
461
		}
462
463
		// save the original content-type header for the body part when forwarding
464
		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...
465
			//continue; // ignore
466
		}
467
		// horde/egw_ mailer does everything as utf-8, the following should not be needed
468
		//$org_charset = $ContentType;
469
		//$ContentType = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $ContentType);
470
		// if the message is a multipart message, then we should use the sent body
471
		if (($smartdata_task == 'new' || $smartdata_task == 'reply' || $smartdata_task == 'forward') &&
472
			((isset($smartdata->replacemime) && $smartdata->replacemime == true) ||
473
			$k == "Content-Type" && preg_match("/multipart/i", $ContentType))) {
474
			$use_orgbody = true;
475
		}
476
		$Body =  $AltBody = "";
477
		// get body of the transmitted message
478
		// if this is a simple message, no structure at all
479
		if (preg_match("/text/i", $ContentType))
480
		{
481
			$simpleBodyType = (preg_match("/html/i", $ContentType)?'text/html':'text/plain');
482
			$bodyObj = $mailObject->findBody(preg_match("/html/i", $ContentType) ? 'html' : 'plain');
483
			$body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]", $bodyObj ?$bodyObj->getContents() : null);
484
			if  ($simpleBodyType == "text/plain")
485
			{
486
				$Body = $body;
487
				$AltBody = "<pre>".nl2br($body)."</pre>";
488
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created AltBody');
489
			}
490
			else
491
			{
492
				$AltBody = $body;
493
				$Body =  trim(Api\Mail\Html::convertHTMLToText($body));
494
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created Body');
495
			}
496
		}
497
		else
498
		{
499
			// if this is a structured message
500
			// prefer plain over html
501
			$Body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
502
				($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null);
503
			$AltBody = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
504
				($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null);
505
		}
506
		if ($this->debugLevel>1 && $Body) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as with MessageContentType:". $ContentType.'=>'.$Body);
507
		if ($this->debugLevel>1 && $AltBody) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched AltBody as with MessageContentType:". $ContentType.'=>'.$AltBody);
508
		//error_log(__METHOD__.__LINE__.array2string($mailObject));
509
		// if this is a multipart message with a boundary, we must use the original body
510
		//if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' mailObject after Inital Parse:'.array2string($mailObject));
511
        if ($use_orgbody) {
512
    	    ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") use_orgbody = true ContentType:".$ContentType);
513
 			// if it is a ClientSideMeetingRequest, we report it as send at all times
514
			if (($cal_body = $mailObject->findBody('calendar')) &&
515
				($cSMRMethod = $cal_body->getContentTypeParameter('method')))
516
			{
517
				if ($cSMRMethod == 'REPLY' && class_exists('calendar_ical'))
518
				{
519
					$organizer = calendar_ical::getIcalOrganizer($cal_body->getContents());
520
				}
521
				if ($this->debugLevel) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") we have a Client Side Meeting Request from organizer=$organizer");
522
				$ClientSideMeetingRequest = true;
523
			}
524
        }
525
		// now handle the addressee list
526
		$toCount = 0;
527
		//error_log(__METHOD__.__LINE__.array2string($toMailAddr));
528
		foreach((array)$toMailAddr as $address) {
529
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
530
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
531
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' &&
532
					calendar_boupdate::email_update_requested($emailAddress, isset($cSMRMethod) ? $cSMRMethod : 'REQUEST',
533
						$organizer && !strcasecmp($emailAddress, $organizer) ? 'CHAIR' : ''))
534
				{
535
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") skiping mail to organizer '$organizer', as it will be send by calendar app");
536
					continue;
537
				}
538
				$mailObject->AddAddress($emailAddress, $addressObject->personal);
539
				$toCount++;
540
			}
541
		}
542
		$ccCount = 0;
543 View Code Duplication
		foreach((array)$ccMailAddr as $address) {
544
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
545
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
546
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
547
				$mailObject->AddCC($emailAddress, $addressObject->personal);
548
				$ccCount++;
549
			}
550
		}
551
		$bccCount = 0;
552 View Code Duplication
		foreach((array)$bccMailAddr as $address) {
553
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
554
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
555
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
556
				$mailObject->AddBCC($emailAddress, $addressObject->personal);
557
				$bccCount++;
558
			}
559
		}
560
		// typical organizer reply will end here with nothing send --> return true, because we suppressed the send above
561
		if ($toCount+$ccCount+$bccCount == 0)
562
		{
563
			return $ClientSideMeetingRequest && $allowSendingInvitations === 'sendifnocalnotif' && $organizer ? true : 0; // noone to send mail to
564
		}
565
		if ($ClientSideMeetingRequest === true && $allowSendingInvitations===false) return true;
566
		// as we use our mailer (horde mailer) it is detecting / setting the mimetype by itself while creating the mail
567
/*
568
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body:'.$body);
569
		$body = str_replace("\r",((preg_match("^text/html^i", $ContentType))?'<br>':""),$body); // what is this for?
570
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body (modified):'.$body);
571
*/
572
		// actually use prepared signature --------------------collected earlier--------------------------
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
			//error_log(__METHOD__.__LINE__.'adding Signature');
705
			$Body = $Body.$sigTextPlain;
706
			$AltBody = $AltBody.$sigTextHtml;
707
		}
708
		// now set the body
709 View Code Duplication
		if ($AltBody && ($html_body = $mailObject->findBody('html')))
710
		{
711
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$AltBody);
712
			//error_log(__METHOD__.__LINE__.' html:'.$AltBody);
713
			$html_body->setContents($AltBody,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
714
		}
715 View Code Duplication
		if ($Body && ($text_body = $mailObject->findBody('plain')))
716
		{
717
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$Body);
718
			//error_log(__METHOD__.__LINE__.' text:'.$Body);
719
			$text_body->setContents($Body,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
720
		}
721
		//advanced debugging
722
		// Horde SMTP Class uses utf-8 by default.
723
        //ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SendMail: parsed message: ". print_r($message,1));
724
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): MailObject:".array2string($mailObject));
725
726
		// set a higher timeout for big messages
727
		@set_time_limit(120);
728
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.' about to send ....');
729
		// send
730
		$send = true;
731
		try {
732
			$mailObject->Send();
733
		}
734
		catch(Exception $e) {
735
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") The email could not be sent. Last-SMTP-error: ". $e->getMessage());
736
			$send = false;
737
		}
738
739
		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...
740
		{
741
			$uid = $smartdata->source->itemid;
742
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' tASK:'.$smartdata_task." FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
743
			$this->splitID($smartdata->source->folderid, $account, $folder);
744
			//error_log(__METHOD__.__LINE__.' Folder:'.$folder.' Uid:'.$uid);
745
			$this->mail->reopen($folder);
746
			// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
747
			// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
748
			if ($this->mail->isDraftFolder($folder) && !$this->mail->isTemplateFolder($folder))
749
			{
750
				$this->mail->deleteMessages(array($uid),$folder);
751
			} else {
752
				$this->mail->flagMessages("answered", array($uid),$folder);
753
				if ($smartdata_task== "forward")
754
				{
755
					$this->mail->flagMessages("forwarded", array($uid),$folder);
756
				}
757
			}
758
		}
759
760
		$asf = ($send ? true:false); // initalize accordingly
761
		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...
762
		{
763
			$asf = false;
764
			$sentFolder = $this->mail->getSentFolder();
765
			if ($this->_sentID) {
766
				$folderArray[] = $this->_sentID;
767
			}
768
			else if(isset($sentFolder) && $sentFolder != 'none')
769
			{
770
				$folderArray[] = $sentFolder;
771
			}
772
			// No Sent folder set, try defaults
773
			else
774
			{
775
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP-SendMail: No Sent mailbox set");
776
				// we dont try guessing
777
				$asf = true;
778
			}
779
			if (count($folderArray) > 0) {
780
				foreach((array)$bccMailAddr as $address) {
781
					foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
782
						$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
783
						$mailAddr[] = array($emailAddress, $addressObject->personal);
784
					}
785
				}
786
				//$BCCmail='';
787
				if (count($mailAddr)>0) $mailObject->forceBccHeader();
788
				//$BCCmail = $mailObject->AddrAppend("Bcc",$mailAddr);
789
				foreach($folderArray as $folderName) {
790 View Code Duplication
					if($this->mail->isSentFolder($folderName)) {
791
						$flags = '\\Seen';
792
					} elseif($this->mail->isDraftFolder($folderName)) {
793
						$flags = '\\Draft';
794
					} else {
795
						$flags = '';
796
					}
797
					$asf = true;
798
					//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.array2string($this->mail->icServer));
799
					$this->mail->openConnection(self::$profileID,false);
800
					if ($this->mail->folderExists($folderName)) {
801
						try
802
						{
803
							$this->mail->appendMessage($folderName,$mailObject->getRaw(), null,
804
									$flags);
805
						}
806
						catch (Api\Exception\WrongUserinput $e)
807
						{
808
							//$asf = false;
809
							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()));
810
						}
811
					}
812
					else
813
					{
814
						//$asf = false;
815
						ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$mailObject->getHeader('Subject'),$folderName));
816
					}
817
			        ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Outgoing mail saved in configured 'Sent' folder '".$folderName."': ". (($asf)?"success":"failed"));
818
				}
819
				//$this->mail->closeConnection();
820
			}
821
		}
822
823
		$this->debugLevel=0;
824
825
		if ($send && $asf)
826
		{
827
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> send successfully');
828
			return true;
829
		}
830
		else
831
		{
832
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." returning ".($ClientSideMeetingRequest ? true : 120)." (MailSubmissionFailed)".($ClientSideMeetingRequest ?" is ClientSideMeetingRequest (we ignore the failure)":""));
833
			return ($ClientSideMeetingRequest ? true : 120);   //MAIL Submission failed, see MS-ASCMD
834
		}
835
	}
836
837
	/**
838
	 * For meeting requests (iCal attachments with method='request') we call calendar plugin with iCal to get SyncMeetingRequest object,
839
	 * and do NOT return the attachment itself!
840
	 *
841
	 * @param string $folderid
842
	 * @param string $id
843
	 * @param ContentParameters $contentparameters  parameters of the requested message (truncation, mimesupport etc)
844
	 *  object with attributes foldertype, truncation, rtftruncation, conflict, filtertype, bodypref, deletesasmoves, filtertype, contentclass, mimesupport, conversationmode
845
	 *  bodypref object with attributes: ]truncationsize, allornone, preview
846
	 * @return $messageobject|boolean false on error
847
	 */
848
	public function GetMessage($folderid, $id, $contentparameters)
849
	{
850
		//$this->debugLevel=4;
851
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' ContentParams='.array2string($contentparameters));
852
		$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
853
		$mimesupport = $contentparameters->GetMimeSupport();
854
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() truncsize=$truncsize, mimeSupport=".array2string($mimesupport));
855
		$bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */
856
857
		// fix for z-push bug returning additional bodypreference type 4, even if only 1 is requested and mimessupport = 0
858
		if (!$mimesupport && ($key = array_search('4', $bodypreference))) unset($bodypreference[$key]);
859
860
		//$this->debugLevel=4;
861 View Code Duplication
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
862
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' TruncSize:'.$truncsize.' Bodypreference: '.array2string($bodypreference));
863
		$account = $_folderName = $xid = null;
864
		$this->splitID($folderid,$account,$_folderName,$xid);
865
		$this->mail->reopen($_folderName);
866
		$messages = $this->fetchMessages($folderid, NULL, $id, true);	// true: return all headers
867
		$headers = $messages[$id];
868
		if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($headers));
869
		// StatMessage should reopen the folder in question, so we dont need folderids in the following statements.
870
		if ($headers)
871
		{
872
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." Message $id with stat ".array2string($headers));
873
			// initialize the object
874
			$output = new SyncMail();
875
			//$rawHeaders = $this->mail->getMessageRawHeader($id);
876
			// simple style
877
			// start AS12 Stuff (bodypreference === false) case = old behaviour
878 View Code Duplication
			if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__. ' for message with ID:'.$id.' with headers:'.array2string($headers));
879
880
			if ($bodypreference === false) {
881
				$bodyStruct = $this->mail->getMessageBody($id, 'only_if_no_text', '', null, true,$_folderName);
882
				$raw_body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
883
				//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
884
				if (stripos($raw_body,'<style')!==false) $body = preg_replace("/<style.*?<\/style>/is", "", $raw_body); // in case there is only a html part
885
				// remove all other html
886
				$body = strip_tags($raw_body);
887
				if(strlen($body) > $truncsize) {
888
					$body = Utils::Utf8_truncate($body, $truncsize);
889
					$output->bodytruncated = 1;
890
				}
891
				else
892
				{
893
					$output->bodytruncated = 0;
894
				}
895
				$output->bodysize = strlen($body);
896
				$output->body = $body;
897
			}
898
			else // style with bodypreferences
899
			{
900
				//Select body type preference
901
				$bpReturnType = 1;//SYNC_BODYPREFERENCE_PLAIN;
902
				if ($bodypreference !== false) {
903
					// bodypreference can occur multiple times
904
					// usually we would use Utils::GetBodyPreferenceBestMatch($bodypreference);
905
					$bpReturnType = Utils::GetBodyPreferenceBestMatch($bodypreference);
906
/*
907
					foreach($bodypreference as $bpv)
908
					{
909
						// we use the last, or MIMEMESSAGE when present
910
						$bpReturnType = $bpv;
911
						if ($bpReturnType==SYNC_BODYPREFERENCE_MIME) break;
912
					}
913
*/
914
				}
915
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." getBodyPreferenceBestMatch: ".array2string($bpReturnType));
916
				// set the protocoll class
917
				$output->asbody = new SyncBaseBody();
918
919
				// return full mime-message without any (charset) conversation directly as stream
920
				if ($bpReturnType==SYNC_BODYPREFERENCE_MIME)
921
				{
922
					//SYNC_BODYPREFERENCE_MIME
923
					$output->asbody->type = SYNC_BODYPREFERENCE_MIME;
924
					$stream = $this->mail->getMessageRawBody($id, '', $_folderName, true);
925
					$fstat = fstat($stream);
926
					fseek($stream, 0, SEEK_SET);
927
					$output->asbody->data = $stream;
928
					$output->asbody->estimatedDataSize = $fstat['size'];
929
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." bodypreference 4=SYNC_BODYPREFERENCE_MIME=full mime message requested, size=$fstat[size]");
930
				}
931
				else
932
				{
933
					// fetch the body (try to gather data only once)
934
					$css ='';
935
					$bodyStruct = $this->mail->getMessageBody($id, 'html_only', '', null, true,$_folderName);
936
					if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only Struct:'.array2string($bodyStruct));
937
					$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
938
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$body);
939
					if ($body != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
940
						// may be html
941
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:html (fetched with html_only)');
942
						$css = $this->mail->getStyles($bodyStruct);
943
						$output->nativebodytype=2;
944
					} else {
945
						// plain text Message
946
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:plain, fetch text (HTML, if no text available)');
947
						$output->nativebodytype=1;
948
						$bodyStruct = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
949
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' plain text Struct:'.array2string($bodyStruct));
950
						$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
951
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' never display html(plain text only):'.$body);
952
					}
953
					// whatever format decode (using the correct encoding)
954 View Code Duplication
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__."MIME Body".' Type:'.($output->nativebodytype==2?' html ':' plain ').$body);
955
					//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
956
					// prepare plaintextbody
957
					$plainBody='';
958
					if ($output->nativebodytype == 2)
959
					{
960
						$bodyStructplain = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
961
						if(isset($bodyStructplain[0])&&isset($bodyStructplain[0]['error'])&&$bodyStructplain[0]['error']==1)
962
						{
963
							$plainBody = Api\Mail\Html::convertHTMLToText($body); // always display with preserved HTML
964
						}
965
						else
966
						{
967
							$plainBody = $this->mail->getdisplayableBody($this->mail,$bodyStructplain,false,false);
968
						}
969
					}
970
					//if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".$body);
971
					$plainBody = preg_replace("/<style.*?<\/style>/is", "", (strlen($plainBody)?$plainBody:$body));
972
					// remove all other html
973
					$plainBody = preg_replace("/<br.*>/is","\r\n",$plainBody);
974
					$plainBody = strip_tags($plainBody);
975
					if ($this->debugLevel>3 && $output->nativebodytype==1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Plain Text:'.$plainBody);
976
					//$body = str_replace("\n","\r\n", str_replace("\r","",$body)); // do we need that?
977
978
					if ($bpReturnType==2) //SYNC_BODYPREFERENCE_HTML
979
					{
980
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "HTML Body with requested pref 2");
981
						// Send HTML if requested and native type was html
982
						$output->asbody->type = 2;
983
						$htmlbody = '<html>'.
984
							'<head>'.
985
							'<meta name="Generator" content="Z-Push">'.
986
							'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'.
987
							$css.
988
							'</head>'.
989
							'<body>';
990
						if ($output->nativebodytype==2)
991
						{
992
							// as we fetch html, and body is HTML, we may not need to handle this
993
							$htmlbody .= $body;
994
						}
995
						else
996
						{
997
							// html requested but got only plaintext, so fake html
998
							$htmlbody .= str_replace("\n","<BR>",str_replace("\r","<BR>", str_replace("\r\n","<BR>",$plainBody)));
999
						}
1000
						$htmlbody .= '</body>'.
1001
								'</html>';
1002
1003 View Code Duplication
						if(isset($truncsize) && strlen($htmlbody) > $truncsize)
1004
						{
1005
							$htmlbody = Utils::Utf8_truncate($htmlbody,$truncsize);
1006
							$output->asbody->truncated = 1;
1007
						}
1008
						// output->nativebodytype is used as marker that the original message was of type ... but is now converted to, as type 2 is requested.
1009
						$output->nativebodytype = 2;
1010
						$output->asbody->data = StringStreamWrapper::Open($htmlbody);
1011
						$output->asbody->estimatedDataSize = strlen($htmlbody);
1012
					}
1013
					else
1014
					{
1015
						// Send Plaintext as Fallback or if original body is plainttext
1016
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "Plaintext Body:".$plainBody);
1017
						/* we use plainBody (set above) instead
1018
						$bodyStruct = $this->mail->getMessageBody($id,'only_if_no_text'); //'never_display');
1019
						$plain = $this->mail->getdisplayableBody($this->mail,$bodyStruct);
1020
						$plain = html_entity_decode($plain,ENT_QUOTES,$this->mail->detect_encoding($plain));
1021
						$plain = strip_tags($plain);
1022
						//$plain = str_replace("\n","\r\n",str_replace("\r","",$plain));
1023
						*/
1024
						$output->asbody->type = 1;
1025
						$output->nativebodytype = 1;
1026 View Code Duplication
						if(isset($truncsize) &&
1027
							strlen($plainBody) > $truncsize)
1028
						{
1029
							$plainBody = Utils::Utf8_truncate($plainBody, $truncsize);
1030
							$output->asbody->truncated = 1;
1031
						}
1032
						$output->asbody->data = StringStreamWrapper::Open((string)$plainBody !== '' ? $plainBody : ' ');
1033
						$output->asbody->estimatedDataSize = strlen($plainBody);
1034
					}
1035
					// In case we have nothing for the body, send at least a blank...
1036
					// dw2412 but only in case the body is not rtf!
1037
					if ($output->asbody->type != 3 && !isset($output->asbody->data))
1038
					{
1039
						$output->asbody->data = StringStreamWrapper::Open(" ");
1040
						$output->asbody->estimatedDataSize = 1;
1041
					}
1042
				}
1043
			}
1044
			// end AS12 Stuff
1045
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Header info:'.$headers['subject'].' from:'.$headers['date']);
1046
			$output->read = $headers["flags"];
1047
1048
			$output->flag = new SyncMailFlags();
1049
			if ($headers['flagged'] == 1)
1050
			{
1051
				$output->flag->flagstatus = 2;
1052
				//$output->flag->flagtype = "Flag for Follow up";
1053
			} else {
1054
				$output->flag->flagstatus = 0;
1055
			}
1056
			if ($headers['answered'])
1057
			{
1058
				$output->lastverexecuted = AS_REPLYTOSENDER;
1059
			}
1060
			elseif ($headers['forwarded'])
1061
			{
1062
				$output->lastverexecuted = AS_FORWARD;
1063
			}
1064
			$output->subject = $headers['subject'];
1065
			$output->importance = $headers['priority'] > 3 ? 0 :
1066
				($headers['priority'] < 3 ? 2 : 1) ;
1067
			$output->datereceived = $this->mail->_strtotime($headers['date'],'ts',true);
1068
			$output->to = $headers['to_address'];
1069
			if ($headers['to']) $output->displayto = $headers['to_address']; //$headers['FETCHED_HEADER']['to_name']
1070
			$output->from = $headers['sender_address'];
1071
			if (isset($headers['cc_addresses']) && $headers['cc_addresses']) $output->cc = $headers['cc_addresses'];
1072
			if (isset($headers['reply_to_address']) && $headers['reply_to_address']) $output->reply_to = $headers['reply_to_address'];
1073
1074
			$output->messageclass = "IPM.Note";
1075
			if (stripos($headers['mimetype'],'multipart')!== false &&
1076
				stripos($headers['mimetype'],'signed')!== false)
1077
			{
1078
				$output->messageclass = "IPM.Note.SMIME.MultipartSigned";
1079
			}
1080
			if (Request::GetProtocolVersion() >= 12.0) {
1081
				$output->contentclass = "urn:content-classes:message";
1082
			}
1083
1084
			// start handle Attachments (include text/calendar multipart alternative)
1085
			$attachments = $this->mail->getMessageAttachments($id, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true, true, $_folderName);
1086
			// Attachments should not needed for MIME messages, so skip this part if bpReturnType==4
1087
			if (/*$bpReturnType != SYNC_BODYPREFERENCE_MIME &&*/ is_array($attachments) && count($attachments)>0)
1088
			{
1089
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Attachments for MessageID:'.$id.' found:'.count($attachments));
1090
				//error_log(__METHOD__.__LINE__.array2string($attachments));
1091
				foreach ($attachments as $key => $attach)
1092
				{
1093 View Code Duplication
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attach));
1094
1095
					// pass meeting requests to calendar plugin
1096
					if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1097
						isset($GLOBALS['egw_info']['user']['apps']['calendar']) &&
1098
						($attachment = $this->mail->getAttachment($id, $attach['partID'],0,false,false,$_folderName)) &&
1099
						($output->meetingrequest = calendar_zpush::meetingRequest($attachment['attachment'])))
1100
					{
1101
						//overwrite the globalobjId from calendar object, as: if you delete the mail, that is
1102
						//the meeting-request its using the globalobjid as reference and deletes both:
1103
						//mail AND meeting. we dont want this. accepting meeting requests with the mobile does nothing
1104
						$output->meetingrequest->globalobjid = activesync_backend::uid2globalObjId($id);
1105
						$output->messageclass = "IPM.Schedule.Meeting.Request";
1106
						//$output->messageclass = "IPM.Schedule.Meeting";
1107
						unset($attachment);
1108
						continue;	// do NOT add attachment as attachment
1109
					}
1110
					if (Request::GetProtocolVersion() >= 12.0) {
1111
						$attachment = new SyncBaseAttachment();
1112
						if (!isset($output->asattachments) || !is_array($output->asattachments))
1113
							$output->asattachments = array();
1114
						$attachment->estimatedDataSize = $attach['size'];
1115
						$attachment->method = 1;
1116
						$attachment->filereference = $folderid . ":" . $id . ":" . $attach['partID'];
1117
					} else {
1118
						$attachment = new SyncAttachment();
1119
						if (!isset($output->attachments) || !is_array($output->attachments))
1120
							$output->attachments = array();
1121
						$attachment->attsize = $attach['size'];
1122
						$attachment->attmethod = 1;
1123
						$attachment->attname = $folderid . ":" . $id . ":" . $attach['partID'];//$key;
1124
					}
1125
1126
					$attachment->displayname = $attach['name'];
1127
					//error_log(__METHOD__.__LINE__.'->'.$folderid . ":" . $id . ":" . $attach['partID']);
1128
1129
					$attachment->attoid = "";//isset($part->headers['content-id']) ? trim($part->headers['content-id']) : "";
1130
					//$attachment->isinline=0; // if not inline, do not use isinline
1131
					if (!empty($attach['cid']) && $attach['cid'] <> 'NIL' )
1132
					{
1133
						if ($bpReturnType != 4 && $attach['disposition'] == 'inline')
1134
						{
1135
							$attachment->isinline = true;
1136
						}
1137
						if (Request::GetProtocolVersion() >= 12.0) {
1138
							$attachment->method=1;
1139
							$attachment->contentid= str_replace(array("<",">"), "",$attach['cid']);
1140
						} else {
1141
							$attachment->attmethod=6;
1142
							$attachment->attoid = str_replace(array("<",">"), "",$attach['cid']);
1143
						}
1144
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-id']."'  ".$attachment->contentid);
1145
						$attachment->contenttype = trim($attach['mimeType']);
1146
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-type']."'  ".$attachment->contentid);
1147
					}
1148
					if (Request::GetProtocolVersion() >= 12.0) {
1149
						array_push($output->asattachments, $attachment);
1150
					} else {
1151
						array_push($output->attachments, $attachment);
1152
					}
1153
					unset($attachment);
1154
				}
1155
			}
1156
			//$this->debugLevel=0;
1157
			// end handle Attachments
1158
			unset($attachments);
1159
1160
            // Language Code Page ID: http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx
1161
            $output->internetcpid = INTERNET_CPID_UTF8;
1162
1163 View Code Duplication
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($output));
1164
//$this->debugLevel=0;
1165
			return $output;
1166
		}
1167
//$this->debugLevel=0;
1168
		return false;
1169
	}
1170
1171
	/**
1172
	 * Process response to meeting request
1173
	 *
1174
	 * mail plugin only extracts the iCal attachment and let's calendar plugin deal with adding it
1175
	 *
1176
	 * @see BackendDiff::MeetingResponse()
1177
	 * @param string $folderid folder of meeting request mail
1178
	 * @param int|string $requestid uid of mail with meeting request
1179
	 * @param int $response 1=accepted, 2=tentative, 3=decline
1180
	 * @return int|boolean id of calendar item, false on error
1181
	 */
1182
	function MeetingResponse($folderid, $requestid, $response)
1183
	{
1184
		if (!class_exists('calendar_zpush'))
1185
		{
1186
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(...) no EGroupware calendar installed!");
1187
			return null;
1188
		}
1189
		if (!($stat = $this->StatMessage($folderid, $requestid)))
1190
		{
1191
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning FALSE (can NOT stat message)");
1192
			return false;
1193
		}
1194
		$ret = false;
1195
		foreach($this->mail->getMessageAttachments($requestid, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true) as $key => $attach)
1196
		{
1197
			if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1198
				($attachment = $this->mail->getAttachment($requestid, $attach['partID'],0,false)))
1199
			{
1200
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) iCal found, calling now backend->MeetingResponse('$attachment[attachment]')");
1201
1202
				// calling backend again with iCal attachment, to let calendar add the event
1203
				$ret = $this->backend->MeetingResponse($attachment['attachment'],
1204
					$this->backend->createID('calendar',$GLOBALS['egw_info']['user']['account_id']),
1205
					$response);
1206
				break;
1207
			}
1208
		}
1209
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning ".array2string($ret));
1210
		return $ret;
1211
	}
1212
1213
	/**
1214
	 * GetAttachmentData
1215
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1216
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1217
	 * encode any information you need to find the attachment in that 'attname' property.
1218
	 *
1219
     * @param string $fid - id
1220
     * @param string $attname - should contain (folder)id
1221
	 * @return SyncItemOperationsAttachment-object
1222
	 */
1223
	function GetAttachmentData($fid,$attname) {
1224
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1225
		return $this->_GetAttachmentData($fid,$attname);
1226
	}
1227
1228
	/**
1229
	 * ItemOperationsGetAttachmentData
1230
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1231
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1232
	 * encode any information you need to find the attachment in that 'attname' property.
1233
	 *
1234
     * @param string $fid - id
1235
     * @param string $attname - should contain (folder)id
1236
	 * @return SyncItemOperationsAttachment-object
1237
	 */
1238
	function ItemOperationsGetAttachmentData($fid,$attname) {
1239
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1240
		return $this->_GetAttachmentData($fid,$attname);
1241
	}
1242
1243
	/**
1244
	 * _GetAttachmentData implements
1245
	 * -ItemOperationsGetAttachmentData
1246
	 * -GetAttachmentData
1247
	 *
1248
     * @param string $fid - id
1249
     * @param string $attname - should contain (folder)id
1250
	 * @return SyncItemOperationsAttachment-object
1251
	 */
1252
	private function _GetAttachmentData($fid,$attname)
1253
	{
1254
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')".function_backtrace());
1255
		//error_log(__METHOD__.__LINE__." Fid: $fid (attname: '$attname')");
1256
		list($folderid, $id, $part) = explode(":", $attname);
1257
1258
		$this->splitID($folderid, $account, $folder);
1259
1260 View Code Duplication
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1261
1262
		$this->mail->reopen($folder);
1263
		$attachment = $this->mail->getAttachment($id,$part,0,false,true,$folder);
1264
		$SIOattachment = new SyncItemOperationsAttachment();
1265
		fseek($attachment['attachment'], 0, SEEK_SET);	// z-push requires stream seeked to start
1266
		$SIOattachment->data = $attachment['attachment'];
1267
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname') Data:".$attachment['attachment']);
1268
		if (isset($attachment['type']) )
1269
			$SIOattachment->contenttype = $attachment['type'];
1270
1271
		unset($attachment);
1272
1273
		return $SIOattachment;
1274
	}
1275
1276
	/**
1277
	 * StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
1278
	 *
1279
	 * 'id'	 => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
1280
	 * 'flags'	 => simply '0' for unread, '1' for read
1281
	 * 'mod'	=> modification signature. As soon as this signature changes, the item is assumed to be completely
1282
	 *			 changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
1283
	 *			 time for this field, which will change as soon as the contents have changed.
1284
	 *
1285
	 * @param string $folderid
1286
	 * @param int $id id (uid) of message
1287
	 * @return array
1288
	 */
1289
	public function StatMessage($folderid, $id)
1290
	{
1291
		$messages = $this->fetchMessages($folderid, NULL, $id);
1292
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid','$id') returning ".array2string($messages[$id]));
1293
		return $messages[$id];
1294
	}
1295
1296
	/**
1297
	 * Called when a message has been changed on the mobile.
1298
	 * Added support for FollowUp flag
1299
	 *
1300
	 * @param string              $folderid            id of the folder
1301
	 * @param string              $id                  id of the message
1302
	 * @param SyncXXX             $message             the SyncObject containing a message
1303
	 * @param ContentParameters   $contentParameters
1304
	 *
1305
	 * @access public
1306
	 * @return array                        same return value as StatMessage()
1307
	 * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1308
	 */
1309
	function ChangeMessage($folderid, $id, $message, $contentParameters)
1310
	{
1311
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." $folderid, $id,".array2string($message).",".array2string($contentParameters));
1312
		//unset($folderid, $id, $message, $contentParameters);
1313
		$account = $folder = null;
1314
		$this->splitID($folderid, $account, $folder);
1315
		if (isset($message->flag)) {
1316
			if (isset($message->flag->flagstatus) && $message->flag->flagstatus == 2) {
1317
				$rv = $this->mail->flagMessages((($message->flag->flagstatus == 2) ? "flagged" : "unflagged"), $id,$folder);
1318
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . (($message->flag->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
1319
			} else {
1320
				$rv = $this->mail->flagMessages("unflagged", $id,$folder);
1321
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . "unflagged" . "-->". $rv);
1322
			}
1323
		}
1324
		return $this->StatMessage($folderid, $id);
1325
	}
1326
1327
	/**
1328
	 * This function is called when the user moves an item on the PDA. You should do whatever is needed
1329
	 * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
1330
	 * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
1331
	 * at all on the source folder, and the destination folder will show the new message
1332
	 *
1333
	 * @param string              $folderid            id of the source folder
1334
	 * @param string              $id                  id of the message
1335
	 * @param string              $newfolderid         id of the destination folder
1336
	 * @param ContentParameters   $contentParameters
1337
	 *
1338
	 * @return boolean                      status of the operation
1339
	 * @throws StatusException              could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
1340
	 */
1341
	public function MoveMessage($folderid, $id, $newfolderid, $contentParameters)
1342
	{
1343
		unset($contentParameters);	// not used, but required by function signature
1344
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (sfid: '$folderid'  id: '$id'  dfid: '$newfolderid' )");
1345
		$account = $srcFolder = $destFolder = null;
1346
		$this->splitID($folderid, $account, $srcFolder);
1347
		$this->splitID($newfolderid, $account, $destFolder);
1348
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (SourceFolder: '$srcFolder'  id: '$id'  DestFolder: '$destFolder' )");
1349 View Code Duplication
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1350
		$this->mail->reopen($destFolder);
1351
		$status = $this->mail->getFolderStatus($destFolder);
1352
		$uidNext = $status['uidnext'];
1353
		$this->mail->reopen($srcFolder);
1354
1355
		// move message
1356
		$rv = $this->mail->moveMessages($destFolder,(array)$id,true,$srcFolder,true);
1357
		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
1358
		// return the new id "as string"
1359
		return ($rv===true ? $uidNext : $rv[$id]) . "";
1360
	}
1361
1362
	/**
1363
	 *  Get all messages of a folder with optional cutoffdate
1364
	 *
1365
	 *  @param int $cutoffdate =null timestamp with cutoffdate, default 12 weeks
1366
	 */
1367
	public function GetMessageList($folderid, $cutoffdate=NULL)
1368
	{
1369
		if ($cutoffdate > 0)
1370
		{
1371
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.' for Folder:'.$folderid.' SINCE:'.$cutoffdate.'/'.date("d-M-Y", $cutoffdate));
1372
		}
1373
		else
1374
		{
1375
			$maximumSyncRangeInDays = self::PAST_LIMIT; // corresponds to our default value
1376 View Code Duplication
			if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange']))
1377
			{
1378
				$maximumSyncRangeInDays = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange'];
1379
			}
1380
			$cutoffdate = (is_numeric($maximumSyncRangeInDays) ? Api\DateTime::to('now','ts')-(3600*24*$maximumSyncRangeInDays):null);
1381
			if (is_numeric($maximumSyncRangeInDays)) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' Client set no truncationdate. Using '.$maximumSyncRangeInDays.' days.'.date("d-M-Y", $cutoffdate));
1382
		}
1383
		try {
1384
			return $this->fetchMessages($folderid, $cutoffdate);
1385
		} catch (Exception $e)
1386
		{
1387
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' failed for '.$e->getMessage().($e->details?$e->details:''));
0 ignored issues
show
Bug introduced by
The property details does not seem to exist in Exception.

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

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

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