Completed
Push — master ( 334697...5c2723 )
by Ralf
20:28
created

mail_zpush::AlterPingChanges()   B

Complexity

Conditions 7

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nop 2
dl 0
loc 27
rs 8.8333
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Mail. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
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
0 ignored issues
show
Bug introduced by
The type activesync_plugin_meeting_response was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type activesync_plugin_search_mailbox was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type activesync_plugin_sendmail was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
introduced by
The private property $messages is not used, and could 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)
0 ignored issues
show
Bug introduced by
The type activesync_backend was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
		if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID:'.array2string(self::$profileID));
100
101
		if (is_null(self::$profileID))
102
		{
103
			if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' self::ProfileID isNUll:'.array2string(self::$profileID));
104
			self::$profileID =& Api\Cache::getSession('mail','activeSyncProfileID');
105
			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
		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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$settings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $settings = array(); before regardless.
Loading history...
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),
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with self::PAST_LIMIT. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
			'label'  => /** @scrutinizer ignore-call */ lang('How many days to sync in the past when client does not specify a date-range (default %1)', self::PAST_LIMIT),

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. Please note the @ignore annotation hint above.

Loading history...
181
			'name'   => 'mail-maximumSyncRange',
182
			'help'   => 'if the client sets no sync range, you may override the setting (preventing client crash that may be caused by too many mails/too much data). If you want to sync way-back into the past: set a large number',
183
			'xmlrpc' => True,
184
			'admin'  => False,
185
		);
186
187
/*
188
		$sigOptions = array(
189
				'send'=>'yes, always add EGroupware signatures to outgoing mails',
190
				'nosend'=>'no, never add EGroupware signatures to outgoing mails',
191
			);
192
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group'))&&$hook_data['account_id'])
193
		{
194
			$pID=self::$profileID;
195
			if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']=='G')
196
			{
197
				$pID=Mail\Account::get_default_acc_id();
198
			}
199
			$acc = Mail\Account::read($pID);
200
			error_log(__METHOD__.__LINE__.':'.$pID.'->'.array2string($acc));
201
			$Identities = Mail\Account::identities($pID);
202
			foreach($Identities as &$identity)
203
			{
204
				$Identity = self::identity_name($identity);
205
			}
206
			error_log(__METHOD__.__LINE__.array2string($Identities));
207
		}
208
		$settings['mail-useSignature'] = array(
209
			'type'   => 'select',
210
			'label'  => 'control if and which available signature is added to outgoing mails',
211
			'name'   => 'mail-useSignature',
212
			'help'   => 'control the use of signatures',
213
			'values' => $sigOptions,
214
			'xmlrpc' => True,
215
			'default' => 'nosend',
216
			'admin'  => False,
217
		);
218
*/
219
		return $settings;
220
	}
221
222
	/**
223
	 * Verify preferences
224
	 *
225
	 * @param array|string $hook_data
226
	 * @return array with error-messages from all plugins
227
	 */
228
	function verify_settings($hook_data)
229
	{
230
		$errors = array();
231
232
		// check if an eSync eMail profile is set (might not be set as default or forced!)
233
		if (isset($hook_data['prefs']['mail-ActiveSyncProfileID']) || $hook_data['type'] == 'user')
234
		{
235
			// eSync and eMail translations are not (yet) loaded
236
			Api\Translation::add_app('activesync');
237
			Api\Translation::add_app('mail');
238
239
			// inject preference to verify and call constructor
240
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] =
241
				$hook_data['prefs']['mail-ActiveSyncProfileID'];
242
			$this->__construct($this->backend);
243
244
			try {
245
				$this->_connect(0,true);
0 ignored issues
show
Unused Code introduced by
The call to mail_zpush::_connect() has too many arguments starting with true. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

245
				$this->/** @scrutinizer ignore-call */ 
246
           _connect(0,true);

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. Please note the @ignore annotation hint above.

Loading history...
246
				$this->_disconnect();
247
248
				if (!$this->_wasteID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('trash').'</b>');
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with '<b>' . lang('trash') . '</b>'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

248
				if (!$this->_wasteID) $errors[] = /** @scrutinizer ignore-call */ lang('No valid %1 folder configured!', '<b>'.lang('trash').'</b>');

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. Please note the @ignore annotation hint above.

Loading history...
249
				if (!$this->_sentID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('send').'</b>');
250
			}
251
			catch(Exception $e) {
252
				$errors[] = lang('Can not open IMAP connection').': '.$e->getMessage();
253
			}
254
			if ($errors)
255
			{
256
				$errors[] = '<b>'.lang('eSync will FAIL without a working eMail configuration!').'</b>';
257
			}
258
		}
259
		//error_log(__METHOD__.'('.array2string($hook_data).') returning '.array2string($errors));
260
		return $errors;
261
	}
262
263
	/**
264
	 * Open IMAP connection
265
	 *
266
	 * @param int $account integer id of account to use
267
	 * @todo support different accounts
268
	 */
269
	private function _connect($account=0)
270
	{
271
		if (!$account) $account = self::$profileID ? self::$profileID : 0;
272
		if ($this->mail && $this->account != $account) $this->_disconnect();
273
274
		$this->_wasteID = false;
275
		$this->_sentID = false;
276
277
		if (!$this->mail)
278
		{
279
			$this->account = $account;
280
			// todo: tell mail which account to use
281
			//error_log(__METHOD__.__LINE__.' create object with ProfileID:'.array2string(self::$profileID));
282
			$this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
283
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
284
		}
285
		else
286
		{
287
			//error_log(__METHOD__.__LINE__." connect with profileID: ".self::$profileID);
288
			if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
289
		}
290
		$this->mail->openConnection(self::$profileID,false);
0 ignored issues
show
Unused Code introduced by
The call to EGroupware\Api\Mail::openConnection() has too many arguments starting with false. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

290
		$this->mail->/** @scrutinizer ignore-call */ 
291
               openConnection(self::$profileID,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. Please note the @ignore annotation hint above.

Loading history...
291
292
		$this->_wasteID = $this->mail->getTrashFolder(false);
293
		//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
294
		$this->_sentID = $this->mail->getSentFolder(false);
295
		$this->mail->getOutboxFolder(true);
296
		//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
297
		//error_log(__METHOD__.__LINE__.' Connection Status for ProfileID:'.self::$profileID.'->'.$this->mail->icServer->_connected);
298
	}
299
300
	/**
301
	 * Close IMAP connection
302
	 */
303
	private function _disconnect()
304
	{
305
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__);
306
		if ($this->mail) $this->mail->closeConnection();
307
308
		unset($this->mail);
309
		unset($this->account);
310
		unset($this->folders);
311
	}
312
313
	/**
314
	 *  GetFolderList
315
	 *
316
	 *  @ToDo loop over available email accounts
317
	 */
318
	public function GetFolderList()
319
	{
320
		$folderlist = array();
321
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
322
		/*foreach($available_accounts as $account)*/ $account = 0;
323
		{
324
			$this->_connect($account);
325
			if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false,$_alwaysGetDefaultFolders=true);
326
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($this->folders));
327
328
			foreach ($this->folders as $folder => $folderObj) {
329
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' folder='.$folder);
330
				$folderlist[] = $f = array(
331
					'id'     => $this->createID($account,$folder),
332
					'mod'    => $folderObj->shortDisplayName,
333
					'parent' => $this->getParentID($account,$folder),
334
				);
335
				if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($f));
336
			}
337
		}
338
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() returning ".array2string($folderlist));
339
340
		return $folderlist;
341
	}
342
343
    /**
344
     * Sends a message which is passed as rfc822. You basically can do two things
345
     * 1) Send the message to an SMTP server as-is
346
     * 2) Parse the message yourself, and send it some other way
347
     * It is up to you whether you want to put the message in the sent items folder. If you
348
     * want it in 'sent items', then the next sync on the 'sent items' folder should return
349
     * the new message as any other new message in a folder.
350
     *
351
     * @param array $smartdata = IMAP-SendMail: SyncSendMail (
352
     *        (S) clientid => SendMail-30722448149304
353
     *        (S) saveinsent => empty
354
     *        (S) replacemime => null
355
     *        (S) accountid => null
356
     *        (S) source => SyncSendMailSource (
357
     *                                (S) folderid => 101000000000
358
     *                                (S) itemid => 33776
359
     *                                (S) longid => null
360
     *                                (S) instanceid => null
361
     *                                unsetVars(Array) size: 0
362
     *                                flags => false
363
     *                                content => null
364
     *                        )
365
     *        (S) mime => Date: Tue, 23 Jun 2015 14:13:23 +0200
366
     *Subject: AW: Blauer himmel
367
     *....
368
     *        (S) replyflag => true
369
     *        (S) forwardflag => null
370
     *        unsetVars(Array) size: 0
371
     *        flags => false
372
     *        content => null
373
     *)
374
	 *
375
     * @return boolean true on success, false on error
376
     *
377
     * @see eg. BackendIMAP::SendMail()
378
     * @todo implement either here or in mail backend
379
     * 	(maybe sending here and storing to sent folder in plugin, as sending is supposed to always work in EGroupware)
380
     */
