mail_compose   F
last analyzed

Complexity

Total Complexity 951

Size/Duplication

Total Lines 3869
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 2013
c 3
b 0
f 0
dl 0
loc 3869
rs 0.8
wmc 951

34 Methods

Rating   Name   Duplication   Size   Complexity  
A changeProfile() 0 11 5
C getToolbarActions() 0 167 12
A __construct() 0 17 4
A generateComposeID() 0 3 1
A _getHostName() 0 8 2
C getForwardData() 0 73 13
F send() 0 521 150
F getComposeFrom() 0 117 24
A _getCleanHTML() 0 23 4
B _get_uids_as_attachments() 0 24 8
F getAttachment() 0 101 27
F ajax_searchFolder() 0 68 22
A get_lists() 0 32 4
D ajax_searchAddress() 0 124 29
C _getAttachmentLinks() 0 46 14
A stripSlashes() 0 6 2
B resolveEmailAddressList() 0 27 10
C _encrypt() 0 34 12
A addMessageAttachment() 0 11 2
B addAttachment() 0 45 10
A replaceEmailAdresses() 0 5 1
F createMessage() 0 209 58
C setDefaults() 0 36 14
F ajax_saveAsDraft() 0 87 17
D saveAsDraft() 0 74 13
A testIfOneKeyInArrayDoesExistInString() 0 11 3
F addPresetFiles() 0 80 28
F ajax_merge() 0 66 16
A getErrorInfo() 0 8 2
A convertHTMLToText() 0 6 2
F getDraftData() 0 189 52
A generateRFC822Address() 0 8 6
F getReplyData() 0 227 50
F compose() 0 1095 334

How to fix   Complexity   

Complex Class

Complex classes like mail_compose often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use mail_compose, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * EGroupware - Mail - interface class for compose mails in popup
4
 *
5
 * @link http://www.egroupware.org
6
 * @package mail
7
 * @author EGroupware GmbH [[email protected]]
8
 * @copyright (c) 2013-2016 by EGroupware GmbH <info-AT-egroupware.org>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
use EGroupware\Api\Framework;
16
use EGroupware\Api\Egw;
17
use EGroupware\Api\Acl;
18
use EGroupware\Api\Etemplate;
19
use EGroupware\Api\Vfs;
20
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...
21
22
/**
23
 * Mail interface class for compose mails in popup
24
 */
25
class mail_compose
26
{
27
	var $public_functions = array
28
	(
29
		'compose'		=> True,
30
		'getAttachment'		=> True,
31
	);
32
33
	/**
34
	 * class vars for destination, priorities, mimeTypes
35
	 */
36
	static $destinations = array(
37
		'to' 		=> 'to',  // lang('to')
38
		'cc'		=> 'cc',  // lang('cc')
39
		'bcc'		=> 'bcc', // lang('bcc')
40
		'replyto'	=> 'replyto', // lang('replyto')
41
		'folder'	=> 'folder'  // lang('folder')
42
	);
43
	static $priorities = array(
44
		1=>"high", // lang('high')
45
		3=>"normal", // lang('normal')
46
		5=>"low"  // lang('low')
47
	);
48
	static $mimeTypes = array(
49
		"plain"=>"plain",
50
		"html"=>"html"
51
	);
52
53
	/**
54
	 * Instance of Mail
55
	 *
56
	 * @var Mail
57
	 */
58
	var $mail_bo;
59
60
	/**
61
	 * Active preferences, reference to $this->mail_bo->mailPreferences
62
	 *
63
	 * @var array
64
	 */
65
	var $mailPreferences;
66
	var $attachments;	// Array of attachments
67
	var $displayCharset;
68
	var $composeID;
69
	var $sessionData;
70
71
	function __construct()
72
	{
73
		$this->displayCharset   = Api\Translation::charset();
74
75
		$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
76
		$this->mail_bo	= Mail::getInstance(true,$profileID);
77
		$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->mail_bo->profileID;
78
79
		$this->mailPreferences	=& $this->mail_bo->mailPreferences;
80
		//force the default for the forwarding -> asmail
81
		if (!is_array($this->mailPreferences) || empty($this->mailPreferences['message_forwarding']))
0 ignored issues
show
introduced by
The condition is_array($this->mailPreferences) is always true.
Loading history...
82
		{
83
			$this->mailPreferences['message_forwarding'] = 'asmail';
84
		}
85
		if (is_null(Mail::$mailConfig)) Mail::$mailConfig = Api\Config::read('mail');
86
87
		$this->mailPreferences  =& $this->mail_bo->mailPreferences;
88
	}
89
90
	/**
91
	 * changeProfile
92
	 *
93
	 * @param int $_icServerID
94
	 */
95
	function changeProfile($_icServerID)
96
	{
97
		if ($this->mail_bo->profileID!=$_icServerID)
98
		{
99
			if (Mail::$debug) error_log(__METHOD__.__LINE__.'->'.$this->mail_bo->profileID.'<->'.$_icServerID);
100
			$this->mail_bo = Mail::getInstance(false,$_icServerID);
101
			if (Mail::$debug) error_log(__METHOD__.__LINE__.' Fetched IC Server:'.$this->mail_bo->profileID.':'.function_backtrace());
102
			// no icServer Object: something failed big time
103
			if (!isset($this->mail_bo->icServer)) exit; // ToDo: Exception or the dialog for setting up a server config
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
104
			$this->mail_bo->openConnection($this->mail_bo->profileID);
105
			$this->mailPreferences  =& $this->mail_bo->mailPreferences;
106
		}
107
	}
108
109
	/**
110
	 * Provide toolbar actions used for compose toolbar
111
	 * @param array $content content of compose temp
112
	 *
113
	 * @return array an array of actions
114
	 */
115
	static function getToolbarActions($content)
116
	{
117
		$group = 0;
118
		$actions = array(
119
			'send' => array(
120
				'caption' => 'Send',
121
				'icon'	=> 'mail_send',
122
				'group' => ++$group,
123
				'onExecute' => 'javaScript:app.mail.compose_submitAction',
124
				'hint' => 'Send',
125
				'shortcut' => array('ctrl' => true, 'keyCode' => 83, 'caption' => 'Ctrl + S'),
126
				'toolbarDefault' => true
127
			),
128
			'button[saveAsDraft]' => array(
129
				'caption' => 'Save',
130
				'icon' => 'apply',
131
				'group' => ++$group,
132
				'onExecute' => 'javaScript:app.mail.saveAsDraft',
133
				'hint' => 'Save as Draft',
134
				'toolbarDefault' => true
135
			),
136
			'button[saveAsDraftAndPrint]' => array(
137
				'caption' => 'Print',
138
				'icon' => 'print',
139
				'group' => $group,
140
				'onExecute' => 'javaScript:app.mail.saveAsDraft',
141
				'hint' => 'Save as Draft and Print'
142
			),
143
			'save2vfs' => array (
144
				'caption' => 'Save to filemanager',
145
				'icon' => 'filesave',
146
				'group' => $group,
147
				'onExecute' => 'javaScript:app.mail.compose_saveDraft2fm',
148
				'hint' => 'Save the drafted message as eml file into VFS'
149
			),
150
			'selectFromVFSForCompose' => array(
151
				'caption' => 'VFS',
152
				'icon' => 'filemanager/navbar',
153
				'group' => ++$group,
154
				'onExecute' => 'javaScript:app.mail.compose_triggerWidget',
155
				'hint' => 'Select file(s) from VFS',
156
				'toolbarDefault' => true
157
			),
158
			'uploadForCompose' => array(
159
				'caption' => 'Upload files...',
160
				'icon' => 'attach',
161
				'group' => $group,
162
				'onExecute' => 'javaScript:app.mail.compose_triggerWidget',
163
				'hint' => 'Select files to upload',
164
				'toolbarDefault' => true
165
			),
166
			'to_infolog' => array(
167
				'caption' => 'Infolog',
168
				'icon' => 'infolog/navbar',
169
				'group' => ++$group,
170
				'checkbox' => true,
171
				'hint' => 'check to save as infolog on send',
172
				'toolbarDefault' => true,
173
				'onExecute' => 'javaScript:app.mail.compose_setToggle'
174
			),
175
			'to_tracker' => array(
176
				'caption' => 'Tracker',
177
				'icon' => 'tracker/navbar',
178
				'group' => $group,
179
				'checkbox' => true,
180
				'hint' => 'check to save as tracker entry on send',
181
				'onExecute' => 'javaScript:app.mail.compose_setToggle',
182
				'mail_import' => Api\Hooks::single(array('location' => 'mail_import'),'tracker'),
183
			),
184
			'to_calendar' => array(
185
				'caption' => 'Calendar',
186
				'icon' => 'calendar/navbar',
187
				'group' => $group,
188
				'checkbox' => true,
189
				'hint' => 'check to save as calendar event on send',
190
				'onExecute' => 'javaScript:app.mail.compose_setToggle'
191
			),
192
			'disposition' => array(
193
				'caption' => 'Notification',
194
				'icon' => 'notification_message',
195
				'group' => ++$group,
196
				'checkbox' => true,
197
				'hint' => 'check to receive a notification when the message is read (note: not all clients support this and/or the receiver may not authorize the notification)',
198
				'onExecute' => 'javaScript:app.mail.compose_setToggle'
199
			),
200
			'prty' => array(
201
				'caption' => 'Priority',
202
				'group' => $group,
203
				'icon' => 'priority',
204
				'children' => array(),
205
				'hint' => 'Select the message priority tag',
206
			),
207
			'pgp' => array(
208
				'caption' => 'Encrypt',
209
				'icon' => 'lock',
210
				'group' => ++$group,
211
				'onExecute' => 'javaScript:app.mail.togglePgpEncrypt',
212
				'hint' => 'Send message PGP encrypted: requires keys from all recipients!',
213
				'checkbox' => true,
214
				'toolbarDefault' => true
215
			),
216
217
		);
218
		$acc_smime = Mail\Smime::get_acc_smime($content['mailaccount']);
219
		if ($acc_smime['acc_smime_password'])
220
		{
221
			$actions = array_merge($actions, array(
222
				'smime_sign' => array (
223
					'caption' => 'SMIME Sign',
224
					'icon' => 'smime_sign',
225
					'group' => ++$group,
226
					'onExecute' => 'javaScript:app.mail.compose_setToggle',
227
					'checkbox' => true,
228
					'hint' => 'Sign your message with smime certificate'
229
				),
230
				'smime_encrypt' => array (
231
					'caption' => 'SMIME Encryption',
232
					'icon' => 'smime_encrypt',
233
					'group' => $group,
234
					'onExecute' => 'javaScript:app.mail.compose_setToggle',
235
					'checkbox' => true,
236
					'hint' => 'Encrypt your message with smime certificate'
237
			)));
238
		}
239
		foreach (self::$priorities as $key => $priority)
240
		{
241
			$actions['prty']['children'][$key] = array(
242
						'caption' => $priority,
243
						'icon' => 'prio_high',
244
						'default' => false,
245
						'onExecute' => 'javaScript:app.mail.compose_priorityChange'
246
			);
247
			switch ($priority)
248
			{
249
				case 'high':
250
					$actions['prty']['children'][$key]['icon'] = 'prio_high';
251
					break;
252
				case 'normal':
253
					$actions['prty']['children'][$key]['icon'] = 'priority';
254
					break;
255
				case 'low':
256
					$actions['prty']['children'][$key]['icon'] = 'prio_low';
257
			}
258
		}
259
		// Set the priority action its current state
260
		if ($content['priority'])
261
		{
262
			$actions['prty']['children'][$content['priority']]['default'] = true;
263
		}
264
		if (Api\Header\UserAgent::mobile())
265
		{
266
			foreach (array_keys($actions) as $key)
267
			{
268
				if (!in_array($key, array('send','button[saveAsDraft]','uploadForCompose' ))) {
269
					$actions[$key]['toolbarDefault'] = false;
270
				}
271
			}
272
			unset($actions['pgp']);
273
		}
274
		if ($GLOBALS['egw_info']['server']['disable_pgp_encryption']) unset($actions['pgp']);
275
		// remove vfs actions if the user has no run access to filemanager
276
		if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
277
		{
278
			unset($actions['save2vfs']);
279
			unset($actions['selectFromVFSForCompose']);
280
		}
281
		return $actions;
282
	}
283
284
	/**
285
	 * Compose dialog
286
	 *
287
	 * @var arra $_content =null etemplate content array
288
	 * @var string $msg =null a possible message to be passed and displayed to the userinterface
289
	 * @var string $_focusElement ='to' subject, to, body supported
290
	 * @var boolean $suppressSigOnTop =false
291
	 * @var boolean $isReply =false
292
	 */
293
	function compose(array $_content=null,$msg=null, $_focusElement='to',$suppressSigOnTop=false, $isReply=false)
294
	{
295
		if ($msg) Framework::message($msg);
296
297
		if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']))
298
		{
299
			$sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'];
300
		}
301
		else
302
		{
303
			$sigPref = array();
304
		}
305
		// split mailaccount (acc_id) and identity (ident_id)
306
		if ($_content && isset($_content['mailaccount']))
307
		{
308
			list($_content['mailaccount'], $_content['mailidentity']) = explode(':', $_content['mailaccount']);
309
		}
310
		//error_log(__METHOD__.__LINE__.array2string($sigPref));
311
		//lang('compose'),lang('from') // needed to be found by translationtools
312
		//error_log(__METHOD__.__LINE__.array2string($_REQUEST).function_backtrace());
313
		//error_log(__METHOD__.__LINE__.array2string($_content).function_backtrace());
314
		$_contentHasSigID = $_content?array_key_exists('mailidentity',(array)$_content):false;
315
		$_contentHasMimeType = $_content? array_key_exists('mimeType',(array)$_content):false;
316
317
		// fetch appendix data which is an assistance input value consisiting of json data
318
		if ($_content['appendix_data'])
319
		{
320
			$appendix_data = json_decode($_content['appendix_data'], true);
321
			$_content['appendix_data'] = '';
322
		}
323
324
		if ($appendix_data['emails'])
325
		{
326
			try {
327
				if ($appendix_data['emails']['processedmail_id']) $_content['processedmail_id'] .= ','.$appendix_data['emails']['processedmail_id'];
328
				$attched_uids = $this->_get_uids_as_attachments($appendix_data['emails']['ids'], $_content['serverID']);
329
				if (is_array($attched_uids))
0 ignored issues
show
introduced by
The condition is_array($attched_uids) is always true.
Loading history...
330
				{
331
					$_content['attachments'] = array_merge_recursive((array)$_content['attachments'], $attched_uids);
332
				}
333
			} catch (Exception $ex) {
334
				Framework::message($ex->getMessage(), 'error');
335
			}
336
			$suppressSigOnTop = true;
337
			unset($appendix_data);
338
		}
339
340
		if (isset($_GET['reply_id'])) $replyID = $_GET['reply_id'];
341
		if (!$replyID && isset($_GET['id'])) $replyID = $_GET['id'];
342
343
		// Process different places we can use as a start for composing an email
344
		$actionToProcess = 'compose';
345
		if($_GET['from'] && $replyID)
346
		{
347
			$_content = array_merge((array)$_content, $this->getComposeFrom(
348
				// Parameters needed for fetching appropriate data
349
				$replyID, $_GET['part_id'], $_GET['from'],
350
				// Additionally may be changed
351
				$_focusElement, $suppressSigOnTop, $isReply
352
			));
353
			if (Mail\Smime::get_acc_smime($this->mail_bo->profileID))
354
			{
355
				if (isset($_GET['smime_type'])) $smime_type = $_GET['smime_type'];
356
				// pre set smime_sign and smime_encrypt actions if the original
357
				// message is smime.
358
				$_content['smime_sign'] = $smime_type == (Mail\Smime::TYPE_SIGN ||
359
					$smime_type == Mail\Smime::TYPE_SIGN_ENCRYPT) ? 'on' : 'off';
360
				$_content['smime_encrypt'] = ($smime_type == Mail\Smime::TYPE_ENCRYPT) ? 'on' : 'off';
361
			}
362
363
			$actionToProcess = $_GET['from'];
364
			unset($_GET['from']);
365
			unset($_GET['reply_id']);
366
			unset($_GET['part_id']);
367
			unset($_GET['id']);
368
			unset($_GET['mode']);
369
			//error_log(__METHOD__.__LINE__.array2string($_content));
370
		}
371
372
		$composeCache = array();
373
		if (isset($_content['composeID'])&&!empty($_content['composeID']))
374
		{
375
			$isFirstLoad = false;
376
			$composeCache = Api\Cache::getCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$_content['composeID'],$callback=null,$callback_params=array(),$expiration=60*60*2);
377
			$this->composeID = $_content['composeID'];
378
			//error_log(__METHOD__.__LINE__.array2string($composeCache));
379
		}
380
		else
381
		{
382
			// as we use isFirstLoad to trigger the initalStyle on ckEditor, we
383
			// respect that composeasnew may not want that, as we assume there
384
			// is some style already set and our initalStyle always adds a span with &nbsp;
385
			// and we want to avoid that
386
			$isFirstLoad = !($actionToProcess=='composeasnew');//true;
387
			$this->composeID = $_content['composeID'] = $this->generateComposeID();
388
			if (!is_array($_content))
389
			{
390
				$_content = $this->setDefaults();
391
			}
392
			else
393
			{
394
				$_content = $this->setDefaults($_content);
395
			}
396
		}
397
		// VFS Selector was used
398
		if (is_array($_content['selectFromVFSForCompose']))
399
		{
400
			$suppressSigOnTop = true;
401
			foreach ($_content['selectFromVFSForCompose'] as $i => $path)
402
			{
403
				$_content['uploadForCompose'][] = array(
404
					'name' => Vfs::basename($path),
405
					'type' => Vfs::mime_content_type($path),
406
					'file' => Vfs::PREFIX.$path,
407
					'size' => filesize(Vfs::PREFIX.$path),
408
				);
409
			}
410
			unset($_content['selectFromVFSForCompose']);
411
		}
412
		// check everything that was uploaded
413
		if (is_array($_content['uploadForCompose']))
414
		{
415
			$suppressSigOnTop = true;
416
			foreach ($_content['uploadForCompose'] as $i => &$upload)
417
			{
418
				if (!isset($upload['file'])) $upload['file'] = $upload['tmp_name'];
419
				try
420
				{
421
					$upload['file'] = $upload['tmp_name'] = Mail::checkFileBasics($upload,$this->composeID,false);
422
				}
423
				catch (Api\Exception\WrongUserinput $e)
424
				{
425
					Framework::message($e->getMessage(), 'error');
426
					unset($_content['uploadForCompose'][$i]);
427
					continue;
428
				}
429
				if (is_dir($upload['file']) && (!$_content['filemode'] || $_content['filemode'] == Vfs\Sharing::ATTACH))
430
				{
431
					$_content['filemode'] = Vfs\Sharing::READONLY;
432
					Framework::message(lang('Directories have to be shared.'), 'info');
433
				}
434
			}
435
		}
436
		// check if someone did hit delete on the attachments list
437
		if (!empty($_content['attachments']['delete']))
438
		{
439
			//error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments']));
440
			//error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments']['delete']));
441
442
			$suppressSigOnTop = true;
443
			$toDelete = $_content['attachments']['delete'];
444
			unset($_content['attachments']['delete']);
445
			$attachments = $_content['attachments'];
446
			unset($_content['attachments']);
447
			foreach($attachments as $i => $att)
448
			{
449
				$remove=false;
450
				foreach(array_keys($toDelete) as $k)
451
				{
452
					if ($att['tmp_name']==$k) $remove=true;
453
				}
454
				if (!$remove) $_content['attachments'][] = $att;
455
			}
456
		}
457
		// someone clicked something like send, or saveAsDraft
458
		// make sure, we are connected to the correct server for sending and storing the send message
459
		$activeProfile = $composeProfile = $this->mail_bo->profileID; // active profile may not be the profile uised in/for compose
460
		$activeFolderCache = Api\Cache::getCache(Api\Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*10);
461
		if (!empty($activeFolderCache[$this->mail_bo->profileID]))
462
		{
463
			//error_log(__METHOD__.__LINE__.' CurrentFolder:'.$activeFolderCache[$this->mail_bo->profileID]);
464
			$activeFolder = $activeFolderCache[$this->mail_bo->profileID];
465
		}
466
		//error_log(__METHOD__.__LINE__.array2string($_content));
467
		if (!empty($_content['serverID']) && $_content['serverID'] != $this->mail_bo->profileID &&
468
			($_content['composeToolbar'] === 'send' || $_content['button']['saveAsDraft']||$_content['button']['saveAsDraftAndPrint'])
469
		)
470
		{
471
			$this->changeProfile($_content['serverID']);
472
			$composeProfile = $this->mail_bo->profileID;
473
		}
474
		// make sure $acc is set/initalized properly with the current composeProfile, as $acc is used down there
475
		// at several locations and not neccesaryly initialized before
476
		$acc = Mail\Account::read($composeProfile);
0 ignored issues
show
Unused Code introduced by
The assignment to $acc is dead and can be removed.
Loading history...
477
		$buttonClicked = false;
478
		if ($_content['composeToolbar'] === 'send')
479
		{
480
			$buttonClicked = $suppressSigOnTop = true;
481
			$sendOK = true;
482
			$_content['body'] = ($_content['body'] ? $_content['body'] : $_content['mail_'.($_content['mimeType'] == 'html'?'html':'plain').'text']);
483
			/*
484
			perform some simple checks, before trying to send on:
485
			$_content['to'];$_content['cc'];$_content['bcc'];
486
			trim($_content['subject']);
487
			trim(strip_tags(str_replace('&nbsp;','',$_content['body'])));
488
			*/
489
			if (strlen(trim(strip_tags(str_replace('&nbsp;','',$_content['body']))))==0 && count($_content['attachments'])==0)
490
			{
491
				$sendOK = false;
492
				$_content['msg'] = $message = lang("no message body supplied");
493
			}
494
			if ($sendOK && strlen(trim($_content['subject']))==0)
495
			{
496
				$sendOK = false;
497
				$_content['msg'] = $message = lang("no subject supplied");
498
			}
499
			if ($sendOK && empty($_content['to']) && empty($_content['cc']) && empty($_content['bcc']))
500
			{
501
				$sendOK = false;
502
				$_content['msg'] = $message = lang("no adress, to send this mail to, supplied");
503
			}
504
			if ($sendOK)
505
			{
506
				try
507
				{
508
					$success = $this->send($_content);
509
					if ($success==false)
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...
510
					{
511
						$sendOK=false;
512
						$message = $this->errorInfo;
513
					}
514
					if (!empty($_content['mailidentity']) && $_content['mailidentity'] != $sigPref[$this->mail_bo->profileID])
515
					{
516
						$sigPref[$this->mail_bo->profileID]=$_content['mailidentity'];
517
						$GLOBALS['egw']->preferences->add('mail','LastSignatureIDUsed',$sigPref,'user');
518
						// save prefs
519
						$GLOBALS['egw']->preferences->save_repository(true);
520
					}
521
				}
522
				catch (Api\Exception\WrongUserinput $e)
523
				{
524
					$sendOK = false;
525
					$message = $e->getMessage();
526
				}
527
			}
528
			if ($activeProfile != $composeProfile)
529
			{
530
				$this->changeProfile($activeProfile);
531
				$activeProfile = $this->mail_bo->profileID;
532
			}
533
			if ($sendOK)
534
			{
535
				$workingFolder = $activeFolder;
536
				$mode = 'compose';
537
				$idsForRefresh = array();
538
				if (isset($_content['mode']) && !empty($_content['mode']))
539
				{
540
					$mode = $_content['mode'];
541
					if ($_content['mode']=='forward' && !empty($_content['processedmail_id']))
542
					{
543
						$_content['processedmail_id'] = explode(',',$_content['processedmail_id']);
544
						foreach ($_content['processedmail_id'] as $k =>$rowid)
545
						{
546
							$fhA = mail_ui::splitRowID($rowid);
547
							//$this->sessionData['uid'][] = $fhA['msgUID'];
548
							//$this->sessionData['forwardedUID'][] = $fhA['msgUID'];
549
							$idsForRefresh[] = mail_ui::generateRowID($fhA['profileID'], $fhA['folder'], $fhA['msgUID'], $_prependApp=false);
550
							if (!empty($fhA['folder'])) $workingFolder = $fhA['folder'];
551
						}
552
					}
553
					if ($_content['mode']=='reply' && !empty($_content['processedmail_id']))
554
					{
555
						$rhA = mail_ui::splitRowID($_content['processedmail_id']);
556
						//$this->sessionData['uid'] = $rhA['msgUID'];
557
						$idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false);
558
						$workingFolder = $rhA['folder'];
559
					}
560
				}
561
				//the line/condition below should not be needed
562
				if (empty($idsForRefresh) && !empty($_content['processedmail_id']))
563
				{
564
					$rhA = mail_ui::splitRowID($_content['processedmail_id']);
565
					$idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false);
566
				}
567
				$response = Api\Json\Response::get();
568
				if ($activeProfile != $composeProfile)
569
				{
570
					// we need a message only, when account ids (composeProfile vs. activeProfile) differ
571
					$response->call('opener.egw_message',lang('Message send successfully.'));
572
				}
573
				elseif ($activeProfile == $composeProfile && ($workingFolder==$activeFolder['mailbox'] && $mode != 'compose') || ($this->mail_bo->isSentFolder($workingFolder)||$this->mail_bo->isDraftFolder($workingFolder)))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($activeProfile == $comp...tFolder($workingFolder), Probably Intended Meaning: $activeProfile == $compo...Folder($workingFolder))
Loading history...
574
				{
575
					if ($this->mail_bo->isSentFolder($workingFolder)||$this->mail_bo->isDraftFolder($workingFolder))
576
					{
577
						// we may need a refresh when on sent folder or in drafts, as drafted messages will/should be deleted after succeeded send action
578
						$response->call('opener.egw_refresh',lang('Message send successfully.'),'mail');
579
					}
580
					else
581
					{
582
						//error_log(__METHOD__.__LINE__.array2string($idsForRefresh));
583
						$response->call('opener.egw_refresh',lang('Message send successfully.'),'mail',$idsForRefresh,'update');
584
					}
585
				}
586
				else
587
				{
588
					$response->call('opener.egw_message',lang('Message send successfully.'));
589
				}
590
				//egw_framework::refresh_opener(lang('Message send successfully.'),'mail');
591
				Framework::window_close();
592
			}