381
	public function SendMail($smartdata)
382
	{
383
		//$this->debugLevel=2;
384
		$ClientSideMeetingRequest = false;
385
		$allowSendingInvitations = 'sendifnocalnotif';
386
		if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
387
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']=='nosend')
388
		{
389
			$allowSendingInvitations = false;
390
		}
391
		elseif (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
392
			$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']!='nosend')
393
		{
394
			$allowSendingInvitations = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations'];
395
		}
396
		$smartdata_task = ($smartdata->replyflag?'reply':($smartdata->forwardflag?'forward':'new'));
397
398
   		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__ . (isset($smartdata->mime) ? $smartdata->mime : ""). "task: ".(isset($smartdata_task) ? $smartdata_task : "")." itemid: ".(isset($smartdata->source->itemid) ? $smartdata->source->itemid : "")." folder: ".(isset($smartdata->source->folderid) ? $smartdata->source->folderid : ""));
399
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Smartdata = ".array2string($smartdata));
400
		//error_log("IMAP-Sendmail: Smartdata = ".array2string($smartdata));
401
402
		// initialize our Mail
403
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
404
		$activeMailProfiles = $this->mail->getAccountIdentities(self::$profileID);
405
		// use the standardIdentity
406
		$activeMailProfile = Mail::getStandardIdentityForProfile($activeMailProfiles,self::$profileID);
407
408
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.")".' ProfileID:'.self::$profileID.' ActiveMailProfile:'.array2string($activeMailProfile));
409
		// collect identity / signature for later usage, and to determine if we may have to manipulate TransferEncoding and Charset
410
		try
411
		{
412
			$acc = Mail\Account::read($this->mail->icServer->ImapServerId);
413
			//error_log(__METHOD__.__LINE__.array2string($acc));
414
			$_signature = Mail\Account::read_identity($acc['ident_id'],true);
415
		}
416
		catch (Exception $e)
417
		{
418
			$_signature=array();
419
		}
420
		$signature = $_signature['ident_signature'];
421
		if ((isset($preferencesArray['disableRulerForSignatureSeparation']) &&
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $preferencesArray seems to never exist and therefore isset should always be false.
Loading history...
422
			$preferencesArray['disableRulerForSignatureSeparation']) ||
423
			empty($signature) || trim(Api\Mail\Html::convertHTMLToText($signature)) =='')
424
		{
425
			$disableRuler = true;
426
		}
427
		$beforePlain = $beforeHtml = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $beforeHtml is dead and can be removed.
Loading history...
Unused Code introduced by
The assignment to $beforePlain is dead and can be removed.
Loading history...
428
		$beforeHtml = ($disableRuler ?'&nbsp;<br>':'&nbsp;<br><hr style="border:dotted 1px silver; width:90%; border:dotted 1px silver;">');
429
		$beforePlain = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
430
		$sigText = Mail::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
431
		if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Signature to use:'.$sigText);
432
		$sigTextHtml = $beforeHtml.$sigText;
433
		$sigTextPlain = $beforePlain.Api\Mail\Html::convertHTMLToText($sigText);
434
435
		$force8bit=false;
436
		if (Api\Translation::detect_encoding($sigTextPlain)!='ascii') $force8bit=true;
437
		// beware. the section below might cause trouble regarding bcc and attachments, so maybe this is to be handeled differently
438
		if ($force8bit)
439
		{
440
			$converterObj =  new Api\Mailer('initbasic');
441
			$smartdata->mime = $converterObj->convertMessageTextParts($smartdata->mime,false,'utf-8');
442
		}
443
		// initialize the new Api\Mailer object for sending
444
		$mailObject = new Api\Mailer(self::$profileID);
445
446
		$this->mail->parseRawMessageIntoMailObject($mailObject,$smartdata->mime,$force8bit);
447
		// Horde SMTP Class uses utf-8 by default. as we set charset always to utf-8
448
		$mailObject->Sender  = $activeMailProfile['ident_email'];
0 ignored issues
show
Bug introduced by
The property Sender does not seem to exist on EGroupware\Api\Mailer.
Loading history...
449
		$mailObject->setFrom($activeMailProfile['ident_email'],Mail::generateIdentityString($activeMailProfile,false));
450
		$mailObject->addHeader('X-Mailer', 'mail-Activesync');
451
452
		// prepare addressee list; moved the adding of addresses to the mailobject down
453
		// to
454
		foreach(Mail::parseAddressList($mailObject->getHeader("To")) as $addressObject) {
455
			if (!$addressObject->valid) continue;
456
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail To: ".array2string($addressObject) );
457
			//$mailObject->AddAddress($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
458
			$toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
459
		}
460
		// CC
461
		foreach(Mail::parseAddressList($mailObject->getHeader("Cc")) as $addressObject) {
462
			if (!$addressObject->valid) continue;
463
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail CC: ".array2string($addressObject) );
464
			//$mailObject->AddCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
465
			$ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
466
		}
467
		// BCC
468
		foreach($mailObject->getAddresses('bcc') as $addressObject) {
469
			if (!$addressObject->valid) continue;
470
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") Header Sentmail BCC: ".array2string($addressObject) );
471
			//$mailObject->AddBCC($addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : ''),$addressObject->personal);
472
			$bccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
473
		}
474
		$mailObject->clearAllRecipients();
475
476
		$use_orgbody = false;
477
478
		$k = 'Content-Type';
479
		$ContentType =$mailObject->getHeader('Content-Type');
480
		//error_log(__METHOD__.__LINE__." Header Sentmail original Header (filtered): " . $k.  " = ".trim($ContentType));
481
		// if the message is a multipart message, then we should use the sent body
482
		if (preg_match("/multipart/i", $ContentType)) {
483
			$use_orgbody = true;
484
		}
485
486
		// save the original content-type header for the body part when forwarding
487
		if ($smartdata_task == 'forward' && $smartdata->source->itemid && !$use_orgbody) {
488
			//continue; // ignore
489
		}
490
		// horde/egw_ mailer does everything as utf-8, the following should not be needed
491
		//$org_charset = $ContentType;
492
		//$ContentType = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $ContentType);
493
		// if the message is a multipart message, then we should use the sent body
494
		if (($smartdata_task == 'new' || $smartdata_task == 'reply' || $smartdata_task == 'forward') &&
495
			((isset($smartdata->replacemime) && $smartdata->replacemime == true) ||
496
			$k == "Content-Type" && preg_match("/multipart/i", $ContentType))) {
497
			$use_orgbody = true;
498
		}
499
		$Body =  $AltBody = "";
0 ignored issues
show
Unused Code introduced by
The assignment to $Body is dead and can be removed.
Loading history...
500
		// get body of the transmitted message
501
		// if this is a simple message, no structure at all
502
		if (preg_match("/text/i", $ContentType))
503
		{
504
			$simpleBodyType = (preg_match("/html/i", $ContentType)?'text/html':'text/plain');
505
			$bodyObj = $mailObject->findBody(preg_match("/html/i", $ContentType) ? 'html' : 'plain');
506
			$body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]", $bodyObj ?$bodyObj->getContents() : null);
0 ignored issues
show
introduced by
$bodyObj is of type Horde_Mime_Part, thus it always evaluated to true.
Loading history...
507
			if  ($simpleBodyType == "text/plain")
508
			{
509
				$Body = $body;
510
				$AltBody = "<pre>".nl2br($body)."</pre>";
511
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created AltBody');
512
			}
513
			else
514
			{
515
				$AltBody = $body;
516
				$Body =  trim(Api\Mail\Html::convertHTMLToText($body));
517
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as :". $simpleBodyType.'=> Created Body');
518
			}
519
		}
520
		else
521
		{
522
			// if this is a structured message
523
			// prefer plain over html
524
			$Body = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
525
				($text_body = $mailObject->findBody('plain')) ? $text_body->getContents() : null);
526
			$AltBody = preg_replace("/(<|&lt;)*(([\w\.,-.,_.,0-9.]+)@([\w\.,-.,_.,0-9.]+))(>|&gt;)*/i","[$2]",
527
				($html_body = $mailObject->findBody('html')) ? $html_body->getContents() : null);
528
		}
529
		if ($this->debugLevel>1 && $Body) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as with MessageContentType:". $ContentType.'=>'.$Body);