593
			if ($sendOK == false)
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...
introduced by
The condition $sendOK == false is always true.
Loading history...
594
			{
595
				$response = Api\Json\Response::get();
596
				Framework::message(lang('Message send failed: %1',$message),'error');// maybe error is more appropriate
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $message. ( Ignorable by Annotation )

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

596
				Framework::message(/** @scrutinizer ignore-call */ lang('Message send failed: %1',$message),'error');// maybe error is more appropriate

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...
597
				$response->call('app.mail.clearIntevals');
598
			}
599
		}
600
601
		if ($activeProfile != $composeProfile) $this->changeProfile($activeProfile);
602
		$insertSigOnTop = false;
603
		$content = (is_array($_content)?$_content:array());
604
		if ($_contentHasMimeType)
605
		{
606
			// mimeType is now a checkbox; convert it here to match expectations
607
			// ToDo: match Code to meet checkbox value
608
			if ($content['mimeType']==1)
609
			{
610
				$_content['mimeType'] = $content['mimeType']='html';
611
			}
612
			else
613
			{
614
				$_content['mimeType'] = $content['mimeType']='plain';
615
			}
616
617
		}
618
		// user might have switched desired mimetype, so we should convert
619
		if ($content['is_html'] && $content['mimeType']=='plain')
620
		{
621
			//error_log(__METHOD__.__LINE__.$content['mail_htmltext']);
622
			$suppressSigOnTop = true;
623
			if (stripos($content['mail_htmltext'],'<pre>')!==false)
624
			{
625
				$contentArr = Api\Mail\Html::splithtmlByPRE($content['mail_htmltext']);
626
				if (is_array($contentArr))
627
				{
628
					foreach ($contentArr as $k =>&$elem)
629
					{
630
						if (stripos($elem,'<pre>')!==false) $elem = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$elem);
631
					}
632
					$content['mail_htmltext'] = implode('',$contentArr);
633
				}
634
			}
635
			$content['mail_htmltext'] = $this->_getCleanHTML($content['mail_htmltext']);
636
			$content['mail_htmltext'] = Api\Mail\Html::convertHTMLToText($content['mail_htmltext'],$charset=false,false,true);
637
638
			$content['body'] = $content['mail_htmltext'];
639
			unset($content['mail_htmltext']);
640
			$content['is_html'] = false;
641
			$content['is_plain'] = true;
642
		}
643
		if ($content['is_plain'] && $content['mimeType']=='html')
644
		{
645
			// the possible font span should only be applied on first load or on switch plain->html
646
			$isFirstLoad = "switchedplaintohtml";
647
			//error_log(__METHOD__.__LINE__.$content['mail_plaintext']);
648
			$suppressSigOnTop = true;
649
			$content['mail_plaintext'] = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$content['mail_plaintext']);
650
			//$this->replaceEmailAdresses($content['mail_plaintext']);
651
			$content['body'] = $content['mail_plaintext'];
652
			unset($content['mail_plaintext']);
653
			$content['is_html'] = true;
654
			$content['is_plain'] = false;
655
		}
656
657
		$content['body'] = ($content['body'] ? $content['body'] : $content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text']);
658
		unset($_content['body']);
659
		unset($_content['mail_htmltext']);
660
		unset($_content['mail_plaintext']);
661
		$_currentMode = $_content['mimeType'];
662
663
		// we have to keep comments to be able to changing signatures
664
		// signature is wraped in "<!-- HTMLSIGBEGIN -->$signature<!-- HTMLSIGEND -->"
665
		Mail::$htmLawed_config['comment'] = 2;
666
667
		// form was submitted either by clicking a button or by changing one of the triggering selectboxes
668
		// identity and signatureid; this might trigger that the signature in mail body may have to be altered
669
		if ( !empty($content['body']) &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($content['body'...seCache['mailidentity'], Probably Intended Meaning: ! empty($content['body']...eCache['mailidentity'])
Loading history...
670
			(!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount']) ||
671
			(!empty($composeCache['mailidentity']) && !empty($_content['mailidentity']) && $_content['mailidentity'] != $composeCache['mailidentity'])
672
		)
673
		{
674
			$buttonClicked = true;
675
			$suppressSigOnTop = true;
676
			if (!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount'])
677
			{
678
				$acc = Mail\Account::read($_content['mailaccount']);
679
				//error_log(__METHOD__.__LINE__.array2string($acc));
680
				$Identities = Mail\Account::read_identity($acc['ident_id'],true);
681
				//error_log(__METHOD__.__LINE__.array2string($Identities));
682
				if ($Identities['ident_id'])
683
				{
684
					$newSig = $Identities['ident_id'];
685
				}
686
				else
687
				{
688
					$newSig = $this->mail_bo->getDefaultIdentity();
689
					if ($newSig === false) $newSig = -2;
0 ignored issues
show
introduced by
The condition $newSig === false is always false.
Loading history...
690
				}
691
			}
692
			$_oldSig = $composeCache['mailidentity'];
693
			$_signatureid = ($newSig?$newSig:$_content['mailidentity']);
694
695
			if ($_oldSig != $_signatureid)
696
			{
697
				if($this->_debug) error_log(__METHOD__.__LINE__.' old,new ->'.$_oldSig.','.$_signatureid.'#'.$content['body']);
0 ignored issues
show
Bug Best Practice introduced by
The property _debug does not exist on mail_compose. Did you maybe forget to declare it?
Loading history...
698
				// prepare signatures, the selected sig may be used on top of the body
699
				try
700
				{
701
					$oldSignature = Mail\Account::read_identity($_oldSig,true);
702
					//error_log(__METHOD__.__LINE__.'Old:'.array2string($oldSignature).'#');
703
					$oldSigText = $oldSignature['ident_signature'];
704
				}
705
				catch (Exception $e)
706
				{
707
					$oldSignature=array();
0 ignored issues
show
Unused Code introduced by
The assignment to $oldSignature is dead and can be removed.
Loading history...
708
					$oldSigText = null;
709
				}
710
				try
711
				{
712
					$signature = Mail\Account::read_identity($_signatureid,true);
713
					//error_log(__METHOD__.__LINE__.'New:'.array2string($signature).'#');
714
					$sigText = $signature['ident_signature'];
715
				}
716
				catch (Exception $e)
717
				{
718
					$signature=array();
0 ignored issues
show
Unused Code introduced by
The assignment to $signature is dead and can be removed.
Loading history...
719
					$sigText = null;
720
				}
721
				//error_log(__METHOD__.'Old:'.$oldSigText.'#');
722
				//error_log(__METHOD__.'New:'.$sigText.'#');
723
				if ($_currentMode == 'plain')
724
				{
725
					$oldSigText = $this->convertHTMLToText($oldSigText,true,true);
726
					$sigText = $this->convertHTMLToText($sigText,true,true);
727
					if($this->_debug) error_log(__METHOD__." Old signature:".$oldSigText);
728
				}
729
730
				//$oldSigText = Mail::merge($oldSigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
731
				//error_log(__METHOD__.'Old+:'.$oldSigText.'#');
732
				//$sigText = Mail::merge($sigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
733
				//error_log(__METHOD__.'new+:'.$sigText.'#');
734
				$_htmlConfig = Mail::$htmLawed_config;
735
				Mail::$htmLawed_config['transform_anchor'] = false;
736
				$oldSigTextCleaned = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"),
737
					$_currentMode == 'html' ? Api\Html::purify($oldSigText, null, array(), true) : $oldSigText);
738
				//error_log(__METHOD__.'Old(clean):'.$oldSigTextCleaned.'#');
739
				if ($_currentMode == 'html')
740
				{
741
					$content['body'] = str_replace("\n",'\n',$content['body']);	// dont know why, but \n screws up preg_replace
742
					$styles = Mail::getStyles(array(array('body'=>$content['body'])));
743
					if (stripos($content['body'],'style')!==false) Api\Mail\Html::replaceTagsCompletley($content['body'],'style',$endtag='',true); // clean out empty or pagewide style definitions / left over tags
744
				}
745
				$content['body'] = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"),
746
					$_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']);
747
				Mail::$htmLawed_config = $_htmlConfig;
748
				if ($_currentMode == 'html')
749
				{
750
					$replaced = null;
751
					$content['body'] = preg_replace($reg='|'.preg_quote('<!-- HTMLSIGBEGIN -->','|').'.*'.preg_quote('<!-- HTMLSIGEND -->','|').'|u',
752
						$rep='<!-- HTMLSIGBEGIN -->'.$sigText.'<!-- HTMLSIGEND -->', $in=$content['body'], -1, $replaced);
753
					$content['body'] = str_replace(array('\n',"\xe2\x80\x93","\xe2\x80\x94","\xe2\x82\xac"),array("\n",'&ndash;','&mdash;','&euro;'),$content['body']);
754
					//error_log(__METHOD__."() preg_replace('$reg', '$rep', '$in', -1)='".$content['body']."', replaced=$replaced");
755
					unset($rep, $in);
756
					if ($replaced)
757
					{
758
						$content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid;
759
						$found = false; // this way we skip further replacement efforts
760
					}
761
					else
762
					{
763
						// try the old way
764
						$found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false);
765
					}
766
				}
767
				else
768
				{
769
					$found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false);
770
				}
771
772
				if ($found !== false && $_oldSig != -2 && !(empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) ==''))
773
				{
774
					//error_log(__METHOD__.'Old Content:'.$content['body'].'#');
775
					$_oldSigText = preg_quote($oldSigTextCleaned,'~');
776
					//error_log(__METHOD__.'Old(masked):'.$_oldSigText.'#');
777
					$content['body'] = preg_replace('~'.$_oldSigText.'~mi',$sigText,$content['body'],1);
778
					//error_log(__METHOD__.'new Content:'.$content['body'].'#');
779
				}
780
781
				if ($_oldSig == -2 && (empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) ==''))
782
				{
783
					// if there is no sig selected, there is no way to replace a signature
784
				}
785
786
				if ($found === false)
787
				{
788
					if($this->_debug) error_log(__METHOD__." Old Signature failed to match:".$oldSigTextCleaned);
789
					if($this->_debug) error_log(__METHOD__." Compare content:".$content['body']);
790
				}
791
				else
792
				{
793
					$content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid;
794
				}
795
				if ($styles)
796
				{
797
					//error_log($styles);
798
					$content['body'] = $styles.$content['body'];
799
				}
800
			}
801
		}
802
		/*run the purify on compose body unconditional*/
803
		$content['body'] = str_replace(array("\r", "\t", "<br />\n"), array("", "", "<br />"),
804
		$_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']);
805
806
		// do not double insert a signature on a server roundtrip
807
		if ($buttonClicked) $suppressSigOnTop = true;
808
809
		// On submit reads external_vcard widget's value and addes them as attachments.
810
		// this happens when we send vcards from addressbook to an opened compose
811
		// dialog.
812
		if ($appendix_data['files'])
813
		{
814
			$_REQUEST['preset']['file'] = $appendix_data['files']['file'];
815
			$_REQUEST['preset']['type'] = $appendix_data['files']['type'];
816
			$_content['filemode'] = !empty($appendix_data['files']['filemode']) &&
817
						isset(Vfs\Sharing::$modes[$appendix_data['files']['filemode']]) ?
818
							$appendix_data['files']['filemode'] : Vfs\Sharing::ATTACH;
819
			$suppressSigOnTop = true;
820
			unset($_content['attachments']);
821
			$this->addPresetFiles($content, $insertSigOnTop, true);
822
		}
823
824
		if ($isFirstLoad)
825
		{
826
			$alwaysAttachVCardAtCompose = false; // we use this to eliminate double attachments, if users VCard is already present/attached
827
			if ( isset($GLOBALS['egw_info']['apps']['stylite']) && (isset($this->mailPreferences['attachVCardAtCompose']) &&
828
				$this->mailPreferences['attachVCardAtCompose']))
829
			{
830
				$alwaysAttachVCardAtCompose = true;
831
				if (!is_array($_REQUEST['preset']['file']) && !empty($_REQUEST['preset']['file']))
832
				{
833
					$f = $_REQUEST['preset']['file'];
834
					$_REQUEST['preset']['file'] = array($f);
835
				}
836
				$_REQUEST['preset']['file'][] = "vfs://default/apps/addressbook/".$GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')."/.entry";
837
			}
838
			// an app passed the request for fetching and mailing an entry
839
			if (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id']))
840
			{
841
				$app = $_REQUEST['app'];
842
				$mt = $_REQUEST['method'];
843
				$id = $_REQUEST['id'];
844
				// passed method MUST be registered
845
				$method = Link::get_registry($app,$mt);
846
				//error_log(__METHOD__.__LINE__.array2string($method));
847
				if ($method)
848
				{
849
					$res = ExecMethod($method,array($id,'html'));
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

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

849
					$res = /** @scrutinizer ignore-deprecated */ ExecMethod($method,array($id,'html'));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
850
					//error_log(__METHOD__.__LINE__.array2string($res));
851
					if (!empty($res))
852
					{
853
						$insertSigOnTop = 'below';
854
						if (isset($res['attachments']) && is_array($res['attachments']))
855
						{
856
							foreach($res['attachments'] as $f)
857
							{
858
								$_REQUEST['preset']['file'][] = $f;
859
							}
860
						}
861
						$content['subject'] = lang($app).' #'.$res['id'].': ';
862
						foreach(array('subject','body','mimetype') as $name) {
863
							$sName = $name;
864
							if ($name=='mimetype'&&$res[$name])
865
							{
866
								$sName = 'mimeType';
867
								$content[$sName] = $res[$name];
868
							}
869
							else
870
							{
871
								if ($res[$name]) $content[$sName] .= (strlen($content[$sName])>0 ? ' ':'') .$res[$name];
872
							}
873
						}
874
					}
875
				}
876
			}
877
			// handle preset info/values
878
			if (is_array($_REQUEST['preset']))
879
			{
880
				$alreadyProcessed=array();
881
				//_debug_array($_REQUEST);
882
				if ($_REQUEST['preset']['mailto']) {
883
					// handle mailto strings such as
884
					// mailto:larry,dan?cc=mike&bcc=sue&subject=test&body=type+your&body=message+here
885
					// the above string may be htmlentyty encoded, then multiple body tags are supported
886
					// first, strip the mailto: string out of the mailto URL
887
					$tmp_send_to = (stripos($_REQUEST['preset']['mailto'],'mailto')===false?$_REQUEST['preset']['mailto']:trim(substr(html_entity_decode($_REQUEST['preset']['mailto']),7)));
888
					// check if there is more than the to address
889
					$mailtoArray = explode('?',$tmp_send_to,2);
890
					if ($mailtoArray[1]) {
891
						// check if there are more than one requests
892
						$addRequests = explode('&',$mailtoArray[1]);
893
						foreach ($addRequests as $key => $reqval) {
894
							// the additional requests should have a =, to separate key from value.
895
							$reqval = preg_replace('/__AMPERSAND__/i', "&", $reqval);
896
							$keyValuePair = explode('=',$reqval,2);
897
							$content[$keyValuePair[0]] .= (strlen($content[$keyValuePair[0]])>0 ? ' ':'') . $keyValuePair[1];
898
						}
899
					}
900
					$content['to']= preg_replace('/__AMPERSAND__/i', "&", $mailtoArray[0]);
901
					$alreadyProcessed['to']='to';
902
					// if the mailto string is not htmlentity decoded the arguments are passed as simple requests
903
					foreach(array('cc','bcc','subject','body') as $name) {
904
						$alreadyProcessed[$name]=$name;
905
						if ($_REQUEST[$name]) $content[$name] .= (strlen($content[$name])>0 ? ( $name == 'cc' || $name == 'bcc' ? ',' : ' ') : '') . $_REQUEST[$name];
906
					}
907
				}
908
909
				if ($_REQUEST['preset']['mailtocontactbyid']) {
910
					if ($GLOBALS['egw_info']['user']['apps']['addressbook']) {
911
						$contacts_obj = new Api\Contacts();
912
						$addressbookprefs =& $GLOBALS['egw_info']['user']['preferences']['addressbook'];
913
						if (method_exists($contacts_obj,'search')) {
914
915
							$addressArray = explode(',',$_REQUEST['preset']['mailtocontactbyid']);
916
							foreach ((array)$addressArray as $id => $addressID)
917
							{
918
								$addressID = (int) $addressID;
919
								if (!($addressID>0))
920
								{
921
									unset($addressArray[$id]);
922
								}
923
							}
924
							if (count($addressArray))
925
							{
926
								$_searchCond = array('contact_id'=>$addressArray);
927
								//error_log(__METHOD__.__LINE__.$_searchString);
928
								$showAccounts= $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1';
929
								$filter = ($showAccounts?array():array('account_id' => null));
930
								$filter['cols_to_search']=array('n_fn','email','email_home');
931
								$contacts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),$filter);
932
								// additionally search the accounts, if the contact storage is not the account storage
933
								if ($showAccounts &&
934
									$GLOBALS['egw_info']['server']['account_repository'] == 'ldap' &&
935
									$GLOBALS['egw_info']['server']['contact_repository'] == 'sql')
936
								{
937
									$accounts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),array('owner' => 0));
938
939
									if ($contacts && $accounts)
0 ignored issues
show
Bug Best Practice introduced by
The expression $contacts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $accounts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
940
									{
941
										$contacts = array_merge($contacts,$accounts);
942
										usort($contacts, function($a, $b)
943
										{
944
											return strcasecmp($a['n_fn'], $b['n_fn']);
945
										});
946
									}
947
									elseif($accounts)
0 ignored issues
show
Bug Best Practice introduced by
The expression $accounts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
948
									{
949
										$contacts =& $accounts;
950
									}
951
									unset($accounts);
952
								}
953
							}
954
							if(is_array($contacts)) {
955
								$mailtoArray = array();
956
								$primary = $addressbookprefs['distributionListPreferredMail'];
957
								if ($primary != 'email' && $primary != 'email_home') $primary = 'email';
958
								$secondary = ($primary == 'email'?'email_home':'email');
959
								//error_log(__METHOD__.__LINE__.array2string($contacts));
960
								foreach($contacts as $contact) {
961
									$innerCounter=0;
962
									foreach(array($contact[$primary],$contact[$secondary]) as $email) {
963
										// use pref distributionListPreferredMail for the primary address
964
										// avoid wrong addresses, if an rfc822 encoded address is in addressbook
965
										$email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email);
966
										$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
967
										$completeMailString = addslashes(trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']) .' <'. trim($email) .'>');
968
										if($innerCounter==0 && !empty($email) && in_array($completeMailString ,$mailtoArray) === false) {
969
											$i++;
970
											$innerCounter++;
971
											$mailtoArray[$i] = $completeMailString;
972
										}
973
									}
974
								}
975
							}
976
							//error_log(__METHOD__.__LINE__.array2string($mailtoArray));
977
							$alreadyProcessed['to']='to';
978
							$content['to']=$mailtoArray;
979
						}
980
					}
981
				}
982
983
				if (isset($_REQUEST['preset']['file']))