0 ignored issues
show
Bug introduced by
Are you sure $ContentType of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

529
		if ($this->debugLevel>1 && $Body) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched Body as with MessageContentType:". /** @scrutinizer ignore-type */ $ContentType.'=>'.$Body);
Loading history...
530
		if ($this->debugLevel>1 && $AltBody) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") fetched AltBody as with MessageContentType:". $ContentType.'=>'.$AltBody);
531
		//error_log(__METHOD__.__LINE__.array2string($mailObject));
532
		// if this is a multipart message with a boundary, we must use the original body
533
		//if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' mailObject after Inital Parse:'.array2string($mailObject));
534
        if ($use_orgbody) {
535
    	    ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") use_orgbody = true ContentType:".$ContentType);
536
 			// if it is a ClientSideMeetingRequest, we report it as send at all times
537
			if (($cal_body = $mailObject->findBody('calendar')) &&
538
				($cSMRMethod = $cal_body->getContentTypeParameter('method')))
539
			{
540
				if ($cSMRMethod == 'REPLY' && class_exists('calendar_ical'))
541
				{
542
					$organizer = calendar_ical::getIcalOrganizer($cal_body->getContents());
543
				}
544
				if ($this->debugLevel) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") we have a Client Side Meeting Request from organizer=$organizer");
545
				$ClientSideMeetingRequest = true;
546
			}
547
        }
548
		// now handle the addressee list
549
		$toCount = 0;
550
		//error_log(__METHOD__.__LINE__.array2string($toMailAddr));
551
		foreach((array)$toMailAddr as $address) {
552
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
553
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
554
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' &&
555
					calendar_boupdate::email_update_requested($emailAddress, isset($cSMRMethod) ? $cSMRMethod : 'REQUEST',
556
						$organizer && !strcasecmp($emailAddress, $organizer) ? 'CHAIR' : ''))
557
				{
558
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") skiping mail to organizer '$organizer', as it will be send by calendar app");
559
					continue;
560
				}
561
				$mailObject->AddAddress($emailAddress, $addressObject->personal);
562
				$toCount++;
563
			}
564
		}
565
		$ccCount = 0;
566
		foreach((array)$ccMailAddr as $address) {
567
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
568
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
569
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
570
				$mailObject->AddCC($emailAddress, $addressObject->personal);
571
				$ccCount++;
572
			}
573
		}
574
		$bccCount = 0;
575
		foreach((array)$bccMailAddr as $address) {
576
			foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
577
				$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
578
				if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
579
				$mailObject->AddBCC($emailAddress, $addressObject->personal);
580
				$bccCount++;
581
			}
582
		}
583
		// typical organizer reply will end here with nothing send --> return true, because we suppressed the send above
584
		if ($toCount+$ccCount+$bccCount == 0)
585
		{
586
			return $ClientSideMeetingRequest && $allowSendingInvitations === 'sendifnocalnotif' && $organizer ? true : 0; // noone to send mail to
587
		}
588
		if ($ClientSideMeetingRequest === true && $allowSendingInvitations===false) return true;
589
		// as we use our mailer (horde mailer) it is detecting / setting the mimetype by itself while creating the mail
590
/*
591
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body:'.$body);
592
		$body = str_replace("\r",((preg_match("^text/html^i", $ContentType))?'<br>':""),$body); // what is this for?
593
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' retrieved Body (modified):'.$body);
594
*/
595
		// actually use prepared signature --------------------collected earlier--------------------------
596
		$isreply = $isforward = false;
597
		// reply ---------------------------------------------------------------------------
598
		if ($smartdata_task == 'reply' && isset($smartdata->source->itemid) &&
599
			isset($smartdata->source->folderid) && $smartdata->source->itemid && $smartdata->source->folderid &&
600
			(!isset($smartdata->replacemime) ||
601
			(isset($smartdata->replacemime) && $smartdata->replacemime == false)))
602
		{
603
			// now get on, and fetch the original mail
604
			$uid = $smartdata->source->itemid;
605
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP Smartreply is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
606
			$this->splitID($smartdata->source->folderid, $account, $folder);
607
608
			$this->mail->reopen($folder);
609
			$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
610
			$bodyBUFFHtml = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
611
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$bodyBUFFHtml);
612
		    if ($bodyBUFFHtml != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
613
				// may be html
614
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:html (fetched with html_only):'.$bodyBUFFHtml);
615
				$AltBody = $AltBody."</br>".$bodyBUFFHtml.$sigTextHtml;
616
				$isreply = true;
617
			}
618
			// plain text Message part
619
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain, fetch text:');
620
			// if the new part of the message is html, we must preserve it, and handle that the original mail is text/plain
621
			$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
622
			$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
623
			if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/plain')) {
624
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain (fetched with never_display):'.$bodyBUFF);
625
				$Body = $Body."\r\n".$bodyBUFF.$sigTextPlain;
626
				$isreply = true;
627
			}
628
			if (!empty($bodyBUFF) && empty($bodyBUFFHtml) && !empty($AltBody))
629
			{
630
				$isreply = true;
631
				$AltBody = $AltBody."</br><pre>".nl2br($bodyBUFF).'</pre>'.$sigTextHtml;
632
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." no Api\Html Body found use modified plaintext body for txt/html: ".$AltBody);
633
			}
634
		}
635
636
		// how to forward and other prefs
637
		$preferencesArray =& $GLOBALS['egw_info']['user']['preferences']['mail'];
638
639
		// forward -------------------------------------------------------------------------
640
		if ($smartdata_task == 'forward' && isset($smartdata->source->itemid) &&
641
			isset($smartdata->source->folderid) && $smartdata->source->itemid && $smartdata->source->folderid &&
642
			(!isset($smartdata->replacemime) ||
643
			(isset($smartdata->replacemime) && $smartdata->replacemime == false)))
644
		{
645
			//force the default for the forwarding -> asmail
646
			if (is_array($preferencesArray)) {
647
				if (!array_key_exists('message_forwarding',$preferencesArray)
648
					|| !isset($preferencesArray['message_forwarding'])
649
					|| empty($preferencesArray['message_forwarding'])) $preferencesArray['message_forwarding'] = 'asmail';
650
			} else {
651
				$preferencesArray['message_forwarding'] = 'asmail';
652
			}
653
			// construct the uid of the message out of the itemid - seems to be the uid, no construction needed
654
			$uid = $smartdata->source->itemid;
655
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.")IMAP Smartfordward is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
656
			$this->splitID($smartdata->source->folderid, $account, $folder);
657
658
			$this->mail->reopen($folder);
659
            // receive entire mail (header + body)
660
			// get message headers for specified message
661
			$headers	= $this->mail->getMessageEnvelope($uid, $_partID, true, $folder);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $_partID seems to be never defined.
Loading history...
662
			// build a new mime message, forward entire old mail as file
663
			if ($preferencesArray['message_forwarding'] == 'asmail')
664
			{
665
				$rawHeader      = $this->mail->getMessageRawHeader($smartdata->source->itemid, $_partID,$folder);
666
				$rawBody        = $this->mail->getMessageRawBody($smartdata->source->itemid, $_partID,$folder);
667
				$mailObject->AddStringAttachment($rawHeader.$rawBody, $headers['SUBJECT'].'.eml', 'message/rfc822');
668
				$AltBody = $AltBody."</br>".lang("See Attachments for Content of the Orignial Mail").$sigTextHtml;
669
				$Body = $Body."\r\n".lang("See Attachments for Content of the Orignial Mail").$sigTextPlain;
670
				$isforward = true;
671
			}
672
			else
673
			{
674
				// now get on, and fetch the original mail
675
				$uid = $smartdata->source->itemid;
676
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP Smartreply is called with FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
677
				$this->splitID($smartdata->source->folderid, $account, $folder);
678
679
				$this->mail->reopen($folder);
680
				$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
681
				$bodyBUFFHtml = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
682
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$bodyBUFFHtml);
683
				if ($bodyBUFFHtml != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
684
					// may be html
685
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:html (fetched with html_only):'.$bodyBUFFHtml);
686
					$AltBody = $AltBody."</br>".$bodyBUFFHtml.$sigTextHtml;
687
					$isforward = true;
688
				}
689
				// plain text Message part
690
				if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain, fetch text:');
691
				// if the new part of the message is html, we must preserve it, and handle that the original mail is text/plain
692
				$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
693
				$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
694
				if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/plain')) {
695
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") MIME Body".' Type:plain (fetched with never_display):'.$bodyBUFF);
696
					$Body = $Body."\r\n".$bodyBUFF.$sigTextPlain;
697
					$isforward = true;
698
				}
699
				if (!empty($bodyBUFF) && empty($bodyBUFFHtml) && !empty($AltBody))
700
				{
701
					$AltBody = $AltBody."</br><pre>".nl2br($bodyBUFF).'</pre>'.$sigTextHtml;
702
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." no html Body found use modified plaintext body for txt/html: ".$AltBody);
703
					$isforward = true;
704
				}
705
				// get all the attachments and add them too.
706
				// start handle Attachments
707
				$attachments = $this->mail->getMessageAttachments($uid,null,null,true,false,true,$folder);
708
				$attachmentNames = false;
709
				if (is_array($attachments) && count($attachments)>0)
710
				{
711
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Attachments for BodyCreation of/for MessageID:'.$uid.' found:'.count($attachments));
712
					foreach((array)$attachments as $key => $attachment)
713
					{
714
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attachment));
715
						$attachmentNames .= $attachment['name']."\n";
716
						$attachmentData	= $this->mail->getAttachment($uid, $attachment['partID'],0,false,false,$folder);
717
						/*$x =*/ $mailObject->AddStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['mimeType']);
718
						ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' added part with number:'.$x);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $x seems to be never defined.
Loading history...
719
					}
720
				}
721
			}
722
		} // end forward
723
		// add signature, in case its not already added in forward or reply
724
		if (!$isreply && !$isforward)
725
		{
726
			//error_log(__METHOD__.__LINE__.'adding Signature');
727
			$Body = $Body.$sigTextPlain;
728
			$AltBody = $AltBody.$sigTextHtml;
729
		}
730
		// now set the body
731
		if ($AltBody && ($html_body = $mailObject->findBody('html')))
732
		{
733
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$AltBody);
734
			//error_log(__METHOD__.__LINE__.' html:'.$AltBody);
735
			$html_body->setContents($AltBody,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
736
		}
737
		if ($Body && ($text_body = $mailObject->findBody('plain')))
738
		{
739
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.$Body);
740
			//error_log(__METHOD__.__LINE__.' text:'.$Body);
741
			$text_body->setContents($Body,array('encoding'=>Horde_Mime_Part::DEFAULT_ENCODING));
742
		}
743
		//advanced debugging
744
		// Horde SMTP Class uses utf-8 by default.
745
        //ZLog::Write(LOGLEVEL_DEBUG, "IMAP-SendMail: parsed message: ". print_r($message,1));
746
		if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): MailObject:".array2string($mailObject));
747
748
		// set a higher timeout for big messages
749
		@set_time_limit(120);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

749
		/** @scrutinizer ignore-unhandled */ @set_time_limit(120);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
750
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> '.' about to send ....');
751
		// send
752
		$send = true;
753
		try {
754
			$mailObject->Send();
755
		}
756
		catch(Exception $e) {
757
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") The email could not be sent. Last-SMTP-error: ". $e->getMessage());
758
			$send = false;
759
		}
760
761
		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...
762
		{
763
			$uid = $smartdata->source->itemid;
764
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' tASK:'.$smartdata_task." FolderID:".$smartdata->source->folderid.' and ItemID:'.$smartdata->source->itemid);
765
			$this->splitID($smartdata->source->folderid, $account, $folder);
766
			//error_log(__METHOD__.__LINE__.' Folder:'.$folder.' Uid:'.$uid);
767
			$this->mail->reopen($folder);
768
			// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
769
			// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
770
			if ($this->mail->isDraftFolder($folder) && !$this->mail->isTemplateFolder($folder))
771
			{
772
				$this->mail->deleteMessages(array($uid),$folder);
773
			} else {
774
				$this->mail->flagMessages("answered", array($uid),$folder);
775
				if ($smartdata_task== "forward")
776
				{
777
					$this->mail->flagMessages("forwarded", array($uid),$folder);
778
				}
779
			}
780
		}
781
782
		$asf = ($send ? true:false); // initalize accordingly
783
		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...
784
		{
785
			$asf = false;
786
			$sentFolder = $this->mail->getSentFolder();
787
			if ($this->_sentID) {
788
				$folderArray[] = $this->_sentID;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$folderArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $folderArray = array(); before regardless.
Loading history...
789
			}
790
			else if(isset($sentFolder) && $sentFolder != 'none')
791
			{
792
				$folderArray[] = $sentFolder;
793
			}
794
			// No Sent folder set, try defaults
795
			else
796
			{
797
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__.") IMAP-SendMail: No Sent mailbox set");
798
				// we dont try guessing
799
				$asf = true;
800
			}
801
			if (count($folderArray) > 0) {
802
				foreach((array)$bccMailAddr as $address) {
803
					foreach(Mail::parseAddressList((get_magic_quotes_gpc()?stripslashes($address):$address)) as $addressObject) {
804
						$emailAddress = $addressObject->mailbox. ($addressObject->host ? '@'.$addressObject->host : '');
805
						$mailAddr[] = array($emailAddress, $addressObject->personal);
806
					}
807
				}
808
				//$BCCmail='';
809
				if (count($mailAddr)>0) $mailObject->forceBccHeader();
810
				//$BCCmail = $mailObject->AddrAppend("Bcc",$mailAddr);
811
				foreach($folderArray as $folderName) {
812
					if($this->mail->isSentFolder($folderName)) {
813
						$flags = '\\Seen';
814
					} elseif($this->mail->isDraftFolder($folderName)) {
815
						$flags = '\\Draft';
816
					} else {
817
						$flags = '';
818
					}
819
					$asf = true;
820
					//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.array2string($this->mail->icServer));
821
					$this->mail->openConnection(self::$profileID,false);
0 ignored issues
show
Unused Code introduced by
The call to EGroupware\Api\Mail::openConnection() has too many arguments starting with false. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

821
					$this->mail->/** @scrutinizer ignore-call */ 
822
                  openConnection(self::$profileID,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. Please note the @ignore annotation hint above.

Loading history...
822
					if ($this->mail->folderExists($folderName)) {
823
						try
824
						{
825
							$this->mail->appendMessage($folderName,$mailObject->getRaw(), null,
826
									$flags);
827
						}
828
						catch (Api\Exception\WrongUserinput $e)
829
						{
830
							//$asf = false;
831
							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()));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $mailObject->getHeader('Subject'). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

831
							ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'./** @scrutinizer ignore-call */ lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$mailObject->getHeader('Subject'),$folderName,$e->getMessage()));

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. Please note the @ignore annotation hint above.

Loading history...
832
						}
833
					}
834
					else
835
					{
836
						//$asf = false;
837
						ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$mailObject->getHeader('Subject'),$folderName));
838
					}
839
			        ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(".__LINE__."): Outgoing mail saved in configured 'Sent' folder '".$folderName."': ". (($asf)?"success":"failed"));
840
				}
841
				//$this->mail->closeConnection();
842
			}
843
		}
844
845
		$this->debugLevel=0;
846
847
		if ($send && $asf)
848
		{
849
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' -> send successfully');
850
			return true;
851
		}
852
		else
853
		{
854
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." returning ".($ClientSideMeetingRequest ? true : 120)." (MailSubmissionFailed)".($ClientSideMeetingRequest ?" is ClientSideMeetingRequest (we ignore the failure)":""));
0 ignored issues
show
Bug introduced by
Are you sure $ClientSideMeetingRequest ? true : 120 of type integer|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

854
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." returning ".(/** @scrutinizer ignore-type */ $ClientSideMeetingRequest ? true : 120)." (MailSubmissionFailed)".($ClientSideMeetingRequest ?" is ClientSideMeetingRequest (we ignore the failure)":""));
Loading history...
855
			return ($ClientSideMeetingRequest ? true : 120);   //MAIL Submission failed, see MS-ASCMD
856
		}
857
	}
858
859
	/**
860
	 * For meeting requests (iCal attachments with method='request') we call calendar plugin with iCal to get SyncMeetingRequest object,
861
	 * and do NOT return the attachment itself!
862
	 *
863
	 * @param string $folderid
864
	 * @param string $id
865
	 * @param ContentParameters $contentparameters  parameters of the requested message (truncation, mimesupport etc)
866
	 *  object with attributes foldertype, truncation, rtftruncation, conflict, filtertype, bodypref, deletesasmoves, filtertype, contentclass, mimesupport, conversationmode
867
	 *  bodypref object with attributes: ]truncationsize, allornone, preview
868
	 * @return $messageobject|boolean false on error
0 ignored issues
show
Documentation Bug introduced by
The doc comment $messageobject|boolean at position 0 could not be parsed: Unknown type name '$messageobject' at position 0 in $messageobject|boolean.
Loading history...
869
	 */
870
	public function GetMessage($folderid, $id, $contentparameters)