984
				{
985
					$content['filemode'] = !empty($_REQUEST['preset']['filemode']) &&
986
						isset(Vfs\Sharing::$modes[$_REQUEST['preset']['filemode']]) ?
987
							$_REQUEST['preset']['filemode'] : Vfs\Sharing::ATTACH;
988
989
					$this->addPresetFiles($content, $insertSigOnTop, $alwaysAttachVCardAtCompose);
990
					$remember = array();
991
					if (isset($_REQUEST['preset']['mailto']) || (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id'])))
992
					{
993
						foreach(array_keys($content) as $k)
994
						{
995
							if (in_array($k,array('to','cc','bcc','subject','body','mimeType'))&&isset($this->sessionData[$k]))
996
							{
997
								$alreadyProcessed[$k]=$k;
998
								$remember[$k] = $this->sessionData[$k];
999
							}
1000
						}
1001
					}
1002
					if(!empty($remember)) $content = array_merge($content,$remember);
1003
				}
1004
				foreach(array('to','cc','bcc','subject','body','mimeType') as $name)
1005
				{
1006
					//always handle mimeType
1007
					if ($name=='mimeType' && $_REQUEST['preset'][$name])
1008
					{
1009
						$_content[$name]=$content[$name]=$_REQUEST['preset'][$name];
1010
					}
1011
					//skip if already processed by "preset Routines"
1012
					if ($alreadyProcessed[$name]) continue;
1013
					//error_log(__METHOD__.__LINE__.':'.$name.'->'. $_REQUEST['preset'][$name]);
1014
					if ($_REQUEST['preset'][$name]) $content[$name] = $_REQUEST['preset'][$name];
1015
				}
1016
			}
1017
			// is the to address set already?
1018
			if (!empty($_REQUEST['send_to']))
1019
			{
1020
				$content['to'] = base64_decode($_REQUEST['send_to']);
1021
				// first check if there is a questionmark or ampersand
1022
				if (strpos($content['to'],'?')!== false) list($content['to'],$rest) = explode('?',$content['to'],2);
1023
				$content['to'] = html_entity_decode($content['to']);
1024
				if (($at_pos = strpos($content['to'],'@')) !== false)
1025
				{
1026
					if (($amp_pos = strpos(substr($content['to'],$at_pos),'&')) !== false)
1027
					{
1028
						//list($email,$addoptions) = explode('&',$value,2);
1029
						$email = substr($content['to'],0,$amp_pos+$at_pos);
1030
						$rest = substr($content['to'], $amp_pos+$at_pos+1);
1031
						//error_log(__METHOD__.__LINE__.$email.' '.$rest);
1032
						$content['to'] = $email;
1033
					}
1034
				}
1035
				if (strpos($content['to'],'%40')!== false) $content['to'] = Api\Html::purify(str_replace('%40','@',$content['to']));
1036
				$rarr = array(Api\Html::purify($rest));
1037
				if (isset($rest)&&!empty($rest) && strpos($rest,'&')!== false) $rarr = explode('&',$rest);
1038
				//error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($rarr));
1039
				$karr = array();
1040
				foreach ($rarr as &$rval)
1041
				{
1042
					//must contain =
1043
					if (strpos($rval,'=')!== false)
1044
					{
1045
						list($k,$v) = explode('=',$rval,2);
1046
						$karr[$k] = (string)$v;
1047
						unset($k,$v);
1048
					}
1049
				}
1050
				//error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($karr));
1051
				foreach(array('cc','bcc','subject','body') as $name)
1052
				{
1053
					if ($karr[$name]) $content[$name] = $karr[$name];
1054
				}
1055
				if (!empty($_REQUEST['subject'])) $content['subject'] = Api\Html::purify(trim(html_entity_decode($_REQUEST['subject'])));
1056
			}
1057
		}
1058
		//error_log(__METHOD__.__LINE__.array2string($content));
1059
		//is the MimeType set/requested
1060
		if ($isFirstLoad && !empty($_REQUEST['mimeType']))
1061
		{
1062
			$_content['mimeType'] = $content['mimeType'];
1063
			if (($_REQUEST['mimeType']=="text" ||$_REQUEST['mimeType']=="plain") && $content['mimeType'] == 'html')
1064
			{
1065
				$_content['mimeType'] = $content['mimeType']  = 'plain';
1066
				$content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body']));
0 ignored issues
show
Bug introduced by
str_replace(array(' ', ... ' ', $content['body']) cannot be passed to mail_compose::convertHTMLToText() as the parameter $_html expects a reference. ( Ignorable by Annotation )

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

1066
				$content['body'] = $this->convertHTMLToText(/** @scrutinizer ignore-type */ str_replace(array("\n\r","\n"),' ',$content['body']));
Loading history...
1067
			}
1068
			if ($_REQUEST['mimeType']=="html" && $content['mimeType'] != 'html')
1069
			{
1070
				$_content['mimeType'] = $content['mimeType']  = 'html';
1071
				$content['body'] = "<pre>".$content['body']."</pre>";
1072
				// take care this assumption is made on the creation of the reply header in bocompose::getReplyData
1073
				if (strpos($content['body'],"<pre> \r\n \r\n---")===0) $content['body'] = substr_replace($content['body']," <br>\r\n<pre>---",0,strlen("<pre> \r\n \r\n---")-1);
1074
			}
1075
		}
1076
		else
1077
		{
1078
			// try to enforce a mimeType on reply ( if type is not of the wanted type )
1079
			if ($isReply)
1080
			{
1081
				if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="text" &&
1082
					$content['mimeType'] == 'html')
1083
				{
1084
					$_content['mimeType'] = $content['mimeType']  = 'plain';
1085
					$content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body']));
1086
				}
1087
				if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="html" &&
1088
					$content['mimeType'] != 'html')
1089
				{
1090
					$_content['mimeType'] = $content['mimeType']  = 'html';
1091
					$content['body'] = "<pre>".$content['body']."</pre>";
1092
					// take care this assumption is made on the creation of the reply header in bocompose::getReplyData
1093
					if (strpos($content['body'],"<pre> \r\n \r\n---")===0) $content['body'] = substr_replace($content['body']," <br>\r\n<pre>---",0,strlen("<pre> \r\n \r\n---")-1);
1094
				}
1095
			}
1096
		}
1097
1098
		if ($content['mimeType'] == 'html' && Api\Html::htmlarea_availible()===false)
0 ignored issues
show
introduced by
The condition EGroupware\Api\Html::htm...a_availible() === false is always false.
Loading history...
1099
		{
1100
			$_content['mimeType'] = $content['mimeType'] = 'plain';
1101
			$content['body'] = $this->convertHTMLToText($content['body']);
1102
		}
1103
		// is a certain signature requested?
1104
		// only the following values are supported (and make sense)
1105
		// no => means -2
1106
		// system => means -1
1107
		// default => fetches the default, which is standard behavior
1108
		if (!empty($_REQUEST['signature']) && (strtolower($_REQUEST['signature']) == 'no' || strtolower($_REQUEST['signature']) == 'system'))
1109
		{
1110
			$content['mailidentity'] = $presetSig = (strtolower($_REQUEST['signature']) == 'no' ? -2 : -1);
1111
		}
1112
1113
		$disableRuler = false;
1114
		//_debug_array(($presetSig ? $presetSig : $content['mailidentity']));
1115
		try
1116
		{
1117
			$signature = Mail\Account::read_identity($content['mailidentity'] ? $content['mailidentity'] : $presetSig,true);
1118
		}
1119
		catch (Exception $e)
1120
		{
1121
			//PROBABLY NOT FOUND
1122
			$signature=array();
1123
		}
1124
		if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) &&
1125
			$this->mailPreferences['disableRulerForSignatureSeparation']) ||
1126
			empty($signature['ident_signature']) ||
1127
			trim($this->convertHTMLToText($signature['ident_signature'],true,true)) =='' ||
1128
			$this->mailPreferences['insertSignatureAtTopOfMessage'] == '1')
1129
		{
1130
			$disableRuler = true;
1131
		}
1132
		$font_span = $font_part = '';
1133
		if($content['mimeType'] == 'html') {
1134
			// User preferences for style
1135
			$font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font'];
1136
			$font_size = Etemplate\Widget\HtmlArea::font_size_from_prefs();
1137
			$font_part = '<span style="width:100%; display: inline; '.($font?'font-family:'.$font.'; ':'').($font_size?'font-size:'.$font_size.'; ':'').'">';
1138
			$font_span = $font_part.'&#8203;</span>';
1139
			if (empty($font) && empty($font_size)) $font_span = '';
1140
		}
1141
		// the font span should only be applied on first load or on switch plain->html and the absence of the font_part of the span
1142
		if (!$isFirstLoad && !empty($font_span) && stripos($content['body'],$font_part)===false) $font_span = '';
1143
		//remove possible html header stuff
1144
		if (stripos($content['body'],'<html><head></head><body>')!==false) $content['body'] = str_ireplace(array('<html><head></head><body>','</body></html>'),array('',''),$content['body']);
1145
		//error_log(__METHOD__.__LINE__.array2string($this->mailPreferences));
1146
		$blockElements = array('address','blockquote','center','del','dir','div','dl','fieldset','form','h1','h2','h3','h4','h5','h6','hr','ins','isindex','menu','noframes','noscript','ol','p','pre','table','ul');
1147
		if ($this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend' &&
1148
			!(isset($_POST['mySigID']) && !empty($_POST['mySigID']) ) && !$suppressSigOnTop
1149
		)
1150
		{
1151
			// ON tOP OR BELOW? pREF CAN TELL
1152
			/*
1153
				Signature behavior preference changed. New default, if not set -> 0
1154
						'0' => 'after reply, visible during compose',
1155
						'1' => 'before reply, visible during compose',
1156
						'no_belowaftersend'  => 'appended after reply before sending',
1157
			*/
1158
			$insertSigOnTop = ($insertSigOnTop?$insertSigOnTop:($this->mailPreferences['insertSignatureAtTopOfMessage']?$this->mailPreferences['insertSignatureAtTopOfMessage']:'below'));
1159
			$sigText = Mail::merge($signature['ident_signature'],array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
1160
			if ($content['mimeType'] == 'html')
1161
			{
1162
				$sigTextStartsWithBlockElement = ($disableRuler?false:true);
1163
				foreach($blockElements as $e)
1164
				{
1165
					if ($sigTextStartsWithBlockElement) break;
1166
					if (stripos(trim($sigText),'<'.$e)===0) $sigTextStartsWithBlockElement = true;
1167
				}
1168
			}
1169
			if($content['mimeType'] == 'html') {
1170
				$before = $disableRuler ? '' : '<hr style="border:1px dotted silver; width:100%;">';
1171
				$inbetween = '';
1172
			} else {
1173
				$before = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
1174
				$inbetween = "\r\n";
1175
			}
1176
			if ($content['mimeType'] == 'html')
1177
			{
1178
				$sigText = ($sigTextStartsWithBlockElement?'':"<div>")."<!-- HTMLSIGBEGIN -->".$sigText."<!-- HTMLSIGEND -->".($sigTextStartsWithBlockElement?'':"</div>");
1179
			}
1180
1181
			if ($insertSigOnTop === 'below')
1182
			{
1183
				$content['body'] = $font_span.$content['body'].$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true));
1184
			}
1185
			else
1186
			{
1187
				$content['body'] = $font_span.$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true)).$inbetween.$content['body'];
1188
			}
1189
		}
1190
		// Skip this part if we're merging, it would add an extra line at the top
1191
		else if (!$content['body'])
1192
		{
1193
			$content['body'] = ($font_span?($isFirstLoad === "switchedplaintohtml"?$font_part:$font_span):'').($isFirstLoad === "switchedplaintohtml"?"</span>":"");
1194
		}
1195
		//error_log(__METHOD__.__LINE__.$content['body']);
1196
1197
		// prepare body
1198
		// in a way, this tests if we are having real utf-8 (the displayCharset) by now; we should if charsets reported (or detected) are correct
1199
		$content['body'] = Api\Translation::convert_jsonsafe($content['body'],'utf-8');
1200
		//error_log(__METHOD__.__LINE__.array2string($content));
1201
1202
		// get identities of all accounts as "$acc_id:$ident_id" => $identity
1203
		$sel_options['mailaccount'] = $identities = array();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sel_options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sel_options = array(); before regardless.
Loading history...
1204
		foreach(Mail\Account::search(true,false) as $acc_id => $account)
1205
		{
1206
			// do NOT add SMTP only accounts as identities
1207
			if (!$account->is_imap(false)) continue;
1208
1209
			foreach($account->identities($acc_id) as $ident_id => $identity)
1210
			{
1211
				$sel_options['mailaccount'][$acc_id.':'.$ident_id] = $identity;
1212
				$identities[$ident_id] = $identity;
1213
			}
1214
			unset($account);
1215
		}
1216
1217
		//$content['bcc'] = array('[email protected]','[email protected]');
1218
		// address stuff like from, to, cc, replyto
1219
		$destinationRows = 0;
1220
		foreach(self::$destinations as $destination) {
1221
			if (!is_array($content[$destination]))
1222
			{
1223
				if (!empty($content[$destination])) $content[$destination] = (array)$content[$destination];
1224
			}
1225
			$addr_content = $content[strtolower($destination)];
1226
			// we clear the given address array and rebuild it
1227
			unset($content[strtolower($destination)]);
1228
			foreach((array)$addr_content as $key => $value) {
1229
				if ($value=="NIL@NIL") continue;
1230
				if ($destination=='replyto' && str_replace('"','',$value) ==
1231
					str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()]))
1232
				{
1233
					// preserve/restore the value to content.
1234
					$content[strtolower($destination)][]=$value;
1235
					continue;
1236
				}
1237
				//error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value)));
1238
				$value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT));
1239
				foreach(Mail::parseAddressList($value) as $addressObject) {
1240
					if ($addressObject->host == '.SYNTAX-ERROR.') continue;
1241
					$address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal);
1242
					//$address = Mail::htmlentities($address, $this->displayCharset);
1243
					$content[strtolower($destination)][]=$address;
1244
					$destinationRows++;
1245
				}
1246
			}
1247
		}
1248
		if ($_content)
1249
		{
1250
			//input array of _content had no signature information but was seeded later, and content has a valid setting
1251
			if (!$_contentHasSigID && $content['mailidentity'] && array_key_exists('mailidentity',$_content)) unset($_content['mailidentity']);
1252
			$content = array_merge($content,$_content);
1253
1254
			if (!empty($content['folder'])) $sel_options['folder']=$this->ajax_searchFolder(0,true);
1255
			if (empty($content['mailaccount'])) $content['mailaccount'] = $this->mail_bo->profileID;
1256
		}
1257
		else
1258
		{
1259
			//error_log(__METHOD__.__LINE__.array2string(array($sel_options['mailaccount'],$selectedSender)));
1260
			$content['mailaccount'] = $this->mail_bo->profileID;
1261
			//error_log(__METHOD__.__LINE__.$content['body']);
1262
		}
1263
		$content['is_html'] = ($content['mimeType'] == 'html'?true:'');
1264
		$content['is_plain'] = ($content['mimeType'] == 'html'?'':true);
1265
		$content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body'];
1266
		$content['showtempname']=0;
1267
		//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.'before merging content with uploadforCompose:'.array2string($content['attachments']));
1268
		$content['attachments']=(is_array($content['attachments'])&&is_array($content['uploadForCompose'])?array_merge($content['attachments'],(!empty($content['uploadForCompose'])?$content['uploadForCompose']:array())):(is_array($content['uploadForCompose'])?$content['uploadForCompose']:(is_array($content['attachments'])?$content['attachments']:null)));
1269
		//if (is_array($content['attachments'])) foreach($content['attachments'] as $k => &$file) $file['delete['.$file['tmp_name'].']']=0;
1270
		$content['no_griddata'] = empty($content['attachments']);
1271
		$preserv['attachments'] = $content['attachments'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$preserv was never initialized. Although not strictly required by PHP, it is generally a good practice to add $preserv = array(); before regardless.
Loading history...
1272
1273
		//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.' Attachments:'.array2string($content['attachments']));
1274
		// if no filemanager -> no vfsFileSelector
1275
		if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
1276
		{
1277
			$content['vfsNotAvailable'] = "mail_DisplayNone";
1278
		}
1279
		// if no infolog -> no save as infolog
1280
		if (!$GLOBALS['egw_info']['user']['apps']['infolog'])
1281
		{
1282
			$content['noInfologAvailable'] = "mail_DisplayNone";
1283
		}
1284
		// if no tracker -> no save as tracker
1285
		if (!$GLOBALS['egw_info']['user']['apps']['tracker'])
1286
		{
1287
			$content['noTrackerAvailable'] = "mail_DisplayNone";
1288
		}
1289
		if (!$GLOBALS['egw_info']['user']['apps']['infolog'] && !$GLOBALS['egw_info']['user']['apps']['tracker'])
1290
		{
1291
			$content['noSaveAsAvailable'] = "mail_DisplayNone";
1292
		}
1293
		// composeID to detect if we have changes to certain content
1294
		$preserv['composeID'] = $content['composeID'] = $this->composeID;
1295
		//error_log(__METHOD__.__LINE__.' ComposeID:'.$preserv['composeID']);
1296
		$preserv['is_html'] = $content['is_html'];
1297
		$preserv['is_plain'] = $content['is_plain'];
1298
		if (isset($content['mimeType'])) $preserv['mimeType'] = $content['mimeType'];
1299
		$sel_options['mimeType'] = self::$mimeTypes;
1300
		$sel_options['priority'] = self::$priorities;
1301
		$sel_options['filemode'] = Vfs\Sharing::$modes;
1302
		if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3;
1303
		//$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed
1304
		$etpl = new Etemplate('mail.compose');
1305
1306
		$etpl->setElementAttribute('composeToolbar', 'actions', self::getToolbarActions($content));
1307
		if ($content['mimeType']=='html')
1308
		{
1309
			//mode="$cont[rtfEditorFeatures]" validation_rules="$cont[validation_rules]" base_href="$cont[upload_dir]"
1310
			$_htmlConfig = Mail::$htmLawed_config;
1311
			Mail::$htmLawed_config['comment'] = 2;
1312
			Mail::$htmLawed_config['transform_anchor'] = false;
1313
			$content['validation_rules']= json_encode(Mail::$htmLawed_config);
1314
			$etpl->setElementAttribute('mail_htmltext','validation_rules',$content['validation_rules']);
1315
			Mail::$htmLawed_config = $_htmlConfig;
1316
		}
1317
1318
		if (isset($content['composeID'])&&!empty($content['composeID']))
1319
		{
1320
			$composeCache = $content;
1321
			unset($composeCache['body']);
1322
			unset($composeCache['mail_htmltext']);
1323
			unset($composeCache['mail_plaintext']);
1324
			Api\Cache::setCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$this->composeID,$composeCache,$expiration=60*60*2);
1325
		}
1326
		if (!isset($_content['serverID'])||empty($_content['serverID']))
1327
		{
1328
			$content['serverID'] = $this->mail_bo->profileID;
1329
		}
1330
		$preserv['serverID'] = $content['serverID'];
1331
		$preserv['lastDrafted'] = $content['lastDrafted'];
1332
		$preserv['processedmail_id'] = $content['processedmail_id'];
1333
		$preserv['references'] = $content['references'];
1334
		$preserv['in-reply-to'] = $content['in-reply-to'];
1335
		// thread-topic is a proprietary microsoft header and deprecated with the current version
1336
		// horde does not support the encoding of thread-topic, and probably will not no so in the future
1337
		//$preserv['thread-topic'] = $content['thread-topic'];
1338
		$preserv['thread-index'] = $content['thread-index'];
1339
		$preserv['list-id'] = $content['list-id'];
1340
		$preserv['mode'] = $content['mode'];
1341
		// convert it back to checkbox expectations
1342
		if($content['mimeType'] == 'html') {
1343
			$content['mimeType']=1;
1344
		} else {
1345
			$content['mimeType']=0;
1346
		}
1347
		// set the current selected mailaccount as param for folderselection
1348
		$etpl->setElementAttribute('folder','autocomplete_params',array('mailaccount'=>$content['mailaccount']));
1349
		// join again mailaccount and identity
1350
		$content['mailaccount'] .= ':'.$content['mailidentity'];
1351
		//Try to set the initial selected account to the first identity match found
1352
		// which fixes the issue of prefered identity never get selected.
1353
		if (!in_array($content['mailaccount'], array_keys($sel_options['mailaccount'])))
1354
		{
1355
			foreach ($sel_options['mailaccount'] as $ident => $value)
1356
			{
1357
				$idnt_acc_parts = explode(':', $ident);
1358
1359
				if ($content['mailidentity'] == $idnt_acc_parts[1])
1360
				{
1361
					$content['mailaccount'] = $ident;
1362
					break;
1363
				}
1364
			}
1365
		}
1366
		// Resolve distribution list before send content to client
1367
		foreach(array('to', 'cc', 'bcc', 'replyto')  as $f)
1368
		{
1369
			if (is_array($content[$f])) $content[$f]= self::resolveEmailAddressList ($content[$f]);
1370
		}
1371
1372
		// set filemode icons for all attachments
1373
		if($content['attachments'] && is_array($content['attachments']))
1374
		{
1375
			foreach($content['attachments'] as &$attach)
1376
			{
1377
				$attach['is_dir'] = is_dir($attach['file']);
1378
				$attach['filemode_icon'] = !is_dir($attach['file']) &&
1379
						($content['filemode'] == Vfs\Sharing::READONLY || $content['filemode'] == Vfs\Sharing::WRITABLE)
1380
						? Vfs\Sharing::LINK : $content['filemode'];
1381
				$attach['filemode_title'] = lang(Vfs\Sharing::$modes[$attach['filemode_icon']]['label']);
1382
			}
1383
		}
1384
1385
		$content['to'] = self::resolveEmailAddressList($content['to']);
1386
		//error_log(__METHOD__.__LINE__.array2string($content));
1387
		$etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2);
1388
	}
1389
1390
	/**
1391
	 * Add preset files like vcard as attachments into content array
1392
	 *
1393
	 * Preset attachments are read from $_REQUEST['preset']['file'] with
1394
	 * optional ['type'] and ['name'].
1395
	 *
1396
	 * Attachments must either be in EGroupware Vfs or configured temp. directory!
1397
	 *
1398
	 * @param array $_content content
1399
	 * @param string $_insertSigOnTop
1400
	 * @param boolean $_eliminateDoubleAttachments
1401
	 */
1402
	function addPresetFiles (&$_content, &$_insertSigOnTop, $_eliminateDoubleAttachments)
1403
	{
1404
		// check if JSON was used
1405
		if (!is_array($_REQUEST['preset']['file']) &&
1406
			($_REQUEST['preset']['file'][0] === '[' && substr($_REQUEST['preset']['file'], -1) === ']' ||
1407
			$_REQUEST['preset']['file'][0] === '{' && substr($_REQUEST['preset']['file'], -1) === '}') &&
1408
			($files = json_decode($_REQUEST['preset']['file'], true)))
1409
		{
1410
			$types = !empty($_REQUEST['preset']['type']) ?
1411
				json_decode($_REQUEST['preset']['type'], true) : array();
1412
			$names = !empty($_REQUEST['preset']['name']) ?
1413
				json_decode($_REQUEST['preset']['name'], true) : array();
1414
		}
1415
		else
1416
		{
1417
			$files = (array)$_REQUEST['preset']['file'];
1418
			$types = !empty($_REQUEST['preset']['type']) ?
1419
				(array)$_REQUEST['preset']['type'] : array();
1420
			$names = !empty($_REQUEST['preset']['name']) ?
1421
				(array)$_REQUEST['preset']['name'] : array();
1422
		}
1423
1424
		foreach($files as $k => $path)
1425
		{
1426
			if (!empty($types[$k]) && stripos($types[$k],'text/calendar')!==false)
1427
			{
1428
				$_insertSigOnTop = 'below';
1429
			}
1430
			//error_log(__METHOD__.__LINE__.$path.'->'.array2string(parse_url($path,PHP_URL_SCHEME == 'vfs')));
1431
			if (($scheme = parse_url($path,PHP_URL_SCHEME)) === 'vfs')
0 ignored issues
show
Unused Code introduced by
The assignment to $scheme is dead and can be removed.
Loading history...
1432
			{
1433
				$type = Vfs::mime_content_type($path);
1434
				// special handling for attaching vCard of iCal --> use their link-title as name
1435
				if (substr($path,-7) != '/.entry' ||
1436
					!(list($app,$id) = array_slice(explode('/',$path),-3)) ||
1437
					!($name = Link::title($app, $id)))
1438
				{
1439
					$name = Vfs::decodePath(Vfs::basename($path));
1440
				}
1441
				else
1442
				{
1443
					$name .= '.'.Api\MimeMagic::mime2ext($type);
1444
				}
1445
				// use type specified by caller, if Vfs reports only default, or contains specified type (eg. "text/vcard; charset=utf-8")
1446
				if (!empty($types[$k]) && ($type == 'application/octet-stream' || stripos($types[$k], $type) === 0))
1447
				{
1448
					$type = $types[$k];
1449
				}
1450
				$path = str_replace('+','%2B',$path);
1451
				$formData = array(
1452
					'name' => $name,
1453
					'type' => $type,
1454
					'file' => Vfs::decodePath($path),
1455
					'size' => filesize(Vfs::decodePath($path)),
1456
				);
1457
				if ($formData['type'] == Vfs::DIR_MIME_TYPE && $_content['filemode'] == Vfs\Sharing::ATTACH)
1458
				{
1459
					$_content['filemode'] = Vfs\Sharing::READONLY;
1460
					Framework::message(lang('Directories have to be shared.'), 'info');
1461
				}
1462
			}
1463
			// do not allow to attache something from server filesystem outside configured temp_dir
1464
			elseif (strpos(realpath(parse_url($path, PHP_URL_PATH)), realpath($GLOBALS['egw_info']['server']['temp_dir']).'/') !== 0)
1465
			{
1466
				error_log(__METHOD__."() Attaching '$path' outside configured temp. directory '{$GLOBALS['egw_info']['server']['temp_dir']}' denied!");
1467
			}
1468
			elseif(is_readable($path))
1469
			{
1470
				$formData = array(
1471
					'name' => isset($names[$k]) ? $names[$k] : basename($path),
1472
					'type' => isset($types[$k]) ? $types[$k] : (function_exists('mime_content_type') ? mime_content_type($path) : Api\MimeMagic::filename2mime($path)),
1473
					'file' => $path,
1474
					'size' => filesize($path),
1475
				);
1476
			}
1477
			else
1478
			{
1479
				continue;
1480
			}
1481
			$this->addAttachment($formData,$_content, $_eliminateDoubleAttachments);
1482
		}
1483
	}