871
	{
872
		//$this->debugLevel=4;
873
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' ContentParams='.array2string($contentparameters));
874
		$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
875
		$mimesupport = $contentparameters->GetMimeSupport();
876
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."() truncsize=$truncsize, mimeSupport=".array2string($mimesupport));
877
		$bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */
878
879
		// fix for z-push bug returning additional bodypreference type 4, even if only 1 is requested and mimessupport = 0
880
		if (!$mimesupport && ($key = array_search('4', $bodypreference))) unset($bodypreference[$key]);
881
882
		//$this->debugLevel=4;
883
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
884
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' TruncSize:'.$truncsize.' Bodypreference: '.array2string($bodypreference));
885
		$account = $_folderName = $xid = null;
886
		$this->splitID($folderid,$account,$_folderName,$xid);
887
		$this->mail->reopen($_folderName);
888
		$messages = $this->fetchMessages($folderid, NULL, $id, true);	// true: return all headers
889
		$headers = $messages[$id];
890
		if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($headers));
891
		// StatMessage should reopen the folder in question, so we dont need folderids in the following statements.
892
		if ($headers)
893
		{
894
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." Message $id with stat ".array2string($headers));
895
			// initialize the object
896
			$output = new SyncMail();
897
			//$rawHeaders = $this->mail->getMessageRawHeader($id);
898
			// simple style
899
			// start AS12 Stuff (bodypreference === false) case = old behaviour
900
			if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__. ' for message with ID:'.$id.' with headers:'.array2string($headers));
901
902
			if ($bodypreference === false) {
903
				$bodyStruct = $this->mail->getMessageBody($id, 'only_if_no_text', '', null, true,$_folderName);
904
				$raw_body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
905
				//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
906
				if (stripos($raw_body,'<style')!==false) $body = preg_replace("/<style.*?<\/style>/is", "", $raw_body); // in case there is only a html part
0 ignored issues
show
Unused Code introduced by
The assignment to $body is dead and can be removed.
Loading history...
907
				// remove all other html
908
				$body = strip_tags($raw_body);
909
				if(strlen($body) > $truncsize) {
910
					$body = Utils::Utf8_truncate($body, $truncsize);
911
					$output->bodytruncated = 1;
912
				}
913
				else
914
				{
915
					$output->bodytruncated = 0;
916
				}
917
				$output->bodysize = strlen($body);
918
				$output->body = $body;
919
			}
920
			else // style with bodypreferences
921
			{
922
				//Select body type preference
923
				$bpReturnType = 1;//SYNC_BODYPREFERENCE_PLAIN;
924
				if ($bodypreference !== false) {
925
					// bodypreference can occur multiple times
926
					// usually we would use Utils::GetBodyPreferenceBestMatch($bodypreference);
927
					$bpReturnType = Utils::GetBodyPreferenceBestMatch($bodypreference);
928
/*
929
					foreach($bodypreference as $bpv)
930
					{
931
						// we use the last, or MIMEMESSAGE when present
932
						$bpReturnType = $bpv;
933
						if ($bpReturnType==SYNC_BODYPREFERENCE_MIME) break;
934
					}
935
*/
936
				}
937
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." getBodyPreferenceBestMatch: ".array2string($bpReturnType));
938
				// set the protocoll class
939
				$output->asbody = new SyncBaseBody();
940
941
				// return full mime-message without any (charset) conversation directly as stream
942
				if ($bpReturnType==SYNC_BODYPREFERENCE_MIME)
943
				{
944
					//SYNC_BODYPREFERENCE_MIME
945
					$output->asbody->type = SYNC_BODYPREFERENCE_MIME;
946
					$stream = $this->mail->getMessageRawBody($id, '', $_folderName, true);
947
					$fstat = fstat($stream);
948
					fseek($stream, 0, SEEK_SET);
949
					$output->asbody->data = $stream;
950
					$output->asbody->estimatedDataSize = $fstat['size'];
951
					ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." bodypreference 4=SYNC_BODYPREFERENCE_MIME=full mime message requested, size=$fstat[size]");
952
				}
953
				else
954
				{
955
					// fetch the body (try to gather data only once)
956
					$css ='';
957
					$bodyStruct = $this->mail->getMessageBody($id, 'html_only', '', null, true,$_folderName);
958
					if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only Struct:'.array2string($bodyStruct));
959
					$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true,false);
960
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' html_only:'.$body);
961
					if ($body != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
962
						// may be html
963
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:html (fetched with html_only)');
964
						$css = $this->mail->getStyles($bodyStruct);
965
						$output->nativebodytype=2;
966
					} else {
967
						// plain text Message
968
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".' Type:plain, fetch text (HTML, if no text available)');
969
						$output->nativebodytype=1;
970
						$bodyStruct = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
971
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' plain text Struct:'.array2string($bodyStruct));
972
						$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,false,false);
973
						if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' never display html(plain text only):'.$body);
974
					}
975
					// whatever format decode (using the correct encoding)
976
					if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__."MIME Body".' Type:'.($output->nativebodytype==2?' html ':' plain ').$body);
977
					//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
978
					// prepare plaintextbody
979
					$plainBody='';
980
					if ($output->nativebodytype == 2)
981
					{
982
						$bodyStructplain = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
983
						if(isset($bodyStructplain[0])&&isset($bodyStructplain[0]['error'])&&$bodyStructplain[0]['error']==1)
984
						{
985
							$plainBody = Api\Mail\Html::convertHTMLToText($body); // always display with preserved HTML
986
						}
987
						else
988
						{
989
							$plainBody = $this->mail->getdisplayableBody($this->mail,$bodyStructplain,false,false);
990
						}
991
					}
992
					//if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "MIME Body".$body);
993
					$plainBody = preg_replace("/<style.*?<\/style>/is", "", (strlen($plainBody)?$plainBody:$body));
994
					// remove all other html
995
					$plainBody = preg_replace("/<br.*>/is","\r\n",$plainBody);
996
					$plainBody = strip_tags($plainBody);
997
					if ($this->debugLevel>3 && $output->nativebodytype==1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Plain Text:'.$plainBody);
998
					//$body = str_replace("\n","\r\n", str_replace("\r","",$body)); // do we need that?
999
1000
					if ($bpReturnType==2) //SYNC_BODYPREFERENCE_HTML
1001
					{
1002
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "HTML Body with requested pref 2");
1003
						// Send HTML if requested and native type was html
1004
						$output->asbody->type = 2;
1005
						$htmlbody = '<html>'.
1006
							'<head>'.
1007
							'<meta name="Generator" content="Z-Push">'.
1008
							'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'.
1009
							$css.
1010
							'</head>'.
1011
							'<body>';
1012
						if ($output->nativebodytype==2)
1013
						{
1014
							if ($css) Api\Mail\Html::replaceTagsCompletley($body,'style');
1015
							// as we fetch html, and body is HTML, we may not need to handle this
1016
							$htmlbody .= $body;
1017
						}
1018
						else
1019
						{
1020
							// html requested but got only plaintext, so fake html
1021
							$htmlbody .= str_replace("\n","<BR>",str_replace("\r","<BR>", str_replace("\r\n","<BR>",$plainBody)));
1022
						}
1023
						$htmlbody .= '</body>'.
1024
								'</html>';
1025
1026
						if(isset($truncsize) && strlen($htmlbody) > $truncsize)
1027
						{
1028
							$htmlbody = Utils::Utf8_truncate($htmlbody,$truncsize);
1029
							$output->asbody->truncated = 1;
1030
						}
1031
						// output->nativebodytype is used as marker that the original message was of type ... but is now converted to, as type 2 is requested.
1032
						$output->nativebodytype = 2;
1033
						$output->asbody->data = StringStreamWrapper::Open($htmlbody);
1034
						$output->asbody->estimatedDataSize = strlen($htmlbody);
1035
					}
1036
					else
1037
					{
1038
						// Send Plaintext as Fallback or if original body is plainttext
1039
						if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG, "Plaintext Body:".$plainBody);
1040
						/* we use plainBody (set above) instead
1041
						$bodyStruct = $this->mail->getMessageBody($id,'only_if_no_text'); //'never_display');
1042
						$plain = $this->mail->getdisplayableBody($this->mail,$bodyStruct);
1043
						$plain = html_entity_decode($plain,ENT_QUOTES,$this->mail->detect_encoding($plain));
1044
						$plain = strip_tags($plain);
1045
						//$plain = str_replace("\n","\r\n",str_replace("\r","",$plain));
1046
						*/
1047
						$output->asbody->type = 1;
1048
						$output->nativebodytype = 1;
1049
						if(isset($truncsize) &&
1050
							strlen($plainBody) > $truncsize)