1484
1485
	/**
1486
	 * Get pre-fill a new compose based on an existing email
1487
	 *
1488
	 * @param type $mail_id If composing based on an existing mail, this is the ID of the mail
1489
	 * @param type $part_id For multi-part mails, indicates which part
1490
	 * @param type $from Indicates what the mail is based on, and how to extract data.
1491
	 *	One of 'compose', 'composeasnew', 'reply', 'reply_all', 'forward' or 'merge'
1492
	 * @param boolean $_focusElement varchar subject, to, body supported
1493
	 * @param boolean $suppressSigOnTop
1494
	 * @param boolean $isReply
1495
	 *
1496
	 * @return mixed[] Content array pre-filled according to source mail
1497
	 */
1498
	private function getComposeFrom($mail_id, $part_id, $from, &$_focusElement, &$suppressSigOnTop, &$isReply)
1499
	{
1500
		$content = array();
1501
		//error_log(__METHOD__.__LINE__.array2string($mail_id).", $part_id, $from, $_focusElement, $suppressSigOnTop, $isReply");
1502
		// on forward we may have to support multiple ids
1503
		if ($from=='forward')
0 ignored issues
show
introduced by
The condition $from == 'forward' is always false.
Loading history...
1504
		{
1505
			$replyIds = explode(',',$mail_id);
1506
			$mail_id = $replyIds[0];
1507
		}
1508
		$hA = mail_ui::splitRowID($mail_id);
1509
		$msgUID = $hA['msgUID'];
1510
		$folder = $hA['folder'];
1511
		$icServerID = $hA['profileID'];
1512
		if ($icServerID != $this->mail_bo->profileID)
1513
		{
1514
			$this->changeProfile($icServerID);
1515
		}
1516
		$icServer = $this->mail_bo->icServer;
1517
		if (!empty($folder) && !empty($msgUID) )
1518
		{
1519
			// this fill the session data with the values from the original email
1520
			switch($from)
1521
			{
1522
				case 'composefromdraft':
1523
				case 'composeasnew':
1524
					$content = $this->getDraftData($icServer, $folder, $msgUID, $part_id);
1525
					if ($from =='composefromdraft') $content['mode'] = 'composefromdraft';
1526
					$content['processedmail_id'] = $mail_id;
1527
1528
					$_focusElement = 'body';
1529
					$suppressSigOnTop = true;
1530
					break;
1531
				case 'reply':
1532
				case 'reply_all':
1533
					$content = $this->getReplyData($from == 'reply' ? 'single' : 'all', $icServer, $folder, $msgUID, $part_id);
1534
					$content['processedmail_id'] = $mail_id;
1535
					$content['mode'] = 'reply';
1536
					$_focusElement = 'body';
1537
					$suppressSigOnTop = false;
1538
					$isReply = true;
1539
					break;
1540
				case 'forward':
1541
					$mode  = ($_GET['mode']=='forwardinline'?'inline':'asmail');
1542
					// this fill the session data with the values from the original email
1543
					foreach ($replyIds as &$mail_id)
0 ignored issues
show
introduced by
$mail_id is overwriting one of the parameters of this function.
Loading history...
1544
					{
1545
						//error_log(__METHOD__.__LINE__.' ID:'.$mail_id.' Mode:'.$mode);
1546
						$hA = mail_ui::splitRowID($mail_id);
1547
						$msgUID = $hA['msgUID'];
1548
						$folder = $hA['folder'];
1549
						$content = $this->getForwardData($icServer, $folder, $msgUID, $part_id, $mode);
1550
					}
1551
					$content['processedmail_id'] = implode(',',$replyIds);
1552
					$content['mode'] = 'forward';
1553
					$isReply = ($mode?$mode=='inline':$this->mailPreferences['message_forwarding'] == 'inline');
1554
					$suppressSigOnTop = false;// ($mode && $mode=='inline'?true:false);// may be a better solution
1555
					$_focusElement = 'to';
1556
					break;
1557
				default:
1558
					error_log('Unhandled compose source: ' . $from);
1559
			}
1560
		}
1561
		else if ($from == 'merge' && $_REQUEST['document'])
0 ignored issues
show
introduced by
The condition $from == 'merge' is always false.
Loading history...
1562
		{
1563
			/*
1564
			 * Special merge from everywhere else because all other apps merge gives
1565
			 * a document to be downloaded, this opens a compose dialog.
1566
			 * Use ajax_merge to merge & send multiple
1567
			 */
1568
			// Merge selected ID (in mailtocontactbyid or $mail_id) into given document
1569
			$merge_class = preg_match('/^([a-z_-]+_merge)$/', $_REQUEST['merge']) ? $_REQUEST['merge'] : 'EGroupware\\Api\\Contacts\\Merge';
1570
			$document_merge = new $merge_class();
1571
			$this->mail_bo->openConnection();
1572
			$merge_ids = $_REQUEST['preset']['mailtocontactbyid'] ? $_REQUEST['preset']['mailtocontactbyid'] : $mail_id;
1573
			if (!is_array($merge_ids)) $merge_ids = explode(',',$merge_ids);
1574
			try
1575
			{
1576
				$merged_mail_id = '';
1577
				$folder = $this->mail_bo->getDraftFolder();
1578
				if(($error = $document_merge->check_document($_REQUEST['document'],'')))
1579
				{
1580
					$content['msg'] = $error;
1581
					return $content;
1582
				}
1583
1584
				// Merge does not work correctly (missing to) if current app is not addressbook
1585
				//$GLOBALS['egw_info']['flags']['currentapp'] = 'addressbook';
1586
1587
				// Actually do the merge
1588
				if(count($merge_ids) <= 1)
1589
				{
1590
					$results = $this->mail_bo->importMessageToMergeAndSend(
0 ignored issues
show
Unused Code introduced by
The assignment to $results is dead and can be removed.
Loading history...
1591
						$document_merge, Vfs::PREFIX . $_REQUEST['document'], $merge_ids, $folder, $merged_mail_id
1592
					);
1593
1594
					// Open compose
1595
					$merged_mail_id = trim($GLOBALS['egw_info']['user']['account_id']).mail_ui::$delimiter.
1596
						$this->mail_bo->profileID.mail_ui::$delimiter.
1597
						base64_encode($folder).mail_ui::$delimiter.$merged_mail_id;
1598
					$content = $this->getComposeFrom($merged_mail_id, $part_id, 'composefromdraft', $_focusElement, $suppressSigOnTop, $isReply);
1599
				}
1600
				else
1601
				{
1602
					$success = implode(', ',$results['success']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $results seems to be never defined.
Loading history...
1603
					$fail = implode(', ', $results['failed']);
1604
					if($success) Framework::message($success, 'success');
1605
					Framework::window_close($fail);
1606
				}
1607
			}
1608
			catch (Api\Exception\WrongUserinput $e)
1609
			{
1610
				// if this returns with an exeption, something failed big time
1611
				$content['msg'] = $e->getMessage();
1612
			}
1613
		}
1614
		return $content;
1615
	}
1616
1617
	/**
1618
	 * previous bocompose stuff
1619
	 */
1620
1621
	/**
1622
	 * replace emailaddresses eclosed in <> (eg.: <[email protected]>) with the emailaddress only (e.g: [email protected])
1623
	 * always returns 1
1624
	 */
1625
	static function replaceEmailAdresses(&$text)
1626
	{
1627
		// replace emailaddresses eclosed in <> (eg.: <[email protected]>) with the emailaddress only (e.g: [email protected])
1628
		Api\Mail\Html::replaceEmailAdresses($text);
1629
		return 1;
1630
	}
1631
1632
	function convertHTMLToText(&$_html,$sourceishtml = true, $stripcrl=false, $noRepEmailAddr = false)
1633
	{
1634
		$stripalltags = true;
1635
		// third param is stripalltags, we may not need that, if the source is already in ascii
1636
		if (!$sourceishtml) $stripalltags=false;
1637
		return Api\Mail\Html::convertHTMLToText($_html,$this->displayCharset,$stripcrl,$stripalltags, $noRepEmailAddr);
1638
	}
1639
1640
	function generateRFC822Address($_addressObject)
1641
	{
1642
		if($_addressObject->personal && $_addressObject->mailbox && $_addressObject->host) {
1643
			return sprintf('"%s" <%s@%s>', $this->mail_bo->decode_header($_addressObject->personal), $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE'));
1644
		} elseif($_addressObject->mailbox && $_addressObject->host) {
1645
			return sprintf("%s@%s", $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE'));
1646
		} else {
1647
			return $this->mail_bo->decode_header($_addressObject->mailbox,true);
1648
		}
1649
	}
1650
1651
	/**
1652
	 *  create a unique id, to keep track of different compose windows
1653
	 */
1654
	function generateComposeID()
1655
	{
1656
		return Mail::getRandomString();
1657
	}
1658
1659
	// $_mode can be:
1660
	// single: for a reply to one address
1661
	// all: for a reply to all
1662
	function getDraftData($_icServer, $_folder, $_uid, $_partID=NULL)
1663
	{
1664
		unset($_icServer);	// not used
1665
		$this->sessionData['to'] = array();
1666
1667
		$mail_bo = $this->mail_bo;
1668
		$mail_bo->openConnection();
1669
		$mail_bo->reopen($_folder);
1670
1671
		// get message headers for specified message
1672
		#$headers	= $mail_bo->getMessageHeader($_folder, $_uid);
1673
		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID);
1674
		$addHeadInfo = $mail_bo->getMessageHeader($_uid, $_partID);
1675
		// thread-topic is a proprietary microsoft header and deprecated with the current version
1676
		// horde does not support the encoding of thread-topic, and probably will not no so in the future
1677
		//if ($addHeadInfo['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $addHeadInfo['THREAD-TOPIC'];
1678
1679
		//error_log(__METHOD__.__LINE__.array2string($headers));
1680
		if (!empty($addHeadInfo['X-MAILFOLDER'])) {
1681
			foreach ( explode('|',$addHeadInfo['X-MAILFOLDER']) as $val ) {
1682
				$fval=$val;
1683
				$icServerID = $mail_bo->icServer->ImapServerId;
1684
				if (stripos($val,'::')!==false) list($icServerID,$fval) = explode('::',$val,2);
1685
				if ($icServerID != $mail_bo->icServer->ImapServerId) continue;
1686
				if ($mail_bo->folderExists($fval)) $this->sessionData['folder'][] = $val;
1687
			}
1688
		}
1689
		if (!empty($addHeadInfo['X-MAILIDENTITY'])) {
1690
			// with the new system it would be the identity
1691
			try
1692
			{
1693
				Mail\Account::read_identity($addHeadInfo['X-MAILIDENTITY']);
1694
				$this->sessionData['mailidentity'] = $addHeadInfo['X-MAILIDENTITY'];
1695
			}
1696
			catch (Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1697
			{
1698
			}
1699
		}
1700
		/*
1701
		if (!empty($addHeadInfo['X-STATIONERY'])) {
1702
			$this->sessionData['stationeryID'] = $addHeadInfo['X-STATIONERY'];
1703
		}
1704
		*/
1705
		if (!empty($addHeadInfo['X-MAILACCOUNT'])) {
1706
			// with the new system it would the identity is the account id
1707
			try
1708
			{
1709
				Mail\Account::read($addHeadInfo['X-MAILACCOUNT']);
1710
				$this->sessionData['mailaccount'] = $addHeadInfo['X-MAILACCOUNT'];
1711
			}
1712
			catch (Exception $e)
1713
			{
1714
				unset($e);
1715
				// fail silently
1716
				$this->sessionData['mailaccount'] = $mail_bo->profileID;
1717
			}
1718
		}
1719
		// if the message is located within the draft folder, add it as last drafted version (for possible cleanup on abort))
1720
		if ($mail_bo->isDraftFolder($_folder)) $this->sessionData['lastDrafted'] = mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid);//array('uid'=>$_uid,'folder'=>$_folder);
1721
		$this->sessionData['uid'] = $_uid;
1722
		$this->sessionData['messageFolder'] = $_folder;
1723
		$this->sessionData['isDraft'] = true;
1724
		$foundAddresses = array();
1725
		foreach((array)$headers['CC'] as $val) {
1726
			$rfcAddr=Mail::parseAddressList($val);
1727
			$_rfcAddr = $rfcAddr[0];
1728
			if (!$_rfcAddr->valid) continue;
1729
			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) {
1730
				continue;
1731
			}
1732
			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1733
			if(!$foundAddresses[$keyemail]) {
1734
				$address = $this->mail_bo->decode_header($val,true);
0 ignored issues
show
Unused Code introduced by
The assignment to $address is dead and can be removed.
Loading history...
1735
				$this->sessionData['cc'][] = $val;
1736
				$foundAddresses[$keyemail] = true;
1737
			}
1738
		}
1739
1740
		foreach((array)$headers['TO'] as $val) {
1741
			if(!is_array($val))
1742
			{
1743
				$this->sessionData['to'][] = $val;
1744
				continue;
1745
			}
1746
			$rfcAddr=Mail::parseAddressList($val);
1747
			$_rfcAddr = $rfcAddr[0];
1748
			if (!$_rfcAddr->valid) continue;
1749
			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) {
1750
				continue;
1751
			}
1752
			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1753
			if(!$foundAddresses[$keyemail]) {
1754
				$address = $this->mail_bo->decode_header($val,true);
1755
				$this->sessionData['to'][] = $val;
1756
				$foundAddresses[$keyemail] = true;
1757
			}
1758
		}
1759
1760
		$fromAddr = Mail::parseAddressList($addHeadInfo['FROM'])[0];
1761
		foreach((array)$headers['REPLY-TO'] as $val) {
1762
			$rfcAddr=Mail::parseAddressList($val);
1763
			$_rfcAddr = $rfcAddr[0];
1764
			if (!$_rfcAddr->valid || ($_rfcAddr->mailbox == $fromAddr->mailbox && $_rfcAddr->host == $fromAddr->host)) continue;
1765
			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) {
1766
				continue;
1767
			}
1768
			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1769
			if(!$foundAddresses[$keyemail]) {
1770
				$address = $this->mail_bo->decode_header($val,true);
1771
				$this->sessionData['replyto'][] = $val;
1772
				$foundAddresses[$keyemail] = true;
1773
			}
1774
		}
1775
1776
		foreach((array)$headers['BCC'] as $val) {
1777
			$rfcAddr=Mail::parseAddressList($val);
1778
			$_rfcAddr = $rfcAddr[0];
1779
			if (!$_rfcAddr->valid) continue;
1780
			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) {
1781
				continue;
1782
			}
1783
			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1784
			if(!$foundAddresses[$keyemail]) {
1785
				$address = $this->mail_bo->decode_header($val,true);
1786
				$this->sessionData['bcc'][] = $val;
1787
				$foundAddresses[$keyemail] = true;
1788
			}
1789
		}
1790
		//_debug_array($this->sessionData);
1791
		$this->sessionData['subject']	= $mail_bo->decode_header($headers['SUBJECT']);
1792
		// remove a printview tag if composing
1793
		$searchfor = '/^\['.lang('printview').':\]/';
1794
		$this->sessionData['subject'] = preg_replace($searchfor,'',$this->sessionData['subject']);
1795
		$bodyParts = $mail_bo->getMessageBody($_uid,'always_display', $_partID);
1796
		//_debug_array($bodyParts);
1797
		#$fromAddress = ($headers['FROM'][0]['PERSONAL_NAME'] != 'NIL') ? $headers['FROM'][0]['RFC822_EMAIL'] : $headers['FROM'][0]['EMAIL'];
1798
		if($bodyParts['0']['mimeType'] == 'text/html') {
1799
			$this->sessionData['mimeType'] 	= 'html';
1800
1801
			for($i=0; $i<count($bodyParts); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1802
				if($i>0) {
1803
					$this->sessionData['body'] .= '<hr>';
1804
				}
1805
				if($bodyParts[$i]['mimeType'] == 'text/plain') {
1806
					#$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body']);
1807
					$bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>";
1808
				}
1809
				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
0 ignored issues
show
Bug introduced by
The method detect_encoding() does not exist on EGroupware\Api\Mail. ( Ignorable by Annotation )

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

1809
				if ($bodyParts[$i]['charSet']===false) /** @scrutinizer ignore-call */ $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1810
				$bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
1811
				#error_log( "GetDraftData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
1812
				$this->sessionData['body'] .= ($i>0?"<br>":""). $bodyParts[$i]['body'] ;
1813
			}
1814
			$this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID);
1815
1816
		} else {
1817
			$this->sessionData['mimeType']	= 'plain';
1818
1819
			for($i=0; $i<count($bodyParts); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1820
				if($i>0) {
1821
					$this->sessionData['body'] .= "<hr>";
1822
				}
1823
				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
1824
				$bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
1825
				#error_log( "GetDraftData (Plain) CharSet".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
1826
				$this->sessionData['body'] .= ($i>0?"\r\n":""). $bodyParts[$i]['body'] ;
1827
			}
1828
			$this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID,'plain');
1829
		}
1830
1831
		if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID))) {
1832
			foreach($attachments as $attachment) {
1833
				//error_log(__METHOD__.__LINE__.array2string($attachment));
1834
				$cid = $attachment['cid'];
1835
				$match=null;
1836
				preg_match("/cid:{$cid}/", $bodyParts['0']['body'], $match);
1837
				//error_log(__METHOD__.__LINE__.'searching for cid:'."/cid:{$cid}/".'#'.$r.'#'.array2string($match));
1838
				if (!$match || !$attachment['cid'])
1839
				{
1840
					$this->addMessageAttachment($_uid, $attachment['partID'],
1841
						$_folder,
1842
						$attachment['name'],
1843
						$attachment['mimeType'],
1844
						$attachment['size'],
1845
						$attachment['is_winmail']);
1846
				}
1847
			}
1848
		}
1849
		$mail_bo->closeConnection();
1850
		return $this->sessionData;
1851
	}
1852
1853
	function getErrorInfo()
1854
	{
1855
		if(isset($this->errorInfo)) {
1856
			$errorInfo = $this->errorInfo;
1857
			unset($this->errorInfo);
1858
			return $errorInfo;
1859
		}
1860
		return false;
1861
	}
1862
1863
	function getForwardData($_icServer, $_folder, $_uid, $_partID, $_mode=false)
1864
	{
1865
		if ($_mode)
1866
		{
1867
			$modebuff = $this->mailPreferences['message_forwarding'];
1868
			$this->mailPreferences['message_forwarding'] = $_mode;
1869
		}
1870
		if  ($this->mailPreferences['message_forwarding'] == 'inline') {
1871
			$this->getReplyData('forward', $_icServer, $_folder, $_uid, $_partID);
1872
		}
1873
		$mail_bo    = $this->mail_bo;
1874
		$mail_bo->openConnection();
1875
		$mail_bo->reopen($_folder);
1876
1877
		// get message headers for specified message
1878
		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder);
1879
		//error_log(__METHOD__.__LINE__.array2string($headers));
1880
		//_debug_array($headers); exit;
1881
		// check for Re: in subject header
1882
		$this->sessionData['subject'] 	= "[FWD] " . $mail_bo->decode_header($headers['SUBJECT']);
0 ignored issues
show
Bug introduced by
Are you sure $mail_bo->decode_header($headers['SUBJECT']) of type array|mixed 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

1882
		$this->sessionData['subject'] 	= "[FWD] " . /** @scrutinizer ignore-type */ $mail_bo->decode_header($headers['SUBJECT']);
Loading history...
1883
		// the three attributes below are substituted by processedmail_id and mode
1884
		//$this->sessionData['sourceFolder']=$_folder;
1885
		//$this->sessionData['forwardFlag']='forwarded';
1886
		//$this->sessionData['forwardedUID']=$_uid;
1887
		if  ($this->mailPreferences['message_forwarding'] == 'asmail') {
1888
			$this->sessionData['mimeType']  = $this->mailPreferences['composeOptions'];
1889
			if($headers['SIZE'])
1890
				$size				= $headers['SIZE'];
1891
			else
1892
				$size				= lang('unknown');
1893
1894
			$this->addMessageAttachment($_uid, $_partID, $_folder,
1895
				$mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
0 ignored issues
show
Bug introduced by
Are you sure $mail_bo->decode_header(...] : lang('no subject')) 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

1895
				/** @scrutinizer ignore-type */ $mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
Loading history...
1896
				'MESSAGE/RFC822', $size);
1897
		}
1898
		else
1899
		{
1900
			unset($this->sessionData['in-reply-to']);
1901
			unset($this->sessionData['to']);
1902
			unset($this->sessionData['cc']);
1903
			try
1904
			{
1905
				if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID,null,true,false,false))) {
1906
					//error_log(__METHOD__.__LINE__.':'.array2string($attachments));
1907
					foreach($attachments as $attachment) {
1908
						if (!($attachment['cid'] && preg_match("/image\//",$attachment['mimeType'])) || $attachment['disposition'] == 'attachment')
1909
						{
1910
							$this->addMessageAttachment($_uid, $attachment['partID'],
1911
								$_folder,
1912
								$attachment['name'],
1913
								$attachment['mimeType'],
1914
								$attachment['size']);
1915
						}
1916
					}
1917
				}
1918
			}
1919
			catch (Mail\Smime\PassphraseMissing $e)
1920
			{
1921
				error_log(__METHOD__.'() Failed to forward because of smime '.$e->getMessage());
1922
				Framework::message(lang('Forwarding of this message failed'.
1923
						' because the content of this message seems to be encrypted'.
1924
						' and can not be decrypted properly. If you still wish to'.
1925
						' forward content of this encrypted message, you may try'.
1926
						' to use forward as attachment instead.'),'error');
1927
			}