1051
						{
1052
							$plainBody = Utils::Utf8_truncate($plainBody, $truncsize);
1053
							$output->asbody->truncated = 1;
1054
						}
1055
						$output->asbody->data = StringStreamWrapper::Open((string)$plainBody !== '' ? $plainBody : ' ');
1056
						$output->asbody->estimatedDataSize = strlen($plainBody);
1057
					}
1058
					// In case we have nothing for the body, send at least a blank...
1059
					// dw2412 but only in case the body is not rtf!
1060
					if ($output->asbody->type != 3 && !isset($output->asbody->data))
1061
					{
1062
						$output->asbody->data = StringStreamWrapper::Open(" ");
1063
						$output->asbody->estimatedDataSize = 1;
1064
					}
1065
				}
1066
			}
1067
			// end AS12 Stuff
1068
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Header info:'.$headers['subject'].' from:'.$headers['date']);
1069
			$output->read = $headers["flags"];
1070
1071
			$output->flag = new SyncMailFlags();
1072
			if ($headers['flagged'] == 1)
1073
			{
1074
				$output->flag->flagstatus = 2;
1075
				//$output->flag->flagtype = "Flag for Follow up";
1076
			} else {
1077
				$output->flag->flagstatus = 0;
1078
			}
1079
			if ($headers['answered'])
1080
			{
1081
				$output->lastverexecuted = AS_REPLYTOSENDER;
0 ignored issues
show
Bug introduced by
The property lastverexecuted does not exist on SyncMail. Did you mean lastverbexecuted?
Loading history...
1082
			}
1083
			elseif ($headers['forwarded'])
1084
			{
1085
				$output->lastverexecuted = AS_FORWARD;
1086
			}
1087
			$output->subject = $headers['subject'];
1088
			$output->importance = $headers['priority'] > 3 ? 0 :
1089
				($headers['priority'] < 3 ? 2 : 1) ;
1090
			$output->datereceived = $this->mail->_strtotime($headers['date'],'ts',true);
1091
			$output->to = $headers['to_address'];
1092
			if ($headers['to']) $output->displayto = $headers['to_address']; //$headers['FETCHED_HEADER']['to_name']
1093
			$output->from = $headers['sender_address'];
1094
			if (isset($headers['cc_addresses']) && $headers['cc_addresses']) $output->cc = $headers['cc_addresses'];
1095
			if (isset($headers['reply_to_address']) && $headers['reply_to_address']) $output->reply_to = $headers['reply_to_address'];
1096
1097
			$output->messageclass = "IPM.Note";
1098
			if (stripos($headers['mimetype'],'multipart')!== false &&
1099
				stripos($headers['mimetype'],'signed')!== false)
1100
			{
1101
				$output->messageclass = "IPM.Note.SMIME.MultipartSigned";
1102
			}
1103
			if (Request::GetProtocolVersion() >= 12.0) {
1104
				$output->contentclass = "urn:content-classes:message";
1105
			}
1106
1107
			// start handle Attachments (include text/calendar multipart alternative)
1108
			$attachments = $this->mail->getMessageAttachments($id, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true, true, $_folderName);
1109
			// Attachments should not needed for MIME messages, so skip this part if bpReturnType==4
1110
			if (/*$bpReturnType != SYNC_BODYPREFERENCE_MIME &&*/ is_array($attachments) && count($attachments)>0)
1111
			{
1112
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Attachments for MessageID:'.$id.' found:'.count($attachments));
1113
				//error_log(__METHOD__.__LINE__.array2string($attachments));
1114
				foreach ($attachments as $key => $attach)
1115
				{
1116
					if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attach));
1117
1118
					// pass meeting requests to calendar plugin
1119
					if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1120
						isset($GLOBALS['egw_info']['user']['apps']['calendar']) &&
1121
						($attachment = $this->mail->getAttachment($id, $attach['partID'],0,false,false,$_folderName)) &&
1122
						($output->meetingrequest = calendar_zpush::meetingRequest($attachment['attachment'])))
1123
					{
1124
						//overwrite the globalobjId from calendar object, as: if you delete the mail, that is
1125
						//the meeting-request its using the globalobjid as reference and deletes both:
1126
						//mail AND meeting. we dont want this. accepting meeting requests with the mobile does nothing
1127
						$output->meetingrequest->globalobjid = activesync_backend::uid2globalObjId($id);
1128
						$output->messageclass = "IPM.Schedule.Meeting.Request";
1129
						//$output->messageclass = "IPM.Schedule.Meeting";
1130
						unset($attachment);
1131
						continue;	// do NOT add attachment as attachment
1132
					}
1133
					if (Request::GetProtocolVersion() >= 12.0) {
1134
						$attachment = new SyncBaseAttachment();
1135
						if (!isset($output->asattachments) || !is_array($output->asattachments))
1136
							$output->asattachments = array();
1137
						$attachment->estimatedDataSize = $attach['size'];
1138
						$attachment->method = 1;
1139
						$attachment->filereference = $folderid . ":" . $id . ":" . $attach['partID'];
1140
					} else {
1141
						$attachment = new SyncAttachment();
1142
						if (!isset($output->attachments) || !is_array($output->attachments))
1143
							$output->attachments = array();
1144
						$attachment->attsize = $attach['size'];
1145
						$attachment->attmethod = 1;
1146
						$attachment->attname = $folderid . ":" . $id . ":" . $attach['partID'];//$key;
1147
					}
1148
1149
					$attachment->displayname = $attach['name'];
1150
					//error_log(__METHOD__.__LINE__.'->'.$folderid . ":" . $id . ":" . $attach['partID']);
1151
1152
					$attachment->attoid = "";//isset($part->headers['content-id']) ? trim($part->headers['content-id']) : "";
0 ignored issues
show
Bug introduced by
The property attoid does not seem to exist on SyncBaseAttachment.
Loading history...
1153
					//$attachment->isinline=0; // if not inline, do not use isinline
1154
					if (!empty($attach['cid']) && $attach['cid'] <> 'NIL' )
1155
					{
1156
						if ($bpReturnType != 4 && $attach['disposition'] == 'inline')
1157
						{
1158
							$attachment->isinline = true;
0 ignored issues
show
Bug introduced by
The property isinline does not seem to exist on SyncAttachment.
Loading history...
1159
						}
1160
						if (Request::GetProtocolVersion() >= 12.0) {
1161
							$attachment->method=1;
0 ignored issues
show
Bug introduced by
The property method does not exist on SyncAttachment. Did you mean attmethod?
Loading history...
1162
							$attachment->contentid= str_replace(array("<",">"), "",$attach['cid']);
0 ignored issues
show
Bug introduced by
The property contentid does not exist on SyncAttachment. Did you mean content?
Loading history...
1163
						} else {
1164
							$attachment->attmethod=6;
0 ignored issues
show
Bug introduced by
The property attmethod does not exist on SyncBaseAttachment. Did you mean method?
Loading history...
1165
							$attachment->attoid = str_replace(array("<",">"), "",$attach['cid']);
1166
						}
1167
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-id']."'  ".$attachment->contentid);
1168
						$attachment->contenttype = trim($attach['mimeType']);
0 ignored issues
show
Bug introduced by
The property contenttype does not exist on SyncBaseAttachment. Did you mean content?
Loading history...
Bug introduced by
The property contenttype does not exist on SyncAttachment. Did you mean content?
Loading history...
1169
						//	ZLog::Write(LOGLEVEL_DEBUG, "'".$part->headers['content-type']."'  ".$attachment->contentid);
1170
					}
1171
					if (Request::GetProtocolVersion() >= 12.0) {
1172
						array_push($output->asattachments, $attachment);
1173
					} else {
1174
						array_push($output->attachments, $attachment);
1175
					}
1176
					unset($attachment);
1177
				}
1178
			}
1179
			//$this->debugLevel=0;
1180
			// end handle Attachments
1181
			unset($attachments);
1182
1183
            // Language Code Page ID: http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx
1184
            $output->internetcpid = INTERNET_CPID_UTF8;
1185
1186
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.array2string($output));
1187
//$this->debugLevel=0;
1188
			return $output;
1189
		}
1190
//$this->debugLevel=0;
1191
		return false;
1192
	}
1193
1194
	/**
1195
	 * Process response to meeting request
1196
	 *
1197
	 * mail plugin only extracts the iCal attachment and let's calendar plugin deal with adding it
1198
	 *
1199
	 * @see BackendDiff::MeetingResponse()
1200
	 * @param string $folderid folder of meeting request mail
1201
	 * @param int|string $requestid uid of mail with meeting request
1202
	 * @param int $response 1=accepted, 2=tentative, 3=decline
1203
	 * @return int|boolean id of calendar item, false on error
1204
	 */