1928
		}
1929
		$mail_bo->closeConnection();
1930
		if ($_mode)
1931
		{
1932
			$this->mailPreferences['message_forwarding'] = $modebuff;
1933
		}
1934
		//error_log(__METHOD__.__LINE__.array2string($this->sessionData));
1935
		return $this->sessionData;
1936
	}
1937
1938
	/**
1939
	 * adds uploaded files or files in eGW's temp directory as attachments
1940
	 *
1941
	 * passes the given $_formData representing an attachment to $_content
1942
	 *
1943
	 * @param array $_formData fields of the compose form (to,cc,bcc,reply-to,subject,body,priority,signature), plus data of the file (name,file,size,type)
1944
	 * @param array $_content the content passed to the function and to be modified
1945
	 * @return void
1946
	 */
1947
	function addAttachment($_formData,&$_content,$eliminateDoubleAttachments=false)
1948
	{
1949
		//error_log(__METHOD__.__LINE__.' Formdata:'.array2string($_formData).' Content:'.array2string($_content));
1950
1951
		$attachfailed = false;
1952
		// to guard against exploits the file must be either uploaded or be in the temp_dir
1953
		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
1954
		try
1955
		{
1956
			$tmpFileName = Mail::checkFileBasics($_formData,$this->composeID,false);
1957
		}
1958
		catch (Api\Exception\WrongUserinput $e)
1959
		{
1960
			$attachfailed = true;
1961
			$alert_msg = $e->getMessage();
1962
			Framework::message($e->getMessage(), 'error');
1963
		}
1964
		//error_log(__METHOD__.__LINE__.array2string($tmpFileName));
1965
		//error_log(__METHOD__.__LINE__.array2string($_formData));
1966
1967
		if ($eliminateDoubleAttachments == true)
1968
		{
1969
			foreach ((array)$_content['attachments'] as $attach)
1970
			{
1971
				if ($attach['name'] && $attach['name'] == $_formData['name'] &&
1972
					strtolower($_formData['type'])== strtolower($attach['type']) &&
1973
					stripos($_formData['file'],'vfs://') !== false) return;
1974
			}
1975
		}
1976
		if ($attachfailed === false)
1977
		{
1978
			$buffer = array(
1979
				'name'	=> $_formData['name'],
1980
				'type'	=> $_formData['type'],
1981
				'file'	=> $tmpFileName,
1982
				'tmp_name'	=> $tmpFileName,
1983
				'size'	=> $_formData['size']
1984
			);
1985
			if (!is_array($_content['attachments'])) $_content['attachments']=array();
1986
			$_content['attachments'][] = $buffer;
1987
			unset($buffer);
1988
		}
1989
		else
1990
		{
1991
			error_log(__METHOD__.__LINE__.array2string($alert_msg));
1992
		}
1993
	}
1994
1995
	function addMessageAttachment($_uid, $_partID, $_folder, $_name, $_type, $_size, $_is_winmail= null)
1996
	{
1997
		$this->sessionData['attachments'][]=array (
1998
			'uid'		=> $_uid,
1999
			'partID'	=> $_partID,
2000
			'name'		=> $_name,
2001
			'type'		=> $_type,
2002
			'size'		=> $_size,
2003
			'folder'	=> $_folder,
2004
			'winmailFlag' => $_is_winmail,
2005
			'tmp_name'	=> mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid).'_'.(!empty($_partID)?$_partID:count($this->sessionData['attachments'])+1),
2006
		);
2007
	}
2008
2009
	function getAttachment()
2010
	{
2011
		// read attachment data from etemplate request, use tmpname only to identify it
2012
		if (($request = Etemplate\Request::read($_GET['etemplate_exec_id'])))
2013
		{
2014
			foreach($request->preserv['attachments'] as $attachment)
2015
			{
2016
				if ($_GET['tmpname'] === $attachment['tmp_name']) break;
2017
			}
2018
		}
2019
		if (!$request || $_GET['tmpname'] !== $attachment['tmp_name'])
2020
		{
2021
			header('HTTP/1.1 404 Not found');
2022
			die('Attachment '.htmlspecialchars($_GET['tmpname']).' NOT found!');
2023
		}
2024
2025
		//error_log(__METHOD__.__LINE__.array2string($_GET));
2026
		if (parse_url($attachment['tmp_name'],PHP_URL_SCHEME) == 'vfs')
2027
		{
2028
			Vfs::load_wrapper('vfs');
2029
		}
2030
		// attachment data in temp_dir, only use basename of given name, to not allow path traversal
2031
		else
2032
		{
2033
			$attachment['tmp_name'] = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['tmp_name']);
2034
		}
2035
		if(!file_exists($attachment['tmp_name']))
2036
		{
2037
			header('HTTP/1.1 404 Not found');
2038
			die('Attachment '.htmlspecialchars($attachment['tmp_name']).' NOT found!');
2039
		}
2040
		$attachment['attachment'] = file_get_contents($attachment['tmp_name']);
2041
2042
		//error_log(__METHOD__.__LINE__.' FileSize:'.filesize($attachment['tmp_name']));
2043
		if ($_GET['mode'] != "save")
2044
		{
2045
			if (strtoupper($attachment['type']) == 'TEXT/DIRECTORY')
2046
			{
2047
				$sfxMimeType = $attachment['type'];
2048
				$buff = explode('.',$attachment['tmp_name']);
2049
				$suffix = '';
2050
				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
2051
				if (!empty($suffix)) $sfxMimeType = Api\MimeMagic::ext2mime($suffix);
2052
				$attachment['type'] = $sfxMimeType;
2053
				if (strtoupper($sfxMimeType) == 'TEXT/VCARD' || strtoupper($sfxMimeType) == 'TEXT/X-VCARD') $attachment['type'] = strtoupper($sfxMimeType);
2054
			}
2055
			//error_log(__METHOD__.print_r($attachment,true));
2056
			if (strtoupper($attachment['type']) == 'TEXT/CALENDAR' || strtoupper($attachment['type']) == 'TEXT/X-VCALENDAR')
2057
			{
2058
				//error_log(__METHOD__."about to call calendar_ical");
2059
				$calendar_ical = new calendar_ical();
2060
				$eventid = $calendar_ical->search($attachment['attachment'],-1);
2061
				//error_log(__METHOD__.array2string($eventid));
2062
				if (!$eventid) $eventid = -1;
2063
				$event = $calendar_ical->importVCal($attachment['attachment'],(is_array($eventid)?$eventid[0]:$eventid),null,true);
2064
				//error_log(__METHOD__.$event);
2065
				if ((int)$event > 0)
2066
				{
2067
					$vars = array(
2068
						'menuaction'      => 'calendar.calendar_uiforms.edit',
2069
						'cal_id'      => $event,
2070
					);
2071
					$GLOBALS['egw']->redirect_link('../index.php',$vars);
2072
				}
2073
				//Import failed, download content anyway
2074
			}
2075
			if (strtoupper($attachment['type']) == 'TEXT/X-VCARD' || strtoupper($attachment['type']) == 'TEXT/VCARD')
2076
			{
2077
				$addressbook_vcal = new addressbook_vcal();
2078
				// double \r\r\n seems to end a vcard prematurely, so we set them to \r\n
2079
				//error_log(__METHOD__.__LINE__.$attachment['attachment']);
2080
				$attachment['attachment'] = str_replace("\r\r\n", "\r\n", $attachment['attachment']);
2081
				$vcard = $addressbook_vcal->vcardtoegw($attachment['attachment']);
2082
				if ($vcard['uid'])
2083
				{
2084
					$vcard['uid'] = trim($vcard['uid']);
2085
					//error_log(__METHOD__.__LINE__.print_r($vcard,true));
2086
					$contact = $addressbook_vcal->find_contact($vcard,false);
2087
				}
2088
				if (!$contact) $contact = null;
2089
				// if there are not enough fields in the vcard (or the parser was unable to correctly parse the vcard (as of VERSION:3.0 created by MSO))
2090
				if ($contact || count($vcard)>2)
2091
				{
2092
					$contact = $addressbook_vcal->addVCard($attachment['attachment'],(is_array($contact)?array_shift($contact):$contact),true);
2093
				}
2094
				if ((int)$contact > 0)
2095
				{
2096
					$vars = array(
2097
						'menuaction'	=> 'addressbook.addressbook_ui.edit',
2098
						'contact_id'	=> $contact,
2099
					);
2100
					$GLOBALS['egw']->redirect_link('../index.php',$vars);
2101
				}
2102
				//Import failed, download content anyway
2103
			}
2104
		}
2105
		//error_log(__METHOD__.__LINE__.'->'.array2string($attachment));
2106
		Api\Header\Content::safe($attachment['attachment'], $attachment['name'], $attachment['type'], $size=0, true, $_GET['mode'] == "save");
2107
		echo $attachment['attachment'];
2108
2109
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2110
	}
2111
2112
	/**
2113
	 * Test if string contains one of the keys of an array
2114
	 *
2115
	 * @param array arrayToTestAgainst to test its keys against haystack
0 ignored issues
show
Bug introduced by
The type arrayToTestAgainst 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...
2116
	 * @param string haystack
0 ignored issues
show
Bug introduced by
The type haystack 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...
2117
	 * @return boolean
2118
	 */
2119
	function testIfOneKeyInArrayDoesExistInString($arrayToTestAgainst,$haystack) {
2120
		foreach (array_keys($arrayToTestAgainst) as $k)
2121
		{
2122
			//error_log(__METHOD__.__LINE__.':'.$k.'<->'.$haystack);
2123
			if (stripos($haystack,$k)!==false)
2124
			{
2125
				//error_log(__METHOD__.__LINE__.':FOUND:'.$k.'<->'.$haystack.function_backtrace());
2126
				return true;
2127
			}
2128
		}
2129
		return false;
2130
	}
2131
2132
	/**
2133
	 * Gather the replyData and save it with the session, to be used then
2134
	 *
2135
	 * @param $_mode can be:
0 ignored issues
show
Bug introduced by
The type can 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...
2136
	 * 		single: for a reply to one address
2137
	 * 		all: for a reply to all
2138
	 * 		forward: inlineforwarding of a message with its attachments
2139
	 * @param $_icServer number (0 as it is the active Profile)
2140
	 * @param $_folder string
2141
	 * @param $_uid number
2142
	 * @param $_partID number
2143
	 */
2144
	function getReplyData($_mode, $_icServer, $_folder, $_uid, $_partID)
2145
	{
2146
		unset($_icServer);	// not used
2147
		$foundAddresses = array();
2148
2149
		$mail_bo  = $this->mail_bo;
2150
		$mail_bo->openConnection();
2151
		$mail_bo->reopen($_folder);
2152
2153
		$userEMailAddresses = $mail_bo->getUserEMailAddresses();
2154
2155
		// get message headers for specified message
2156
		//print "AAAA: $_folder, $_uid, $_partID<br>";
2157
		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder,$useHeaderInsteadOfEnvelope=true);
2158
		//$headers	= $mail_bo->getMessageHeader($_uid, $_partID, true, true, $_folder);
2159
		$this->sessionData['uid'] = $_uid;
2160
		$this->sessionData['messageFolder'] = $_folder;
2161
		$this->sessionData['in-reply-to'] = ($headers['IN-REPLY-TO']?$headers['IN-REPLY-TO']:$headers['MESSAGE_ID']);
2162
		$this->sessionData['references'] = ($headers['REFERENCES']?$headers['REFERENCES']:$headers['MESSAGE_ID']);
2163
2164
		// break reference into multiple lines if they're greater than 998 chars
2165
		// and remove comma seperation. Fix error serer does not support binary
2166
		// data due to long references.
2167
		if (strlen($this->sessionData['references'])> 998)
2168
		{
2169
			$temp_refs = explode(',',$this->sessionData['references']);
2170
			$this->sessionData['references'] = implode(" ",$temp_refs);
2171
		}
2172
2173
		// thread-topic is a proprietary microsoft header and deprecated with the current version
2174
		// horde does not support the encoding of thread-topic, and probably will not no so in the future
2175
		//if ($headers['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $headers['THREAD-TOPIC'];
2176
		if ($headers['THREAD-INDEX']) $this->sessionData['thread-index'] = $headers['THREAD-INDEX'];
2177
		if ($headers['LIST-ID']) $this->sessionData['list-id'] = $headers['LIST-ID'];
2178
		//error_log(__METHOD__.__LINE__.' Mode:'.$_mode.':'.array2string($headers));
2179
		// check for Reply-To: header and use if available
2180
		if(!empty($headers['REPLY-TO']) && ($headers['REPLY-TO'] != $headers['FROM'])) {
2181
			foreach($headers['REPLY-TO'] as $val) {
2182
				if(!$foundAddresses[$val]) {
2183
					$oldTo[] = $val;
2184
					$foundAddresses[$val] = true;
2185
				}
2186
			}
2187
			$oldToAddress	= (is_array($headers['REPLY-TO'])?$headers['REPLY-TO'][0]:$headers['REPLY-TO']);
2188
		} else {
2189
			foreach($headers['FROM'] as $val) {
2190
				if(!$foundAddresses[$val]) {
2191
					$oldTo[] = $val;
2192
					$foundAddresses[$val] = true;
2193
				}
2194
			}
2195
			$oldToAddress	= (is_array($headers['FROM'])?$headers['FROM'][0]:$headers['FROM']);
2196
		}
2197
		//error_log(__METHOD__.__LINE__.' OldToAddress:'.$oldToAddress.'#');
2198
		if($_mode != 'all' || ($_mode == 'all' && !empty($oldToAddress) && !$this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$oldToAddress)) ) {
2199
			$this->sessionData['to'] = $oldTo;
2200
		}
2201
2202
		if($_mode == 'all') {
2203
			// reply to any address which is cc, but not to my self
2204
			#if($headers->cc) {
2205
				foreach($headers['CC'] as $val) {
2206
					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2207
						continue;
2208
					}
2209
					if(!$foundAddresses[$val]) {
2210
						$this->sessionData['cc'][] = $val;
2211
						$foundAddresses[$val] = true;
2212
					}
2213
				}
2214
			#}
2215
2216
			// reply to any address which is to, but not to my self
2217
			#if($headers->to) {
2218
				foreach($headers['TO'] as $val) {
2219
					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2220
						continue;
2221
					}
2222
					if(!$foundAddresses[$val]) {
2223
						$this->sessionData['to'][] = $val;
2224
						$foundAddresses[$val] = true;
2225
					}
2226
				}
2227
			#}
2228
2229
			#if($headers->from) {
2230
				foreach($headers['FROM'] as $val) {
2231
					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2232
						continue;
2233
					}
2234
					//error_log(__METHOD__.__LINE__.' '.$val);
2235
					if(!$foundAddresses[$val]) {
2236
						$this->sessionData['to'][] = $val;
2237
						$foundAddresses[$val] = true;
2238
					}
2239
				}
2240
			#}
2241
		}
2242
2243
		// check for Re: in subject header
2244
		if(strtolower(substr(trim($mail_bo->decode_header($headers['SUBJECT'])), 0, 3)) == "re:") {
2245
			$this->sessionData['subject'] = $mail_bo->decode_header($headers['SUBJECT']);
2246
		} else {
2247
			$this->sessionData['subject'] = "Re: " . $mail_bo->decode_header($headers['SUBJECT']);
0 ignored issues
show
Bug introduced by
Are you sure $mail_bo->decode_header($headers['SUBJECT']) of type array|mixed 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

2247
			$this->sessionData['subject'] = "Re: " . /** @scrutinizer ignore-type */ $mail_bo->decode_header($headers['SUBJECT']);
Loading history...
2248
		}
2249
2250
		//_debug_array($headers);
2251
		//error_log(__METHOD__.__LINE__.'->'.array2string($this->mailPreferences['htmlOptions']));
2252
		try {
2253
			$bodyParts = $mail_bo->getMessageBody($_uid, ($this->mailPreferences['htmlOptions']?$this->mailPreferences['htmlOptions']:''), $_partID);
2254
		}
2255
		catch (Mail\Smime\PassphraseMissing $e)
2256
		{
2257
			$bodyParts = '';
2258
			error_log(__METHOD__.'() Failed to reply because of smime '.$e->getMessage());
2259
			Framework::message(lang('Replying to this message failed'.
2260
				' because the content of this message seems to be encrypted'.
2261
				' and can not be decrypted properly. If you still wish to include'.
2262
				' content of this encrypted message, you may try to use forward as'.
2263
				' attachment instead.'),'error');
2264
		}
2265
		//_debug_array($bodyParts);
2266
		$styles = Mail::getStyles($bodyParts);
2267
2268
		$fromAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$headers['FROM']));
2269
2270
		$toAddressA = array();
2271
		$toAddress = '';
2272
		foreach ($headers['TO'] as $mailheader) {
2273
			$toAddressA[] =  $mailheader;
2274
		}
2275
		if (count($toAddressA)>0)
2276
		{
2277
			$toAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$toAddressA));
2278
			$toAddress = @htmlspecialchars(lang("to")).": ".$toAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n");
2279
		}
2280
		$ccAddressA = array();
2281
		$ccAddress = '';
2282
		foreach ($headers['CC'] as $mailheader) {
2283
			$ccAddressA[] =  $mailheader;
2284
		}
2285
		if (count($ccAddressA)>0)
2286
		{
2287
			$ccAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$ccAddressA));
2288
			$ccAddress = @htmlspecialchars(lang("cc")).": ".$ccAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n");
2289
		}
2290
		if($bodyParts['0']['mimeType'] == 'text/html') {
2291
			$this->sessionData['body']	= /*"<br>".*//*"&nbsp;".*/"<div>".'----------------'.lang("original message").'-----------------'."".'<br>'.
2292
				@htmlspecialchars(lang("from")).": ".$fromAddress."<br>".
2293
				$toAddress.$ccAddress.
2294
				@htmlspecialchars(lang("date").": ".$headers['DATE'],ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."<br>".
2295
				'----------------------------------------------------------'."</div>";
2296
			$this->sessionData['mimeType'] 	= 'html';
2297
			if (!empty($styles)) $this->sessionData['body'] .= $styles;
2298
			$this->sessionData['body']	.= '<blockquote type="cite">';
2299
2300
			for($i=0; $i<count($bodyParts); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2301
				if($i>0) {
2302
					$this->sessionData['body'] .= '<hr>';
2303
				}
2304
				if($bodyParts[$i]['mimeType'] == 'text/plain') {
2305
					#$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body'])."<br>";
2306
					$bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>";
2307
				}
2308
				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
2309
2310
				$_htmlConfig = Mail::$htmLawed_config;
2311
				Mail::$htmLawed_config['comment'] = 2;
2312
				Mail::$htmLawed_config['transform_anchor'] = false;
2313
				$this->sessionData['body'] .= "<br>".self::_getCleanHTML(Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']));
2314
				Mail::$htmLawed_config = $_htmlConfig;
2315
				#error_log( "GetReplyData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
2316
			}
2317
2318
			$this->sessionData['body']	.= '</blockquote><br>';
2319
			$this->sessionData['body'] =  mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID, 'html');
2320
		} else {
2321
			//$this->sessionData['body']	= @htmlspecialchars(lang("on")." ".$headers['DATE']." ".$mail_bo->decode_header($fromAddress), ENT_QUOTES) . " ".lang("wrote").":\r\n";
2322
			// take care the way the ReplyHeader is created here, is used later on in uicompose::compose, in case you force replys to be HTML (prefs)
2323
            $this->sessionData['body']  = " \r\n \r\n".'----------------'.lang("original message").'-----------------'."\r\n".
2324
                @htmlspecialchars(lang("from")).": ".$fromAddress."\r\n".
2325
				$toAddress.$ccAddress.
2326
				@htmlspecialchars(lang("date").": ".$headers['DATE'], ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."\r\n".
2327
                '-------------------------------------------------'."\r\n \r\n ";
2328
			$this->sessionData['mimeType']	= 'plain';
2329
2330
			for($i=0; $i<count($bodyParts); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2331
				if($i>0) {
2332
					$this->sessionData['body'] .= "<hr>";
2333
				}
2334
2335
				// add line breaks to $bodyParts
2336
				$newBody2 = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'],$bodyParts[$i]['charSet']);
2337
				#error_log( "GetReplyData (Plain) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
2338
				$newBody = mail_ui::resolve_inline_images($newBody2, $_folder, $_uid, $_partID, 'plain');
2339
				$this->sessionData['body'] .= "\r\n";
2340
				$hasSignature = false;
2341
				// create body new, with good line breaks and indention
2342
				foreach(explode("\n",$newBody) as $value) {
2343
					// the explode is removing the character
2344
					//$value .= 'ee';
2345
2346
					// Try to remove signatures from qouted parts to avoid multiple
2347
					// signatures problem in reply (rfc3676#section-4.3).
2348
					if ($_mode != 'forward' && ($hasSignature || ($hasSignature = preg_match("/^--\s[\r\n]$/",$value))))
2349
					{
2350
						continue;
2351
					}
2352
2353
					$numberOfChars = strspn(trim($value), ">");
2354
					$appendString = str_repeat('>', $numberOfChars + 1);
2355
2356
					$bodyAppend = $this->mail_bo->wordwrap($value, 76-strlen("\r\n$appendString "), "\r\n$appendString ",'>');
2357
2358
					if($bodyAppend[0] == '>') {
2359
						$bodyAppend = '>'. $bodyAppend;
2360
					} else {
2361
						$bodyAppend = '> '. $bodyAppend;
2362
					}
2363
2364
					$this->sessionData['body'] .= $bodyAppend;
2365
				}
2366
			}
2367
		}
2368
2369
		$mail_bo->closeConnection();
2370
		return $this->sessionData;
2371
2372
	}
2373
2374
	/**
2375
	 * HTML cleanup
2376
	 *
2377
	 * @param type $_body message
2378
	 * @param type $_useTidy = false, if true tidy extention will be loaded and tidy will try to clean body message
2379
	 *			since the tidy causes segmentation fault ATM, we set the default to false.
2380
	 * @return type
2381
	 */
2382
	static function _getCleanHTML($_body, $_useTidy = false)
2383
	{
2384
		static $nonDisplayAbleCharacters = array('[\016]','[\017]',
2385
				'[\020]','[\021]','[\022]','[\023]','[\024]','[\025]','[\026]','[\027]',
2386
				'[\030]','[\031]','[\032]','[\033]','[\034]','[\035]','[\036]','[\037]');
2387
2388
		if ($_useTidy && extension_loaded('tidy') )
2389
		{
2390
			$tidy = new tidy();
2391
			$cleaned = $tidy->repairString($_body, Mail::$tidy_config,'utf8');
2392
			// Found errors. Strip it all so there's some output
2393
			if($tidy->getStatus() == 2)
2394
			{
2395
				error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$tidy->errorBuffer);
2396
			}
2397
			else
2398
			{
2399
				$_body = $cleaned;
2400
			}
2401
		}
2402
2403
		Mail::getCleanHTML($_body);
2404
		return preg_replace($nonDisplayAbleCharacters, '', $_body);
2405
	}
2406
2407
	static function _getHostName()
2408
	{
2409
		if (isset($_SERVER['SERVER_NAME'])) {
2410
			$result = $_SERVER['SERVER_NAME'];
2411
		} else {
2412
			$result = 'localhost.localdomain';
2413
		}
2414
		return $result;
2415
	}
2416
2417
	/**
2418
	 * Create a message from given data and identity
2419
	 *
2420
	 * @param Api\Mailer $_mailObject
2421
	 * @param array $_formData
2422
	 * @param array $_identity
2423
	 * @param boolean $_autosaving =false true: autosaving, false: save-as-draft or send
2424
	 *
2425
	 * @return array returns found inline images as attachment structure
2426
	 */
2427
	function createMessage(Api\Mailer $_mailObject, array $_formData, array $_identity, $_autosaving=false)
2428
	{
2429
		if (substr($_formData['body'], 0, 27) == '-----BEGIN PGP MESSAGE-----')
2430
		{
2431
			$_formData['mimeType'] = 'openpgp';
2432
		}
2433
		$mail_bo	= $this->mail_bo;
2434
		$activeMailProfile = Mail\Account::read($this->mail_bo->profileID);
2435
2436
		// you need to set the sender, if you work with different identities, since most smtp servers, dont allow
2437
		// sending in the name of someone else
2438
		if ($_identity['ident_id'] != $activeMailProfile['ident_id'] && !empty($_identity['ident_email']) && strtolower($activeMailProfile['ident_email']) != strtolower($_identity['ident_email']))
2439
		{
2440
			error_log(__METHOD__.__LINE__.' Faking From/SenderInfo for '.$activeMailProfile['ident_email'].' with ID:'.$activeMailProfile['ident_id'].'. Identitiy to use for sending:'.array2string($_identity));
2441
		}
2442
		$email_From =  $_identity['ident_email'] ? $_identity['ident_email'] : $activeMailProfile['ident_email'];
2443
		// Try to fix identity email with no domain part set
2444
		$_mailObject->setFrom(Mail::fixInvalidAliasAddress(Api\Accounts::id2name($_identity['account_id'], 'account_email'), $email_From),
2445
			Mail::generateIdentityString($_identity,false));
2446
2447
		$_mailObject->addHeader('X-Priority', $_formData['priority']);
2448
		$_mailObject->addHeader('X-Mailer', 'EGroupware-Mail');
2449
		if(!empty($_formData['in-reply-to'])) {
2450
			if (stripos($_formData['in-reply-to'],'<')===false) $_formData['in-reply-to']='<'.trim($_formData['in-reply-to']).'>';
2451
			$_mailObject->addHeader('In-Reply-To', $_formData['in-reply-to']);
2452
		}
2453
		if(!empty($_formData['references'])) {
2454
			if (stripos($_formData['references'],'<')===false)
2455
			{
2456
				$_formData['references']='<'.trim($_formData['references']).'>';
2457
			}
2458
			$_mailObject->addHeader('References', $_formData['references']);
2459
		}
2460
2461
		if(!empty($_formData['thread-index'])) {
2462
			$_mailObject->addHeader('Thread-Index', $_formData['thread-index']);
2463
		}
2464
		if(!empty($_formData['list-id'])) {
2465
			$_mailObject->addHeader('List-Id', $_formData['list-id']);
2466
		}
2467
		if($_formData['disposition']=='on') {
2468
			$_mailObject->addHeader('Disposition-Notification-To', $_identity['ident_email']);
2469
		}
2470
2471
		// Expand any mailing lists
2472
		foreach(array('to', 'cc', 'bcc', 'replyto')  as $field)
2473
		{
2474
			if ($field != 'replyto') $_formData[$field] = self::resolveEmailAddressList($_formData[$field]);
2475
2476
			if ($_formData[$field]) $_mailObject->addAddress($_formData[$field], '', $field);
2477
		}
2478
2479
		$_mailObject->addHeader('Subject', $_formData['subject']);
2480
2481
		// this should never happen since we come from the edit dialog
2482
		if (Mail::detect_qp($_formData['body'])) {
2483
			$_formData['body'] = preg_replace('/=\r\n/', '', $_formData['body']);
2484
			$_formData['body'] = quoted_printable_decode($_formData['body']);
2485
		}
2486
		$disableRuler = false;
2487
		$signature = $_identity['ident_signature'];
2488
		$sigAlreadyThere = $this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend'?1:0;
2489
		if ($sigAlreadyThere)
2490
		{
2491
			// note: if you use stationery ' s the insert signatures at the top does not apply here anymore, as the signature
2492
			// is already part of the body, so the signature part of the template will not be applied.
2493
			$signature = null; // note: no signature, no ruler!!!!
2494
		}
2495
		if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) &&
2496
			$this->mailPreferences['disableRulerForSignatureSeparation']) ||
2497
			empty($signature) || trim($this->convertHTMLToText($signature)) =='')
2498
		{
2499
			$disableRuler = true;
2500
		}
2501
		if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving)
2502
		{
2503
			$attachment_links = $this->_getAttachmentLinks($_formData['attachments'], $_formData['filemode'],
2504
				$_formData['mimeType'] == 'html',
2505
				array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])),
2506
				$_formData['expiration'], $_formData['password']);
2507
		}
2508
		switch ($_formData['mimeType'])
2509
		{
2510
			case 'html':
2511
				$body = $_formData['body'];
2512
				if ($attachment_links)
2513
				{
2514
					if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false)
2515
					{
2516
						$body = str_replace('<!-- HTMLSIGBEGIN -->', $attachment_links.'<!-- HTMLSIGBEGIN -->', $body);
2517
					}
2518
					else
2519
					{
2520
						$body .= $attachment_links;
2521
					}
2522
				}
2523
				if(!empty($signature))
2524
				{
2525
					$_mailObject->setBody($this->convertHTMLToText($body, true, true).
2526
						($disableRuler ? "\r\n" : "\r\n-- \r\n").
2527
						$this->convertHTMLToText($signature, true, true));
2528
2529
					$body .= ($disableRuler ?'<br>':'<hr style="border:1px dotted silver; width:90%;">').$signature;
2530
				}
2531
				else
2532
				{
2533
					$_mailObject->setBody($this->convertHTMLToText($body, true, true));
2534
				}
2535
				// convert URL Images to inline images - if possible
2536
				if (!$_autosaving) $inline_images = Mail::processURL2InlineImages($_mailObject, $body, $mail_bo);
2537
				if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false)
2538
				{
2539
					$body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body);
2540
				}
2541
				$_mailObject->setHtmlBody($body, null, false);	// false = no automatic alternative, we called setBody()
2542
				break;
2543
			case 'openpgp':
2544
				$_mailObject->setOpenPgpBody($_formData['body']);
2545
				break;
2546
			default:
2547
				$body = $this->convertHTMLToText($_formData['body'],false, false, true, true);
0 ignored issues
show
Unused Code introduced by
The call to mail_compose::convertHTMLToText() 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

2547
				/** @scrutinizer ignore-call */ 
2548
    $body = $this->convertHTMLToText($_formData['body'],false, false, true, 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...
2548
2549
				if ($attachment_links) $body .= $attachment_links;
2550
2551
				#$_mailObject->Body = $_formData['body'];
2552
				if(!empty($signature)) {
2553
					$body .= ($disableRuler ?"\r\n":"\r\n-- \r\n").
2554
						$this->convertHTMLToText($signature,true,true);
2555
				}
2556
				$_mailObject->setBody($body);
2557
		}
2558
		// add the attachments
2559
		if (is_array($_formData) && isset($_formData['attachments']))
2560
		{
2561
			$connection_opened = false;
2562
			$tnfattachments = null;
2563
			foreach((array)$_formData['attachments'] as $attachment) {
2564
				if(is_array($attachment))
2565
				{
2566
					if (!empty($attachment['uid']) && !empty($attachment['folder'])) {
2567
						/* Example:
2568
						Array([0] => Array(
2569
						[uid] => 21178
2570
						[partID] => 2
2571
						[name] => [Untitled].pdf
2572
						[type] => application/pdf
2573
						[size] => 622379
2574
						[folder] => INBOX))
2575
						*/
2576
						if (!$connection_opened)
2577
						{
2578
							$mail_bo->openConnection($mail_bo->profileID);
2579
							$connection_opened = true;
2580
						}
2581
						$mail_bo->reopen($attachment['folder']);
2582
						switch(strtoupper($attachment['type'])) {
2583
							case 'MESSAGE/RFC':
2584
							case 'MESSAGE/RFC822':
2585
								$rawBody='';
2586
								if (isset($attachment['partID'])) {
2587
									$eml = $mail_bo->getAttachment($attachment['uid'],$attachment['partID'],0,false,true,$attachment['folder']);
2588
									$rawBody=$eml['attachment'];
2589
								} else {
2590
									$rawBody        = $mail_bo->getMessageRawBody($attachment['uid'], $attachment['partID'],$attachment['folder']);
2591
								}
2592
								$_mailObject->addStringAttachment($rawBody, $attachment['name'], 'message/rfc822');
2593
								break;
2594
							default:
2595
								$attachmentData	= $mail_bo->getAttachment($attachment['uid'], $attachment['partID'],0,false);
2596
								if ($attachmentData['type'] == 'APPLICATION/MS-TNEF')
2597
								{
2598
									if (!is_array($tnfattachments)) $tnfattachments = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID']);
0 ignored issues
show
Bug introduced by
The method decode_winmail() does not exist on EGroupware\Api\Mail. ( Ignorable by Annotation )

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

2598
									if (!is_array($tnfattachments)) /** @scrutinizer ignore-call */ $tnfattachments = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID']);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2599
									foreach ($tnfattachments as $k)
2600
									{
2601
										if ($k['name'] == $attachment['name'])
2602
										{
2603
											$tnfpart = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID'],$k['is_winmail']);
2604
											$attachmentData['attachment'] = $tnfpart['attachment'];
2605
											break;
2606
										}
2607
									}
2608
								}
2609
								$_mailObject->addStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['type']);
2610
								break;
2611
						}
2612
					}
2613
					// attach files not for autosaving
2614
					elseif ($_formData['filemode'] == Vfs\Sharing::ATTACH && !$_autosaving)
2615
					{
2616
						if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs')
2617
						{
2618
							Vfs::load_wrapper('vfs');
2619
							$tmp_path = $attachment['file'];
2620
						}
2621
						else	// non-vfs file has to be in temp_dir
2622
						{
2623
							$tmp_path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['file']);
2624
						}
2625
						$_mailObject->addAttachment (
2626
							$tmp_path,
2627
							$attachment['name'],
2628
							$attachment['type']
2629
						);
2630
					}
2631
				}
2632
			}
2633
			if ($connection_opened) $mail_bo->closeConnection();
2634
		}
2635
		return is_array($inline_images)?$inline_images:array();
2636
	}
2637
2638
	/**
2639
	 * Get html or text containing links to attachments
2640
	 *
2641
	 * We only care about file attachments, not forwarded messages or parts
2642
	 *
2643
	 * @param array $attachments
2644
	 * @param string $filemode Vfs\Sharing::(ATTACH|LINK|READONL|WRITABLE)
2645
	 * @param boolean $html
2646
	 * @param array $recipients =array()
2647
	 * @param string $expiration =null
2648
	 * @param string $password =null
2649
	 * @return string might be empty if no file attachments found
2650
	 */
2651
	protected function _getAttachmentLinks(array $attachments, $filemode, $html, $recipients=array(), $expiration=null, $password=null)
2652
	{
2653
		if ($filemode == Vfs\Sharing::ATTACH) return '';
2654
2655
		$links = array();
2656
		foreach($attachments as $attachment)
2657
		{
2658
			$path = $attachment['file'];
2659
			if (empty($path)) continue;	// we only care about file attachments, not forwarded messages or parts
2660
			if (parse_url($attachment['file'],PHP_URL_SCHEME) != 'vfs')
2661
			{
2662
				$path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($path);
2663
			}
2664
			// create share
2665
			if ($filemode == Vfs\Sharing::WRITABLE || $expiration || $password)
2666
			{
2667
				$share = stylite_sharing::create($path, $filemode, $attachment['name'], $recipients, $expiration, $password);
0 ignored issues
show
Bug introduced by
The type stylite_sharing 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...
2668
			}
2669
			else
2670
			{
2671
				$share = Vfs\Sharing::create($path, $filemode, $attachment['name'], $recipients);
2672
			}
2673
			$link = Vfs\Sharing::share2link($share);
2674
2675
			$name = Vfs::basename($attachment['name'] ? $attachment['name'] : $attachment['file']);
2676
2677
			if ($html)
2678
			{
2679
				$links[] = Api\Html::a_href($name, $link).' '.
2680
					(is_dir($path) ? lang('Directory') : Vfs::hsize($attachment['size']));
2681
			}
2682
			else
2683
			{
2684
				$links[] = $name.' '.Vfs::hsize($attachment['size']).': '.
2685
					(is_dir($path) ? lang('Directory') : $link);
2686
			}
2687
		}
2688
		if (!$links)
2689
		{
2690
			return null;	// no file attachments found
2691
		}
2692
		elseif ($html)
2693
		{
2694
			return '<p>'.lang('Download attachments').":</p>\n<ul><li>".implode("</li>\n<li>", $links)."</li></ul>\n";
2695
		}
2696
		return lang('Download attachments').":\n- ".implode("\n- ", $links)."\n";
2697
	}
2698
2699
	/**
2700
	 * Save compose mail as draft
2701
	 *
2702
	 * @param array $content content sent from client-side
2703
	 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]'
2704
	 */
2705
	public function ajax_saveAsDraft ($content, $action='button[saveAsDraft]')
2706
	{
2707
		//error_log(__METHOD__.__LINE__.array2string($content)."(, action=$action)");
2708
		$response = Api\Json\Response::get();
2709
		$success = true;
2710
2711
		// check if default account is changed then we need to change profile
2712
		if (!empty($content['serverID']) && $content['serverID'] != $this->mail_bo->profileID)
2713
		{
2714
			$this->changeProfile($content['serverID']);
2715
		}
2716
2717
		$formData = array_merge($content, array(
2718
			'isDrafted' => 1,
2719
			'body' => $content['mail_'.($content['mimeType']?'htmltext':'plaintext')],
2720
			'mimeType' => $content['mimeType']?'html':'plain' // checkbox has only true|false value
2721
		));
2722
2723
		//Saving draft procedure
2724
		try
2725
		{
2726
			$folder = $this->mail_bo->getDraftFolder();
2727
			$this->mail_bo->reopen($folder);
2728
			$status = $this->mail_bo->getFolderStatus($folder);
2729
			if (($messageUid = $this->saveAsDraft($formData, $folder, $action)))
2730
			{
2731
				// saving as draft, does not mean closing the message
2732
				$messageUid = ($messageUid===true ? $status['uidnext'] : $messageUid);
0 ignored issues
show
introduced by
The condition $messageUid === true is always true.
Loading history...
2733
				if (is_array($this->mail_bo->getMessageHeader($messageUid, '',false, false, $folder)))
0 ignored issues
show
introduced by
The condition is_array($this->mail_bo-...false, false, $folder)) is always true.
Loading history...
2734
				{
2735
					$draft_id = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $messageUid);
2736
					if ($content['lastDrafted'] != $draft_id && isset($content['lastDrafted']))
2737
					{
2738
						$dhA = mail_ui::splitRowID($content['lastDrafted']);
2739
						$duid = $dhA['msgUID'];
2740
						$dmailbox = $dhA['folder'];
2741
						// beware: do not delete the original mail as found in processedmail_id
2742
						$pMuid='';
2743
						if ($content['processedmail_id'])
2744
						{
2745
							$pMhA = mail_ui::splitRowID($content['processedmail_id']);
2746
							$pMuid = $pMhA['msgUID'];
2747
						}
2748
						//error_log(__METHOD__.__LINE__."#$pMuid#$pMuid!=$duid#".array2string($content['attachments']));
2749
						// do not delete the original message if attachments are present
2750
						if (empty($pMuid) || $pMuid!=$duid || empty($content['attachments']))
2751
						{
2752
							try
2753
							{
2754
								$this->mail_bo->deleteMessages($duid,$dmailbox,'remove_immediately');
2755
							}
2756
							catch (Api\Exception $e)
2757
							{
2758
								$msg = str_replace('"',"'",$e->getMessage());
2759
								$success = false;
2760
								error_log(__METHOD__.__LINE__.$msg);
2761
							}
2762
						} else {
2763
							error_log(__METHOD__.__LINE__.': original message ('.$pMuid.') has attachments and lastDrafted ID ('.$duid.') equals the former');
2764
						}
2765
					} else {
2766
						error_log(__METHOD__.__LINE__." No current draftID (".$draft_id."), or no lastDrafted Info (".$content['lastDrafted'].") or the former being equal:".array2string($content)."(, action=$action)");
2767
					}
2768
				} else {
2769
					error_log(__METHOD__.__LINE__.' No headerdata found for messageUID='.$messageUid.' in Folder:'.$folder.':'.array2string($content)."(, action=$action)");
2770
				}
2771
			}
2772
			else
2773
			{
2774
				throw new Api\Exception\WrongUserinput(lang("Error: Could not save Message as Draft"));
2775
			}
2776
		}
2777
		catch (Api\Exception\WrongUserinput $e)
2778
		{
2779
			$msg = str_replace('"',"'",$e->getMessage());
2780
			error_log(__METHOD__.__LINE__.$msg);
2781
			$success = false;
2782
		}
2783
2784
		if ($success) $msg = lang('Message saved successfully.');
2785
2786
		// Include new information to json respose, because we need them in client-side callback
2787
		$response->data(array(
2788
			'draftedId' => $draft_id,
2789
			'message' => $msg,
2790
			'success' => $success,
2791
			'draftfolder' => $this->mail_bo->profileID.mail_ui::$delimiter.$this->mail_bo->getDraftFolder()
0 ignored issues
show
Bug introduced by
Are you sure $this->mail_bo->getDraftFolder() of type false|mixed|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

2791
			'draftfolder' => $this->mail_bo->profileID.mail_ui::$delimiter./** @scrutinizer ignore-type */ $this->mail_bo->getDraftFolder()
Loading history...
2792
		));
2793
	}
2794
2795
	/**
2796
	 * resolveEmailAddressList
2797
	 * @param array $_emailAddressList list of emailaddresses, may contain distributionlists
2798
	 * @return array return the list of emailaddresses with distributionlists resolved
2799
	 */
2800
	static function resolveEmailAddressList($_emailAddressList)
2801
	{
2802
		$contacts_obs = null;
2803
		$addrFromList=array();
2804
		foreach((array)$_emailAddressList as $ak => $address)
2805
		{
2806
			if(is_int($address))
2807
			{
2808
				if (!isset($contacts_obs)) $contacts_obj = new Api\Contacts();
2809
				// List was selected, expand to addresses
2810
				unset($_emailAddressList[$ak]);
2811
				$list = $contacts_obj->search('',array('n_fn','n_prefix','n_given','n_family','org_name','email','email_home'),'','','',False,'AND',false,array('list' =>(int)$address));
2812
				// Just add email addresses, they'll be checked below
2813
				foreach($list as $email)
2814
				{
2815
					$addrFromList[] = $email['email'] ? $email['email'] : $email['email_home'];
2816
				}
2817
			}
2818
		}
2819
		if (!empty($addrFromList))
2820
		{
2821
			foreach ($addrFromList as $addr)
2822
			{
2823
				if (!empty($addr)) $_emailAddressList[]=$addr;
2824
			}
2825
		}
2826
		return is_array($_emailAddressList) ? array_values($_emailAddressList) : (array)$_emailAddressList;
0 ignored issues
show
introduced by
The condition is_array($_emailAddressList) is always true.
Loading history...
2827
	}
2828
2829
	/**
2830
	 * Save message as draft to specific folder
2831
	 *
2832
	 * @param array $_formData content
2833
	 * @param string &$savingDestination ='' destination folder
2834
	 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]'
2835
	 * @return boolean return messageUID| false due to an error
2836
	 */
2837
	function saveAsDraft($_formData, &$savingDestination='', $action='button[saveAsDraft]')