1205
	function MeetingResponse($folderid, $requestid, $response)
1206
	{
1207
		if (!class_exists('calendar_zpush'))
1208
		{
1209
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."(...) no EGroupware calendar installed!");
1210
			return null;
1211
		}
1212
		if (!($stat = $this->StatMessage($folderid, $requestid)))
0 ignored issues
show
Unused Code introduced by
The assignment to $stat is dead and can be removed.
Loading history...
1213
		{
1214
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning FALSE (can NOT stat message)");
1215
			return false;
1216
		}
1217
		$ret = false;
1218
		foreach($this->mail->getMessageAttachments($requestid, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true) as $key => $attach)
1219
		{
1220
			if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
1221
				($attachment = $this->mail->getAttachment($requestid, $attach['partID'],0,false)))
1222
			{
1223
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) iCal found, calling now backend->MeetingResponse('$attachment[attachment]')");
1224
1225
				// calling backend again with iCal attachment, to let calendar add the event
1226
				$ret = $this->backend->MeetingResponse($attachment['attachment'],
1227
					$this->backend->createID('calendar',$GLOBALS['egw_info']['user']['account_id']),
1228
					$response);
1229
1230
				// delete message after meeting-response is processed successful by calendar
1231
				if ($ret) $this->DeleteMessage($folderid, $requestid, null);
1232
				break;
1233
			}
1234
		}
1235
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__."($requestid, '$folderid', $response) returning ".array2string($ret));
1236
		return $ret;
1237
	}
1238
1239
	/**
1240
	 * GetAttachmentData
1241
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1242
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1243
	 * encode any information you need to find the attachment in that 'attname' property.
1244
	 *
1245
     * @param string $fid - id
1246
     * @param string $attname - should contain (folder)id
1247
	 * @return SyncItemOperationsAttachment-object
0 ignored issues
show
Documentation Bug introduced by
The doc comment SyncItemOperationsAttachment-object at position 0 could not be parsed: Unknown type name 'SyncItemOperationsAttachment-object' at position 0 in SyncItemOperationsAttachment-object.
Loading history...
1248
	 */
1249
	function GetAttachmentData($fid,$attname) {
1250
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1251
		return $this->_GetAttachmentData($fid,$attname);
1252
	}
1253
1254
	/**
1255
	 * ItemOperationsGetAttachmentData
1256
	 * Should return attachment data for the specified attachment. The passed attachment identifier is
1257
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
1258
	 * encode any information you need to find the attachment in that 'attname' property.
1259
	 *
1260
     * @param string $fid - id
1261
     * @param string $attname - should contain (folder)id
1262
	 * @return SyncItemOperationsAttachment-object
0 ignored issues
show
Documentation Bug introduced by
The doc comment SyncItemOperationsAttachment-object at position 0 could not be parsed: Unknown type name 'SyncItemOperationsAttachment-object' at position 0 in SyncItemOperationsAttachment-object.
Loading history...
1263
	 */
1264
	function ItemOperationsGetAttachmentData($fid,$attname) {
1265
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
1266
		return $this->_GetAttachmentData($fid,$attname);
1267
	}
1268
1269
	/**
1270
	 * _GetAttachmentData implements
1271
	 * -ItemOperationsGetAttachmentData
1272
	 * -GetAttachmentData
1273
	 *
1274
     * @param string $fid - id
1275
     * @param string $attname - should contain (folder)id
1276
	 * @return SyncItemOperationsAttachment-object
0 ignored issues
show
Documentation Bug introduced by
The doc comment SyncItemOperationsAttachment-object at position 0 could not be parsed: Unknown type name 'SyncItemOperationsAttachment-object' at position 0 in SyncItemOperationsAttachment-object.
Loading history...
1277
	 */
1278
	private function _GetAttachmentData($fid,$attname)
1279
	{
1280
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')".function_backtrace());
1281
		//error_log(__METHOD__.__LINE__." Fid: $fid (attname: '$attname')");
1282
		list($folderid, $id, $part) = explode(":", $attname);
1283
1284
		$this->splitID($folderid, $account, $folder);
1285
1286
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1287
1288
		$this->mail->reopen($folder);
1289
		$attachment = $this->mail->getAttachment($id,$part,0,false,true,$folder);
1290
		$SIOattachment = new SyncItemOperationsAttachment();
1291
		fseek($attachment['attachment'], 0, SEEK_SET);	// z-push requires stream seeked to start
1292
		$SIOattachment->data = $attachment['attachment'];
1293
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname') Data:".$attachment['attachment']);
1294
		if (isset($attachment['type']) )
1295
			$SIOattachment->contenttype = $attachment['type'];
1296
1297
		unset($attachment);
1298
1299
		return $SIOattachment;
1300
	}
1301
1302
	/**
1303
	 * StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
1304
	 *
1305
	 * 'id'	 => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
1306
	 * 'flags'	 => simply '0' for unread, '1' for read
1307
	 * 'mod'	=> modification signature. As soon as this signature changes, the item is assumed to be completely
1308
	 *			 changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
1309
	 *			 time for this field, which will change as soon as the contents have changed.
1310
	 *
1311
	 * @param string $folderid
1312
	 * @param int $id id (uid) of message
1313
	 * @return array
1314
	 */
1315
	public function StatMessage($folderid, $id)
1316
	{
1317
		$messages = $this->fetchMessages($folderid, NULL, $id);
1318
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid','$id') returning ".array2string($messages[$id]));
1319
		return $messages[$id];
1320
	}
1321
1322
	/**
1323
	 * Called when a message has been changed on the mobile.
1324
	 * Added support for FollowUp flag
1325
	 *
1326
	 * @param string              $folderid            id of the folder
1327
	 * @param string              $id                  id of the message
1328
	 * @param SyncXXX             $message             the SyncObject containing a message
0 ignored issues
show
Bug introduced by
The type SyncXXX was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1329
	 * @param ContentParameters   $contentParameters
1330
	 *
1331
	 * @access public
1332
	 * @return array                        same return value as StatMessage()
1333
	 * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1334
	 */
1335
	function ChangeMessage($folderid, $id, $message, $contentParameters)
1336
	{
1337
		ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." $folderid, $id,".array2string($message).",".array2string($contentParameters));
1338
		//unset($folderid, $id, $message, $contentParameters);
1339
		$account = $folder = null;
1340
		$this->splitID($folderid, $account, $folder);
1341
		if (isset($message->flag)) {
1342
			if (isset($message->flag->flagstatus) && $message->flag->flagstatus == 2) {
1343
				$rv = $this->mail->flagMessages((($message->flag->flagstatus == 2) ? "flagged" : "unflagged"), $id,$folder);
1344
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . (($message->flag->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
1345
			} else {
1346
				$rv = $this->mail->flagMessages("unflagged", $id,$folder);
1347
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__." -> set ".array2string($id).' in Folder '.$folder." as " . "unflagged" . "-->". $rv);
1348
			}
1349
		}
1350
		return $this->StatMessage($folderid, $id);
1351
	}
1352
1353
	/**
1354
	 * This function is called when the user moves an item on the PDA. You should do whatever is needed
1355
	 * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
1356
	 * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
1357
	 * at all on the source folder, and the destination folder will show the new message
1358
	 *
1359
	 * @param string              $folderid            id of the source folder
1360
	 * @param string              $id                  id of the message
1361
	 * @param string              $newfolderid         id of the destination folder
1362
	 * @param ContentParameters   $contentParameters
1363
	 *
1364
	 * @return boolean                      status of the operation
1365
	 * @throws StatusException              could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
1366
	 */
1367
	public function MoveMessage($folderid, $id, $newfolderid, $contentParameters)
1368
	{
1369
		unset($contentParameters);	// not used, but required by function signature
1370
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (sfid: '$folderid'  id: '$id'  dfid: '$newfolderid' )");
1371
		$account = $srcFolder = $destFolder = null;
1372
		$this->splitID($folderid, $account, $srcFolder);
1373
		$this->splitID($newfolderid, $account, $destFolder);
1374
		ZLog::Write(LOGLEVEL_DEBUG, "IMAP-MoveMessage: (SourceFolder: '$srcFolder'  id: '$id'  DestFolder: '$destFolder' )");
1375
		if (!isset($this->mail)) $this->mail = Mail::getInstance(false,self::$profileID,true,false,true);
1376
		$this->mail->reopen($destFolder);
1377
		$status = $this->mail->getFolderStatus($destFolder);
1378
		$uidNext = $status['uidnext'];
1379
		$this->mail->reopen($srcFolder);
1380
1381
		// move message
1382
		$rv = $this->mail->moveMessages($destFolder,(array)$id,true,$srcFolder,true);
1383
		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
1384
		// return the new id "as string"
1385
		return ($rv===true ? $uidNext : $rv[$id]) . "";
1386
	}