2838
	{
2839
		//error_log(__METHOD__."(..., $savingDestination, action=$action)");
2840
		$mail_bo	= $this->mail_bo;
2841
		$mail		= new Api\Mailer($this->mail_bo->profileID);
2842
2843
		// preserve the bcc and if possible the save to folder information
2844
		$this->sessionData['folder']    = $_formData['folder'];
2845
		$this->sessionData['bcc']   = $_formData['bcc'];
2846
		$this->sessionData['mailidentity'] = $_formData['mailidentity'];
2847
		//$this->sessionData['stationeryID'] = $_formData['stationeryID'];
2848
		$this->sessionData['mailaccount']  = $_formData['mailaccount'];
2849
		$this->sessionData['attachments']  = $_formData['attachments'];
2850
		try
2851
		{
2852
			$acc = Mail\Account::read($this->sessionData['mailaccount']);
2853
			//error_log(__METHOD__.__LINE__.array2string($acc));
2854
			$identity = Mail\Account::read_identity($acc['ident_id'],true);
2855
		}
2856
		catch (Exception $e)
2857
		{
2858
			$identity=array();
2859
		}
2860
2861
		$flags = '\\Seen \\Draft';
2862
2863
		$this->createMessage($mail, $_formData, $identity, $action === 'autosaving');
2864
2865
		// folder list as Customheader
2866
		if (!empty($this->sessionData['folder']))
2867
		{
2868
			$folders = implode('|',array_unique($this->sessionData['folder']));
2869
			$mail->addHeader('X-Mailfolder', $folders);
2870
		}
2871
		$mail->addHeader('X-Mailidentity', $this->sessionData['mailidentity']);
2872
		//$mail->addHeader('X-Stationery', $this->sessionData['stationeryID']);
2873
		$mail->addHeader('X-Mailaccount', (int)$this->sessionData['mailaccount']);
2874
		// decide where to save the message (default to draft folder, if we find nothing else)
2875
		// if the current folder is in draft or template folder save it there
2876
		// if it is called from printview then save it with the draft folder
2877
		if (empty($savingDestination)) $savingDestination = $mail_bo->getDraftFolder();
2878
		if (empty($this->sessionData['messageFolder']) && !empty($this->sessionData['mailbox']))
2879
		{
2880
			$this->sessionData['messageFolder'] = $this->sessionData['mailbox'];
2881
		}
2882
		if (!empty($this->sessionData['messageFolder']) && ($mail_bo->isDraftFolder($this->sessionData['messageFolder'])
2883
			|| $mail_bo->isTemplateFolder($this->sessionData['messageFolder'])))
2884
		{
2885
			$savingDestination = $this->sessionData['messageFolder'];
2886
			//error_log(__METHOD__.__LINE__.' SavingDestination:'.$savingDestination);
2887
		}
2888
		if (  !empty($_formData['printit']) && $_formData['printit'] == 0 ) $savingDestination = $mail_bo->getDraftFolder();
2889
2890
		// normaly Bcc is only added to recipients, but not as header visible to all recipients
2891
		$mail->forceBccHeader();
2892
2893
		$mail_bo->openConnection();
2894
		if ($mail_bo->folderExists($savingDestination,true)) {
2895
			try
2896
			{
2897
				$messageUid = $mail_bo->appendMessage($savingDestination, $mail->getRaw(), null, $flags);
2898
			}
2899
			catch (Api\Exception\WrongUserinput $e)
2900
			{
2901
				error_log(__METHOD__.__LINE__.lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",__METHOD__,$savingDestination,$e->getMessage()));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with __METHOD__. ( Ignorable by Annotation )

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

2901
				error_log(__METHOD__.__LINE__./** @scrutinizer ignore-call */ lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",__METHOD__,$savingDestination,$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...
2902
				return false;
2903
			}
2904
2905
		} else {
2906
			error_log(__METHOD__.__LINE__."->".lang("folder")." ". $savingDestination." ".lang("does not exist on IMAP Server."));
2907
			return false;
2908
		}
2909
		$mail_bo->closeConnection();
2910
		return $messageUid;
2911
	}
2912
2913
	function send($_formData)
2914
	{
2915
		$mail_bo	= $this->mail_bo;
2916
		$mail 		= new Api\Mailer($mail_bo->profileID);
2917
		$messageIsDraft	=  false;
2918
2919
		$this->sessionData['mailaccount']	= $_formData['mailaccount'];
2920
		$this->sessionData['to']	= self::resolveEmailAddressList($_formData['to']);
2921
		$this->sessionData['cc']	= self::resolveEmailAddressList($_formData['cc']);
2922
		$this->sessionData['bcc']	= self::resolveEmailAddressList($_formData['bcc']);
2923
		$this->sessionData['folder']	= $_formData['folder'];
2924
		$this->sessionData['replyto']	= $_formData['replyto'];
2925
		$this->sessionData['subject']	= trim($_formData['subject']);
2926
		$this->sessionData['body']	= $_formData['body'];
2927
		$this->sessionData['priority']	= $_formData['priority'];
2928
		$this->sessionData['mailidentity'] = $_formData['mailidentity'];
2929
		//$this->sessionData['stationeryID'] = $_formData['stationeryID'];
2930
		$this->sessionData['disposition'] = $_formData['disposition'];
2931
		$this->sessionData['mimeType']	= $_formData['mimeType'];
2932
		$this->sessionData['to_infolog'] = $_formData['to_infolog'];
2933
		$this->sessionData['to_tracker'] = $_formData['to_tracker'];
2934
		$this->sessionData['attachments']  = $_formData['attachments'];
2935
		$this->sessionData['smime_sign']  = $_formData['smime_sign'];
2936
		$this->sessionData['smime_encrypt']  = $_formData['smime_encrypt'];
2937
2938
		if (isset($_formData['lastDrafted']) && !empty($_formData['lastDrafted']))
2939
		{
2940
			$this->sessionData['lastDrafted'] = $_formData['lastDrafted'];
2941
		}
2942
		//error_log(__METHOD__.__LINE__.' Mode:'.$_formData['mode'].' PID:'.$_formData['processedmail_id']);
2943
		if (isset($_formData['mode']) && !empty($_formData['mode']))
2944
		{
2945
			if ($_formData['mode']=='forward' && !empty($_formData['processedmail_id']))
2946
			{
2947
				$this->sessionData['forwardFlag']='forwarded';
2948
				$_formData['processedmail_id'] = explode(',',$_formData['processedmail_id']);
2949
				$this->sessionData['uid']=array();
2950
				foreach ($_formData['processedmail_id'] as $k =>$rowid)
2951
				{
2952
					$fhA = mail_ui::splitRowID($rowid);
2953
					$this->sessionData['uid'][] = $fhA['msgUID'];
2954
					$this->sessionData['forwardedUID'][] = $fhA['msgUID'];
2955
					if (!empty($fhA['folder'])) $this->sessionData['sourceFolder'] = $fhA['folder'];
2956
				}
2957
			}
2958
			if ($_formData['mode']=='reply' && !empty($_formData['processedmail_id']))
2959
			{
2960
				$rhA = mail_ui::splitRowID($_formData['processedmail_id']);
2961
				$this->sessionData['uid'] = $rhA['msgUID'];
2962
				$this->sessionData['messageFolder'] = $rhA['folder'];
2963
			}
2964
			if ($_formData['mode']=='composefromdraft' && !empty($_formData['processedmail_id']))
2965
			{
2966
				$dhA = mail_ui::splitRowID($_formData['processedmail_id']);
2967
				$this->sessionData['uid'] = $dhA['msgUID'];
2968
				$this->sessionData['messageFolder'] = $dhA['folder'];
2969
			}
2970
		}
2971
		// if the body is empty, maybe someone pasted something with scripts, into the message body
2972
		// this should not happen anymore, unless you call send directly, since the check was introduced with the action command
2973
		if(empty($this->sessionData['body']))
2974
		{
2975
			// this is to be found with the egw_unset_vars array for the _POST['body'] array
2976
			$name='_POST';
2977
			$key='body';
2978
			#error_log($GLOBALS['egw_unset_vars'][$name.'['.$key.']']);
2979
			if (isset($GLOBALS['egw_unset_vars'][$name.'['.$key.']']))
2980
			{
2981
				$this->sessionData['body'] = self::_getCleanHTML( $GLOBALS['egw_unset_vars'][$name.'['.$key.']']);
2982
				$_formData['body']=$this->sessionData['body'];
2983
			}
2984
			#error_log($this->sessionData['body']);
2985
		}
2986
		if(empty($this->sessionData['to']) && empty($this->sessionData['cc']) &&
2987
		   empty($this->sessionData['bcc']) && empty($this->sessionData['folder'])) {
2988
		   	$messageIsDraft = true;
2989
		}
2990
		try
2991
		{
2992
			$identity = Mail\Account::read_identity((int)$this->sessionData['mailidentity'],true);
2993
		}
2994
		catch (Exception $e)
2995
		{
2996
			$identity = array();
2997
		}
2998
		//error_log($this->sessionData['mailaccount']);
2999
		//error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['mailidentity']).'->'.array2string($identity));
3000
		// create the messages and store inline images
3001
		$inline_images = $this->createMessage($mail, $_formData, $identity);
3002
		// remember the identity
3003
		if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') $fromAddress = $mail->From;//$mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':'');
0 ignored issues
show
Bug introduced by
The property From does not seem to exist on EGroupware\Api\Mailer.
Loading history...
3004
		#print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>";
3005
		#print "<pre>". $mail->getMessageBody() ."</pre><hr><br>";
3006
		#exit;
3007
		// check if there are folders to be used
3008
		$folderToCheck = (array)$this->sessionData['folder'];
3009
		$folder = array(); //for counting only
3010
		$folderOnServerID = array();
3011
		$folderOnMailAccount = array();
3012
		foreach ($folderToCheck as $k => $f)
3013
		{
3014
			$fval=$f;
3015
			$icServerID = $_formData['serverID'];//folders always assumed with serverID
3016
			if (stripos($f,'::')!==false) list($icServerID,$fval) = explode('::',$f,2);
3017
			if ($_formData['serverID']!=$_formData['mailaccount'])
3018
			{
3019
				if ($icServerID == $_formData['serverID'] )
3020
				{
3021
					$folder[$fval] = $fval;
3022
					$folderOnServerID[] = $fval;
3023
				}
3024
				if ($icServerID == $_formData['mailaccount'])
3025
				{
3026
					$folder[$fval] = $fval;
3027
					$folderOnMailAccount[] = $fval;
3028
				}
3029
			}
3030
			else
3031
			{
3032
				if ($icServerID == $_formData['serverID'] )
3033
				{
3034
					$folder[$fval] = $fval;
3035
					$folderOnServerID[] = $fval;
3036
				}
3037
			}
3038
		}
3039
		//error_log(__METHOD__.__LINE__.'#'.array2string($_formData['serverID']).'<serverID<->mailaccount>'.array2string($_formData['mailaccount']));
3040
		// serverID ($_formData['serverID']) specifies where we originally came from.
3041
		// mailaccount ($_formData['mailaccount']) specifies the mailaccount we send from and where the sent-copy should end up
3042
		// serverID : is or may be needed to mark a mail as replied/forwarded or delete the original draft.
3043
		// all other folders are tested against serverID that is carried with the foldername ID::Foldername; See above
3044
		// (we work the folder from formData into folderOnMailAccount and folderOnServerID)
3045
		// right now only folders from serverID or mailaccount should be selectable in compose form/dialog
3046
		// we use the sentFolder settings of the choosen mailaccount
3047
		// sentFolder is account specific
3048
		$changeProfileOnSentFolderNeeded = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $changeProfileOnSentFolderNeeded is dead and can be removed.
Loading history...
3049
		if ($_formData['serverID']!=$_formData['mailaccount'])
3050
		{
3051
			$this->changeProfile($_formData['mailaccount']);
3052
			//error_log(__METHOD__.__LINE__.'#'.$this->mail_bo->profileID.'<->'.$mail_bo->profileID.'#');
3053
			$changeProfileOnSentFolderNeeded = true;
3054
			// sentFolder is account specific
3055
			$sentFolder = $this->mail_bo->getSentFolder();
3056
			//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3057
			if ($sentFolder&& $sentFolder!= 'none' && !$this->mail_bo->folderExists($sentFolder, true)) $sentFolder=false;
3058
		}
3059
		else
3060
		{
3061
			$sentFolder = $mail_bo->getSentFolder();
3062
			//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3063
			if ($sentFolder&& $sentFolder!= 'none' && !$mail_bo->folderExists($sentFolder, true)) $sentFolder=false;
3064
		}
3065
		//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3066
3067
		// we switch $this->mail_bo back to the account we used to work on
3068
		if ($_formData['serverID']!=$_formData['mailaccount'])
3069
		{
3070
			$this->changeProfile($_formData['serverID']);
3071
		}
3072
3073
3074
		if(isset($sentFolder) && $sentFolder && $sentFolder != 'none' &&
3075
			$this->mailPreferences['sendOptions'] != 'send_only' &&
3076
			$messageIsDraft == false)
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...
3077
		{
3078
			if ($sentFolder)
3079
			{
3080
				if ($_formData['serverID']!=$_formData['mailaccount'])
3081
				{
3082
					$folderOnMailAccount[] = $sentFolder;
3083
				}
3084
				else
3085
				{
3086
					$folderOnServerID[] = $sentFolder;
3087
				}
3088
				$folder[$sentFolder] = $sentFolder;
3089
			}
3090
			else
3091
			{
3092
				$this->errorInfo = lang("No (valid) Send Folder set in preferences");
0 ignored issues
show
Bug Best Practice introduced by
The property errorInfo does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
3093
			}
3094
		}
3095
		else
3096
		{
3097
			if (((!isset($sentFolder)||$sentFolder==false) && $this->mailPreferences['sendOptions'] != 'send_only') ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! IssetNode || $sentFol...& $sentFolder != 'none', Probably Intended Meaning: ! IssetNode || $sentFold... $sentFolder != 'none')
Loading history...
3098
				($this->mailPreferences['sendOptions'] != 'send_only' &&
3099
				$sentFolder != 'none')) $this->errorInfo = lang("No Send Folder set in preferences");
3100
		}
3101
		// draftFolder is on Server we start from
3102
		if($messageIsDraft == 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...
3103
			$draftFolder = $mail_bo->getDraftFolder();
3104
			if(!empty($draftFolder) && $mail_bo->folderExists($draftFolder,true)) {
3105
				$this->sessionData['folder'] = array($draftFolder);
3106
				$folderOnServerID[] = $draftFolder;
3107
				$folder[$draftFolder] = $draftFolder;
3108
			}
3109
		}
3110
		if ($folderOnServerID) $folderOnServerID = array_unique($folderOnServerID);
3111
		if ($folderOnMailAccount) $folderOnMailAccount = array_unique($folderOnMailAccount);
3112
		if (($this->mailPreferences['sendOptions'] != 'send_only' && $sentFolder != 'none') &&
3113
			!( count($folder) > 0) &&
3114
			!($_formData['to_infolog']=='on' || $_formData['to_tracker']=='on'))
3115
		{
3116
			$this->errorInfo = lang("Error: ").lang("No Folder destination supplied, and no folder to save message or other measure to store the mail (save to infolog/tracker) provided, but required.").($this->errorInfo?' '.$this->errorInfo:'');
3117
			#error_log($this->errorInfo);
3118
			return false;
3119
		}
3120
		// SMIME SIGN/ENCRYPTION
3121
		if ($_formData['smime_sign'] == 'on' || $_formData['smime_encrypt'] == 'on' )
3122
		{
3123
			$recipients = array_merge($_formData['to'], (array) $_formData['cc'], (array) $_formData['bcc']);
3124
			try	{
3125
				if ($_formData['smime_sign'] == 'on')
3126
				{
3127
					if ($_formData['smime_passphrase'] != '') {
3128
						Api\Cache::setSession(
3129
							'mail',
3130
							'smime_passphrase',
3131
							$_formData['smime_passphrase'],
3132
							$GLOBALS['egw_info']['user']['preferences']['mail']['smime_pass_exp'] * 60
3133
						);
3134
					}
3135
					$smime_success = $this->_encrypt(
3136
						$mail,
3137
						$_formData['smime_encrypt'] == 'on'? Mail\Smime::TYPE_SIGN_ENCRYPT: Mail\Smime::TYPE_SIGN,
3138
						Mail::stripRFC822Addresses($recipients),
3139
						$identity['ident_email'],
3140
						$_formData['smime_passphrase']
3141
					);
3142
					if (!$smime_success)
3143
					{
3144
						$response = Api\Json\Response::get();
3145
						$this->errorInfo = $_formData['smime_passphrase'] == ''?
3146
								lang('You need to enter your S/MIME passphrase to send this message.'):
3147
								lang('The entered passphrase is not correct! Please try again.');
3148
						$response->call('app.mail.smimePassDialog', $this->errorInfo);
3149
						return false;
3150
					}
3151
				}
3152
				elseif ($_formData['smime_sign'] == 'off' && $_formData['smime_encrypt'] == 'on')
3153
				{
3154
					$smime_success =  $this->_encrypt(
0 ignored issues
show
Unused Code introduced by
The assignment to $smime_success is dead and can be removed.
Loading history...
3155
						$mail,
3156
						Mail\Smime::TYPE_ENCRYPT,
3157
						Mail::stripRFC822Addresses($recipients),
3158
						$identity['ident_email']
3159
					);
3160
				}
3161
			}
3162
			catch (Exception $ex)
3163
			{
3164
				$response = Api\Json\Response::get();
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
3165
				$this->errorInfo = $ex->getMessage();
3166
				return false;
3167
			}
3168
		}
3169
3170
		// set a higher timeout for big messages
3171
		@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

3171
		/** @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...
3172
		//$mail->SMTPDebug = 10;
3173
		//error_log("Folder:".count(array($this->sessionData['folder']))."To:".count((array)$this->sessionData['to'])."CC:". count((array)$this->sessionData['cc']) ."bcc:".count((array)$this->sessionData['bcc']));
3174
		if(count((array)$this->sessionData['to']) > 0 || count((array)$this->sessionData['cc']) > 0 || count((array)$this->sessionData['bcc']) > 0) {
3175
			try {
3176
				// do no close the session before sending, if we have to store the send text for infolog or other integration in the session
3177
				if (!($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' ))
3178
				{
3179
					$GLOBALS['egw']->session->commit_session();
3180
				}
3181
				$mail->send();
3182
			}
3183
			catch(Exception $e) {
3184
				_egw_log_exception($e);
3185
				//if( $e->details ) error_log(__METHOD__.__LINE__.array2string($e->details));
3186
				$this->errorInfo = $e->getMessage().($e->details?'<br/>'.$e->details:'');
3187
				return false;
3188
			}
3189
		} else {
3190
			if (count(array($this->sessionData['folder']))>0 && !empty($this->sessionData['folder'])) {
3191
				//error_log(__METHOD__.__LINE__."Folders:".print_r($this->sessionData['folder'],true));
3192
			} else {
3193
				$this->errorInfo = lang("Error: ").lang("No Address TO/CC/BCC supplied, and no folder to save message to provided.");
3194
				//error_log(__METHOD__.__LINE__.$this->errorInfo);
3195
				return false;
3196
			}
3197
		}
3198
		//error_log(__METHOD__.__LINE__."Mail Sent.!");
3199
		//error_log(__METHOD__.__LINE__."Number of Folders to move copy the message to:".count($folder));
3200
		//error_log(__METHOD__.__LINE__.array2string($folder));
3201
		if ((count($folder) > 0) || (isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder']))
3202
            || (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) {
3203
			$mail_bo = $this->mail_bo;
3204
			$mail_bo->openConnection();
3205
			//$mail_bo->reopen($this->sessionData['messageFolder']);
3206
			#error_log("(re)opened Connection");
3207
		}
3208
		// if copying mail to folder, or saving mail to infolog, we need to gather the needed information
3209
		if (count($folder) > 0 || $_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') {
3210
			//error_log(__METHOD__.__LINE__.array2string($this->sessionData['bcc']));
3211
3212
			// normaly Bcc is only added to recipients, but not as header visible to all recipients
3213
			$mail->forceBccHeader();
3214
		}
3215
		// copying mail to folder
3216
		if (count($folder) > 0)
3217
		{
3218
			foreach($folderOnServerID as $folderName) {
3219
				if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all
3220
				//error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName));
3221
				// if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID
3222
				// if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue;
3223
				if ($mail_bo->folderExists($folderName,true)) {
3224
					if($mail_bo->isSentFolder($folderName)) {
3225
						$flags = '\\Seen';
3226
					} elseif($mail_bo->isDraftFolder($folderName)) {
3227
						$flags = '\\Draft';
3228
					} else {
3229
						$flags = '\\Seen';
3230
					}
3231
					#$mailHeader=explode('From:',$mail->getMessageHeader());
3232
					#$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1],
3233
					//error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName));
3234
					//$mail_bo->reopen($folderName);
3235
					try
3236
					{
3237
						//error_log(__METHOD__.__LINE__.array2string($folderName));
3238
						$mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags);
3239
					}
3240
					catch (Api\Exception\WrongUserinput $e)
3241
					{
3242
						error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$this->sessionData['subject'],$folderName,$e->getMessage()));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->sessionData['subject']. ( Ignorable by Annotation )

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

3242
						error_log(__METHOD__.__LINE__.'->'./** @scrutinizer ignore-call */ lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$this->sessionData['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...
3243
					}
3244
				}
3245
				else
3246
				{
3247
					error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName));
3248
				}
3249
			}
3250
			// if we choose to send from a differing profile
3251
			if ($folderOnMailAccount)  $this->changeProfile($_formData['mailaccount']);
3252
			foreach($folderOnMailAccount as $folderName) {
3253
				if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all
3254
				//error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName));
3255
				// if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID
3256
				// if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue;
3257
				if ($this->mail_bo->folderExists($folderName,true)) {
3258
					if($this->mail_bo->isSentFolder($folderName)) {
3259
						$flags = '\\Seen';
3260
					} elseif($this->mail_bo->isDraftFolder($folderName)) {
3261
						$flags = '\\Draft';
3262
					} else {
3263
						$flags = '\\Seen';
3264
					}
3265
					#$mailHeader=explode('From:',$mail->getMessageHeader());
3266
					#$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1],
3267
					//error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName));
3268
					//$mail_bo->reopen($folderName);
3269
					try
3270
					{
3271
						//error_log(__METHOD__.__LINE__.array2string($folderName));
3272
						$this->mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags);
3273
					}
3274
					catch (Api\Exception\WrongUserinput $e)
3275
					{
3276
						error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$this->sessionData['subject'],$folderName,$e->getMessage()));
3277
					}
3278
				}
3279
				else
3280
				{
3281
					error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName));
3282
				}
3283
			}
3284
			if ($folderOnMailAccount)  $this->changeProfile($_formData['serverID']);
3285
3286
			//$mail_bo->closeConnection();
3287
		}
3288
		// handle previous drafted versions of that mail
3289
		$lastDrafted = false;
3290
		if (isset($this->sessionData['lastDrafted']))
3291
		{
3292
			$lastDrafted=array();
3293
			$dhA = mail_ui::splitRowID($this->sessionData['lastDrafted']);
3294
			$lastDrafted['uid'] = $dhA['msgUID'];
3295
			$lastDrafted['folder'] = $dhA['folder'];
3296
			if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']);
3297
			// manually drafted, do not delete
3298
			// will be handled later on IF mode was $_formData['mode']=='composefromdraft'
3299
			if (isset($lastDrafted['uid']) && (empty($lastDrafted['uid']) || $lastDrafted['uid'] == $this->sessionData['uid'])) $lastDrafted=false;
3300
			//error_log(__METHOD__.__LINE__.array2string($lastDrafted));
3301
		}
3302
		if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder']))
3303
		{
3304
			try
3305
			{
3306
				if ($this->sessionData['lastDrafted'] != $this->sessionData['uid'] || !($_formData['mode']=='composefromdraft' &&
3307
					($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )&&$this->sessionData['attachments']))
3308
				{
3309
					//error_log(__METHOD__.__LINE__."#".$lastDrafted['uid'].'#'.$lastDrafted['folder'].array2string($_formData));
3310
					//error_log(__METHOD__.__LINE__."#".array2string($_formData));
3311
					//error_log(__METHOD__.__LINE__."#".array2string($this->sessionData));
3312
					$mail_bo->deleteMessages($lastDrafted['uid'],$lastDrafted['folder'],'remove_immediately');
3313
				}
3314
			}
3315
			catch (Api\Exception $e)
3316
			{
3317
				//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3318
				unset($e);
3319
			}
3320
		}
3321
		unset($this->sessionData['lastDrafted']);
3322
3323
		//error_log("handling draft messages, flagging and such");
3324
		if((isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder']))
3325
			|| (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) {
3326
			// mark message as answered
3327
			$mail_bo->openConnection();
3328
			$mail_bo->reopen(($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder']));
3329
			// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
3330
			// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
3331
			if ($mail_bo->isDraftFolder($this->sessionData['messageFolder']) && !$mail_bo->isTemplateFolder($this->sessionData['messageFolder']))
3332
			{
3333
				try // message may be deleted already, as it maybe done by autosave
3334
				{
3335
					if ($_formData['mode']=='composefromdraft' &&
3336
						!(($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on') && $this->sessionData['attachments']))
3337
					{
3338
						//error_log(__METHOD__.__LINE__."#".$this->sessionData['uid'].'#'.$this->sessionData['messageFolder']);
3339
						$mail_bo->deleteMessages(array($this->sessionData['uid']),$this->sessionData['messageFolder'], 'remove_immediately');
3340
					}
3341
				}
3342
				catch (Api\Exception $e)
3343
				{
3344
					//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3345
					unset($e);
3346
				}
3347
			} else {
3348
				$mail_bo->flagMessages("answered", $this->sessionData['uid'],($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder']));
3349
				//error_log(__METHOD__.__LINE__.array2string(array_keys($this->sessionData)).':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']);
3350
				if (array_key_exists('forwardFlag',$this->sessionData) && $this->sessionData['forwardFlag']=='forwarded')
3351
				{
3352
					try
3353
					{
3354
						//error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']);
3355
						$mail_bo->flagMessages("forwarded", $this->sessionData['forwardedUID'],$this->sessionData['sourceFolder']);
3356
					}
3357
					catch (Api\Exception $e)
3358
					{
3359
						//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3360
						unset($e);
3361
					}
3362
				}
3363
			}
3364
			//$mail_bo->closeConnection();
3365
		}
3366
		if ($mail_bo) $mail_bo->closeConnection();
0 ignored issues
show
introduced by
$mail_bo is of type EGroupware\Api\Mail, thus it always evaluated to true.
Loading history...
3367
		//error_log("performing Infolog Stuff");
3368
		//error_log(print_r($this->sessionData['to'],true));
3369
		//error_log(print_r($this->sessionData['cc'],true));
3370
		//error_log(print_r($this->sessionData['bcc'],true));
3371
		if (is_array($this->sessionData['to']))
3372
		{
3373
			$mailaddresses['to'] = $this->sessionData['to'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$mailaddresses was never initialized. Although not strictly required by PHP, it is generally a good practice to add $mailaddresses = array(); before regardless.
Loading history...
3374
		}
3375
		else
3376
		{
3377
			$mailaddresses = array();
3378
		}
3379
		if (is_array($this->sessionData['cc'])) $mailaddresses['cc'] = $this->sessionData['cc'];
3380
		if (is_array($this->sessionData['bcc'])) $mailaddresses['bcc'] = $this->sessionData['bcc'];
3381
		if (!empty($mailaddresses)) $mailaddresses['from'] = Mail\Html::decodeMailHeader($fromAddress);
3382
3383
		if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )
3384
		{
3385
			$this->sessionData['attachments'] = array_merge((array)$this->sessionData['attachments'], (array)$inline_images);
3386
3387
			foreach(array('to_infolog','to_tracker','to_calendar') as $app_key)
3388
			{
3389
				$entryid = $_formData['to_integrate_ids'][0][$app_key];
3390
				if ($_formData[$app_key] == 'on')
3391
				{
3392
					$app_name = substr($app_key,3);
3393
					// Get registered hook data of the app called for integration
3394
					$hook = Api\Hooks::single(array('location'=> 'mail_import'),$app_name);
3395
3396
					// store mail / eml in temp. file to not have to download it from mail-server again
3397
					$eml = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'mail_integrate');
3398
					$eml_fp = fopen($eml, 'w');
3399
					stream_copy_to_stream($mail->getRaw(), $eml_fp);
3400
					fclose($eml_fp);
3401
					$target = array(
3402
						'menuaction' => $hook['menuaction'],
3403
						'egw_data' => Link::set_data(null,'mail_integration::integrate',array(
3404
							$mailaddresses,
3405
							$this->sessionData['subject'],
3406
							$this->convertHTMLToText($this->sessionData['body']),
3407
							$this->sessionData['attachments'],
3408
							false, // date
3409
							$eml,
3410
							$_formData['serverID']),true),
3411
						'app' => $app_name
3412
					);
3413
					if ($entryid) $target['entry_id'] = $entryid;
3414
					// Open the app called for integration in a popup
3415
					// and store the mail raw data as egw_data, in order to
3416
					// be stored from registered app method later
3417
					Framework::popup(Egw::link('/index.php', $target),'_blank',$hook['popup']);
3418
				}
3419
			}
3420
		}
3421
		// only clean up temp-files, if we dont need them for mail_integration::integrate
3422
		elseif(is_array($this->sessionData['attachments']))
3423
		{
3424
			foreach($this->sessionData['attachments'] as $value) {
3425
				if (!empty($value['file']) && parse_url($value['file'],PHP_URL_SCHEME) != 'vfs') {	// happens when forwarding mails
3426
					unlink($GLOBALS['egw_info']['server']['temp_dir'].'/'.$value['file']);
3427
				}
3428
			}
3429
		}
3430
3431
		$this->sessionData = '';
3432
3433
		return true;
3434
	}
3435
3436
	/**
3437
	 * setDefaults, sets some defaults
3438
	 *
3439
	 * @param array $content
3440
	 * @return array - the input, enriched with some not set attributes
3441
	 */
3442
	function setDefaults($content=array())
3443
	{
3444
		// if there's not already an identity selected for current account
3445
		if (empty($content['mailidentity']))
3446
		{
3447
			// check if there a preference / previous selection of identity for current account
3448
			if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']))
3449
			{
3450
				$sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'];
3451
				if (!empty($sigPref[$this->mail_bo->profileID]) && $sigPref[$this->mail_bo->profileID]>0)
3452
				{
3453
					$content['mailidentity'] = $sigPref[$this->mail_bo->profileID];
3454
				}
3455
			}
3456
			// if we have no preference search for first identity with non-empty signature
3457
			if (empty($content['mailidentity']))
3458
			{
3459
				$default_identity = null;
3460
				foreach(Mail\Account::identities($this->mail_bo->profileID, true, 'params') as $identity)
3461
				{
3462
					if (!isset($default_identity)) $default_identity = $identity['ident_id'];
3463
					if (!empty($identity['ident_signature']))
3464
					{
3465
						$content['mailidentity'] = $identity['ident_id'];
3466
						break;
3467
					}
3468
				}
3469
			}
3470
			if (empty($content['mailidentity'])) $content['mailidentity'] = $default_identity;
3471
		}
3472
		if (!isset($content['mimeType']) || empty($content['mimeType']))
3473
		{
3474
			$content['mimeType'] = 'html';
3475
			if (!empty($this->mailPreferences['composeOptions']) && $this->mailPreferences['composeOptions']=="text") $content['mimeType']  = 'plain';
3476
		}
3477
		return $content;
3478
3479
	}
3480
3481
	function stripSlashes($_string)
3482
	{
3483
		if (get_magic_quotes_gpc()) {
3484
			return stripslashes($_string);
3485
		} else {
3486
			return $_string;
3487
		}
3488
	}
3489
	/**
3490
	 * Callback function to search mail folders
3491
	 *
3492
	 * @param int $_searchStringLength
3493
	 * @param boolean $_returnList
3494
	 * @param int $_mailaccountToSearch
3495
	 * @param boolean $_noPrefixId = false, if set to true folders name does not get prefixed by account id
3496
	 * @return type
3497
	 */
3498
	function ajax_searchFolder($_searchStringLength=2, $_returnList=false, $_mailaccountToSearch=null, $_noPrefixId=false) {
3499
		//error_log(__METHOD__.__LINE__.':'.array2string($_REQUEST));
3500
		static $useCacheIfPossible = null;
3501
		if (is_null($useCacheIfPossible)) $useCacheIfPossible = true;
3502
		$_searchString = trim($_REQUEST['query']);
3503
		$results = array();
3504
		$rememberServerID = $this->mail_bo->icServer->ImapServerId;
3505
		if (is_null($_mailaccountToSearch) && !empty($_REQUEST['mailaccount'])) $_mailaccountToSearch = $_REQUEST['mailaccount'];
3506
		if (empty($_mailaccountToSearch)) $_mailaccountToSearch = $this->mail_bo->icServer->ImapServerId;
3507
		if ($this->mail_bo->icServer && $_mailaccountToSearch && $this->mail_bo->icServer->ImapServerId != $_mailaccountToSearch)
3508
		{
3509
			$this->changeProfile($_mailaccountToSearch);
3510
		}
3511
		if (strlen($_searchString)>=$_searchStringLength && isset($this->mail_bo->icServer))
3512
		{
3513
			//error_log(__METHOD__.__LINE__.':'.$this->mail_bo->icServer->ImapServerId);
3514
			$this->mail_bo->openConnection($this->mail_bo->icServer->ImapServerId);
3515
			//error_log(__METHOD__.__LINE__.array2string($_searchString).'<->'.$searchString);
3516
			$folderObjects = $this->mail_bo->getFolderObjects(true,false,true,$useCacheIfPossible);
3517
			if (count($folderObjects)<=1) {
3518
				$useCacheIfPossible = false;
3519
			}
3520
			else
3521
			{
3522
				$useCacheIfPossible = true;
3523
			}
3524
			$searchString = Api\Translation::convert($_searchString, Mail::$displayCharset,'UTF7-IMAP');
3525
			foreach ($folderObjects as $k =>$fA)
3526
			{
3527
				//error_log(__METHOD__.__LINE__.$_searchString.'/'.$searchString.' in '.$k.'->'.$fA->displayName);
3528
				$f=false;
3529
				$key = $_noPrefixId?$k:$_mailaccountToSearch.'::'.$k;
3530
				if ($_searchStringLength<=0)
3531
				{
3532
					$f=true;
3533
					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3534
				}
3535
				if ($f==false && stripos($fA->displayName,$_searchString)!==false)
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...
3536
				{
3537
					$f=true;
3538
					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3539
				}
3540
				if ($f==false && stripos($k,$searchString)!==false)
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...
3541
				{
3542
					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3543
				}
3544
			}
3545
		}
3546
		if ($this->mail_bo->icServer && $rememberServerID != $this->mail_bo->icServer->ImapServerId)
3547
		{
3548
			$this->changeProfile($rememberServerID);
3549
		}
3550
		//error_log(__METHOD__.__LINE__.' IcServer:'.$this->mail_bo->icServer->ImapServerId.':'.array2string($results));
3551
		if ($_returnList)
3552
		{
3553
			foreach ((array)$results as $k => $_result)
3554
			{
3555
				$rL[$_result['id']] = $_result['label'];
3556
			}
3557
			return $rL;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rL seems to be defined by a foreach iteration on line 3553. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
3558
		}
3559
		// switch regular JSON response handling off
3560
		Api\Json\Request::isJSONRequest(false);
3561
3562
		header('Content-Type: application/json; charset=utf-8');
3563
		//error_log(__METHOD__.__LINE__);
3564
		echo json_encode($results);
3565
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
3566
	}
3567
3568
	public static function ajax_searchAddress($_searchStringLength=2) {
3569
		//error_log(__METHOD__. "request from seachAddress " . $_REQUEST['query']);
3570
		$_searchString = trim($_REQUEST['query']);
3571
		$include_lists = (boolean)$_REQUEST['include_lists'];
3572
3573
		$contacts_obj = new Api\Contacts();
3574
		$results = array();
3575
3576
		// Add up to 10 matching mailing lists, and 10 groups
3577
		if($include_lists)
3578
		{
3579
			$results += static::get_lists($_searchString, $contacts_obj);
3580
		}
3581
3582
		if ($GLOBALS['egw_info']['user']['apps']['addressbook'] && strlen($_searchString)>=$_searchStringLength)
3583
		{
3584
			//error_log(__METHOD__.__LINE__.array2string($_searchString));
3585
			$showAccounts = $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1';
3586
			$search = explode(' ', $_searchString);
3587
			foreach ($search as $k => $v)
3588
			{
3589
				if (mb_strlen($v) < 3) unset($search[$k]);
3590
			}
3591
			$search_str = implode(' +', $search);	// tell contacts/so_sql to AND search patterns
3592
			//error_log(__METHOD__.__LINE__.$_searchString);
3593
			$filter = $showAccounts ? array() : array('account_id' => null);
3594
			$filter['cols_to_search'] = array('n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id');
3595
			$cols = array('n_fn','n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id', 'etag');
3596
			$contacts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false, 'OR', array(0,100), $filter);
3597
			$cfs_type_email = Api\Storage\Customfields::get_email_cfs('addressbook');
3598
			// additionally search the accounts, if the contact storage is not the account storage
3599
			if ($showAccounts && $contacts_obj->so_accounts)
3600
			{
3601
				$filter['owner'] = 0;
3602
				$accounts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false,'OR', array(0,100), $filter);
3603
3604
				if ($contacts && $accounts)
3605
				{
3606
					$contacts = array_merge($contacts,$accounts);
3607
					usort($contacts,function($a, $b)
3608
					{
3609
						return strcasecmp($a['n_fn'], $b['n_fn']);
3610
					});
3611
				}
3612
				elseif($accounts)
3613
				{
3614
					$contacts =& $accounts;
3615
				}
3616
				unset($accounts);
3617
			}
3618
		}
3619
3620
		if(is_array($contacts)) {
3621
			foreach($contacts as $contact) {
3622
				$cf_emails = (array)array_values(array_values($contacts_obj->read_customfields($contact['id'], $cfs_type_email))[0]);
3623
				foreach(array_merge(array($contact['email'],$contact['email_home']), $cf_emails) as $email) {
3624
					// avoid wrong addresses, if an rfc822 encoded address is in addressbook
3625
					//$email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email);
3626
					$rfcAddr = Mail::parseAddressList($email);
3627
					$_rfcAddr=$rfcAddr->first();
3628
					if (!$_rfcAddr->valid)
3629
					{
3630
						continue; // skip address if we encounter an error here
3631
					}
3632
					$email = $_rfcAddr->mailbox.'@'.$_rfcAddr->host;
3633
3634
					if (method_exists($contacts_obj,'search'))
3635
					{
3636
						$contact['n_fn']='';
3637
						if (!empty($contact['n_prefix'])) $contact['n_fn'] = $contact['n_prefix'];
3638
						if (!empty($contact['n_given'])) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_given'];
3639
						if (!empty($contact['n_family'])) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_family'];
3640
						if (!empty($contact['org_name'])) $contact['n_fn'] .= ($contact['n_fn']?' ':'').'('.$contact['org_name'].')';
3641
						$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
3642
					}
3643
					else
3644
					{
3645
						$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
3646
					}
3647
					$args = explode('@', trim($email));
3648
					$args[] = trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']);
3649
					$completeMailString = call_user_func_array('imap_rfc822_write_address', $args);
3650
					if(!empty($email) && in_array($completeMailString ,$results) === false) {
3651
						$results[] = array(
3652
							'id'=>$completeMailString,
3653
							'label' => $completeMailString,
3654
							// Add just name for nice display, with title for hover
3655
							'name' => $contact['n_fn'],
3656
							'title' => $email,
3657
							'icon' => Egw::link('/api/avatar.php', array(
3658
								'contact_id' => $contact['id'],
3659
								'etag' => $contact['etag']
3660
							))
3661
						 );
3662
					}
3663
				}
3664
			}
3665
		}
3666
3667
		// Add groups
3668
		$group_options = array('account_type' => 'groups');
3669
		$groups = $GLOBALS['egw']->accounts->link_query($_searchString, $group_options);
3670
		foreach($groups as $g_id => $name)
3671
		{
3672
			$group = $GLOBALS['egw']->accounts->read($g_id);
3673
			if(!$group['account_email']) continue;
3674
			$args = explode('@', trim($group['account_email']));
3675
			$args[] = $name;
3676
			$completeMailString = call_user_func_array('imap_rfc822_write_address', $args);
3677
			$results[] = array(
3678
				'id' => $completeMailString,
3679
				'label' => $completeMailString,
3680
				'name'	=> $name,
3681
				'title' => $group['account_email']
3682
			);
3683
		}
3684
3685
		 // switch regular JSON response handling off
3686
		Api\Json\Request::isJSONRequest(false);
3687
3688
		//error_log(__METHOD__.__LINE__.array2string($jsArray));
3689
		header('Content-Type: application/json; charset=utf-8');
3690
		echo json_encode($results);
3691
		exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
3692
	}
3693
3694
	/**
3695
	 * Get list of matching distribution lists when searching for email addresses
3696
	 *
3697
	 * The results are limited to 10 each of group lists and normal lists
3698
	 *
3699
	 * @param String $_searchString
3700
	 * @param Contacts $contacts_obj
0 ignored issues
show
Bug introduced by
The type Contacts 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...
3701
	 * @return array
3702
	 */
3703
	protected static function get_lists($_searchString, &$contacts_obj)
3704
	{
3705
		$group_lists = array();
3706
		$manual_lists = array();
3707
		$lists = array_filter(
3708
			$contacts_obj->get_lists(Acl::READ),
3709
			function($element) use($_searchString) {
3710
				return (stripos($element, $_searchString) !== false);
3711
			}
3712
		);
3713
3714
		foreach($lists as $key => $list_name)
3715
		{
3716
			$type = $key > 0 ? 'manual' : 'group';
3717
			$list = array(
3718
				'id'	=> $key,
3719
				'name'	=> $list_name,
3720
				'label'	=> $list_name,
3721
				'class' => 'mailinglist ' . "{$type}_list",
3722
				'title' => lang('Mailinglist'),
3723
				'data'	=> $key
3724
			);
3725
			${"${type}_lists"}[] = $list;
3726
		}
3727
		$trim = function($list) {
3728
			$limit = 10;
3729
			if(count($list) <= $limit) return $list;
3730
			$list[$limit-1]['class'].= ' more_results';
3731
			$list[$limit-1]['title'] .= '  (' . lang('%1 more', count($list) - $limit) . ')';
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with count($list) - $limit. ( Ignorable by Annotation )

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

3731
			$list[$limit-1]['title'] .= '  (' . /** @scrutinizer ignore-call */ lang('%1 more', count($list) - $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...
3732
			return array_slice($list, 0, $limit);
3733
		};
3734
		return array_merge($trim($group_lists), $trim($manual_lists));
3735
	}
3736
	/**
3737
	 * Merge the selected contact ID into the document given in $_REQUEST['document']
3738
	 * and send it.
3739
	 *
3740
	 * @param int $contact_id
3741
	 */
3742
	public function ajax_merge($contact_id)
3743
	{
3744
		$response = Api\Json\Response::get();
3745
		if(class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
3746
		{
3747
			$document_merge = new $_REQUEST['merge']();
3748
		}
3749
		else
3750
		{
3751
			$document_merge = new Api\Contacts\Merge();
3752
		}
3753
		$this->mail_bo->openConnection();
3754
3755
		if(($error = $document_merge->check_document($_REQUEST['document'],'')))
3756
		{
3757
			$response->error($error);
3758
			return;
3759
		}
3760
3761
		// Actually do the merge
3762
		$folder = $merged_mail_id = null;
3763
		try
3764
		{
3765
			$results = $this->mail_bo->importMessageToMergeAndSend(
3766
				$document_merge, Vfs::PREFIX . $_REQUEST['document'],
3767
				// Send an extra non-numeric ID to force actual send of document
3768
				// instead of save as draft
3769
				array((int)$contact_id, ''),
3770
				$folder,$merged_mail_id
3771
			);
3772
3773
			// Also save as infolog
3774
			if($merged_mail_id && $_REQUEST['to_app'] && isset($GLOBALS['egw_info']['user']['apps'][$_REQUEST['to_app']]))
0 ignored issues
show
introduced by
$merged_mail_id is of type null, thus it always evaluated to false.
Loading history...
3775
			{
3776
				$rowid = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $merged_mail_id, true);
3777
				$data = mail_integration::get_integrate_data($rowid);
3778
				if($data && $_REQUEST['to_app'] == 'infolog')
3779
				{
3780
					$bo = new infolog_bo();
3781
					$entry = $bo->import_mail($data['addresses'],$data['subject'],$data['message'],$data['attachments'],$data['date']);
3782
					if($_REQUEST['info_type'] && isset($bo->enums['type'][$_REQUEST['info_type']]))
3783
					{
3784
						$entry['info_type'] = $_REQUEST['info_type'];
3785
					}
3786
					$bo->write($entry);
3787
				}
3788
			}
3789
		}
3790
		catch (Exception $e)
3791
		{
3792
			$contact = $document_merge->contacts->read((int)$contact_id);
3793
			//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
3794
			$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
3795
			$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
3796
			$response->error(lang('Sending mail to "%1" failed', "$nfn <$email>").
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $nfn.' <'.$email.'>'. ( Ignorable by Annotation )

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

3796
			$response->error(/** @scrutinizer ignore-call */ lang('Sending mail to "%1" failed', "$nfn <$email>").

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...
3797
				"\n".$e->getMessage()
3798
			);
3799
		}
3800
3801
		if($results['success'])
3802
		{
3803
			$response->data(implode(',',$results['success']));
3804
		}
3805
		if($results['failed'])
3806
		{
3807
			$response->error(implode(',',$results['failed']));
3808
		}
3809
	}
3810
3811
	/**
3812
	 * Method to do encryption on given mail object
3813
	 *
3814
	 * @param Api\Mailer $mail
3815
	 * @param string $type encryption type
3816
	 * @param array|string $recipients list of recipients
3817
	 * @param string $sender email of sender
3818
	 * @param string $passphrase = '', SMIME Private key passphrase
3819
	 *
3820
	 * @return boolean returns true if successful and false if passphrase required
3821
	 * @throws Api\Exception\WrongUserinput if no certificate found
3822
	 */
3823
	protected function _encrypt($mail, $type, $recipients, $sender, $passphrase='')
3824
	{
3825
		$AB = new addressbook_bo();
3826
		 // passphrase of sender private key
3827
		$params['passphrase'] = $passphrase;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
3828
3829
		try
3830
		{
3831
			$sender_cert = $AB->get_smime_keys($sender);
3832
			if (!$sender_cert)	throw new Exception("S/MIME Encryption failed because no certificate has been found for sender address: " . $sender);
3833
			$params['senderPubKey'] = $sender_cert[strtolower($sender)];
3834
3835
			if (isset($sender) && ($type == Mail\Smime::TYPE_SIGN || $type == Mail\Smime::TYPE_SIGN_ENCRYPT))
3836
			{
3837
				$acc_smime = Mail\Smime::get_acc_smime($this->mail_bo->profileID, $params['passphrase']);
3838
				$params['senderPrivKey'] = $acc_smime['pkey'];
3839
				$params['extracerts'] = $acc_smime['extracerts'];
3840
			}
3841
3842
			if (isset($recipients) && ($type == Mail\Smime::TYPE_ENCRYPT || $type == Mail\Smime::TYPE_SIGN_ENCRYPT))
3843
			{
3844
				$params['recipientsCerts'] = $AB->get_smime_keys($recipients);
3845
				foreach ($recipients as &$recipient)
3846
				{
3847
					if (!$params['recipientsCerts'][strtolower($recipient)]) $missingCerts []= $recipient;
3848
				}
3849
				if (is_array($missingCerts)) throw new Exception ('S/MIME Encryption failed because no certificate has been found for following addresses: '. implode ('|', $missingCerts));
3850
			}
3851
3852
			return $mail->smimeEncrypt($type, $params);
3853
		}
3854
		catch(Api\Exception\WrongUserinput $e)
3855
		{
3856
			throw new $e;
3857
		}
3858
	}
3859
3860
	/**
3861
	 * Builds attachments from provided UIDs and add them to sessionData
3862
	 *
3863
	 * @param string|array $_ids series of message ids
3864
	 * @param int $_serverID compose current profileID
3865
	 *
3866
	 * @return array returns an array of attachments
3867
	 *
3868
	 * @throws Exception throws exception on cross account attempt
3869
	 */
3870
	function _get_uids_as_attachments ($_ids, $_serverID)
3871
	{
3872
		$ids = is_array($_ids) ? $_ids : explode(',', $_ids);
3873
		if (is_array($ids) && $_serverID)
3874
		{
3875
			$parts = mail_ui::splitRowID($ids[0]);
3876
			if ($_serverID != $parts['profileID'])
3877
			{
3878
				throw new Exception(lang('Cross account forward attachment is not allowed!'));
3879
			}
3880
		}
3881
		foreach ($ids as &$id)
3882
		{
3883
			$parts = mail_ui::splitRowID($id);
3884
			$mail_bo    = $this->mail_bo;
3885
			$mail_bo->openConnection();
3886
			$mail_bo->reopen($parts['folder']);
3887
			$headers	= $mail_bo->getMessageEnvelope($parts['msgUID'], null,false,$parts['folder']);
3888
			$this->addMessageAttachment($parts['msgUID'], null, $parts['folder'],
3889
					$mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
0 ignored issues
show
Bug introduced by
Are you sure $mail_bo->decode_header(...] : lang('no subject')) 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

3889
					/** @scrutinizer ignore-type */ $mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
Loading history...
3890
					'MESSAGE/RFC822', $headers['SIZE'] ? $headers['SIZE'] : lang('unknown'));
3891
			$mail_bo->closeConnection();
3892
		}
3893
		return $this->sessionData['attachments'];
3894
	}
3895
}
3896