1387
1388
	/**
1389
	 *  Get all messages of a folder with optional cutoffdate
1390
	 *
1391
	 *  @param int $cutoffdate =null timestamp with cutoffdate, default 12 weeks
1392
	 */
1393
	public function GetMessageList($folderid, $cutoffdate=NULL)
1394
	{
1395
		if ($cutoffdate > 0)
1396
		{
1397
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.' for Folder:'.$folderid.' SINCE:'.$cutoffdate.'/'.date("d-M-Y", $cutoffdate));
1398
		}
1399
		else
1400
		{
1401
			$maximumSyncRangeInDays = self::PAST_LIMIT; // corresponds to our default value
1402
			if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange']))
1403
			{
1404
				$maximumSyncRangeInDays = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-maximumSyncRange'];
1405
			}
1406
			$cutoffdate = (is_numeric($maximumSyncRangeInDays) ? Api\DateTime::to('now','ts')-(3600*24*$maximumSyncRangeInDays):null);
1407
			if (is_numeric($maximumSyncRangeInDays)) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' Client set no truncationdate. Using '.$maximumSyncRangeInDays.' days.'.date("d-M-Y", $cutoffdate));
1408
		}
1409
		try {
1410
			return $this->fetchMessages($folderid, $cutoffdate);
1411
		} catch (Exception $e)
1412
		{
1413
			ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' failed for '.$e->getMessage().($e->details?$e->details:''));
1414
			return array();
1415
		}
1416
	}
1417
1418
	/**
1419
	 * Fetch headers for one or all mail of a folder using optional cutoffdate
1420
	 *
1421
	 * Headers of last fetchMessage call of complate folder are cached in static $headers,
1422
	 * to allow to use them without fetching them again.
1423
	 * Next call clears cache
1424
	 *
1425
	 * @param int $folderid
1426
	 * @param int $cutoffdate timestamp with cutoffdate
1427
	 * @param string $_id =null uid of single message to fetch
1428
	 * @param boolean $return_all_headers =false true: additinal contain all headers eg. "subject"
1429
	 * @return array uid => array StatMessage($folderid, $_id)
1430
	 */
1431
	private function fetchMessages($folderid, $cutoffdate=NULL, $_id=NULL, $return_all_headers=false)
1432
	{
1433
		static $headers = array();
1434
1435
		if ($this->debugLevel>1) $gstarttime = microtime (true);
1436
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__);
1437
		$rv_messages = array();
1438
		// if the message is still available within the class, we use it instead of fetching it again
1439
		if ($_id && isset($headers[$_id]) && is_array($headers[$_id]))
1440
		{
1441
			//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." the message ".$_id[0]." is still available within the class, we use it instead of fetching it again");
1442
			$rv_messages = array('header'=>array($headers[$_id]));
1443
		}
1444
		else
1445
		{
1446
			$headers = array();	// clear cache to not use too much memory
1447
1448
			if ($this->debugLevel>1) $starttime = microtime (true);
1449
			$this->_connect($this->account);
1450
			if ($this->debugLevel>1)
1451
			{
1452
				$endtime = microtime(true) - $starttime;
1453
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " connect took : ".$endtime.' for account:'.$this->account);
1454
			}
1455
			$messagelist = $_filter = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $messagelist is dead and can be removed.
Loading history...
1456
			// if not connected, any further action must fail
1457
			if (!empty($cutoffdate)) $_filter = array('status'=>array('UNDELETED'),'range'=>"SINCE",'date'=> date("d-M-Y", $cutoffdate));
1458
			if ($this->debugLevel>1) $starttime = microtime (true);
1459
			$account = $_folderName = $id = null;
1460
			$this->splitID($folderid,$account,$_folderName,$id);
1461
			if ($this->debugLevel>1)
1462
			{
1463
				$endtime = microtime(true) - $starttime;
1464
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " splitID took : ".$endtime.' for FolderID:'.$folderid);
1465
			}
1466
			if ($this->debugLevel>1) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
1467
			if ($this->debugLevel>1) $starttime = microtime (true);
1468
			$_numberOfMessages = (empty($cutoffdate)?250:99999);
1469
			$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages, $_sort=0, $_reverse=false, $_filter, $_id);
1470
			if ($this->debugLevel>1)
1471
			{
1472
				$endtime = microtime(true) - $starttime;
1473
				ZLog::Write(LOGLEVEL_DEBUG,__METHOD__. " getHeaders call took : ".$endtime.' for FolderID:'.$_folderName);
1474
			}
1475
		}
1476
		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 null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1477
		//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Result:'.array2string($rv_messages));
1478
		$messagelist = array();
1479
		if (!isset($rv_messages['header'])||empty($rv_messages['header'])) return $messagelist;
1480
		//if ($_returnModHash) $messageFolderHash = array();
1481
		foreach ((array)$rv_messages['header'] as $k => $vars)
1482
		{
1483
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' ID to process:'.$vars['uid'].' Subject:'.$vars['subject']);
1484
			$headers[$vars['uid']] = $vars;
1485
			if ($this->debugLevel>3) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' MailID:'.$k.'->'.array2string($vars));
1486
			if (!empty($vars['deleted'])) continue; // cut of deleted messages
1487
			if ($cutoffdate && $vars['date'] < $cutoffdate) continue; // message is out of range for cutoffdate, ignore it
0 ignored issues
show
Bug Best Practice introduced by
The expression $cutoffdate of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

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

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2130
	 */
2131
	private $fh_state_maschine;
2132
2133
	/**
2134
	 * state_type (and _key) used to store folder hashes
2135
	 */
2136
	const FOLDER_STATE_TYPE = 'folder_hashes';
2137
2138
	/**
2139
	 * Read hashfile from state dir
2140
	 */
2141
	private function readFolderHashes()
2142
	{
2143
		if (!isset($this->fh_state_maschine))
2144
		{
2145
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
0 ignored issues
show
Bug introduced by
The type activesync_statemachine was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2146
		}
2147
		try {
2148
			$this->folderHashes = $this->fh_state_maschine->getState(Request::GetDeviceID(),
2149
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2150
		}
2151
		catch (Exception $e) {
2152
			unset($e);
2153
			if ((file_exists($file = $this->hashFile()) || file_exists($file = $this->hashFile(true))) &&
2154
				($hashes = file_get_contents($file)))
2155
			{
2156
				$this->folderHashes = json_decode($hashes,true);
2157
				// fallback in case hashes have been serialized instead of being json-encoded
2158
				if (json_last_error()!=JSON_ERROR_NONE)
2159
				{
2160
					//error_log(__METHOD__.__LINE__." error decoding with json");
2161
					$this->folderHashes = unserialize($hashes);
2162
				}
2163
				// store folder-hashes to state
2164
				$this->storeFolderHashes();
2165
			}
2166
			else
2167
			{
2168
				$this->folderHashes = array();
2169
			}
2170
		}
2171
	}
2172
2173
	/**
2174
	 * Store hashfile via state-maschine
2175
	 *
2176
	 * return int|boolean false on error
2177
	 */
2178
	private function storeFolderHashes()
2179
	{
2180
		if (!isset($this->fh_state_maschine))
2181
		{
2182
			$this->fh_state_maschine = new activesync_statemachine($this->backend);
2183
		}
2184
		try {
2185
			$this->fh_state_maschine->setState($this->folderHashes, Request::GetDeviceID(),
2186
				self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
2187
		}
2188
		catch (Exception $e) {
2189
			_egw_log_exception($e);
2190
			return false;
2191
		}
2192
		return true;
2193
	}
2194
2195
	/**
2196
	 * Get name of hashfile in state dir
2197
	 *
2198
	 * New z-push 2 directory is in activesync_statemachine::getDeviceDirectory.
2199
	 * On request this function also returns, but never creates (!), old z-push 1 directory.
2200
	 *
2201
	 * @param boolean $old =false true: return old / pre-15 hash-file
2202
	 * @throws Api\Exception\AssertionFailed
2203
	 */
2204
	private function hashFile($old=false)
2205
	{
2206
		if (!($dev_id=Request::GetDeviceID()))
2207
		{
2208
			throw new Api\Exception\AssertionFailed(__METHOD__."() no DeviceID set!");
2209
		}
2210
		if ($old)
2211
		{
2212
			return STATE_DIR.$dev_id.'/'.$dev_id.'.hashes';
2213
		}
2214
		$dir = activesync_statemachine::getDeviceDirectory($dev_id);
2215
2216
		return $dir.'/'.$dev_id.'.hashes';
2217
	}
2218
}
2219