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 [info@egroupware.org]
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
13use EGroupware\Api;
14use EGroupware\Api\Acl;
15use EGroupware\Api\Egw;
16use EGroupware\Api\Etemplate;
17use EGroupware\Api\Framework;
18use EGroupware\Api\Link;
19use EGroupware\Api\Mail;
20use EGroupware\Api\Vfs;
21
22/**
23 * Mail interface class for compose mails in popup
24 */
25class 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']))
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
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		if (!isset($GLOBALS['egw_info']['user']['apps']['infolog']))
282		{
283			unset($actions['to_infolog']);
284		}
285		if (!isset($GLOBALS['egw_info']['user']['apps']['tracker']))
286		{
287			unset($actions['to_tracker']);
288		}
289		if (!isset($GLOBALS['egw_info']['user']['apps']['calendar']))
290		{
291			unset($actions['to_calendar']);
292		}
293		return $actions;
294	}
295
296	/**
297	 * Compose dialog
298	 *
299	 * @var array $_content =null etemplate content array
300	 * @var string $msg =null a possible message to be passed and displayed to the userinterface
301	 * @var string $_focusElement ='to' subject, to, body supported
302	 * @var boolean $suppressSigOnTop =false
303	 * @var boolean $isReply =false
304	 */
305	function compose(array $_content=null,$msg=null, $_focusElement='to',$suppressSigOnTop=false, $isReply=false)
306	{
307		if ($msg) Framework::message($msg);
308
309		if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']))
310		{
311			$sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'];
312		}
313		else
314		{
315			$sigPref = array();
316		}
317		// split mailaccount (acc_id) and identity (ident_id)
318		if ($_content && isset($_content['mailaccount']))
319		{
320			list($_content['mailaccount'], $_content['mailidentity']) = explode(':', $_content['mailaccount']);
321		}
322		//error_log(__METHOD__.__LINE__.array2string($sigPref));
323		//lang('compose'),lang('from') // needed to be found by translationtools
324		//error_log(__METHOD__.__LINE__.array2string($_REQUEST).function_backtrace());
325		//error_log(__METHOD__.__LINE__.array2string($_content).function_backtrace());
326		$_contentHasSigID = $_content?array_key_exists('mailidentity',(array)$_content):false;
327		$_contentHasMimeType = $_content? array_key_exists('mimeType',(array)$_content):false;
328
329		// fetch appendix data which is an assistance input value consisiting of json data
330		if ($_content['appendix_data'])
331		{
332			$appendix_data = json_decode($_content['appendix_data'], true);
333			$_content['appendix_data'] = '';
334		}
335
336		if ($appendix_data['emails'])
337		{
338			try {
339				if ($appendix_data['emails']['processedmail_id']) $_content['processedmail_id'] .= ','.$appendix_data['emails']['processedmail_id'];
340				$attched_uids = $this->_get_uids_as_attachments($appendix_data['emails']['ids'], $_content['serverID']);
341				if (is_array($attched_uids))
342				{
343					$_content['attachments'] = array_merge_recursive((array)$_content['attachments'], $attched_uids);
344				}
345			} catch (Exception $ex) {
346				Framework::message($ex->getMessage(), 'error');
347			}
348			$suppressSigOnTop = true;
349			unset($appendix_data);
350		}
351
352		if (isset($_GET['reply_id'])) $replyID = $_GET['reply_id'];
353		if (!$replyID && isset($_GET['id'])) $replyID = $_GET['id'];
354
355		// Process different places we can use as a start for composing an email
356		$actionToProcess = 'compose';
357		if($_GET['from'] && $replyID)
358		{
359			$_content = array_merge((array)$_content, $this->getComposeFrom(
360				// Parameters needed for fetching appropriate data
361				$replyID, $_GET['part_id'], $_GET['from'],
362				// Additionally may be changed
363				$_focusElement, $suppressSigOnTop, $isReply
364			));
365			if (Mail\Smime::get_acc_smime($this->mail_bo->profileID))
366			{
367				if (isset($_GET['smime_type'])) $smime_type = $_GET['smime_type'];
368				// pre set smime_sign and smime_encrypt actions if the original
369				// message is smime.
370				$_content['smime_sign'] = $smime_type == (Mail\Smime::TYPE_SIGN ||
371					$smime_type == Mail\Smime::TYPE_SIGN_ENCRYPT) ? 'on' : 'off';
372				$_content['smime_encrypt'] = ($smime_type == Mail\Smime::TYPE_ENCRYPT) ? 'on' : 'off';
373			}
374
375			$actionToProcess = $_GET['from'];
376			unset($_GET['from']);
377			unset($_GET['reply_id']);
378			unset($_GET['part_id']);
379			unset($_GET['id']);
380			unset($_GET['mode']);
381			//error_log(__METHOD__.__LINE__.array2string($_content));
382		}
383
384		$composeCache = array();
385		if (isset($_content['composeID'])&&!empty($_content['composeID']))
386		{
387			$isFirstLoad = false;
388			$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);
389			$this->composeID = $_content['composeID'];
390			//error_log(__METHOD__.__LINE__.array2string($composeCache));
391		}
392		else
393		{
394			// as we use isFirstLoad to trigger the initalStyle on ckEditor, we
395			// respect that composeasnew may not want that, as we assume there
396			// is some style already set and our initalStyle always adds a span with &nbsp;
397			// and we want to avoid that
398			$isFirstLoad = !($actionToProcess=='composeasnew');//true;
399			$this->composeID = $_content['composeID'] = $this->generateComposeID();
400			if (!is_array($_content))
401			{
402				$_content = $this->setDefaults();
403			}
404			else
405			{
406				$_content = $this->setDefaults($_content);
407			}
408		}
409		// VFS Selector was used
410		if (is_array($_content['selectFromVFSForCompose']))
411		{
412			$suppressSigOnTop = true;
413			foreach ($_content['selectFromVFSForCompose'] as $i => $path)
414			{
415				$_content['uploadForCompose'][] = array(
416					'name' => Vfs::basename($path),
417					'type' => Vfs::mime_content_type($path),
418					'file' => Vfs::PREFIX.$path,
419					'size' => filesize(Vfs::PREFIX.$path),
420				);
421			}
422			unset($_content['selectFromVFSForCompose']);
423		}
424		// check everything that was uploaded
425		if (is_array($_content['uploadForCompose']))
426		{
427			$suppressSigOnTop = true;
428			foreach ($_content['uploadForCompose'] as $i => &$upload)
429			{
430				if (!isset($upload['file'])) $upload['file'] = $upload['tmp_name'];
431				try
432				{
433					$upload['file'] = $upload['tmp_name'] = Mail::checkFileBasics($upload,$this->composeID,false);
434				}
435				catch (Api\Exception\WrongUserinput $e)
436				{
437					Framework::message($e->getMessage(), 'error');
438					unset($_content['uploadForCompose'][$i]);
439					continue;
440				}
441				if (is_dir($upload['file']) && (!$_content['filemode'] || $_content['filemode'] == Vfs\Sharing::ATTACH))
442				{
443					$_content['filemode'] = Vfs\Sharing::READONLY;
444					Framework::message(lang('Directories have to be shared.'), 'info');
445				}
446			}
447		}
448		// check if someone did hit delete on the attachments list
449		if (!empty($_content['attachments']['delete']))
450		{
451			//error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments']));
452			//error_log(__METHOD__.__LINE__.':'.array2string($_content['attachments']['delete']));
453
454			$suppressSigOnTop = true;
455			$toDelete = $_content['attachments']['delete'];
456			unset($_content['attachments']['delete']);
457			$attachments = $_content['attachments'];
458			unset($_content['attachments']);
459			foreach($attachments as $i => $att)
460			{
461				$remove=false;
462				foreach(array_keys($toDelete) as $k)
463				{
464					if ($att['tmp_name']==$k) $remove=true;
465				}
466				if (!$remove) $_content['attachments'][] = $att;
467			}
468		}
469		// someone clicked something like send, or saveAsDraft
470		// make sure, we are connected to the correct server for sending and storing the send message
471		$activeProfile = $composeProfile = $this->mail_bo->profileID; // active profile may not be the profile uised in/for compose
472		$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);
473		if (!empty($activeFolderCache[$this->mail_bo->profileID]))
474		{
475			//error_log(__METHOD__.__LINE__.' CurrentFolder:'.$activeFolderCache[$this->mail_bo->profileID]);
476			$activeFolder = $activeFolderCache[$this->mail_bo->profileID];
477		}
478		//error_log(__METHOD__.__LINE__.array2string($_content));
479		if (!empty($_content['serverID']) && $_content['serverID'] != $this->mail_bo->profileID &&
480			($_content['composeToolbar'] === 'send' || $_content['button']['saveAsDraft']||$_content['button']['saveAsDraftAndPrint'])
481		)
482		{
483			$this->changeProfile($_content['serverID']);
484			$composeProfile = $this->mail_bo->profileID;
485		}
486		// make sure $acc is set/initalized properly with the current composeProfile, as $acc is used down there
487		// at several locations and not neccesaryly initialized before
488		$acc = Mail\Account::read($composeProfile);
489		$buttonClicked = false;
490		if ($_content['composeToolbar'] === 'send')
491		{
492			$buttonClicked = $suppressSigOnTop = true;
493			$sendOK = true;
494			$_content['body'] = ($_content['body'] ? $_content['body'] : $_content['mail_'.($_content['mimeType'] == 'html'?'html':'plain').'text']);
495			/*
496			perform some simple checks, before trying to send on:
497			$_content['to'];$_content['cc'];$_content['bcc'];
498			trim($_content['subject']);
499			trim(strip_tags(str_replace('&nbsp;','',$_content['body'])));
500			*/
501			if (strlen(trim(strip_tags(str_replace('&nbsp;','',$_content['body']))))==0 && count($_content['attachments'])==0)
502			{
503				$sendOK = false;
504				$_content['msg'] = $message = lang("no message body supplied");
505			}
506			if ($sendOK && strlen(trim($_content['subject']))==0)
507			{
508				$sendOK = false;
509				$_content['msg'] = $message = lang("no subject supplied");
510			}
511			if ($sendOK && empty($_content['to']) && empty($_content['cc']) && empty($_content['bcc']))
512			{
513				$sendOK = false;
514				$_content['msg'] = $message = lang("no adress, to send this mail to, supplied");
515			}
516			if ($sendOK)
517			{
518				try
519				{
520					$success = $this->send($_content);
521
522					//hook mail_compose_after_save
523					Api\Hooks::process( array(
524							'location' => 'mail_compose_after_save',
525							'content' => $_content,
526					));
527
528					if ($success==false)
529					{
530						$sendOK=false;
531						$message = $this->errorInfo;
532					}
533					if (!empty($_content['mailidentity']) && $_content['mailidentity'] != $sigPref[$this->mail_bo->profileID])
534					{
535						$sigPref[$this->mail_bo->profileID]=$_content['mailidentity'];
536						$GLOBALS['egw']->preferences->add('mail','LastSignatureIDUsed',$sigPref,'user');
537						// save prefs
538						$GLOBALS['egw']->preferences->save_repository(true);
539					}
540				}
541				catch (Api\Exception\WrongUserinput $e)
542				{
543					$sendOK = false;
544					$message = $e->getMessage();
545				}
546			}
547			if ($activeProfile != $composeProfile)
548			{
549				$this->changeProfile($activeProfile);
550				$activeProfile = $this->mail_bo->profileID;
551			}
552			if ($sendOK)
553			{
554				$workingFolder = $activeFolder;
555				$mode = 'compose';
556				$idsForRefresh = array();
557				if (isset($_content['mode']) && !empty($_content['mode']))
558				{
559					$mode = $_content['mode'];
560					if ($_content['mode']=='forward' && !empty($_content['processedmail_id']))
561					{
562						$_content['processedmail_id'] = explode(',',$_content['processedmail_id']);
563						foreach ($_content['processedmail_id'] as $k =>$rowid)
564						{
565							$fhA = mail_ui::splitRowID($rowid);
566							//$this->sessionData['uid'][] = $fhA['msgUID'];
567							//$this->sessionData['forwardedUID'][] = $fhA['msgUID'];
568							$idsForRefresh[] = mail_ui::generateRowID($fhA['profileID'], $fhA['folder'], $fhA['msgUID'], $_prependApp=false);
569							if (!empty($fhA['folder'])) $workingFolder = $fhA['folder'];
570						}
571					}
572					if ($_content['mode']=='reply' && !empty($_content['processedmail_id']))
573					{
574						$rhA = mail_ui::splitRowID($_content['processedmail_id']);
575						//$this->sessionData['uid'] = $rhA['msgUID'];
576						$idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false);
577						$workingFolder = $rhA['folder'];
578					}
579				}
580				//the line/condition below should not be needed
581				if (empty($idsForRefresh) && !empty($_content['processedmail_id']))
582				{
583					$rhA = mail_ui::splitRowID($_content['processedmail_id']);
584					$idsForRefresh[] = mail_ui::generateRowID($rhA['profileID'], $rhA['folder'], $rhA['msgUID'], $_prependApp=false);
585					$workingFolder = $rhA['folder'];	// need folder to refresh eg. drafts folder
586				}
587				$response = Api\Json\Response::get();
588				if ($activeProfile != $composeProfile)
589				{
590					// we need a message only, when account ids (composeProfile vs. activeProfile) differ
591					$response->call('opener.egw_message',lang('Message send successfully.'));
592				}
593				elseif ($activeProfile == $composeProfile && ($workingFolder==$activeFolder['mailbox'] && $mode != 'compose') ||
594					($this->mail_bo->isSentFolder($workingFolder) || $this->mail_bo->isDraftFolder($workingFolder)))
595				{
596					if ($this->mail_bo->isSentFolder($workingFolder)||$this->mail_bo->isDraftFolder($workingFolder))
597					{
598						// we may need a refresh when on sent folder or in drafts, as drafted messages will/should be deleted after succeeded send action
599						$response->call('opener.egw_refresh',lang('Message send successfully.'),'mail');
600					}
601					// we only need to update the icon of the replied or forwarded mails --> 'update-in-place'
602					else
603					{
604						//error_log(__METHOD__.__LINE__.array2string($idsForRefresh));
605						$response->call('opener.egw_refresh',lang('Message send successfully.'),'mail',$idsForRefresh,'update-in-place');
606					}
607				}
608				else
609				{
610					$response->call('opener.egw_message',lang('Message send successfully.'));
611				}
612				//egw_framework::refresh_opener(lang('Message send successfully.'),'mail');
613				Framework::window_close();
614			}
615			if ($sendOK == false)
616			{
617				$response = Api\Json\Response::get();
618				Framework::message(lang('Message send failed: %1',$message),'error');// maybe error is more appropriate
619				$response->call('app.mail.clearIntevals');
620			}
621		}
622
623		if ($activeProfile != $composeProfile) $this->changeProfile($activeProfile);
624		$insertSigOnTop = false;
625		$content = (is_array($_content)?$_content:array());
626		if ($_contentHasMimeType)
627		{
628			// mimeType is now a checkbox; convert it here to match expectations
629			// ToDo: match Code to meet checkbox value
630			if ($content['mimeType']==1)
631			{
632				$_content['mimeType'] = $content['mimeType']='html';
633			}
634			else
635			{
636				$_content['mimeType'] = $content['mimeType']='plain';
637			}
638
639		}
640		// user might have switched desired mimetype, so we should convert
641		if ($content['is_html'] && $content['mimeType']=='plain')
642		{
643			//error_log(__METHOD__.__LINE__.$content['mail_htmltext']);
644			$suppressSigOnTop = true;
645			if (stripos($content['mail_htmltext'],'<pre>')!==false)
646			{
647				$contentArr = Api\Mail\Html::splithtmlByPRE($content['mail_htmltext']);
648				if (is_array($contentArr))
649				{
650					foreach ($contentArr as $k =>&$elem)
651					{
652						if (stripos($elem,'<pre>')!==false) $elem = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$elem);
653					}
654					$content['mail_htmltext'] = implode('',$contentArr);
655				}
656			}
657			$content['mail_htmltext'] = $this->_getCleanHTML($content['mail_htmltext']);
658			$content['mail_htmltext'] = Api\Mail\Html::convertHTMLToText($content['mail_htmltext'],$charset=false,false,true);
659
660			$content['body'] = $content['mail_htmltext'];
661			unset($content['mail_htmltext']);
662			$content['is_html'] = false;
663			$content['is_plain'] = true;
664		}
665		if ($content['is_plain'] && $content['mimeType']=='html')
666		{
667			// the possible font span should only be applied on first load or on switch plain->html
668			$isFirstLoad = "switchedplaintohtml";
669			//error_log(__METHOD__.__LINE__.$content['mail_plaintext']);
670			$suppressSigOnTop = true;
671			$content['mail_plaintext'] = str_replace(array("\r\n","\n","\r"),array("<br>","<br>","<br>"),$content['mail_plaintext']);
672			//$this->replaceEmailAdresses($content['mail_plaintext']);
673			$content['body'] = $content['mail_plaintext'];
674			unset($content['mail_plaintext']);
675			$content['is_html'] = true;
676			$content['is_plain'] = false;
677		}
678
679		$content['body'] = ($content['body'] ? $content['body'] : $content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text']);
680		unset($_content['body']);
681		unset($_content['mail_htmltext']);
682		unset($_content['mail_plaintext']);
683		$_currentMode = $_content['mimeType'];
684
685		// we have to keep comments to be able to changing signatures
686		// signature is wraped in "<!-- HTMLSIGBEGIN -->$signature<!-- HTMLSIGEND -->"
687		Mail::$htmLawed_config['comment'] = 2;
688
689		// form was submitted either by clicking a button or by changing one of the triggering selectboxes
690		// identity and signatureid; this might trigger that the signature in mail body may have to be altered
691		if ( !empty($content['body']) &&
692			(!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount']) ||
693			(!empty($composeCache['mailidentity']) && !empty($_content['mailidentity']) && $_content['mailidentity'] != $composeCache['mailidentity'])
694		)
695		{
696			$buttonClicked = true;
697			$suppressSigOnTop = true;
698			if (!empty($composeCache['mailaccount']) && !empty($_content['mailaccount']) && $_content['mailaccount'] != $composeCache['mailaccount'])
699			{
700				$acc = Mail\Account::read($_content['mailaccount']);
701				//error_log(__METHOD__.__LINE__.array2string($acc));
702				$Identities = Mail\Account::read_identity($acc['ident_id'],true);
703				//error_log(__METHOD__.__LINE__.array2string($Identities));
704				if ($Identities['ident_id'])
705				{
706					$newSig = $Identities['ident_id'];
707				}
708				else
709				{
710					$newSig = $this->mail_bo->getDefaultIdentity();
711					if ($newSig === false) $newSig = -2;
712				}
713			}
714			$_oldSig = $composeCache['mailidentity'];
715			$_signatureid = ($newSig?$newSig:$_content['mailidentity']);
716
717			if ($_oldSig != $_signatureid)
718			{
719				if($this->_debug) error_log(__METHOD__.__LINE__.' old,new ->'.$_oldSig.','.$_signatureid.'#'.$content['body']);
720				// prepare signatures, the selected sig may be used on top of the body
721				try
722				{
723					$oldSignature = Mail\Account::read_identity($_oldSig,true);
724					//error_log(__METHOD__.__LINE__.'Old:'.array2string($oldSignature).'#');
725					$oldSigText = $oldSignature['ident_signature'];
726				}
727				catch (Exception $e)
728				{
729					$oldSignature=array();
730					$oldSigText = null;
731				}
732				try
733				{
734					$signature = Mail\Account::read_identity($_signatureid,true);
735					//error_log(__METHOD__.__LINE__.'New:'.array2string($signature).'#');
736					$sigText = $signature['ident_signature'];
737				}
738				catch (Exception $e)
739				{
740					$signature=array();
741					$sigText = null;
742				}
743				//error_log(__METHOD__.'Old:'.$oldSigText.'#');
744				//error_log(__METHOD__.'New:'.$sigText.'#');
745				if ($_currentMode == 'plain')
746				{
747					$oldSigText = $this->convertHTMLToText($oldSigText,true,true);
748					$sigText = $this->convertHTMLToText($sigText,true,true);
749					if($this->_debug) error_log(__METHOD__." Old signature:".$oldSigText);
750				}
751
752				//$oldSigText = Mail::merge($oldSigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
753				//error_log(__METHOD__.'Old+:'.$oldSigText.'#');
754				//$sigText = Mail::merge($sigText,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
755				//error_log(__METHOD__.'new+:'.$sigText.'#');
756				$_htmlConfig = Mail::$htmLawed_config;
757				Mail::$htmLawed_config['transform_anchor'] = false;
758				$oldSigTextCleaned = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"),
759					$_currentMode == 'html' ? Api\Html::purify($oldSigText, null, array(), true) : $oldSigText);
760				//error_log(__METHOD__.'Old(clean):'.$oldSigTextCleaned.'#');
761				if ($_currentMode == 'html')
762				{
763					$content['body'] = str_replace("\n",'\n',$content['body']);	// dont know why, but \n screws up preg_replace
764					$styles = Mail::getStyles(array(array('body'=>$content['body'])));
765					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
766				}
767				$content['body'] = str_replace(array("\r", "\t", "<br />\n", ": "), array("", "", "<br />", ":"),
768					$_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']);
769				Mail::$htmLawed_config = $_htmlConfig;
770				if ($_currentMode == 'html')
771				{
772					$replaced = null;
773					$content['body'] = preg_replace($reg='|'.preg_quote('<!-- HTMLSIGBEGIN -->','|').'.*'.preg_quote('<!-- HTMLSIGEND -->','|').'|u',
774						$rep='<!-- HTMLSIGBEGIN -->'.$sigText.'<!-- HTMLSIGEND -->', $in=$content['body'], -1, $replaced);
775					$content['body'] = str_replace(array('\n',"\xe2\x80\x93","\xe2\x80\x94","\xe2\x82\xac"),array("\n",'&ndash;','&mdash;','&euro;'),$content['body']);
776					//error_log(__METHOD__."() preg_replace('$reg', '$rep', '$in', -1)='".$content['body']."', replaced=$replaced");
777					unset($rep, $in);
778					if ($replaced)
779					{
780						$content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid;
781						$found = false; // this way we skip further replacement efforts
782					}
783					else
784					{
785						// try the old way
786						$found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false);
787					}
788				}
789				else
790				{
791					$found = (strlen(trim($oldSigTextCleaned))>0?strpos($content['body'],trim($oldSigTextCleaned)):false);
792				}
793
794				if ($found !== false && $_oldSig != -2 && !(empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) ==''))
795				{
796					//error_log(__METHOD__.'Old Content:'.$content['body'].'#');
797					$_oldSigText = preg_quote($oldSigTextCleaned,'~');
798					//error_log(__METHOD__.'Old(masked):'.$_oldSigText.'#');
799					$content['body'] = preg_replace('~'.$_oldSigText.'~mi',$sigText,$content['body'],1);
800					//error_log(__METHOD__.'new Content:'.$content['body'].'#');
801				}
802
803				if ($_oldSig == -2 && (empty($oldSigTextCleaned) || trim($this->convertHTMLToText($oldSigTextCleaned,true,true)) ==''))
804				{
805					// if there is no sig selected, there is no way to replace a signature
806				}
807
808				if ($found === false)
809				{
810					if($this->_debug) error_log(__METHOD__." Old Signature failed to match:".$oldSigTextCleaned);
811					if($this->_debug) error_log(__METHOD__." Compare content:".$content['body']);
812				}
813				else
814				{
815					$content['mailidentity'] = $_content['mailidentity'] = $presetSig = $_signatureid;
816				}
817				if ($styles)
818				{
819					//error_log($styles);
820					$content['body'] = $styles.$content['body'];
821				}
822			}
823		}
824		/*run the purify on compose body unconditional*/
825		$content['body'] = str_replace(array("\r", "\t", "<br />\n"), array("", "", "<br />"),
826		$_currentMode == 'html' ? Api\Html::purify($content['body'], Mail::$htmLawed_config, array(), true) : $content['body']);
827
828		// do not double insert a signature on a server roundtrip
829		if ($buttonClicked) $suppressSigOnTop = true;
830
831		// On submit reads external_vcard widget's value and addes them as attachments.
832		// this happens when we send vcards from addressbook to an opened compose
833		// dialog.
834		if ($appendix_data['files'])
835		{
836			$_REQUEST['preset']['file'] = $appendix_data['files']['file'];
837			$_REQUEST['preset']['type'] = $appendix_data['files']['type'];
838			$_content['filemode'] = !empty($appendix_data['files']['filemode']) &&
839						isset(Vfs\Sharing::$modes[$appendix_data['files']['filemode']]) ?
840							$appendix_data['files']['filemode'] : Vfs\Sharing::ATTACH;
841			$suppressSigOnTop = true;
842			unset($_content['attachments']);
843			$this->addPresetFiles($content, $insertSigOnTop, true);
844		}
845
846		if ($isFirstLoad)
847		{
848			$alwaysAttachVCardAtCompose = false; // we use this to eliminate double attachments, if users VCard is already present/attached
849			if ( isset($GLOBALS['egw_info']['apps']['stylite']) && (isset($this->mailPreferences['attachVCardAtCompose']) &&
850				$this->mailPreferences['attachVCardAtCompose']))
851			{
852				$alwaysAttachVCardAtCompose = true;
853				if (!is_array($_REQUEST['preset']['file']) && !empty($_REQUEST['preset']['file']))
854				{
855					$f = $_REQUEST['preset']['file'];
856					$_REQUEST['preset']['file'] = array($f);
857				}
858				$_REQUEST['preset']['file'][] = "vfs://default/apps/addressbook/".$GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')."/.entry";
859			}
860			// an app passed the request for fetching and mailing an entry
861			if (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id']))
862			{
863				$app = $_REQUEST['app'];
864				$mt = $_REQUEST['method'];
865				$id = $_REQUEST['id'];
866				// passed method MUST be registered
867				$method = Link::get_registry($app,$mt);
868				//error_log(__METHOD__.__LINE__.array2string($method));
869				if ($method)
870				{
871					$res = ExecMethod($method,array($id,'html'));
872					//error_log(__METHOD__.__LINE__.array2string($res));
873					if (!empty($res))
874					{
875						$insertSigOnTop = 'below';
876						if (isset($res['attachments']) && is_array($res['attachments']))
877						{
878							foreach($res['attachments'] as $f)
879							{
880								$_REQUEST['preset']['file'][] = $f;
881							}
882						}
883						$content['subject'] = lang($app).' #'.$res['id'].': ';
884						foreach(array('subject','body','mimetype') as $name) {
885							$sName = $name;
886							if ($name=='mimetype'&&$res[$name])
887							{
888								$sName = 'mimeType';
889								$content[$sName] = $res[$name];
890							}
891							else
892							{
893								if ($res[$name]) $content[$sName] .= (strlen($content[$sName])>0 ? ' ':'') .$res[$name];
894							}
895						}
896					}
897				}
898			}
899			// handle preset info/values
900			if (is_array($_REQUEST['preset']))
901			{
902				$alreadyProcessed=array();
903				//_debug_array($_REQUEST);
904				if ($_REQUEST['preset']['mailto']) {
905					// handle mailto strings such as
906					// mailto:larry,dan?cc=mike&bcc=sue&subject=test&body=type+your&body=message+here
907					// the above string may be htmlentyty encoded, then multiple body tags are supported
908					// first, strip the mailto: string out of the mailto URL
909					$tmp_send_to = (stripos($_REQUEST['preset']['mailto'],'mailto')===false?$_REQUEST['preset']['mailto']:trim(substr(html_entity_decode($_REQUEST['preset']['mailto']),7)));
910					// check if there is more than the to address
911					$mailtoArray = explode('?',$tmp_send_to,2);
912					if ($mailtoArray[1]) {
913						// check if there are more than one requests
914						$addRequests = explode('&',$mailtoArray[1]);
915						foreach ($addRequests as $key => $reqval) {
916							// the additional requests should have a =, to separate key from value.
917							$reqval = preg_replace('/__AMPERSAND__/i', "&", $reqval);
918							$keyValuePair = explode('=',$reqval,2);
919							$content[$keyValuePair[0]] .= (strlen($content[$keyValuePair[0]])>0 ? ' ':'') . $keyValuePair[1];
920						}
921					}
922					$content['to']= preg_replace('/__AMPERSAND__/i', "&", $mailtoArray[0]);
923					$alreadyProcessed['to']='to';
924					// if the mailto string is not htmlentity decoded the arguments are passed as simple requests
925					foreach(array('cc','bcc','subject','body') as $name) {
926						$alreadyProcessed[$name]=$name;
927						if ($_REQUEST[$name]) $content[$name] .= (strlen($content[$name])>0 ? ( $name == 'cc' || $name == 'bcc' ? ',' : ' ') : '') . $_REQUEST[$name];
928					}
929				}
930
931				if ($_REQUEST['preset']['mailtocontactbyid']) {
932					if ($GLOBALS['egw_info']['user']['apps']['addressbook']) {
933						$contacts_obj = new Api\Contacts();
934						$addressbookprefs =& $GLOBALS['egw_info']['user']['preferences']['addressbook'];
935						if (method_exists($contacts_obj,'search')) {
936
937							$addressArray = explode(',',$_REQUEST['preset']['mailtocontactbyid']);
938							foreach ((array)$addressArray as $id => $addressID)
939							{
940								$addressID = (int) $addressID;
941								if (!($addressID>0))
942								{
943									unset($addressArray[$id]);
944								}
945							}
946							if (count($addressArray))
947							{
948								$_searchCond = array('contact_id'=>$addressArray);
949								//error_log(__METHOD__.__LINE__.$_searchString);
950								$showAccounts= $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1';
951								$filter = ($showAccounts?array():array('account_id' => null));
952								$filter['cols_to_search']=array('n_fn','email','email_home');
953								$contacts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),$filter);
954								// additionally search the accounts, if the contact storage is not the account storage
955								if ($showAccounts &&
956									$GLOBALS['egw_info']['server']['account_repository'] == 'ldap' &&
957									$GLOBALS['egw_info']['server']['contact_repository'] == 'sql')
958								{
959									$accounts = $contacts_obj->search($_searchCond,array('n_fn','email','email_home'),'n_fn','','%',false,'OR',array(0,100),array('owner' => 0));
960
961									if ($contacts && $accounts)
962									{
963										$contacts = array_merge($contacts,$accounts);
964										usort($contacts, function($a, $b)
965										{
966											return strcasecmp($a['n_fn'], $b['n_fn']);
967										});
968									}
969									elseif($accounts)
970									{
971										$contacts =& $accounts;
972									}
973									unset($accounts);
974								}
975							}
976							if(is_array($contacts)) {
977								$mailtoArray = array();
978								$primary = $addressbookprefs['distributionListPreferredMail'];
979								if ($primary != 'email' && $primary != 'email_home') $primary = 'email';
980								$secondary = ($primary == 'email'?'email_home':'email');
981								//error_log(__METHOD__.__LINE__.array2string($contacts));
982								foreach($contacts as $contact) {
983									$innerCounter=0;
984									foreach(array($contact[$primary],$contact[$secondary]) as $email) {
985										// use pref distributionListPreferredMail for the primary address
986										// avoid wrong addresses, if an rfc822 encoded address is in addressbook
987										$email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email);
988										$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
989										$completeMailString = addslashes(trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']) .' <'. trim($email) .'>');
990										if($innerCounter==0 && !empty($email) && in_array($completeMailString ,$mailtoArray) === false) {
991											$i++;
992											$innerCounter++;
993											$mailtoArray[$i] = $completeMailString;
994										}
995									}
996								}
997							}
998							//error_log(__METHOD__.__LINE__.array2string($mailtoArray));
999							$alreadyProcessed['to']='to';
1000							$content['to']=$mailtoArray;
1001						}
1002					}
1003				}
1004
1005				if (isset($_REQUEST['preset']['file']))
1006				{
1007					$content['filemode'] = !empty($_REQUEST['preset']['filemode']) &&
1008							(isset(Vfs\Sharing::$modes[$_REQUEST['preset']['filemode']]) || isset(Vfs\HiddenUploadSharing::$modes[$_REQUEST['preset']['filemode']])) ?
1009							$_REQUEST['preset']['filemode'] : Vfs\Sharing::ATTACH;
1010
1011					$this->addPresetFiles($content, $insertSigOnTop, $alwaysAttachVCardAtCompose);
1012					$remember = array();
1013					if (isset($_REQUEST['preset']['mailto']) || (isset($_REQUEST['app']) && isset($_REQUEST['method']) && isset($_REQUEST['id'])))
1014					{
1015						foreach(array_keys($content) as $k)
1016						{
1017							if (in_array($k,array('to','cc','bcc','subject','body','mimeType'))&&isset($this->sessionData[$k]))
1018							{
1019								$alreadyProcessed[$k]=$k;
1020								$remember[$k] = $this->sessionData[$k];
1021							}
1022						}
1023					}
1024					if(!empty($remember)) $content = array_merge($content,$remember);
1025				}
1026				foreach(array('to','cc','bcc','subject','body','mimeType') as $name)
1027				{
1028					//always handle mimeType
1029					if ($name=='mimeType' && $_REQUEST['preset'][$name])
1030					{
1031						$_content[$name]=$content[$name]=$_REQUEST['preset'][$name];
1032					}
1033					//skip if already processed by "preset Routines"
1034					if ($alreadyProcessed[$name]) continue;
1035					//error_log(__METHOD__.__LINE__.':'.$name.'->'. $_REQUEST['preset'][$name]);
1036					if ($_REQUEST['preset'][$name]) $content[$name] = $_REQUEST['preset'][$name];
1037				}
1038			}
1039			// is the to address set already?
1040			if (!empty($_REQUEST['send_to']))
1041			{
1042				$content['to'] = base64_decode($_REQUEST['send_to']);
1043				// first check if there is a questionmark or ampersand
1044				if (strpos($content['to'],'?')!== false) list($content['to'],$rest) = explode('?',$content['to'],2);
1045				$content['to'] = html_entity_decode($content['to']);
1046				if (($at_pos = strpos($content['to'],'@')) !== false)
1047				{
1048					if (($amp_pos = strpos(substr($content['to'],$at_pos),'&')) !== false)
1049					{
1050						//list($email,$addoptions) = explode('&',$value,2);
1051						$email = substr($content['to'],0,$amp_pos+$at_pos);
1052						$rest = substr($content['to'], $amp_pos+$at_pos+1);
1053						//error_log(__METHOD__.__LINE__.$email.' '.$rest);
1054						$content['to'] = $email;
1055					}
1056				}
1057				if (strpos($content['to'],'%40')!== false) $content['to'] = Api\Html::purify(str_replace('%40','@',$content['to']));
1058				$rarr = array(Api\Html::purify($rest));
1059				if (isset($rest)&&!empty($rest) && strpos($rest,'&')!== false) $rarr = explode('&',$rest);
1060				//error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($rarr));
1061				$karr = array();
1062				foreach ($rarr as &$rval)
1063				{
1064					//must contain =
1065					if (strpos($rval,'=')!== false)
1066					{
1067						list($k,$v) = explode('=',$rval,2);
1068						$karr[$k] = (string)$v;
1069						unset($k,$v);
1070					}
1071				}
1072				//error_log(__METHOD__.__LINE__.$content['to'].'->'.array2string($karr));
1073				foreach(array('cc','bcc','subject','body') as $name)
1074				{
1075					if ($karr[$name]) $content[$name] = $karr[$name];
1076				}
1077				if (!empty($_REQUEST['subject'])) $content['subject'] = Api\Html::purify(trim(html_entity_decode($_REQUEST['subject'])));
1078			}
1079		}
1080		//error_log(__METHOD__.__LINE__.array2string($content));
1081		//is the MimeType set/requested
1082		if ($isFirstLoad && !empty($_REQUEST['mimeType']))
1083		{
1084			$_content['mimeType'] = $content['mimeType'];
1085			if (($_REQUEST['mimeType']=="text" ||$_REQUEST['mimeType']=="plain") && $content['mimeType'] == 'html')
1086			{
1087				$_content['mimeType'] = $content['mimeType']  = 'plain';
1088				$content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body']));
1089			}
1090			if ($_REQUEST['mimeType']=="html" && $content['mimeType'] != 'html')
1091			{
1092				$_content['mimeType'] = $content['mimeType']  = 'html';
1093				$content['body'] = "<pre>".$content['body']."</pre>";
1094				// take care this assumption is made on the creation of the reply header in bocompose::getReplyData
1095				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);
1096			}
1097		}
1098		else
1099		{
1100			// try to enforce a mimeType on reply ( if type is not of the wanted type )
1101			if ($isReply)
1102			{
1103				if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="text" &&
1104					$content['mimeType'] == 'html')
1105				{
1106					$_content['mimeType'] = $content['mimeType']  = 'plain';
1107					$content['body'] = $this->convertHTMLToText(str_replace(array("\n\r","\n"),' ',$content['body']));
1108				}
1109				if (!empty($this->mailPreferences['replyOptions']) && $this->mailPreferences['replyOptions']=="html" &&
1110					$content['mimeType'] != 'html')
1111				{
1112					$_content['mimeType'] = $content['mimeType']  = 'html';
1113					$content['body'] = "<pre>".$content['body']."</pre>";
1114					// take care this assumption is made on the creation of the reply header in bocompose::getReplyData
1115					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);
1116				}
1117			}
1118		}
1119
1120		if ($content['mimeType'] == 'html' && Api\Html::htmlarea_availible()===false)
1121		{
1122			$_content['mimeType'] = $content['mimeType'] = 'plain';
1123			$content['body'] = $this->convertHTMLToText($content['body']);
1124		}
1125		// is a certain signature requested?
1126		// only the following values are supported (and make sense)
1127		// no => means -2
1128		// system => means -1
1129		// default => fetches the default, which is standard behavior
1130		if (!empty($_REQUEST['signature']) && (strtolower($_REQUEST['signature']) == 'no' || strtolower($_REQUEST['signature']) == 'system'))
1131		{
1132			$content['mailidentity'] = $presetSig = (strtolower($_REQUEST['signature']) == 'no' ? -2 : -1);
1133		}
1134
1135		$disableRuler = false;
1136		//_debug_array(($presetSig ? $presetSig : $content['mailidentity']));
1137		try
1138		{
1139			$signature = Mail\Account::read_identity($content['mailidentity'] ? $content['mailidentity'] : $presetSig,true);
1140		}
1141		catch (Exception $e)
1142		{
1143			//PROBABLY NOT FOUND
1144			$signature=array();
1145		}
1146		if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) &&
1147			$this->mailPreferences['disableRulerForSignatureSeparation']) ||
1148			empty($signature['ident_signature']) ||
1149			trim($this->convertHTMLToText($signature['ident_signature'],true,true)) =='' ||
1150			$this->mailPreferences['insertSignatureAtTopOfMessage'] == '1')
1151		{
1152			$disableRuler = true;
1153		}
1154		$font_span = $font_part = '';
1155		if($content['mimeType'] == 'html') {
1156			// User preferences for style
1157			$font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font'];
1158			$font_size = Etemplate\Widget\HtmlArea::font_size_from_prefs();
1159			$font_part = '<span style="width:100%; display: inline; '.($font?'font-family:'.$font.'; ':'').($font_size?'font-size:'.$font_size.'; ':'').'">';
1160			$font_span = $font_part.'&#8203;</span>';
1161			if (empty($font) && empty($font_size)) $font_span = '';
1162		}
1163		// 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
1164		if (!$isFirstLoad && !empty($font_span) && stripos($content['body'],$font_part)===false) $font_span = '';
1165		//remove possible html header stuff
1166		if (stripos($content['body'],'<html><head></head><body>')!==false) $content['body'] = str_ireplace(array('<html><head></head><body>','</body></html>'),array('',''),$content['body']);
1167		//error_log(__METHOD__.__LINE__.array2string($this->mailPreferences));
1168		$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');
1169		if ($this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend' &&
1170			!(isset($_POST['mySigID']) && !empty($_POST['mySigID']) ) && !$suppressSigOnTop
1171		)
1172		{
1173			// ON tOP OR BELOW? pREF CAN TELL
1174			/*
1175				Signature behavior preference changed. New default, if not set -> 0
1176						'0' => 'after reply, visible during compose',
1177						'1' => 'before reply, visible during compose',
1178						'no_belowaftersend'  => 'appended after reply before sending',
1179			*/
1180			$insertSigOnTop = ($insertSigOnTop?$insertSigOnTop:($this->mailPreferences['insertSignatureAtTopOfMessage']?$this->mailPreferences['insertSignatureAtTopOfMessage']:'below'));
1181			$sigText = Mail::merge($signature['ident_signature'],array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
1182			if ($content['mimeType'] == 'html')
1183			{
1184				$sigTextStartsWithBlockElement = ($disableRuler?false:true);
1185				foreach($blockElements as $e)
1186				{
1187					if ($sigTextStartsWithBlockElement) break;
1188					if (stripos(trim($sigText),'<'.$e)===0) $sigTextStartsWithBlockElement = true;
1189				}
1190			}
1191			if($content['mimeType'] == 'html') {
1192				$before = $disableRuler ? '' : '<hr style="border:1px dotted silver; width:100%;">';
1193				$inbetween = '';
1194			} else {
1195				$before = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
1196				$inbetween = "\r\n";
1197			}
1198			if ($content['mimeType'] == 'html')
1199			{
1200				$sigText = ($sigTextStartsWithBlockElement?'':"<div>")."<!-- HTMLSIGBEGIN -->".$sigText."<!-- HTMLSIGEND -->".($sigTextStartsWithBlockElement?'':"</div>");
1201			}
1202
1203			if ($insertSigOnTop === 'below')
1204			{
1205				$content['body'] = $font_span.$content['body'].$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true));
1206			}
1207			else
1208			{
1209				$content['body'] = $font_span.$before.($content['mimeType'] == 'html'?$sigText:$this->convertHTMLToText($sigText,true,true)).$inbetween.$content['body'];
1210			}
1211		}
1212		// Skip this part if we're merging, it would add an extra line at the top
1213		else if (!$content['body'])
1214		{
1215			$content['body'] = ($font_span?($isFirstLoad === "switchedplaintohtml"?$font_part:$font_span):'').($isFirstLoad === "switchedplaintohtml"?"</span>":"");
1216		}
1217		//error_log(__METHOD__.__LINE__.$content['body']);
1218
1219		// prepare body
1220		// 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
1221		$content['body'] = Api\Translation::convert_jsonsafe($content['body'],'utf-8');
1222		//error_log(__METHOD__.__LINE__.array2string($content));
1223
1224		// get identities of all accounts as "$acc_id:$ident_id" => $identity
1225		$sel_options['mailaccount'] = $identities = array();
1226		foreach(Mail\Account::search(true,false) as $acc_id => $account)
1227		{
1228			// do NOT add SMTP only accounts as identities
1229			if (!$account->is_imap(false)) continue;
1230
1231			foreach($account->identities($acc_id) as $ident_id => $identity)
1232			{
1233				$sel_options['mailaccount'][$acc_id.':'.$ident_id] = $identity;
1234				$identities[$ident_id] = $identity;
1235			}
1236			unset($account);
1237		}
1238
1239		//$content['bcc'] = array('kl@egroupware.org','kl@leithoff.net');
1240		// address stuff like from, to, cc, replyto
1241		$destinationRows = 0;
1242		foreach(self::$destinations as $destination) {
1243			if (!is_array($content[$destination]))
1244			{
1245				if (!empty($content[$destination])) $content[$destination] = (array)$content[$destination];
1246			}
1247			$addr_content = $content[strtolower($destination)];
1248			// we clear the given address array and rebuild it
1249			unset($content[strtolower($destination)]);
1250			foreach((array)$addr_content as $key => $value) {
1251				if ($value=="NIL@NIL") continue;
1252				if ($destination=='replyto' && str_replace('"','',$value) ==
1253					str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()]))
1254				{
1255					// preserve/restore the value to content.
1256					$content[strtolower($destination)][]=$value;
1257					continue;
1258				}
1259				//error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value)));
1260				$value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT));
1261				foreach(Mail::parseAddressList($value) as $addressObject) {
1262					if ($addressObject->host == '.SYNTAX-ERROR.') continue;
1263					$address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal);
1264					//$address = Mail::htmlentities($address, $this->displayCharset);
1265					$content[strtolower($destination)][]=$address;
1266					$destinationRows++;
1267				}
1268			}
1269		}
1270		if ($_content)
1271		{
1272			//input array of _content had no signature information but was seeded later, and content has a valid setting
1273			if (!$_contentHasSigID && $content['mailidentity'] && array_key_exists('mailidentity',$_content)) unset($_content['mailidentity']);
1274			$content = array_merge($content,$_content);
1275
1276			if (!empty($content['folder'])) $sel_options['folder']=$this->ajax_searchFolder(0,true);
1277			if (empty($content['mailaccount'])) $content['mailaccount'] = $this->mail_bo->profileID;
1278		}
1279		else
1280		{
1281			//error_log(__METHOD__.__LINE__.array2string(array($sel_options['mailaccount'],$selectedSender)));
1282			$content['mailaccount'] = $this->mail_bo->profileID;
1283			//error_log(__METHOD__.__LINE__.$content['body']);
1284		}
1285		$content['is_html'] = ($content['mimeType'] == 'html'?true:'');
1286		$content['is_plain'] = ($content['mimeType'] == 'html'?'':true);
1287		$content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body'];
1288		$content['showtempname']=0;
1289		//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.'before merging content with uploadforCompose:'.array2string($content['attachments']));
1290		$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)));
1291		//if (is_array($content['attachments'])) foreach($content['attachments'] as $k => &$file) $file['delete['.$file['tmp_name'].']']=0;
1292		$content['no_griddata'] = empty($content['attachments']);
1293		$preserv['attachments'] = $content['attachments'];
1294		$content['expiration_blur'] = $GLOBALS['egw_info']['user']['apps']['stylite'] ? lang('Select a date') : lang('EPL only');
1295
1296		//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.' Attachments:'.array2string($content['attachments']));
1297		// if no filemanager -> no vfsFileSelector
1298		if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
1299		{
1300			$content['vfsNotAvailable'] = "mail_DisplayNone";
1301		}
1302		// if no infolog -> no save as infolog
1303		if (!$GLOBALS['egw_info']['user']['apps']['infolog'])
1304		{
1305			$content['noInfologAvailable'] = "mail_DisplayNone";
1306		}
1307		// if no tracker -> no save as tracker
1308		if (!$GLOBALS['egw_info']['user']['apps']['tracker'])
1309		{
1310			$content['noTrackerAvailable'] = "mail_DisplayNone";
1311		}
1312		if (!$GLOBALS['egw_info']['user']['apps']['infolog'] && !$GLOBALS['egw_info']['user']['apps']['tracker'])
1313		{
1314			$content['noSaveAsAvailable'] = "mail_DisplayNone";
1315		}
1316		// composeID to detect if we have changes to certain content
1317		$preserv['composeID'] = $content['composeID'] = $this->composeID;
1318		//error_log(__METHOD__.__LINE__.' ComposeID:'.$preserv['composeID']);
1319		$preserv['is_html'] = $content['is_html'];
1320		$preserv['is_plain'] = $content['is_plain'];
1321		if (isset($content['mimeType'])) $preserv['mimeType'] = $content['mimeType'];
1322		$sel_options['mimeType'] = self::$mimeTypes;
1323		$sel_options['priority'] = self::$priorities;
1324		$sel_options['filemode'] = Vfs\Sharing::$modes;
1325		if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3;
1326		//$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed
1327		$etpl = new Etemplate('mail.compose');
1328
1329		$etpl->setElementAttribute('composeToolbar', 'actions', self::getToolbarActions($content));
1330		if ($content['mimeType']=='html')
1331		{
1332			//mode="$cont[rtfEditorFeatures]" validation_rules="$cont[validation_rules]" base_href="$cont[upload_dir]"
1333			$_htmlConfig = Mail::$htmLawed_config;
1334			Mail::$htmLawed_config['comment'] = 2;
1335			Mail::$htmLawed_config['transform_anchor'] = false;
1336			$content['validation_rules']= json_encode(Mail::$htmLawed_config);
1337			$etpl->setElementAttribute('mail_htmltext','validation_rules',$content['validation_rules']);
1338			Mail::$htmLawed_config = $_htmlConfig;
1339		}
1340
1341		if (isset($content['composeID'])&&!empty($content['composeID']))
1342		{
1343			$composeCache = $content;
1344			unset($composeCache['body']);
1345			unset($composeCache['mail_htmltext']);
1346			unset($composeCache['mail_plaintext']);
1347			Api\Cache::setCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$this->composeID,$composeCache,$expiration=60*60*2);
1348		}
1349		if (!isset($_content['serverID'])||empty($_content['serverID']))
1350		{
1351			$content['serverID'] = $this->mail_bo->profileID;
1352		}
1353		$preserv['serverID'] = $content['serverID'];
1354		$preserv['lastDrafted'] = $content['lastDrafted'];
1355		$preserv['processedmail_id'] = $content['processedmail_id'];
1356		$preserv['references'] = $content['references'];
1357		$preserv['in-reply-to'] = $content['in-reply-to'];
1358		// thread-topic is a proprietary microsoft header and deprecated with the current version
1359		// horde does not support the encoding of thread-topic, and probably will not no so in the future
1360		//$preserv['thread-topic'] = $content['thread-topic'];
1361		$preserv['thread-index'] = $content['thread-index'];
1362		$preserv['list-id'] = $content['list-id'];
1363		$preserv['mode'] = $content['mode'];
1364		// convert it back to checkbox expectations
1365		if($content['mimeType'] == 'html') {
1366			$content['mimeType']=1;
1367		} else {
1368			$content['mimeType']=0;
1369		}
1370		// set the current selected mailaccount as param for folderselection
1371		$etpl->setElementAttribute('folder','autocomplete_params',array('mailaccount'=>$content['mailaccount']));
1372		// join again mailaccount and identity
1373		$content['mailaccount'] .= ':'.$content['mailidentity'];
1374		//Try to set the initial selected account to the first identity match found
1375		// which fixes the issue of prefered identity never get selected.
1376		if (!in_array($content['mailaccount'], array_keys($sel_options['mailaccount'])))
1377		{
1378			foreach ($sel_options['mailaccount'] as $ident => $value)
1379			{
1380				$idnt_acc_parts = explode(':', $ident);
1381
1382				if ($content['mailidentity'] == $idnt_acc_parts[1])
1383				{
1384					$content['mailaccount'] = $ident;
1385					break;
1386				}
1387			}
1388		}
1389		// Resolve distribution list before send content to client
1390		foreach(array('to', 'cc', 'bcc', 'replyto')  as $f)
1391		{
1392			if (is_array($content[$f])) $content[$f]= self::resolveEmailAddressList ($content[$f]);
1393		}
1394
1395		// set filemode icons for all attachments
1396		if($content['attachments'] && is_array($content['attachments']))
1397		{
1398			foreach($content['attachments'] as &$attach)
1399			{
1400				$attach['is_dir'] = is_dir($attach['file']);
1401				$attach['filemode_icon'] = !is_dir($attach['file']) &&
1402						($content['filemode'] == Vfs\Sharing::READONLY || $content['filemode'] == Vfs\Sharing::WRITABLE)
1403						? Vfs\Sharing::LINK : $content['filemode'];
1404				$attach['filemode_title'] = lang(Vfs\Sharing::$modes[$attach['filemode_icon']]['label']);
1405			}
1406		}
1407
1408		$content['to'] = self::resolveEmailAddressList($content['to']);
1409		$content['html_toolbar'] = empty(Mail::$mailConfig['html_toolbar']) ?
1410			join(',', Etemplate\Widget\HtmlArea::$toolbar_default_list) : join(',', Mail::$mailConfig['html_toolbar']);
1411		//error_log(__METHOD__.__LINE__.array2string($content));
1412		$etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2);
1413	}
1414
1415	/**
1416	 * Add preset files like vcard as attachments into content array
1417	 *
1418	 * Preset attachments are read from $_REQUEST['preset']['file'] with
1419	 * optional ['type'] and ['name'].
1420	 *
1421	 * Attachments must either be in EGroupware Vfs or configured temp. directory!
1422	 *
1423	 * @param array $_content content
1424	 * @param string $_insertSigOnTop
1425	 * @param boolean $_eliminateDoubleAttachments
1426	 */
1427	function addPresetFiles (&$_content, &$_insertSigOnTop, $_eliminateDoubleAttachments)
1428	{
1429		// check if JSON was used
1430		if (!is_array($_REQUEST['preset']['file']) &&
1431			($_REQUEST['preset']['file'][0] === '[' && substr($_REQUEST['preset']['file'], -1) === ']' ||
1432			$_REQUEST['preset']['file'][0] === '{' && substr($_REQUEST['preset']['file'], -1) === '}') &&
1433			($files = json_decode($_REQUEST['preset']['file'], true)))
1434		{
1435			$types = !empty($_REQUEST['preset']['type']) ?
1436				json_decode($_REQUEST['preset']['type'], true) : array();
1437			$names = !empty($_REQUEST['preset']['name']) ?
1438				json_decode($_REQUEST['preset']['name'], true) : array();
1439		}
1440		else
1441		{
1442			$files = (array)$_REQUEST['preset']['file'];
1443			$types = !empty($_REQUEST['preset']['type']) ?
1444				(array)$_REQUEST['preset']['type'] : array();
1445			$names = !empty($_REQUEST['preset']['name']) ?
1446				(array)$_REQUEST['preset']['name'] : array();
1447		}
1448
1449		foreach($files as $k => $path)
1450		{
1451			if (!empty($types[$k]) && stripos($types[$k],'text/calendar')!==false)
1452			{
1453				$_insertSigOnTop = 'below';
1454			}
1455			//error_log(__METHOD__.__LINE__.$path.'->'.array2string(parse_url($path,PHP_URL_SCHEME == 'vfs')));
1456			if (($scheme = parse_url($path,PHP_URL_SCHEME)) === 'vfs')
1457			{
1458				$type = Vfs::mime_content_type($path);
1459				// special handling for attaching vCard of iCal --> use their link-title as name
1460				if (substr($path,-7) != '/.entry' ||
1461					!(list($app,$id) = array_slice(explode('/',$path),-3)) ||
1462					!($name = Link::title($app, $id)))
1463				{
1464					$name = Vfs::decodePath(Vfs::basename($path));
1465				}
1466				else
1467				{
1468					$name .= '.'.Api\MimeMagic::mime2ext($type);
1469				}
1470				// use type specified by caller, if Vfs reports only default, or contains specified type (eg. "text/vcard; charset=utf-8")
1471				if (!empty($types[$k]) && ($type == 'application/octet-stream' || stripos($types[$k], $type) === 0))
1472				{
1473					$type = $types[$k];
1474				}
1475				$path = str_replace('+','%2B',$path);
1476				$formData = array(
1477					'name' => $name,
1478					'type' => $type,
1479					'file' => Vfs::decodePath($path),
1480					'size' => filesize(Vfs::decodePath($path)),
1481				);
1482				if ($formData['type'] == Vfs::DIR_MIME_TYPE && $_content['filemode'] == Vfs\Sharing::ATTACH)
1483				{
1484					$_content['filemode'] = Vfs\Sharing::READONLY;
1485					Framework::message(lang('Directories have to be shared.'), 'info');
1486				}
1487			}
1488			// do not allow to attache something from server filesystem outside configured temp_dir
1489			elseif (strpos(realpath(parse_url($path, PHP_URL_PATH)), realpath($GLOBALS['egw_info']['server']['temp_dir']).'/') !== 0)
1490			{
1491				error_log(__METHOD__."() Attaching '$path' outside configured temp. directory '{$GLOBALS['egw_info']['server']['temp_dir']}' denied!");
1492			}
1493			elseif(is_readable($path))
1494			{
1495				$formData = array(
1496					'name' => isset($names[$k]) ? $names[$k] : basename($path),
1497					'type' => isset($types[$k]) ? $types[$k] : (function_exists('mime_content_type') ? mime_content_type($path) : Api\MimeMagic::filename2mime($path)),
1498					'file' => $path,
1499					'size' => filesize($path),
1500				);
1501			}
1502			else
1503			{
1504				continue;
1505			}
1506			$this->addAttachment($formData,$_content, $_eliminateDoubleAttachments);
1507		}
1508	}
1509
1510	/**
1511	 * Get pre-fill a new compose based on an existing email
1512	 *
1513	 * @param type $mail_id If composing based on an existing mail, this is the ID of the mail
1514	 * @param type $part_id For multi-part mails, indicates which part
1515	 * @param type $from Indicates what the mail is based on, and how to extract data.
1516	 *	One of 'compose', 'composeasnew', 'reply', 'reply_all', 'forward' or 'merge'
1517	 * @param boolean $_focusElement varchar subject, to, body supported
1518	 * @param boolean $suppressSigOnTop
1519	 * @param boolean $isReply
1520	 *
1521	 * @return mixed[] Content array pre-filled according to source mail
1522	 */
1523	private function getComposeFrom($mail_id, $part_id, $from, &$_focusElement, &$suppressSigOnTop, &$isReply)
1524	{
1525		$content = array();
1526		//error_log(__METHOD__.__LINE__.array2string($mail_id).", $part_id, $from, $_focusElement, $suppressSigOnTop, $isReply");
1527		// on forward we may have to support multiple ids
1528		if ($from=='forward')
1529		{
1530			$replyIds = explode(',',$mail_id);
1531			$mail_id = $replyIds[0];
1532		}
1533		$hA = mail_ui::splitRowID($mail_id);
1534		$msgUID = $hA['msgUID'];
1535		$folder = $hA['folder'];
1536		$icServerID = $hA['profileID'];
1537		if ($icServerID != $this->mail_bo->profileID)
1538		{
1539			$this->changeProfile($icServerID);
1540		}
1541		$icServer = $this->mail_bo->icServer;
1542		if (!empty($folder) && !empty($msgUID) )
1543		{
1544			// this fill the session data with the values from the original email
1545			switch($from)
1546			{
1547				case 'composefromdraft':
1548				case 'composeasnew':
1549					$content = $this->getDraftData($icServer, $folder, $msgUID, $part_id);
1550					if ($from =='composefromdraft') $content['mode'] = 'composefromdraft';
1551					$content['processedmail_id'] = $mail_id;
1552
1553					$_focusElement = 'body';
1554					$suppressSigOnTop = true;
1555					break;
1556				case 'reply':
1557				case 'reply_all':
1558					$content = $this->getReplyData($from == 'reply' ? 'single' : 'all', $icServer, $folder, $msgUID, $part_id);
1559					$content['processedmail_id'] = $mail_id;
1560					$content['mode'] = 'reply';
1561					$_focusElement = 'body';
1562					$suppressSigOnTop = false;
1563					$isReply = true;
1564					break;
1565				case 'forward':
1566					$mode  = ($_GET['mode']=='forwardinline'?'inline':'asmail');
1567					// this fill the session data with the values from the original email
1568					foreach ($replyIds as &$mail_id)
1569					{
1570						//error_log(__METHOD__.__LINE__.' ID:'.$mail_id.' Mode:'.$mode);
1571						$hA = mail_ui::splitRowID($mail_id);
1572						$msgUID = $hA['msgUID'];
1573						$folder = $hA['folder'];
1574						$content = $this->getForwardData($icServer, $folder, $msgUID, $part_id, $mode);
1575					}
1576					$content['processedmail_id'] = implode(',',$replyIds);
1577					$content['mode'] = 'forward';
1578					$isReply = ($mode?$mode=='inline':$this->mailPreferences['message_forwarding'] == 'inline');
1579					$suppressSigOnTop = false;// ($mode && $mode=='inline'?true:false);// may be a better solution
1580					$_focusElement = 'to';
1581					break;
1582				default:
1583					error_log('Unhandled compose source: ' . $from);
1584			}
1585		}
1586		else if ($from == 'merge' && $_REQUEST['document'])
1587		{
1588			/*
1589			 * Special merge from everywhere else because all other apps merge gives
1590			 * a document to be downloaded, this opens a compose dialog.
1591			 * Use ajax_merge to merge & send multiple
1592			 */
1593			// Merge selected ID (in mailtocontactbyid or $mail_id) into given document
1594			$merge_class = preg_match('/^([a-z_-]+_merge)$/', $_REQUEST['merge']) ? $_REQUEST['merge'] : 'EGroupware\\Api\\Contacts\\Merge';
1595			$document_merge = new $merge_class();
1596			$this->mail_bo->openConnection();
1597			$merge_ids = $_REQUEST['preset']['mailtocontactbyid'] ? $_REQUEST['preset']['mailtocontactbyid'] : $mail_id;
1598			if (!is_array($merge_ids)) $merge_ids = explode(',',$merge_ids);
1599			try
1600			{
1601				$merged_mail_id = '';
1602				$folder = $this->mail_bo->getDraftFolder();
1603				if(($error = $document_merge->check_document($_REQUEST['document'],'')))
1604				{
1605					$content['msg'] = $error;
1606					return $content;
1607				}
1608
1609				// Merge does not work correctly (missing to) if current app is not addressbook
1610				//$GLOBALS['egw_info']['flags']['currentapp'] = 'addressbook';
1611
1612				// Actually do the merge
1613				if(count($merge_ids) <= 1)
1614				{
1615					$results = $this->mail_bo->importMessageToMergeAndSend(
1616						$document_merge, Vfs::PREFIX . $_REQUEST['document'], $merge_ids, $folder, $merged_mail_id
1617					);
1618
1619					// Open compose
1620					$merged_mail_id = trim($GLOBALS['egw_info']['user']['account_id']).mail_ui::$delimiter.
1621						$this->mail_bo->profileID.mail_ui::$delimiter.
1622						base64_encode($folder).mail_ui::$delimiter.$merged_mail_id;
1623					$content = $this->getComposeFrom($merged_mail_id, $part_id, 'composefromdraft', $_focusElement, $suppressSigOnTop, $isReply);
1624				}
1625				else
1626				{
1627					$success = implode(', ',$results['success']);
1628					$fail = implode(', ', $results['failed']);
1629					if($success) Framework::message($success, 'success');
1630					Framework::window_close($fail);
1631				}
1632			}
1633			catch (Api\Exception\WrongUserinput $e)
1634			{
1635				// if this returns with an exeption, something failed big time
1636				$content['msg'] = $e->getMessage();
1637			}
1638		}
1639		return $content;
1640	}
1641
1642	/**
1643	 * previous bocompose stuff
1644	 */
1645
1646	/**
1647	 * replace emailaddresses eclosed in <> (eg.: <me@you.de>) with the emailaddress only (e.g: me@you.de)
1648	 * always returns 1
1649	 */
1650	static function replaceEmailAdresses(&$text)
1651	{
1652		// replace emailaddresses eclosed in <> (eg.: <me@you.de>) with the emailaddress only (e.g: me@you.de)
1653		Api\Mail\Html::replaceEmailAdresses($text);
1654		return 1;
1655	}
1656
1657	function convertHTMLToText(&$_html,$sourceishtml = true, $stripcrl=false, $noRepEmailAddr = false)
1658	{
1659		$stripalltags = true;
1660		// third param is stripalltags, we may not need that, if the source is already in ascii
1661		if (!$sourceishtml) $stripalltags=false;
1662		return Api\Mail\Html::convertHTMLToText($_html,$this->displayCharset,$stripcrl,$stripalltags, $noRepEmailAddr);
1663	}
1664
1665	function generateRFC822Address($_addressObject)
1666	{
1667		if($_addressObject->personal && $_addressObject->mailbox && $_addressObject->host) {
1668			return sprintf('"%s" <%s@%s>', $this->mail_bo->decode_header($_addressObject->personal), $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE'));
1669		} elseif($_addressObject->mailbox && $_addressObject->host) {
1670			return sprintf("%s@%s", $_addressObject->mailbox, $this->mail_bo->decode_header($_addressObject->host,'FORCE'));
1671		} else {
1672			return $this->mail_bo->decode_header($_addressObject->mailbox,true);
1673		}
1674	}
1675
1676	/**
1677	 *  create a unique id, to keep track of different compose windows
1678	 */
1679	function generateComposeID()
1680	{
1681		return Mail::getRandomString();
1682	}
1683
1684	// $_mode can be:
1685	// single: for a reply to one address
1686	// all: for a reply to all
1687	function getDraftData($_icServer, $_folder, $_uid, $_partID=NULL)
1688	{
1689		unset($_icServer);	// not used
1690		$this->sessionData['to'] = array();
1691
1692		$mail_bo = $this->mail_bo;
1693		$mail_bo->openConnection();
1694		$mail_bo->reopen($_folder);
1695
1696		// get message headers for specified message
1697		#$headers	= $mail_bo->getMessageHeader($_folder, $_uid);
1698		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID);
1699		$addHeadInfo = $mail_bo->getMessageHeader($_uid, $_partID);
1700		// thread-topic is a proprietary microsoft header and deprecated with the current version
1701		// horde does not support the encoding of thread-topic, and probably will not no so in the future
1702		//if ($addHeadInfo['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $addHeadInfo['THREAD-TOPIC'];
1703
1704		//error_log(__METHOD__.__LINE__.array2string($headers));
1705		if (!empty($addHeadInfo['X-MAILFOLDER'])) {
1706			foreach ( explode('|',$addHeadInfo['X-MAILFOLDER']) as $val ) {
1707				$fval=$val;
1708				$icServerID = $mail_bo->icServer->ImapServerId;
1709				if (stripos($val,'::')!==false) list($icServerID,$fval) = explode('::',$val,2);
1710				if ($icServerID != $mail_bo->icServer->ImapServerId) continue;
1711				if ($mail_bo->folderExists($fval)) $this->sessionData['folder'][] = $val;
1712			}
1713		}
1714		if (!empty($addHeadInfo['X-MAILIDENTITY'])) {
1715			// with the new system it would be the identity
1716			try
1717			{
1718				Mail\Account::read_identity($addHeadInfo['X-MAILIDENTITY']);
1719				$this->sessionData['mailidentity'] = $addHeadInfo['X-MAILIDENTITY'];
1720			}
1721			catch (Exception $e)
1722			{
1723			}
1724		}
1725		/*
1726		if (!empty($addHeadInfo['X-STATIONERY'])) {
1727			$this->sessionData['stationeryID'] = $addHeadInfo['X-STATIONERY'];
1728		}
1729		*/
1730		if (!empty($addHeadInfo['X-MAILACCOUNT'])) {
1731			// with the new system it would the identity is the account id
1732			try
1733			{
1734				Mail\Account::read($addHeadInfo['X-MAILACCOUNT']);
1735				$this->sessionData['mailaccount'] = $addHeadInfo['X-MAILACCOUNT'];
1736			}
1737			catch (Exception $e)
1738			{
1739				unset($e);
1740				// fail silently
1741				$this->sessionData['mailaccount'] = $mail_bo->profileID;
1742			}
1743		}
1744		// if the message is located within the draft folder, add it as last drafted version (for possible cleanup on abort))
1745		if ($mail_bo->isDraftFolder($_folder)) $this->sessionData['lastDrafted'] = mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid);//array('uid'=>$_uid,'folder'=>$_folder);
1746		$this->sessionData['uid'] = $_uid;
1747		$this->sessionData['messageFolder'] = $_folder;
1748		$this->sessionData['isDraft'] = true;
1749		$foundAddresses = array();
1750		foreach((array)$headers['CC'] as $val) {
1751			$rfcAddr=Mail::parseAddressList($val);
1752			$_rfcAddr = $rfcAddr[0];
1753			if (!$_rfcAddr->valid) continue;
1754			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) {
1755				continue;
1756			}
1757			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1758			if(!$foundAddresses[$keyemail]) {
1759				$address = $this->mail_bo->decode_header($val,true);
1760				$this->sessionData['cc'][] = $val;
1761				$foundAddresses[$keyemail] = true;
1762			}
1763		}
1764
1765		foreach((array)$headers['TO'] as $val) {
1766			if(!is_array($val))
1767			{
1768				$this->sessionData['to'][] = $val;
1769				continue;
1770			}
1771			$rfcAddr=Mail::parseAddressList($val);
1772			$_rfcAddr = $rfcAddr[0];
1773			if (!$_rfcAddr->valid) continue;
1774			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (!$_rfcAddr->mailbox && !$_rfcAddr->host) ) {
1775				continue;
1776			}
1777			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1778			if(!$foundAddresses[$keyemail]) {
1779				$address = $this->mail_bo->decode_header($val,true);
1780				$this->sessionData['to'][] = $val;
1781				$foundAddresses[$keyemail] = true;
1782			}
1783		}
1784
1785		$fromAddr = Mail::parseAddressList($addHeadInfo['FROM'])[0];
1786		foreach((array)$headers['REPLY-TO'] as $val) {
1787			$rfcAddr=Mail::parseAddressList($val);
1788			$_rfcAddr = $rfcAddr[0];
1789			if (!$_rfcAddr->valid || ($_rfcAddr->mailbox == $fromAddr->mailbox && $_rfcAddr->host == $fromAddr->host)) continue;
1790			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) {
1791				continue;
1792			}
1793			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1794			if(!$foundAddresses[$keyemail]) {
1795				$address = $this->mail_bo->decode_header($val,true);
1796				$this->sessionData['replyto'][] = $val;
1797				$foundAddresses[$keyemail] = true;
1798			}
1799		}
1800
1801		foreach((array)$headers['BCC'] as $val) {
1802			$rfcAddr=Mail::parseAddressList($val);
1803			$_rfcAddr = $rfcAddr[0];
1804			if (!$_rfcAddr->valid) continue;
1805			if($_rfcAddr->mailbox == 'undisclosed-recipients' || (empty($_rfcAddr->mailbox) && empty($_rfcAddr->host)) ) {
1806				continue;
1807			}
1808			$keyemail=$_rfcAddr->mailbox.'@'.$_rfcAddr->host;
1809			if(!$foundAddresses[$keyemail]) {
1810				$address = $this->mail_bo->decode_header($val,true);
1811				$this->sessionData['bcc'][] = $val;
1812				$foundAddresses[$keyemail] = true;
1813			}
1814		}
1815		//_debug_array($this->sessionData);
1816		$this->sessionData['subject']	= $mail_bo->decode_header($headers['SUBJECT']);
1817		// remove a printview tag if composing
1818		$searchfor = '/^\['.lang('printview').':\]/';
1819		$this->sessionData['subject'] = preg_replace($searchfor,'',$this->sessionData['subject']);
1820		$bodyParts = $mail_bo->getMessageBody($_uid,'always_display', $_partID);
1821		//_debug_array($bodyParts);
1822		#$fromAddress = ($headers['FROM'][0]['PERSONAL_NAME'] != 'NIL') ? $headers['FROM'][0]['RFC822_EMAIL'] : $headers['FROM'][0]['EMAIL'];
1823		if($bodyParts['0']['mimeType'] == 'text/html') {
1824			$this->sessionData['mimeType'] 	= 'html';
1825
1826			for($i=0; $i<count($bodyParts); $i++) {
1827				if($i>0) {
1828					$this->sessionData['body'] .= '<hr>';
1829				}
1830				if($bodyParts[$i]['mimeType'] == 'text/plain') {
1831					#$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body']);
1832					$bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>";
1833				}
1834				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
1835				$bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
1836				#error_log( "GetDraftData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
1837				$this->sessionData['body'] .= ($i>0?"<br>":""). $bodyParts[$i]['body'] ;
1838			}
1839			$this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID);
1840
1841		} else {
1842			$this->sessionData['mimeType']	= 'plain';
1843
1844			for($i=0; $i<count($bodyParts); $i++) {
1845				if($i>0) {
1846					$this->sessionData['body'] .= "<hr>";
1847				}
1848				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
1849				$bodyParts[$i]['body'] = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']);
1850				#error_log( "GetDraftData (Plain) CharSet".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
1851				$this->sessionData['body'] .= ($i>0?"\r\n":""). $bodyParts[$i]['body'] ;
1852			}
1853			$this->sessionData['body'] = mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID,'plain');
1854		}
1855
1856		if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID))) {
1857			foreach($attachments as $attachment) {
1858				//error_log(__METHOD__.__LINE__.array2string($attachment));
1859				$cid = $attachment['cid'];
1860				$match=null;
1861				preg_match("/cid:{$cid}/", $bodyParts['0']['body'], $match);
1862				//error_log(__METHOD__.__LINE__.'searching for cid:'."/cid:{$cid}/".'#'.$r.'#'.array2string($match));
1863				if (!$match || !$attachment['cid'])
1864				{
1865					$this->addMessageAttachment($_uid, $attachment['partID'],
1866						$_folder,
1867						$attachment['name'],
1868						$attachment['mimeType'],
1869						$attachment['size'],
1870						$attachment['is_winmail']);
1871				}
1872			}
1873		}
1874		$mail_bo->closeConnection();
1875		return $this->sessionData;
1876	}
1877
1878	function getErrorInfo()
1879	{
1880		if(isset($this->errorInfo)) {
1881			$errorInfo = $this->errorInfo;
1882			unset($this->errorInfo);
1883			return $errorInfo;
1884		}
1885		return false;
1886	}
1887
1888	function getForwardData($_icServer, $_folder, $_uid, $_partID, $_mode=false)
1889	{
1890		if ($_mode)
1891		{
1892			$modebuff = $this->mailPreferences['message_forwarding'];
1893			$this->mailPreferences['message_forwarding'] = $_mode;
1894		}
1895		if  ($this->mailPreferences['message_forwarding'] == 'inline') {
1896			$this->getReplyData('forward', $_icServer, $_folder, $_uid, $_partID);
1897		}
1898		$mail_bo    = $this->mail_bo;
1899		$mail_bo->openConnection();
1900		$mail_bo->reopen($_folder);
1901
1902		// get message headers for specified message
1903		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder);
1904		//error_log(__METHOD__.__LINE__.array2string($headers));
1905		//_debug_array($headers); exit;
1906		// check for Re: in subject header
1907		$this->sessionData['subject'] 	= "[FWD] " . $mail_bo->decode_header($headers['SUBJECT']);
1908		// the three attributes below are substituted by processedmail_id and mode
1909		//$this->sessionData['sourceFolder']=$_folder;
1910		//$this->sessionData['forwardFlag']='forwarded';
1911		//$this->sessionData['forwardedUID']=$_uid;
1912		if  ($this->mailPreferences['message_forwarding'] == 'asmail') {
1913			$this->sessionData['mimeType']  = $this->mailPreferences['composeOptions'];
1914			if($headers['SIZE'])
1915				$size				= $headers['SIZE'];
1916			else
1917				$size				= lang('unknown');
1918
1919			$this->addMessageAttachment($_uid, $_partID, $_folder,
1920				$mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
1921				'MESSAGE/RFC822', $size);
1922		}
1923		else
1924		{
1925			unset($this->sessionData['in-reply-to']);
1926			unset($this->sessionData['to']);
1927			unset($this->sessionData['cc']);
1928			try
1929			{
1930				if(($attachments = $mail_bo->getMessageAttachments($_uid,$_partID,null,true,false,false))) {
1931					//error_log(__METHOD__.__LINE__.':'.array2string($attachments));
1932					foreach($attachments as $attachment) {
1933						if (!($attachment['cid'] && preg_match("/image\//",$attachment['mimeType'])) || $attachment['disposition'] == 'attachment')
1934						{
1935							$this->addMessageAttachment($_uid, $attachment['partID'],
1936								$_folder,
1937								$attachment['name'],
1938								$attachment['mimeType'],
1939								$attachment['size']);
1940						}
1941					}
1942				}
1943			}
1944			catch (Mail\Smime\PassphraseMissing $e)
1945			{
1946				error_log(__METHOD__.'() Failed to forward because of smime '.$e->getMessage());
1947				Framework::message(lang('Forwarding of this message failed'.
1948						' because the content of this message seems to be encrypted'.
1949						' and can not be decrypted properly. If you still wish to'.
1950						' forward content of this encrypted message, you may try'.
1951						' to use forward as attachment instead.'),'error');
1952			}
1953		}
1954		$mail_bo->closeConnection();
1955		if ($_mode)
1956		{
1957			$this->mailPreferences['message_forwarding'] = $modebuff;
1958		}
1959		//error_log(__METHOD__.__LINE__.array2string($this->sessionData));
1960		return $this->sessionData;
1961	}
1962
1963	/**
1964	 * adds uploaded files or files in eGW's temp directory as attachments
1965	 *
1966	 * passes the given $_formData representing an attachment to $_content
1967	 *
1968	 * @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)
1969	 * @param array $_content the content passed to the function and to be modified
1970	 * @return void
1971	 */
1972	function addAttachment($_formData,&$_content,$eliminateDoubleAttachments=false)
1973	{
1974		//error_log(__METHOD__.__LINE__.' Formdata:'.array2string($_formData).' Content:'.array2string($_content));
1975
1976		$attachfailed = false;
1977		// to guard against exploits the file must be either uploaded or be in the temp_dir
1978		// check if formdata meets basic restrictions (in tmp dir, or vfs, mimetype, etc.)
1979		try
1980		{
1981			$tmpFileName = Mail::checkFileBasics($_formData,$this->composeID,false);
1982		}
1983		catch (Api\Exception\WrongUserinput $e)
1984		{
1985			$attachfailed = true;
1986			$alert_msg = $e->getMessage();
1987			Framework::message($e->getMessage(), 'error');
1988		}
1989		//error_log(__METHOD__.__LINE__.array2string($tmpFileName));
1990		//error_log(__METHOD__.__LINE__.array2string($_formData));
1991
1992		if ($eliminateDoubleAttachments == true)
1993		{
1994			foreach ((array)$_content['attachments'] as $attach)
1995			{
1996				if ($attach['name'] && $attach['name'] == $_formData['name'] &&
1997					strtolower($_formData['type'])== strtolower($attach['type']) &&
1998					stripos($_formData['file'],'vfs://') !== false) return;
1999			}
2000		}
2001		if ($attachfailed === false)
2002		{
2003			$buffer = array(
2004				'name'	=> $_formData['name'],
2005				'type'	=> $_formData['type'],
2006				'file'	=> $tmpFileName,
2007				'tmp_name'	=> $tmpFileName,
2008				'size'	=> $_formData['size']
2009			);
2010			if (!is_array($_content['attachments'])) $_content['attachments']=array();
2011			$_content['attachments'][] = $buffer;
2012			unset($buffer);
2013		}
2014		else
2015		{
2016			error_log(__METHOD__.__LINE__.array2string($alert_msg));
2017		}
2018	}
2019
2020	function addMessageAttachment($_uid, $_partID, $_folder, $_name, $_type, $_size, $_is_winmail= null)
2021	{
2022		$this->sessionData['attachments'][]=array (
2023			'uid'		=> $_uid,
2024			'partID'	=> $_partID,
2025			'name'		=> $_name,
2026			'type'		=> $_type,
2027			'size'		=> $_size,
2028			'folder'	=> $_folder,
2029			'winmailFlag' => $_is_winmail,
2030			'tmp_name'	=> mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid).'_'.(!empty($_partID)?$_partID:count($this->sessionData['attachments'])+1),
2031		);
2032	}
2033
2034	function getAttachment()
2035	{
2036		// read attachment data from etemplate request, use tmpname only to identify it
2037		if (($request = Etemplate\Request::read($_GET['etemplate_exec_id'])))
2038		{
2039			foreach($request->preserv['attachments'] as $attachment)
2040			{
2041				if ($_GET['tmpname'] === $attachment['tmp_name']) break;
2042			}
2043		}
2044		if (!$request || $_GET['tmpname'] !== $attachment['tmp_name'])
2045		{
2046			header('HTTP/1.1 404 Not found');
2047			die('Attachment '.htmlspecialchars($_GET['tmpname']).' NOT found!');
2048		}
2049
2050		//error_log(__METHOD__.__LINE__.array2string($_GET));
2051		if (parse_url($attachment['tmp_name'],PHP_URL_SCHEME) == 'vfs')
2052		{
2053			Vfs::load_wrapper('vfs');
2054		}
2055		// attachment data in temp_dir, only use basename of given name, to not allow path traversal
2056		else
2057		{
2058			$attachment['tmp_name'] = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['tmp_name']);
2059		}
2060		if(!file_exists($attachment['tmp_name']))
2061		{
2062			header('HTTP/1.1 404 Not found');
2063			die('Attachment '.htmlspecialchars($attachment['tmp_name']).' NOT found!');
2064		}
2065		$attachment['attachment'] = file_get_contents($attachment['tmp_name']);
2066
2067		//error_log(__METHOD__.__LINE__.' FileSize:'.filesize($attachment['tmp_name']));
2068		if ($_GET['mode'] != "save")
2069		{
2070			if (strtoupper($attachment['type']) == 'TEXT/DIRECTORY')
2071			{
2072				$sfxMimeType = $attachment['type'];
2073				$buff = explode('.',$attachment['tmp_name']);
2074				$suffix = '';
2075				if (is_array($buff)) $suffix = array_pop($buff); // take the last extension to check with ext2mime
2076				if (!empty($suffix)) $sfxMimeType = Api\MimeMagic::ext2mime($suffix);
2077				$attachment['type'] = $sfxMimeType;
2078				if (strtoupper($sfxMimeType) == 'TEXT/VCARD' || strtoupper($sfxMimeType) == 'TEXT/X-VCARD') $attachment['type'] = strtoupper($sfxMimeType);
2079			}
2080			//error_log(__METHOD__.print_r($attachment,true));
2081			if (strtoupper($attachment['type']) == 'TEXT/CALENDAR' || strtoupper($attachment['type']) == 'TEXT/X-VCALENDAR')
2082			{
2083				//error_log(__METHOD__."about to call calendar_ical");
2084				$calendar_ical = new calendar_ical();
2085				$eventid = $calendar_ical->search($attachment['attachment'],-1);
2086				//error_log(__METHOD__.array2string($eventid));
2087				if (!$eventid) $eventid = -1;
2088				$event = $calendar_ical->importVCal($attachment['attachment'],(is_array($eventid)?$eventid[0]:$eventid),null,true);
2089				//error_log(__METHOD__.$event);
2090				if ((int)$event > 0)
2091				{
2092					$vars = array(
2093						'menuaction'      => 'calendar.calendar_uiforms.edit',
2094						'cal_id'      => $event,
2095					);
2096					$GLOBALS['egw']->redirect_link('../index.php',$vars);
2097				}
2098				//Import failed, download content anyway
2099			}
2100			if (strtoupper($attachment['type']) == 'TEXT/X-VCARD' || strtoupper($attachment['type']) == 'TEXT/VCARD')
2101			{
2102				$addressbook_vcal = new addressbook_vcal();
2103				// double \r\r\n seems to end a vcard prematurely, so we set them to \r\n
2104				//error_log(__METHOD__.__LINE__.$attachment['attachment']);
2105				$attachment['attachment'] = str_replace("\r\r\n", "\r\n", $attachment['attachment']);
2106				$vcard = $addressbook_vcal->vcardtoegw($attachment['attachment']);
2107				if ($vcard['uid'])
2108				{
2109					$vcard['uid'] = trim($vcard['uid']);
2110					//error_log(__METHOD__.__LINE__.print_r($vcard,true));
2111					$contact = $addressbook_vcal->find_contact($vcard,false);
2112				}
2113				if (!$contact) $contact = null;
2114				// 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))
2115				if ($contact || count($vcard)>2)
2116				{
2117					$contact = $addressbook_vcal->addVCard($attachment['attachment'],(is_array($contact)?array_shift($contact):$contact),true);
2118				}
2119				if ((int)$contact > 0)
2120				{
2121					$vars = array(
2122						'menuaction'	=> 'addressbook.addressbook_ui.edit',
2123						'contact_id'	=> $contact,
2124					);
2125					$GLOBALS['egw']->redirect_link('../index.php',$vars);
2126				}
2127				//Import failed, download content anyway
2128			}
2129		}
2130		//error_log(__METHOD__.__LINE__.'->'.array2string($attachment));
2131		Api\Header\Content::safe($attachment['attachment'], $attachment['name'], $attachment['type'], $size=0, true, $_GET['mode'] == "save");
2132		echo $attachment['attachment'];
2133
2134		exit();
2135	}
2136
2137	/**
2138	 * Test if string contains one of the keys of an array
2139	 *
2140	 * @param array arrayToTestAgainst to test its keys against haystack
2141	 * @param string haystack
2142	 * @return boolean
2143	 */
2144	function testIfOneKeyInArrayDoesExistInString($arrayToTestAgainst,$haystack) {
2145		foreach (array_keys($arrayToTestAgainst) as $k)
2146		{
2147			//error_log(__METHOD__.__LINE__.':'.$k.'<->'.$haystack);
2148			if (stripos($haystack,$k)!==false)
2149			{
2150				//error_log(__METHOD__.__LINE__.':FOUND:'.$k.'<->'.$haystack.function_backtrace());
2151				return true;
2152			}
2153		}
2154		return false;
2155	}
2156
2157	/**
2158	 * Gather the replyData and save it with the session, to be used then
2159	 *
2160	 * @param $_mode can be:
2161	 * 		single: for a reply to one address
2162	 * 		all: for a reply to all
2163	 * 		forward: inlineforwarding of a message with its attachments
2164	 * @param $_icServer number (0 as it is the active Profile)
2165	 * @param $_folder string
2166	 * @param $_uid number
2167	 * @param $_partID number
2168	 */
2169	function getReplyData($_mode, $_icServer, $_folder, $_uid, $_partID)
2170	{
2171		unset($_icServer);	// not used
2172		$foundAddresses = array();
2173
2174		$mail_bo  = $this->mail_bo;
2175		$mail_bo->openConnection();
2176		$mail_bo->reopen($_folder);
2177
2178		$userEMailAddresses = $mail_bo->getUserEMailAddresses();
2179
2180		// get message headers for specified message
2181		//print "AAAA: $_folder, $_uid, $_partID<br>";
2182		$headers	= $mail_bo->getMessageEnvelope($_uid, $_partID,false,$_folder,$useHeaderInsteadOfEnvelope=true);
2183		//$headers	= $mail_bo->getMessageHeader($_uid, $_partID, true, true, $_folder);
2184		$this->sessionData['uid'] = $_uid;
2185		$this->sessionData['messageFolder'] = $_folder;
2186		$this->sessionData['in-reply-to'] = ($headers['IN-REPLY-TO']?$headers['IN-REPLY-TO']:$headers['MESSAGE_ID']);
2187		$this->sessionData['references'] = ($headers['REFERENCES']?$headers['REFERENCES']:$headers['MESSAGE_ID']);
2188
2189		// break reference into multiple lines if they're greater than 998 chars
2190		// and remove comma seperation. Fix error serer does not support binary
2191		// data due to long references.
2192		if (strlen($this->sessionData['references'])> 998)
2193		{
2194			$temp_refs = explode(',',$this->sessionData['references']);
2195			$this->sessionData['references'] = implode(" ",$temp_refs);
2196		}
2197
2198		// thread-topic is a proprietary microsoft header and deprecated with the current version
2199		// horde does not support the encoding of thread-topic, and probably will not no so in the future
2200		//if ($headers['THREAD-TOPIC']) $this->sessionData['thread-topic'] = $headers['THREAD-TOPIC'];
2201		if ($headers['THREAD-INDEX']) $this->sessionData['thread-index'] = $headers['THREAD-INDEX'];
2202		if ($headers['LIST-ID']) $this->sessionData['list-id'] = $headers['LIST-ID'];
2203		//error_log(__METHOD__.__LINE__.' Mode:'.$_mode.':'.array2string($headers));
2204		// check for Reply-To: header and use if available
2205		if(!empty($headers['REPLY-TO']) && ($headers['REPLY-TO'] != $headers['FROM'])) {
2206			foreach($headers['REPLY-TO'] as $val) {
2207				if(!$foundAddresses[$val]) {
2208					$oldTo[] = $val;
2209					$foundAddresses[$val] = true;
2210				}
2211			}
2212			$oldToAddress	= (is_array($headers['REPLY-TO'])?$headers['REPLY-TO'][0]:$headers['REPLY-TO']);
2213		} else {
2214			foreach($headers['FROM'] as $val) {
2215				if(!$foundAddresses[$val]) {
2216					$oldTo[] = $val;
2217					$foundAddresses[$val] = true;
2218				}
2219			}
2220			$oldToAddress	= (is_array($headers['FROM'])?$headers['FROM'][0]:$headers['FROM']);
2221		}
2222		//error_log(__METHOD__.__LINE__.' OldToAddress:'.$oldToAddress.'#');
2223		if($_mode != 'all' || ($_mode == 'all' && !empty($oldToAddress) && !$this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$oldToAddress)) ) {
2224			$this->sessionData['to'] = $oldTo;
2225		}
2226
2227		if($_mode == 'all') {
2228			// reply to any address which is cc, but not to my self
2229			#if($headers->cc) {
2230				foreach($headers['CC'] as $val) {
2231					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2232						continue;
2233					}
2234					if(!$foundAddresses[$val]) {
2235						$this->sessionData['cc'][] = $val;
2236						$foundAddresses[$val] = true;
2237					}
2238				}
2239			#}
2240
2241			// reply to any address which is to, but not to my self
2242			#if($headers->to) {
2243				foreach($headers['TO'] as $val) {
2244					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2245						continue;
2246					}
2247					if(!$foundAddresses[$val]) {
2248						$this->sessionData['to'][] = $val;
2249						$foundAddresses[$val] = true;
2250					}
2251				}
2252			#}
2253
2254			#if($headers->from) {
2255				foreach($headers['FROM'] as $val) {
2256					if($this->testIfOneKeyInArrayDoesExistInString($userEMailAddresses,$val)) {
2257						continue;
2258					}
2259					//error_log(__METHOD__.__LINE__.' '.$val);
2260					if(!$foundAddresses[$val]) {
2261						$this->sessionData['to'][] = $val;
2262						$foundAddresses[$val] = true;
2263					}
2264				}
2265			#}
2266		}
2267
2268		// check for Re: in subject header
2269		if(strtolower(substr(trim($mail_bo->decode_header($headers['SUBJECT'])), 0, 3)) == "re:") {
2270			$this->sessionData['subject'] = $mail_bo->decode_header($headers['SUBJECT']);
2271		} else {
2272			$this->sessionData['subject'] = "Re: " . $mail_bo->decode_header($headers['SUBJECT']);
2273		}
2274
2275		//_debug_array($headers);
2276		//error_log(__METHOD__.__LINE__.'->'.array2string($this->mailPreferences['htmlOptions']));
2277		try {
2278			$bodyParts = $mail_bo->getMessageBody($_uid, ($this->mailPreferences['htmlOptions']?$this->mailPreferences['htmlOptions']:''), $_partID);
2279		}
2280		catch (Mail\Smime\PassphraseMissing $e)
2281		{
2282			$bodyParts = '';
2283			error_log(__METHOD__.'() Failed to reply because of smime '.$e->getMessage());
2284			Framework::message(lang('Replying to this message failed'.
2285				' because the content of this message seems to be encrypted'.
2286				' and can not be decrypted properly. If you still wish to include'.
2287				' content of this encrypted message, you may try to use forward as'.
2288				' attachment instead.'),'error');
2289		}
2290		//_debug_array($bodyParts);
2291		$styles = Mail::getStyles($bodyParts);
2292
2293		$fromAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$headers['FROM']));
2294
2295		$toAddressA = array();
2296		$toAddress = '';
2297		foreach ($headers['TO'] as $mailheader) {
2298			$toAddressA[] =  $mailheader;
2299		}
2300		if (count($toAddressA)>0)
2301		{
2302			$toAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$toAddressA));
2303			$toAddress = @htmlspecialchars(lang("to")).": ".$toAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n");
2304		}
2305		$ccAddressA = array();
2306		$ccAddress = '';
2307		foreach ($headers['CC'] as $mailheader) {
2308			$ccAddressA[] =  $mailheader;
2309		}
2310		if (count($ccAddressA)>0)
2311		{
2312			$ccAddress = implode(', ', str_replace(array('<','>'),array('[',']'),$ccAddressA));
2313			$ccAddress = @htmlspecialchars(lang("cc")).": ".$ccAddress.($bodyParts['0']['mimeType'] == 'text/html'?"<br>":"\r\n");
2314		}
2315		if($bodyParts['0']['mimeType'] == 'text/html') {
2316			$this->sessionData['body']	= /*"<br>".*//*"&nbsp;".*/"<div>".'----------------'.lang("original message").'-----------------'."".'<br>'.
2317				@htmlspecialchars(lang("from")).": ".$fromAddress."<br>".
2318				$toAddress.$ccAddress.
2319				@htmlspecialchars(lang("date").": ".Mail::_strtotime($headers['DATE'],'r',true),ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."<br>".
2320				'----------------------------------------------------------'."</div>";
2321			$this->sessionData['mimeType'] 	= 'html';
2322			if (!empty($styles)) $this->sessionData['body'] .= $styles;
2323			$this->sessionData['body']	.= '<blockquote type="cite">';
2324
2325			for($i=0; $i<count($bodyParts); $i++) {
2326				if($i>0) {
2327					$this->sessionData['body'] .= '<hr>';
2328				}
2329				if($bodyParts[$i]['mimeType'] == 'text/plain') {
2330					#$bodyParts[$i]['body'] = nl2br($bodyParts[$i]['body'])."<br>";
2331					$bodyParts[$i]['body'] = "<pre>".$bodyParts[$i]['body']."</pre>";
2332				}
2333				if ($bodyParts[$i]['charSet']===false) $bodyParts[$i]['charSet'] = Mail::detect_encoding($bodyParts[$i]['body']);
2334
2335				$_htmlConfig = Mail::$htmLawed_config;
2336				Mail::$htmLawed_config['comment'] = 2;
2337				Mail::$htmLawed_config['transform_anchor'] = false;
2338				$this->sessionData['body'] .= "<br>".self::_getCleanHTML(Api\Translation::convert_jsonsafe($bodyParts[$i]['body'], $bodyParts[$i]['charSet']));
2339				Mail::$htmLawed_config = $_htmlConfig;
2340				#error_log( "GetReplyData (HTML) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
2341			}
2342
2343			$this->sessionData['body']	.= '</blockquote><br>';
2344			$this->sessionData['body'] =  mail_ui::resolve_inline_images($this->sessionData['body'], $_folder, $_uid, $_partID, 'html');
2345		} else {
2346			//$this->sessionData['body']	= @htmlspecialchars(lang("on")." ".$headers['DATE']." ".$mail_bo->decode_header($fromAddress), ENT_QUOTES) . " ".lang("wrote").":\r\n";
2347			// 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)
2348            $this->sessionData['body']  = " \r\n \r\n".'----------------'.lang("original message").'-----------------'."\r\n".
2349                @htmlspecialchars(lang("from")).": ".$fromAddress."\r\n".
2350				$toAddress.$ccAddress.
2351				@htmlspecialchars(lang("date").": ".Mail::_strtotime($headers['DATE'],'r',true), ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."\r\n".
2352                '-------------------------------------------------'."\r\n \r\n ";
2353			$this->sessionData['mimeType']	= 'plain';
2354
2355			for($i=0; $i<count($bodyParts); $i++) {
2356				if($i>0) {
2357					$this->sessionData['body'] .= "<hr>";
2358				}
2359
2360				// add line breaks to $bodyParts
2361				$newBody2 = Api\Translation::convert_jsonsafe($bodyParts[$i]['body'],$bodyParts[$i]['charSet']);
2362				#error_log( "GetReplyData (Plain) CharSet:".mb_detect_encoding($bodyParts[$i]['body'] . 'a' , strtoupper($bodyParts[$i]['charSet']).','.strtoupper($this->displayCharset).',UTF-8, ISO-8859-1'));
2363				$newBody = mail_ui::resolve_inline_images($newBody2, $_folder, $_uid, $_partID, 'plain');
2364				$this->sessionData['body'] .= "\r\n";
2365				$hasSignature = false;
2366				// create body new, with good line breaks and indention
2367				foreach(explode("\n",$newBody) as $value) {
2368					// the explode is removing the character
2369					//$value .= 'ee';
2370
2371					// Try to remove signatures from qouted parts to avoid multiple
2372					// signatures problem in reply (rfc3676#section-4.3).
2373					if ($_mode != 'forward' && ($hasSignature || ($hasSignature = preg_match("/^--\s[\r\n]$/",$value))))
2374					{
2375						continue;
2376					}
2377
2378					$numberOfChars = strspn(trim($value), ">");
2379					$appendString = str_repeat('>', $numberOfChars + 1);
2380
2381					$bodyAppend = $this->mail_bo->wordwrap($value, 76-strlen("\r\n$appendString "), "\r\n$appendString ",'>');
2382
2383					if($bodyAppend[0] == '>') {
2384						$bodyAppend = '>'. $bodyAppend;
2385					} else {
2386						$bodyAppend = '> '. $bodyAppend;
2387					}
2388
2389					$this->sessionData['body'] .= $bodyAppend;
2390				}
2391			}
2392		}
2393
2394		$mail_bo->closeConnection();
2395		return $this->sessionData;
2396
2397	}
2398
2399	/**
2400	 * HTML cleanup
2401	 *
2402	 * @param type $_body message
2403	 * @param type $_useTidy = false, if true tidy extention will be loaded and tidy will try to clean body message
2404	 *			since the tidy causes segmentation fault ATM, we set the default to false.
2405	 * @return type
2406	 */
2407	static function _getCleanHTML($_body, $_useTidy = false)
2408	{
2409		static $nonDisplayAbleCharacters = array('[\016]','[\017]',
2410				'[\020]','[\021]','[\022]','[\023]','[\024]','[\025]','[\026]','[\027]',
2411				'[\030]','[\031]','[\032]','[\033]','[\034]','[\035]','[\036]','[\037]');
2412
2413		if ($_useTidy && extension_loaded('tidy') )
2414		{
2415			$tidy = new tidy();
2416			$cleaned = $tidy->repairString($_body, Mail::$tidy_config,'utf8');
2417			// Found errors. Strip it all so there's some output
2418			if($tidy->getStatus() == 2)
2419			{
2420				error_log(__METHOD__.' ('.__LINE__.') '.' ->'.$tidy->errorBuffer);
2421			}
2422			else
2423			{
2424				$_body = $cleaned;
2425			}
2426		}
2427
2428		Mail::getCleanHTML($_body);
2429		return preg_replace($nonDisplayAbleCharacters, '', $_body);
2430	}
2431
2432	static function _getHostName()
2433	{
2434		if (isset($_SERVER['SERVER_NAME'])) {
2435			$result = $_SERVER['SERVER_NAME'];
2436		} else {
2437			$result = 'localhost.localdomain';
2438		}
2439		return $result;
2440	}
2441
2442	/**
2443	 * Create a message from given data and identity
2444	 *
2445	 * @param Api\Mailer $_mailObject
2446	 * @param array $_formData
2447	 * @param array $_identity
2448	 * @param boolean $_autosaving =false true: autosaving, false: save-as-draft or send
2449	 *
2450	 * @return array returns found inline images as attachment structure
2451	 */
2452	function createMessage(Api\Mailer $_mailObject, array $_formData, array $_identity, $_autosaving=false)
2453	{
2454		if (substr($_formData['body'], 0, 27) == '-----BEGIN PGP MESSAGE-----')
2455		{
2456			$_formData['mimeType'] = 'openpgp';
2457		}
2458		$mail_bo	= $this->mail_bo;
2459		$activeMailProfile = Mail\Account::read($this->mail_bo->profileID);
2460
2461		// you need to set the sender, if you work with different identities, since most smtp servers, dont allow
2462		// sending in the name of someone else
2463		if ($_identity['ident_id'] != $activeMailProfile['ident_id'] && !empty($_identity['ident_email']) && strtolower($activeMailProfile['ident_email']) != strtolower($_identity['ident_email']))
2464		{
2465			error_log(__METHOD__.__LINE__.' Faking From/SenderInfo for '.$activeMailProfile['ident_email'].' with ID:'.$activeMailProfile['ident_id'].'. Identitiy to use for sending:'.array2string($_identity));
2466		}
2467		$email_From =  $_identity['ident_email'] ? $_identity['ident_email'] : $activeMailProfile['ident_email'];
2468		// Try to fix identity email with no domain part set
2469		$_mailObject->setFrom(Mail::fixInvalidAliasAddress(Api\Accounts::id2name($_identity['account_id'], 'account_email'), $email_From),
2470			Mail::generateIdentityString($_identity,false));
2471
2472		$_mailObject->addHeader('X-Priority', $_formData['priority']);
2473		$_mailObject->addHeader('X-Mailer', 'EGroupware-Mail');
2474		if(!empty($_formData['in-reply-to'])) {
2475			if (stripos($_formData['in-reply-to'],'<')===false) $_formData['in-reply-to']='<'.trim($_formData['in-reply-to']).'>';
2476			$_mailObject->addHeader('In-Reply-To', $_formData['in-reply-to']);
2477		}
2478		if(!empty($_formData['references'])) {
2479			if (stripos($_formData['references'],'<')===false)
2480			{
2481				$_formData['references']='<'.trim($_formData['references']).'>';
2482			}
2483			$_mailObject->addHeader('References', $_formData['references']);
2484		}
2485
2486		if(!empty($_formData['thread-index'])) {
2487			$_mailObject->addHeader('Thread-Index', $_formData['thread-index']);
2488		}
2489		if(!empty($_formData['list-id'])) {
2490			$_mailObject->addHeader('List-Id', $_formData['list-id']);
2491		}
2492		if($_formData['disposition']=='on') {
2493			$_mailObject->addHeader('Disposition-Notification-To', $_identity['ident_email']);
2494		}
2495
2496		// Expand any mailing lists
2497		foreach(array('to', 'cc', 'bcc', 'replyto')  as $field)
2498		{
2499			if ($field != 'replyto') $_formData[$field] = self::resolveEmailAddressList($_formData[$field]);
2500
2501			if ($_formData[$field]) $_mailObject->addAddress($_formData[$field], '', $field);
2502		}
2503
2504		$_mailObject->addHeader('Subject', $_formData['subject']);
2505
2506		// this should never happen since we come from the edit dialog
2507		if (Mail::detect_qp($_formData['body'])) {
2508			$_formData['body'] = preg_replace('/=\r\n/', '', $_formData['body']);
2509			$_formData['body'] = quoted_printable_decode($_formData['body']);
2510		}
2511		$disableRuler = false;
2512		$signature = $_identity['ident_signature'];
2513		$sigAlreadyThere = $this->mailPreferences['insertSignatureAtTopOfMessage']!='no_belowaftersend'?1:0;
2514		if ($sigAlreadyThere)
2515		{
2516			// note: if you use stationery ' s the insert signatures at the top does not apply here anymore, as the signature
2517			// is already part of the body, so the signature part of the template will not be applied.
2518			$signature = null; // note: no signature, no ruler!!!!
2519		}
2520		if ((isset($this->mailPreferences['disableRulerForSignatureSeparation']) &&
2521			$this->mailPreferences['disableRulerForSignatureSeparation']) ||
2522			empty($signature) || trim($this->convertHTMLToText($signature)) =='')
2523		{
2524			$disableRuler = true;
2525		}
2526		if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving)
2527		{
2528			$attachment_links = $this->_getAttachmentLinks($_formData['attachments'], $_formData['filemode'],
2529				$_formData['mimeType'] == 'html',
2530				array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])),
2531				$_formData['expiration'], $_formData['password']);
2532		}
2533		switch ($_formData['mimeType'])
2534		{
2535			case 'html':
2536				$body = $_formData['body'];
2537				if ($attachment_links)
2538				{
2539					if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false)
2540					{
2541						$body = str_replace('<!-- HTMLSIGBEGIN -->', $attachment_links.'<!-- HTMLSIGBEGIN -->', $body);
2542					}
2543					else
2544					{
2545						$body .= $attachment_links;
2546					}
2547				}
2548				if(!empty($signature))
2549				{
2550					$_mailObject->setBody($this->convertHTMLToText($body, true, true).
2551						($disableRuler ? "\r\n" : "\r\n-- \r\n").
2552						$this->convertHTMLToText($signature, true, true));
2553
2554					$body .= ($disableRuler ?'<br>':'<hr style="border:1px dotted silver; width:90%;">').$signature;
2555				}
2556				else
2557				{
2558					$_mailObject->setBody($this->convertHTMLToText($body, true, true));
2559				}
2560				// convert URL Images to inline images - if possible
2561				if (!$_autosaving) $inline_images = Mail::processURL2InlineImages($_mailObject, $body, $mail_bo);
2562				if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false)
2563				{
2564					$body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body);
2565				}
2566				$_mailObject->setHtmlBody($body, null, false);	// false = no automatic alternative, we called setBody()
2567				break;
2568			case 'openpgp':
2569				$_mailObject->setOpenPgpBody($_formData['body']);
2570				break;
2571			default:
2572				$body = $this->convertHTMLToText($_formData['body'],false, false, true, true);
2573
2574				if ($attachment_links) $body .= $attachment_links;
2575
2576				#$_mailObject->Body = $_formData['body'];
2577				if(!empty($signature)) {
2578					$body .= ($disableRuler ?"\r\n":"\r\n-- \r\n").
2579						$this->convertHTMLToText($signature,true,true);
2580				}
2581				$_mailObject->setBody($body);
2582		}
2583		// add the attachments
2584		if (is_array($_formData) && isset($_formData['attachments']))
2585		{
2586			$connection_opened = false;
2587			$tnfattachments = null;
2588			foreach((array)$_formData['attachments'] as $attachment) {
2589				if(is_array($attachment))
2590				{
2591					if (!empty($attachment['uid']) && !empty($attachment['folder'])) {
2592						/* Example:
2593						Array([0] => Array(
2594						[uid] => 21178
2595						[partID] => 2
2596						[name] => [Untitled].pdf
2597						[type] => application/pdf
2598						[size] => 622379
2599						[folder] => INBOX))
2600						*/
2601						if (!$connection_opened)
2602						{
2603							$mail_bo->openConnection($mail_bo->profileID);
2604							$connection_opened = true;
2605						}
2606						$mail_bo->reopen($attachment['folder']);
2607						switch(strtoupper($attachment['type'])) {
2608							case 'MESSAGE/RFC':
2609							case 'MESSAGE/RFC822':
2610								$rawBody='';
2611								if (isset($attachment['partID'])) {
2612									$eml = $mail_bo->getAttachment($attachment['uid'],$attachment['partID'],0,false,true,$attachment['folder']);
2613									$rawBody=$eml['attachment'];
2614								} else {
2615									$rawBody        = $mail_bo->getMessageRawBody($attachment['uid'], $attachment['partID'],$attachment['folder']);
2616								}
2617								$_mailObject->addStringAttachment($rawBody, $attachment['name'], 'message/rfc822');
2618								break;
2619							default:
2620								$attachmentData	= $mail_bo->getAttachment($attachment['uid'], $attachment['partID'],0,false);
2621								if ($attachmentData['type'] == 'APPLICATION/MS-TNEF')
2622								{
2623									if (!is_array($tnfattachments)) $tnfattachments = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID']);
2624									foreach ($tnfattachments as $k)
2625									{
2626										if ($k['name'] == $attachment['name'])
2627										{
2628											$tnfpart = $mail_bo->decode_winmail($attachment['uid'], $attachment['partID'],$k['is_winmail']);
2629											$attachmentData['attachment'] = $tnfpart['attachment'];
2630											break;
2631										}
2632									}
2633								}
2634								$_mailObject->addStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['type']);
2635								break;
2636						}
2637					}
2638					// attach files not for autosaving
2639					elseif ($_formData['filemode'] == Vfs\Sharing::ATTACH && !$_autosaving)
2640					{
2641						if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs')
2642						{
2643							Vfs::load_wrapper('vfs');
2644							$tmp_path = $attachment['file'];
2645						}
2646						else	// non-vfs file has to be in temp_dir
2647						{
2648							$tmp_path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($attachment['file']);
2649						}
2650						$_mailObject->addAttachment (
2651							$tmp_path,
2652							$attachment['name'],
2653							$attachment['type']
2654						);
2655					}
2656				}
2657			}
2658			if ($connection_opened) $mail_bo->closeConnection();
2659		}
2660		return is_array($inline_images)?$inline_images:array();
2661	}
2662
2663	/**
2664	 * Get html or text containing links to attachments
2665	 *
2666	 * We only care about file attachments, not forwarded messages or parts
2667	 *
2668	 * @param array $attachments
2669	 * @param string $filemode Vfs\Sharing::(ATTACH|LINK|READONL|WRITABLE)
2670	 * @param boolean $html
2671	 * @param array $recipients =array()
2672	 * @param string $expiration =null
2673	 * @param string $password =null
2674	 * @return string might be empty if no file attachments found
2675	 */
2676	protected function _getAttachmentLinks(array $attachments, $filemode, $html, $recipients=array(), $expiration=null, $password=null)
2677	{
2678		if ($filemode == Vfs\Sharing::ATTACH) return '';
2679
2680		$links = array();
2681		foreach($attachments as $attachment)
2682		{
2683			$path = $attachment['file'];
2684			if (empty($path)) continue;	// we only care about file attachments, not forwarded messages or parts
2685			if (parse_url($attachment['file'],PHP_URL_SCHEME) != 'vfs')
2686			{
2687				$path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.basename($path);
2688			}
2689			// create share
2690			if ($filemode == Vfs\Sharing::WRITABLE || $expiration || $password)
2691			{
2692				$share = stylite_sharing::create($path, $filemode, $attachment['name'], $recipients, $expiration, $password);
2693			}
2694			else
2695			{
2696				$share = Vfs\Sharing::create('', $path, $filemode, $attachment['name'], $recipients);
2697			}
2698			$link = Vfs\Sharing::share2link($share);
2699
2700			$name = Vfs::basename($attachment['name'] ? $attachment['name'] : $attachment['file']);
2701
2702			if ($html)
2703			{
2704				$links[] = Api\Html::a_href($name, $link).' '.
2705					(is_dir($path) ? lang('Directory') : Vfs::hsize($attachment['size']));
2706			}
2707			else
2708			{
2709				$links[] = $name.' '.Vfs::hsize($attachment['size']).': '.
2710					(is_dir($path) ? lang('Directory') : $link);
2711			}
2712		}
2713		if (!$links)
2714		{
2715			return null;	// no file attachments found
2716		}
2717		elseif ($html)
2718		{
2719			return '<p>'.lang('Download attachments').":</p>\n<ul><li>".implode("</li>\n<li>", $links)."</li></ul>\n";
2720		}
2721		return lang('Download attachments').":\n- ".implode("\n- ", $links)."\n";
2722	}
2723
2724	/**
2725	 * Save compose mail as draft
2726	 *
2727	 * @param array $content content sent from client-side
2728	 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]'
2729	 */
2730	public function ajax_saveAsDraft ($content, $action='button[saveAsDraft]')
2731	{
2732		//error_log(__METHOD__.__LINE__.array2string($content)."(, action=$action)");
2733		$response = Api\Json\Response::get();
2734		$success = true;
2735
2736		// check if default account is changed then we need to change profile
2737		if (!empty($content['serverID']) && $content['serverID'] != $this->mail_bo->profileID)
2738		{
2739			$this->changeProfile($content['serverID']);
2740		}
2741
2742		$formData = array_merge($content, array(
2743			'isDrafted' => 1,
2744			'body' => $content['mail_'.($content['mimeType']?'htmltext':'plaintext')],
2745			'mimeType' => $content['mimeType']?'html':'plain' // checkbox has only true|false value
2746		));
2747
2748		//Saving draft procedure
2749		try
2750		{
2751			$folder = $this->mail_bo->getDraftFolder();
2752			$this->mail_bo->reopen($folder);
2753			$status = $this->mail_bo->getFolderStatus($folder);
2754			if (($messageUid = $this->saveAsDraft($formData, $folder, $action)))
2755			{
2756				// saving as draft, does not mean closing the message
2757				$messageUid = ($messageUid===true ? $status['uidnext'] : $messageUid);
2758				if (is_array($this->mail_bo->getMessageHeader($messageUid, '',false, false, $folder)))
2759				{
2760					$draft_id = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $messageUid);
2761					if ($content['lastDrafted'] != $draft_id && isset($content['lastDrafted']))
2762					{
2763						$dhA = mail_ui::splitRowID($content['lastDrafted']);
2764						$duid = $dhA['msgUID'];
2765						$dmailbox = $dhA['folder'];
2766						// beware: do not delete the original mail as found in processedmail_id
2767						$pMuid='';
2768						if ($content['processedmail_id'])
2769						{
2770							$pMhA = mail_ui::splitRowID($content['processedmail_id']);
2771							$pMuid = $pMhA['msgUID'];
2772						}
2773						//error_log(__METHOD__.__LINE__."#$pMuid#$pMuid!=$duid#".array2string($content['attachments']));
2774						// do not delete the original message if attachments are present
2775						if (empty($pMuid) || $pMuid!=$duid || empty($content['attachments']))
2776						{
2777							try
2778							{
2779								$this->mail_bo->deleteMessages($duid,$dmailbox,'remove_immediately');
2780							}
2781							catch (Api\Exception $e)
2782							{
2783								$msg = str_replace('"',"'",$e->getMessage());
2784								$success = false;
2785								error_log(__METHOD__.__LINE__.$msg);
2786							}
2787						} else {
2788							error_log(__METHOD__.__LINE__.': original message ('.$pMuid.') has attachments and lastDrafted ID ('.$duid.') equals the former');
2789						}
2790					} else {
2791						error_log(__METHOD__.__LINE__." No current draftID (".$draft_id."), or no lastDrafted Info (".$content['lastDrafted'].") or the former being equal:".array2string($content)."(, action=$action)");
2792					}
2793				} else {
2794					error_log(__METHOD__.__LINE__.' No headerdata found for messageUID='.$messageUid.' in Folder:'.$folder.':'.array2string($content)."(, action=$action)");
2795				}
2796			}
2797			else
2798			{
2799				throw new Api\Exception\WrongUserinput(lang("Error: Could not save Message as Draft"));
2800			}
2801		}
2802		catch (Api\Exception\WrongUserinput $e)
2803		{
2804			$msg = str_replace('"',"'",$e->getMessage());
2805			error_log(__METHOD__.__LINE__.$msg);
2806			$success = false;
2807		}
2808
2809		if ($success) $msg = lang('Message saved successfully.');
2810
2811		// Include new information to json respose, because we need them in client-side callback
2812		$response->data(array(
2813			'draftedId' => $draft_id,
2814			'message' => $msg,
2815			'success' => $success,
2816			'draftfolder' => $this->mail_bo->profileID.mail_ui::$delimiter.$this->mail_bo->getDraftFolder()
2817		));
2818	}
2819
2820	/**
2821	 * resolveEmailAddressList
2822	 * @param array $_emailAddressList list of emailaddresses, may contain distributionlists
2823	 * @return array return the list of emailaddresses with distributionlists resolved
2824	 */
2825	static function resolveEmailAddressList($_emailAddressList)
2826	{
2827		$contacts_obs = null;
2828		$addrFromList=array();
2829		foreach((array)$_emailAddressList as $ak => $address)
2830		{
2831			if(is_int($address))
2832			{
2833				if (!isset($contacts_obs)) $contacts_obj = new Api\Contacts();
2834				// List was selected, expand to addresses
2835				unset($_emailAddressList[$ak]);
2836				$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));
2837				// Just add email addresses, they'll be checked below
2838				foreach($list as $email)
2839				{
2840					$addrFromList[] = $email['email'] ? $email['email'] : $email['email_home'];
2841				}
2842			}
2843		}
2844		if (!empty($addrFromList))
2845		{
2846			foreach ($addrFromList as $addr)
2847			{
2848				if (!empty($addr)) $_emailAddressList[]=$addr;
2849			}
2850		}
2851		return is_array($_emailAddressList) ? array_values($_emailAddressList) : (array)$_emailAddressList;
2852	}
2853
2854	/**
2855	 * Save message as draft to specific folder
2856	 *
2857	 * @param array $_formData content
2858	 * @param string &$savingDestination ='' destination folder
2859	 * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]'
2860	 * @return boolean return messageUID| false due to an error
2861	 */
2862	function saveAsDraft($_formData, &$savingDestination='', $action='button[saveAsDraft]')
2863	{
2864		//error_log(__METHOD__."(..., $savingDestination, action=$action)");
2865		$mail_bo	= $this->mail_bo;
2866		$mail		= new Api\Mailer($this->mail_bo->profileID);
2867
2868		// preserve the bcc and if possible the save to folder information
2869		$this->sessionData['folder']    = $_formData['folder'];
2870		$this->sessionData['bcc']   = $_formData['bcc'];
2871		$this->sessionData['mailidentity'] = $_formData['mailidentity'];
2872		//$this->sessionData['stationeryID'] = $_formData['stationeryID'];
2873		$this->sessionData['mailaccount']  = $_formData['mailaccount'];
2874		$this->sessionData['attachments']  = $_formData['attachments'];
2875		try
2876		{
2877			$acc = Mail\Account::read($this->sessionData['mailaccount']);
2878			//error_log(__METHOD__.__LINE__.array2string($acc));
2879			$identity = Mail\Account::read_identity($acc['ident_id'],true);
2880		}
2881		catch (Exception $e)
2882		{
2883			$identity=array();
2884		}
2885
2886		$flags = '\\Seen \\Draft';
2887
2888		$this->createMessage($mail, $_formData, $identity, $action === 'autosaving');
2889
2890		// folder list as Customheader
2891		if (!empty($this->sessionData['folder']))
2892		{
2893			$folders = implode('|',array_unique($this->sessionData['folder']));
2894			$mail->addHeader('X-Mailfolder', $folders);
2895		}
2896		$mail->addHeader('X-Mailidentity', $this->sessionData['mailidentity']);
2897		//$mail->addHeader('X-Stationery', $this->sessionData['stationeryID']);
2898		$mail->addHeader('X-Mailaccount', (int)$this->sessionData['mailaccount']);
2899		// decide where to save the message (default to draft folder, if we find nothing else)
2900		// if the current folder is in draft or template folder save it there
2901		// if it is called from printview then save it with the draft folder
2902		if (empty($savingDestination)) $savingDestination = $mail_bo->getDraftFolder();
2903		if (empty($this->sessionData['messageFolder']) && !empty($this->sessionData['mailbox']))
2904		{
2905			$this->sessionData['messageFolder'] = $this->sessionData['mailbox'];
2906		}
2907		if (!empty($this->sessionData['messageFolder']) && ($mail_bo->isDraftFolder($this->sessionData['messageFolder'])
2908			|| $mail_bo->isTemplateFolder($this->sessionData['messageFolder'])))
2909		{
2910			$savingDestination = $this->sessionData['messageFolder'];
2911			//error_log(__METHOD__.__LINE__.' SavingDestination:'.$savingDestination);
2912		}
2913		if (  !empty($_formData['printit']) && $_formData['printit'] == 0 ) $savingDestination = $mail_bo->getDraftFolder();
2914
2915		// normaly Bcc is only added to recipients, but not as header visible to all recipients
2916		$mail->forceBccHeader();
2917
2918		$mail_bo->openConnection();
2919		if ($mail_bo->folderExists($savingDestination,true)) {
2920			try
2921			{
2922				$messageUid = $mail_bo->appendMessage($savingDestination, $mail->getRaw(), null, $flags);
2923			}
2924			catch (Api\Exception\WrongUserinput $e)
2925			{
2926				error_log(__METHOD__.__LINE__.lang("Save of message %1 failed. Could not save message to folder %2 due to: %3",__METHOD__,$savingDestination,$e->getMessage()));
2927				return false;
2928			}
2929
2930		} else {
2931			error_log(__METHOD__.__LINE__."->".lang("folder")." ". $savingDestination." ".lang("does not exist on IMAP Server."));
2932			return false;
2933		}
2934		$mail_bo->closeConnection();
2935		return $messageUid;
2936	}
2937
2938	function send($_formData)
2939	{
2940		$mail_bo	= $this->mail_bo;
2941		$mail 		= new Api\Mailer($mail_bo->profileID);
2942		$messageIsDraft	=  false;
2943
2944		$this->sessionData['mailaccount']	= $_formData['mailaccount'];
2945		$this->sessionData['to']	= self::resolveEmailAddressList($_formData['to']);
2946		$this->sessionData['cc']	= self::resolveEmailAddressList($_formData['cc']);
2947		$this->sessionData['bcc']	= self::resolveEmailAddressList($_formData['bcc']);
2948		$this->sessionData['folder']	= $_formData['folder'];
2949		$this->sessionData['replyto']	= $_formData['replyto'];
2950		$this->sessionData['subject']	= trim($_formData['subject']);
2951		$this->sessionData['body']	= $_formData['body'];
2952		$this->sessionData['priority']	= $_formData['priority'];
2953		$this->sessionData['mailidentity'] = $_formData['mailidentity'];
2954		//$this->sessionData['stationeryID'] = $_formData['stationeryID'];
2955		$this->sessionData['disposition'] = $_formData['disposition'];
2956		$this->sessionData['mimeType']	= $_formData['mimeType'];
2957		$this->sessionData['to_infolog'] = $_formData['to_infolog'];
2958		$this->sessionData['to_tracker'] = $_formData['to_tracker'];
2959		$this->sessionData['attachments']  = $_formData['attachments'];
2960		$this->sessionData['smime_sign']  = $_formData['smime_sign'];
2961		$this->sessionData['smime_encrypt']  = $_formData['smime_encrypt'];
2962
2963		if (isset($_formData['lastDrafted']) && !empty($_formData['lastDrafted']))
2964		{
2965			$this->sessionData['lastDrafted'] = $_formData['lastDrafted'];
2966		}
2967		//error_log(__METHOD__.__LINE__.' Mode:'.$_formData['mode'].' PID:'.$_formData['processedmail_id']);
2968		if (isset($_formData['mode']) && !empty($_formData['mode']))
2969		{
2970			if ($_formData['mode']=='forward' && !empty($_formData['processedmail_id']))
2971			{
2972				$this->sessionData['forwardFlag']='forwarded';
2973				$_formData['processedmail_id'] = explode(',',$_formData['processedmail_id']);
2974				$this->sessionData['uid']=array();
2975				foreach ($_formData['processedmail_id'] as $k =>$rowid)
2976				{
2977					$fhA = mail_ui::splitRowID($rowid);
2978					$this->sessionData['uid'][] = $fhA['msgUID'];
2979					$this->sessionData['forwardedUID'][] = $fhA['msgUID'];
2980					if (!empty($fhA['folder'])) $this->sessionData['sourceFolder'] = $fhA['folder'];
2981				}
2982			}
2983			if ($_formData['mode']=='reply' && !empty($_formData['processedmail_id']))
2984			{
2985				$rhA = mail_ui::splitRowID($_formData['processedmail_id']);
2986				$this->sessionData['uid'] = $rhA['msgUID'];
2987				$this->sessionData['messageFolder'] = $rhA['folder'];
2988			}
2989			if ($_formData['mode']=='composefromdraft' && !empty($_formData['processedmail_id']))
2990			{
2991				$dhA = mail_ui::splitRowID($_formData['processedmail_id']);
2992				$this->sessionData['uid'] = $dhA['msgUID'];
2993				$this->sessionData['messageFolder'] = $dhA['folder'];
2994			}
2995		}
2996		// if the body is empty, maybe someone pasted something with scripts, into the message body
2997		// this should not happen anymore, unless you call send directly, since the check was introduced with the action command
2998		if(empty($this->sessionData['body']))
2999		{
3000			// this is to be found with the egw_unset_vars array for the _POST['body'] array
3001			$name='_POST';
3002			$key='body';
3003			#error_log($GLOBALS['egw_unset_vars'][$name.'['.$key.']']);
3004			if (isset($GLOBALS['egw_unset_vars'][$name.'['.$key.']']))
3005			{
3006				$this->sessionData['body'] = self::_getCleanHTML( $GLOBALS['egw_unset_vars'][$name.'['.$key.']']);
3007				$_formData['body']=$this->sessionData['body'];
3008			}
3009			#error_log($this->sessionData['body']);
3010		}
3011		if(empty($this->sessionData['to']) && empty($this->sessionData['cc']) &&
3012		   empty($this->sessionData['bcc']) && empty($this->sessionData['folder'])) {
3013		   	$messageIsDraft = true;
3014		}
3015		try
3016		{
3017			$identity = Mail\Account::read_identity((int)$this->sessionData['mailidentity'],true);
3018		}
3019		catch (Exception $e)
3020		{
3021			$identity = array();
3022		}
3023		//error_log($this->sessionData['mailaccount']);
3024		//error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['mailidentity']).'->'.array2string($identity));
3025		// create the messages and store inline images
3026		$inline_images = $this->createMessage($mail, $_formData, $identity);
3027		// remember the identity
3028		if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') $fromAddress = $mail->From;//$mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':'');
3029		#print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>";
3030		#print "<pre>". $mail->getMessageBody() ."</pre><hr><br>";
3031		#exit;
3032		// check if there are folders to be used
3033		$folderToCheck = (array)$this->sessionData['folder'];
3034		$folder = array(); //for counting only
3035		$folderOnServerID = array();
3036		$folderOnMailAccount = array();
3037		foreach ($folderToCheck as $k => $f)
3038		{
3039			$fval=$f;
3040			$icServerID = $_formData['serverID'];//folders always assumed with serverID
3041			if (stripos($f,'::')!==false) list($icServerID,$fval) = explode('::',$f,2);
3042			if ($_formData['serverID']!=$_formData['mailaccount'])
3043			{
3044				if ($icServerID == $_formData['serverID'] )
3045				{
3046					$folder[$fval] = $fval;
3047					$folderOnServerID[] = $fval;
3048				}
3049				if ($icServerID == $_formData['mailaccount'])
3050				{
3051					$folder[$fval] = $fval;
3052					$folderOnMailAccount[] = $fval;
3053				}
3054			}
3055			else
3056			{
3057				if ($icServerID == $_formData['serverID'] )
3058				{
3059					$folder[$fval] = $fval;
3060					$folderOnServerID[] = $fval;
3061				}
3062			}
3063		}
3064		//error_log(__METHOD__.__LINE__.'#'.array2string($_formData['serverID']).'<serverID<->mailaccount>'.array2string($_formData['mailaccount']));
3065		// serverID ($_formData['serverID']) specifies where we originally came from.
3066		// mailaccount ($_formData['mailaccount']) specifies the mailaccount we send from and where the sent-copy should end up
3067		// serverID : is or may be needed to mark a mail as replied/forwarded or delete the original draft.
3068		// all other folders are tested against serverID that is carried with the foldername ID::Foldername; See above
3069		// (we work the folder from formData into folderOnMailAccount and folderOnServerID)
3070		// right now only folders from serverID or mailaccount should be selectable in compose form/dialog
3071		// we use the sentFolder settings of the choosen mailaccount
3072		// sentFolder is account specific
3073		$changeProfileOnSentFolderNeeded = false;
3074		if ($_formData['serverID']!=$_formData['mailaccount'])
3075		{
3076			$this->changeProfile($_formData['mailaccount']);
3077			//error_log(__METHOD__.__LINE__.'#'.$this->mail_bo->profileID.'<->'.$mail_bo->profileID.'#');
3078			$changeProfileOnSentFolderNeeded = true;
3079			// sentFolder is account specific
3080			$sentFolder = $this->mail_bo->getSentFolder();
3081			//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3082			if ($sentFolder&& $sentFolder!= 'none' && !$this->mail_bo->folderExists($sentFolder, true)) $sentFolder=false;
3083		}
3084		else
3085		{
3086			$sentFolder = $mail_bo->getSentFolder();
3087			//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3088			if ($sentFolder&& $sentFolder!= 'none' && !$mail_bo->folderExists($sentFolder, true)) $sentFolder=false;
3089		}
3090		//error_log(__METHOD__.__LINE__.' SentFolder configured:'.$sentFolder.'#');
3091
3092		// we switch $this->mail_bo back to the account we used to work on
3093		if ($_formData['serverID']!=$_formData['mailaccount'])
3094		{
3095			$this->changeProfile($_formData['serverID']);
3096		}
3097
3098
3099		if(isset($sentFolder) && $sentFolder && $sentFolder != 'none' &&
3100			$this->mailPreferences['sendOptions'] != 'send_only' &&
3101			$messageIsDraft == false)
3102		{
3103			if ($sentFolder)
3104			{
3105				if ($_formData['serverID']!=$_formData['mailaccount'])
3106				{
3107					$folderOnMailAccount[] = $sentFolder;
3108				}
3109				else
3110				{
3111					$folderOnServerID[] = $sentFolder;
3112				}
3113				$folder[$sentFolder] = $sentFolder;
3114			}
3115			else
3116			{
3117				$this->errorInfo = lang("No (valid) Send Folder set in preferences");
3118			}
3119		}
3120		else
3121		{
3122			if (((!isset($sentFolder)||$sentFolder==false) && $this->mailPreferences['sendOptions'] != 'send_only') ||
3123				($this->mailPreferences['sendOptions'] != 'send_only' &&
3124				$sentFolder != 'none')) $this->errorInfo = lang("No Send Folder set in preferences");
3125		}
3126		// draftFolder is on Server we start from
3127		if($messageIsDraft == true) {
3128			$draftFolder = $mail_bo->getDraftFolder();
3129			if(!empty($draftFolder) && $mail_bo->folderExists($draftFolder,true)) {
3130				$this->sessionData['folder'] = array($draftFolder);
3131				$folderOnServerID[] = $draftFolder;
3132				$folder[$draftFolder] = $draftFolder;
3133			}
3134		}
3135		if ($folderOnServerID) $folderOnServerID = array_unique($folderOnServerID);
3136		if ($folderOnMailAccount) $folderOnMailAccount = array_unique($folderOnMailAccount);
3137		if (($this->mailPreferences['sendOptions'] != 'send_only' && $sentFolder != 'none') &&
3138			!( count($folder) > 0) &&
3139			!($_formData['to_infolog']=='on' || $_formData['to_tracker']=='on'))
3140		{
3141			$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:'');
3142			#error_log($this->errorInfo);
3143			return false;
3144		}
3145		// SMIME SIGN/ENCRYPTION
3146		if ($_formData['smime_sign'] == 'on' || $_formData['smime_encrypt'] == 'on' )
3147		{
3148			$recipients = array_merge($_formData['to'], (array) $_formData['cc'], (array) $_formData['bcc']);
3149			try	{
3150				if ($_formData['smime_sign'] == 'on')
3151				{
3152					if ($_formData['smime_passphrase'] != '') {
3153						Api\Cache::setSession(
3154							'mail',
3155							'smime_passphrase',
3156							$_formData['smime_passphrase'],
3157							$GLOBALS['egw_info']['user']['preferences']['mail']['smime_pass_exp'] * 60
3158						);
3159					}
3160					$smime_success = $this->_encrypt(
3161						$mail,
3162						$_formData['smime_encrypt'] == 'on'? Mail\Smime::TYPE_SIGN_ENCRYPT: Mail\Smime::TYPE_SIGN,
3163						Mail::stripRFC822Addresses($recipients),
3164						$identity['ident_email'],
3165						$_formData['smime_passphrase']
3166					);
3167					if (!$smime_success)
3168					{
3169						$response = Api\Json\Response::get();
3170						$this->errorInfo = $_formData['smime_passphrase'] == ''?
3171								lang('You need to enter your S/MIME passphrase to send this message.'):
3172								lang('The entered passphrase is not correct! Please try again.');
3173						$response->call('app.mail.smimePassDialog', $this->errorInfo);
3174						return false;
3175					}
3176				}
3177				elseif ($_formData['smime_sign'] == 'off' && $_formData['smime_encrypt'] == 'on')
3178				{
3179					$smime_success =  $this->_encrypt(
3180						$mail,
3181						Mail\Smime::TYPE_ENCRYPT,
3182						Mail::stripRFC822Addresses($recipients),
3183						$identity['ident_email']
3184					);
3185				}
3186			}
3187			catch (Exception $ex)
3188			{
3189				$response = Api\Json\Response::get();
3190				$this->errorInfo = $ex->getMessage();
3191				return false;
3192			}
3193		}
3194
3195		// set a higher timeout for big messages
3196		@set_time_limit(120);
3197		//$mail->SMTPDebug = 10;
3198		//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']));
3199		if(count((array)$this->sessionData['to']) > 0 || count((array)$this->sessionData['cc']) > 0 || count((array)$this->sessionData['bcc']) > 0) {
3200			try {
3201				// do no close the session before sending, if we have to store the send text for infolog or other integration in the session
3202				if (!($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' ))
3203				{
3204					$GLOBALS['egw']->session->commit_session();
3205				}
3206				$mail->send();
3207			}
3208			catch(Exception $e) {
3209				_egw_log_exception($e);
3210				//if( $e->details ) error_log(__METHOD__.__LINE__.array2string($e->details));
3211				$this->errorInfo = $e->getMessage().($e->details?'<br/>'.$e->details:'');
3212				return false;
3213			}
3214		} else {
3215			if (count(array($this->sessionData['folder']))>0 && !empty($this->sessionData['folder'])) {
3216				//error_log(__METHOD__.__LINE__."Folders:".print_r($this->sessionData['folder'],true));
3217			} else {
3218				$this->errorInfo = lang("Error: ").lang("No Address TO/CC/BCC supplied, and no folder to save message to provided.");
3219				//error_log(__METHOD__.__LINE__.$this->errorInfo);
3220				return false;
3221			}
3222		}
3223		//error_log(__METHOD__.__LINE__."Mail Sent.!");
3224		//error_log(__METHOD__.__LINE__."Number of Folders to move copy the message to:".count($folder));
3225		//error_log(__METHOD__.__LINE__.array2string($folder));
3226		if ((count($folder) > 0) || (isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder']))
3227            || (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) {
3228			$mail_bo = $this->mail_bo;
3229			$mail_bo->openConnection();
3230			//$mail_bo->reopen($this->sessionData['messageFolder']);
3231			#error_log("(re)opened Connection");
3232		}
3233		// if copying mail to folder, or saving mail to infolog, we need to gather the needed information
3234		if (count($folder) > 0 || $_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') {
3235			//error_log(__METHOD__.__LINE__.array2string($this->sessionData['bcc']));
3236
3237			// normaly Bcc is only added to recipients, but not as header visible to all recipients
3238			$mail->forceBccHeader();
3239		}
3240		// copying mail to folder
3241		if (count($folder) > 0)
3242		{
3243			foreach($folderOnServerID as $folderName) {
3244				if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all
3245				//error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName));
3246				// if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID
3247				// if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue;
3248				if ($mail_bo->folderExists($folderName,true)) {
3249					if($mail_bo->isSentFolder($folderName)) {
3250						$flags = '\\Seen';
3251					} elseif($mail_bo->isDraftFolder($folderName)) {
3252						$flags = '\\Draft';
3253					} else {
3254						$flags = '\\Seen';
3255					}
3256					#$mailHeader=explode('From:',$mail->getMessageHeader());
3257					#$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1],
3258					//error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName));
3259					//$mail_bo->reopen($folderName);
3260					try
3261					{
3262						//error_log(__METHOD__.__LINE__.array2string($folderName));
3263						$mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags);
3264					}
3265					catch (Api\Exception\WrongUserinput $e)
3266					{
3267						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()));
3268					}
3269				}
3270				else
3271				{
3272					error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName));
3273				}
3274			}
3275			// if we choose to send from a differing profile
3276			if ($folderOnMailAccount)  $this->changeProfile($_formData['mailaccount']);
3277			foreach($folderOnMailAccount as $folderName) {
3278				if (is_array($folderName)) $folderName = array_shift($folderName); // should not happen at all
3279				//error_log(__METHOD__.__LINE__." attempt to save message to:".array2string($folderName));
3280				// if $_formData['serverID']!=$_formData['mailaccount'] skip copying to sentfolder on serverID
3281				// if($_formData['serverID']!=$_formData['mailaccount'] && $folderName==$sentFolder && $changeProfileOnSentFolderNeeded) continue;
3282				if ($this->mail_bo->folderExists($folderName,true)) {
3283					if($this->mail_bo->isSentFolder($folderName)) {
3284						$flags = '\\Seen';
3285					} elseif($this->mail_bo->isDraftFolder($folderName)) {
3286						$flags = '\\Draft';
3287					} else {
3288						$flags = '\\Seen';
3289					}
3290					#$mailHeader=explode('From:',$mail->getMessageHeader());
3291					#$mailHeader[0].$mail->AddrAppend("Bcc",$mailAddr).'From:'.$mailHeader[1],
3292					//error_log(__METHOD__.__LINE__." Cleared FolderTests; Save Message to:".array2string($folderName));
3293					//$mail_bo->reopen($folderName);
3294					try
3295					{
3296						//error_log(__METHOD__.__LINE__.array2string($folderName));
3297						$this->mail_bo->appendMessage($folderName, $mail->getRaw(), null, $flags);
3298					}
3299					catch (Api\Exception\WrongUserinput $e)
3300					{
3301						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()));
3302					}
3303				}
3304				else
3305				{
3306					error_log(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$this->sessionData['subject'],$folderName));
3307				}
3308			}
3309			if ($folderOnMailAccount)  $this->changeProfile($_formData['serverID']);
3310
3311			//$mail_bo->closeConnection();
3312		}
3313		// handle previous drafted versions of that mail
3314		$lastDrafted = false;
3315		if (isset($this->sessionData['lastDrafted']))
3316		{
3317			$lastDrafted=array();
3318			$dhA = mail_ui::splitRowID($this->sessionData['lastDrafted']);
3319			$lastDrafted['uid'] = $dhA['msgUID'];
3320			$lastDrafted['folder'] = $dhA['folder'];
3321			if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']);
3322			// manually drafted, do not delete
3323			// will be handled later on IF mode was $_formData['mode']=='composefromdraft'
3324			if (isset($lastDrafted['uid']) && (empty($lastDrafted['uid']) || $lastDrafted['uid'] == $this->sessionData['uid'])) $lastDrafted=false;
3325			//error_log(__METHOD__.__LINE__.array2string($lastDrafted));
3326		}
3327		if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder']))
3328		{
3329			try
3330			{
3331				if ($this->sessionData['lastDrafted'] != $this->sessionData['uid'] || !($_formData['mode']=='composefromdraft' &&
3332					($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )&&$this->sessionData['attachments']))
3333				{
3334					//error_log(__METHOD__.__LINE__."#".$lastDrafted['uid'].'#'.$lastDrafted['folder'].array2string($_formData));
3335					//error_log(__METHOD__.__LINE__."#".array2string($_formData));
3336					//error_log(__METHOD__.__LINE__."#".array2string($this->sessionData));
3337					$mail_bo->deleteMessages($lastDrafted['uid'],$lastDrafted['folder'],'remove_immediately');
3338				}
3339			}
3340			catch (Api\Exception $e)
3341			{
3342				//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3343				unset($e);
3344			}
3345		}
3346		unset($this->sessionData['lastDrafted']);
3347
3348		//error_log("handling draft messages, flagging and such");
3349		if((isset($this->sessionData['uid']) && isset($this->sessionData['messageFolder']))
3350			|| (isset($this->sessionData['forwardFlag']) && isset($this->sessionData['sourceFolder']))) {
3351			// mark message as answered
3352			$mail_bo->openConnection();
3353			$mail_bo->reopen(($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder']));
3354			// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
3355			// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
3356			if ($mail_bo->isDraftFolder($this->sessionData['messageFolder']) && !$mail_bo->isTemplateFolder($this->sessionData['messageFolder']))
3357			{
3358				try // message may be deleted already, as it maybe done by autosave
3359				{
3360					if ($_formData['mode']=='composefromdraft' &&
3361						!(($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on') && $this->sessionData['attachments']))
3362					{
3363						//error_log(__METHOD__.__LINE__."#".$this->sessionData['uid'].'#'.$this->sessionData['messageFolder']);
3364						$mail_bo->deleteMessages(array($this->sessionData['uid']),$this->sessionData['messageFolder'], 'remove_immediately');
3365					}
3366				}
3367				catch (Api\Exception $e)
3368				{
3369					//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3370					unset($e);
3371				}
3372			} else {
3373				$mail_bo->flagMessages("answered", $this->sessionData['uid'],($this->sessionData['messageFolder']?$this->sessionData['messageFolder']:$this->sessionData['sourceFolder']));
3374				//error_log(__METHOD__.__LINE__.array2string(array_keys($this->sessionData)).':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']);
3375				if (array_key_exists('forwardFlag',$this->sessionData) && $this->sessionData['forwardFlag']=='forwarded')
3376				{
3377					try
3378					{
3379						//error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['forwardedUID']).' F:'.$this->sessionData['sourceFolder']);
3380						$mail_bo->flagMessages("forwarded", $this->sessionData['forwardedUID'],$this->sessionData['sourceFolder']);
3381					}
3382					catch (Api\Exception $e)
3383					{
3384						//error_log(__METHOD__.__LINE__." ". str_replace('"',"'",$e->getMessage()));
3385						unset($e);
3386					}
3387				}
3388			}
3389			//$mail_bo->closeConnection();
3390		}
3391		if ($mail_bo) $mail_bo->closeConnection();
3392		//error_log("performing Infolog Stuff");
3393		//error_log(print_r($this->sessionData['to'],true));
3394		//error_log(print_r($this->sessionData['cc'],true));
3395		//error_log(print_r($this->sessionData['bcc'],true));
3396		if (is_array($this->sessionData['to']))
3397		{
3398			$mailaddresses['to'] = $this->sessionData['to'];
3399		}
3400		else
3401		{
3402			$mailaddresses = array();
3403		}
3404		if (is_array($this->sessionData['cc'])) $mailaddresses['cc'] = $this->sessionData['cc'];
3405		if (is_array($this->sessionData['bcc'])) $mailaddresses['bcc'] = $this->sessionData['bcc'];
3406		if (!empty($mailaddresses)) $mailaddresses['from'] = Mail\Html::decodeMailHeader($fromAddress);
3407
3408		if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )
3409		{
3410			$this->sessionData['attachments'] = array_merge((array)$this->sessionData['attachments'], (array)$inline_images);
3411
3412			foreach(array('to_infolog','to_tracker','to_calendar') as $app_key)
3413			{
3414				$entryid = $_formData['to_integrate_ids'][0][$app_key];
3415				if ($_formData[$app_key] == 'on')
3416				{
3417					$app_name = substr($app_key,3);
3418					// Get registered hook data of the app called for integration
3419					$hook = Api\Hooks::single(array('location'=> 'mail_import'),$app_name);
3420
3421					// store mail / eml in temp. file to not have to download it from mail-server again
3422					$eml = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'mail_integrate');
3423					$eml_fp = fopen($eml, 'w');
3424					stream_copy_to_stream($mail->getRaw(), $eml_fp);
3425					fclose($eml_fp);
3426					$target = array(
3427						'menuaction' => $hook['menuaction'],
3428						'egw_data' => Link::set_data(null,'mail_integration::integrate',array(
3429							$mailaddresses,
3430							$this->sessionData['subject'],
3431							$this->convertHTMLToText($this->sessionData['body']),
3432							$this->sessionData['attachments'],
3433							false, // date
3434							$eml,
3435							$_formData['serverID']),true),
3436						'app' => $app_name
3437					);
3438					if ($entryid) $target['entry_id'] = $entryid;
3439					// Open the app called for integration in a popup
3440					// and store the mail raw data as egw_data, in order to
3441					// be stored from registered app method later
3442					Framework::popup(Egw::link('/index.php', $target),'_blank',$hook['popup']);
3443				}
3444			}
3445		}
3446		// only clean up temp-files, if we dont need them for mail_integration::integrate
3447		elseif(is_array($this->sessionData['attachments']))
3448		{
3449			foreach($this->sessionData['attachments'] as $value) {
3450				if (!empty($value['file']) && parse_url($value['file'],PHP_URL_SCHEME) != 'vfs') {	// happens when forwarding mails
3451					unlink($GLOBALS['egw_info']['server']['temp_dir'].'/'.$value['file']);
3452				}
3453			}
3454		}
3455
3456		$this->sessionData = '';
3457
3458		return true;
3459	}
3460
3461	/**
3462	 * setDefaults, sets some defaults
3463	 *
3464	 * @param array $content
3465	 * @return array - the input, enriched with some not set attributes
3466	 */
3467	function setDefaults($content=array())
3468	{
3469		// if there's not already an identity selected for current account
3470		if (empty($content['mailidentity']))
3471		{
3472			// check if there a preference / previous selection of identity for current account
3473			if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']))
3474			{
3475				$sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'];
3476				if (!empty($sigPref[$this->mail_bo->profileID]) && $sigPref[$this->mail_bo->profileID]>0)
3477				{
3478					$content['mailidentity'] = $sigPref[$this->mail_bo->profileID];
3479				}
3480			}
3481			// if we have no preference search for first identity with non-empty signature
3482			if (empty($content['mailidentity']))
3483			{
3484				$default_identity = null;
3485				foreach(Mail\Account::identities($this->mail_bo->profileID, true, 'params') as $identity)
3486				{
3487					if (!isset($default_identity)) $default_identity = $identity['ident_id'];
3488					if (!empty($identity['ident_signature']))
3489					{
3490						$content['mailidentity'] = $identity['ident_id'];
3491						break;
3492					}
3493				}
3494			}
3495			if (empty($content['mailidentity'])) $content['mailidentity'] = $default_identity;
3496		}
3497		if (!isset($content['mimeType']) || empty($content['mimeType']))
3498		{
3499			$content['mimeType'] = 'html';
3500			if (!empty($this->mailPreferences['composeOptions']) && $this->mailPreferences['composeOptions']=="text") $content['mimeType']  = 'plain';
3501		}
3502		return $content;
3503
3504	}
3505
3506	function stripSlashes($_string)
3507	{
3508		if (get_magic_quotes_gpc()) {
3509			return stripslashes($_string);
3510		} else {
3511			return $_string;
3512		}
3513	}
3514	/**
3515	 * Callback function to search mail folders
3516	 *
3517	 * @param int $_searchStringLength
3518	 * @param boolean $_returnList
3519	 * @param int $_mailaccountToSearch
3520	 * @param boolean $_noPrefixId = false, if set to true folders name does not get prefixed by account id
3521	 * @return type
3522	 */
3523	function ajax_searchFolder($_searchStringLength=2, $_returnList=false, $_mailaccountToSearch=null, $_noPrefixId=false) {
3524		//error_log(__METHOD__.__LINE__.':'.array2string($_REQUEST));
3525		static $useCacheIfPossible = null;
3526		if (is_null($useCacheIfPossible)) $useCacheIfPossible = true;
3527		$_searchString = trim($_REQUEST['query']);
3528		$results = array();
3529		$rememberServerID = $this->mail_bo->icServer->ImapServerId;
3530		if (is_null($_mailaccountToSearch) && !empty($_REQUEST['mailaccount'])) $_mailaccountToSearch = $_REQUEST['mailaccount'];
3531		if (empty($_mailaccountToSearch)) $_mailaccountToSearch = $this->mail_bo->icServer->ImapServerId;
3532		if ($this->mail_bo->icServer && $_mailaccountToSearch && $this->mail_bo->icServer->ImapServerId != $_mailaccountToSearch)
3533		{
3534			$this->changeProfile($_mailaccountToSearch);
3535		}
3536		if (strlen($_searchString)>=$_searchStringLength && isset($this->mail_bo->icServer))
3537		{
3538			//error_log(__METHOD__.__LINE__.':'.$this->mail_bo->icServer->ImapServerId);
3539			$this->mail_bo->openConnection($this->mail_bo->icServer->ImapServerId);
3540			//error_log(__METHOD__.__LINE__.array2string($_searchString).'<->'.$searchString);
3541			$folderObjects = $this->mail_bo->getFolderObjects(true,false,true,$useCacheIfPossible);
3542			if (count($folderObjects)<=1) {
3543				$useCacheIfPossible = false;
3544			}
3545			else
3546			{
3547				$useCacheIfPossible = true;
3548			}
3549			$searchString = Api\Translation::convert($_searchString, Mail::$displayCharset,'UTF7-IMAP');
3550			foreach ($folderObjects as $k =>$fA)
3551			{
3552				//error_log(__METHOD__.__LINE__.$_searchString.'/'.$searchString.' in '.$k.'->'.$fA->displayName);
3553				$f=false;
3554				$key = $_noPrefixId?$k:$_mailaccountToSearch.'::'.$k;
3555				if ($_searchStringLength<=0)
3556				{
3557					$f=true;
3558					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3559				}
3560				if ($f==false && stripos($fA->displayName,$_searchString)!==false)
3561				{
3562					$f=true;
3563					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3564				}
3565				if ($f==false && stripos($k,$searchString)!==false)
3566				{
3567					$results[] = array('id'=>$key, 'label' => htmlspecialchars($fA->displayName));
3568				}
3569			}
3570		}
3571		if ($this->mail_bo->icServer && $rememberServerID != $this->mail_bo->icServer->ImapServerId)
3572		{
3573			$this->changeProfile($rememberServerID);
3574		}
3575		//error_log(__METHOD__.__LINE__.' IcServer:'.$this->mail_bo->icServer->ImapServerId.':'.array2string($results));
3576		if ($_returnList)
3577		{
3578			foreach ((array)$results as $k => $_result)
3579			{
3580				$rL[$_result['id']] = $_result['label'];
3581			}
3582			return $rL;
3583		}
3584		// switch regular JSON response handling off
3585		Api\Json\Request::isJSONRequest(false);
3586
3587		header('Content-Type: application/json; charset=utf-8');
3588		//error_log(__METHOD__.__LINE__);
3589		echo json_encode($results);
3590		exit();
3591	}
3592
3593	public static function ajax_searchAddress($_searchStringLength=2) {
3594		//error_log(__METHOD__. "request from seachAddress " . $_REQUEST['query']);
3595		$_searchString = trim($_REQUEST['query']);
3596		$include_lists = (boolean)$_REQUEST['include_lists'];
3597
3598		$contacts_obj = new Api\Contacts();
3599		$results = array();
3600		$mailPrefs = $GLOBALS['egw_info']['user']['preferences']['mail'];
3601		$contactLabelPref = !is_array($mailPrefs['contactLabel']) && !empty($mailPrefs['contactLabel']) ?
3602			explode(',', $mailPrefs['contactLabel']) : $mailPrefs['contactLabel'];
3603
3604		// Add some matching mailing lists, and some groups, limited by config
3605		if($include_lists)
3606		{
3607			$results += static::get_lists($_searchString, $contacts_obj);
3608		}
3609
3610		if ($GLOBALS['egw_info']['user']['apps']['addressbook'] && strlen($_searchString)>=$_searchStringLength)
3611		{
3612			//error_log(__METHOD__.__LINE__.array2string($_searchString));
3613			$showAccounts = $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] !== '1';
3614			$search = explode(' ', $_searchString);
3615			foreach ($search as $k => $v)
3616			{
3617				if (mb_strlen($v) < 3) unset($search[$k]);
3618			}
3619			$search_str = implode(' +', $search);	// tell contacts/so_sql to AND search patterns
3620			//error_log(__METHOD__.__LINE__.$_searchString);
3621			$filter = $showAccounts ? array() : array('account_id' => null);
3622			$filter['cols_to_search'] = array('n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id');
3623			$cols = array('n_fn','n_prefix','n_given','n_family','org_name','email','email_home', 'contact_id', 'etag');
3624			$contacts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false, 'OR', array(0,100), $filter);
3625			$cfs_type_email = Api\Storage\Customfields::get_email_cfs('addressbook');
3626			// additionally search the accounts, if the contact storage is not the account storage
3627			if ($showAccounts && $contacts_obj->so_accounts)
3628			{
3629				$filter['owner'] = 0;
3630				$accounts = $contacts_obj->search($search_str, $cols, 'n_fn', '', '%', false,'OR', array(0,100), $filter);
3631
3632				if ($contacts && $accounts)
3633				{
3634					$contacts = array_merge($contacts,$accounts);
3635					usort($contacts,function($a, $b)
3636					{
3637						return strcasecmp($a['n_fn'], $b['n_fn']);
3638					});
3639				}
3640				elseif($accounts)
3641				{
3642					$contacts =& $accounts;
3643				}
3644				unset($accounts);
3645			}
3646		}
3647
3648		if (is_array($contacts))
3649		{
3650			foreach($contacts as $contact)
3651			{
3652				$cf_emails = [];
3653				if ($cfs_type_email && ($cf_emails = $contacts_obj->read_customfields($contact['id'], $cfs_type_email)))
3654				{
3655					// cf_emails: [$contact['id'] => ['cf1'=>'email','cf2'=>'email2',...]]
3656					$cf_emails = array_values(reset($cf_emails));
3657				}
3658				foreach(array_merge(array($contact['email'],$contact['email_home']), $cf_emails) as $email)
3659				{
3660					// avoid wrong addresses, if an rfc822 encoded address is in addressbook
3661					//$email = preg_replace("/(^.*<)([a-zA-Z0-9_\-]+@[a-zA-Z0-9_\-\.]+)(.*)/",'$2',$email);
3662					$rfcAddr = Mail::parseAddressList($email);
3663					$_rfcAddr=$rfcAddr->first();
3664					if (!$_rfcAddr->valid)
3665					{
3666						continue; // skip address if we encounter an error here
3667					}
3668					$email = $_rfcAddr->mailbox.'@'.$_rfcAddr->host;
3669
3670					if (method_exists($contacts_obj,'search'))
3671					{
3672						$contact['n_fn']='';
3673						if (!empty($contact['n_prefix']) && (empty($contactLabelPref) || in_array('n_prefix', $contactLabelPref))) $contact['n_fn'] = $contact['n_prefix'];
3674						if (!empty($contact['n_given']) && (empty($contactLabelPref) || in_array('n_given', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_given'];
3675						if (!empty($contact['n_family']) && (empty($contactLabelPref) || in_array('n_family', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').$contact['n_family'];
3676						if (!empty($contact['org_name']) && (empty($contactLabelPref) || in_array('org_name', $contactLabelPref))) $contact['n_fn'] .= ($contact['n_fn']?' ':'').'('.$contact['org_name'].')';
3677						$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
3678					}
3679					else
3680					{
3681						$contact['n_fn'] = str_replace(array(',','@'),' ',$contact['n_fn']);
3682					}
3683					$args = explode('@', trim($email));
3684					$args[] = trim($contact['n_fn'] ? $contact['n_fn'] : $contact['fn']);
3685					$completeMailString = call_user_func_array('imap_rfc822_write_address', $args);
3686					if(!empty($email) && in_array($completeMailString ,$results) === false) {
3687						$results[] = array(
3688							'id'=>$completeMailString,
3689							'label' => $completeMailString,
3690							// Add just name for nice display, with title for hover
3691							'name' => $contact['n_fn'],
3692							'title' => $email,
3693							'icon' => Egw::link('/api/avatar.php', array(
3694								'contact_id' => $contact['id'],
3695								'etag' => $contact['etag']
3696							))
3697						 );
3698					}
3699				}
3700			}
3701		}
3702
3703		// Add groups
3704		$group_options = array('account_type' => 'groups');
3705		$groups = $GLOBALS['egw']->accounts->link_query($_searchString, $group_options);
3706		foreach($groups as $g_id => $name)
3707		{
3708			$group = $GLOBALS['egw']->accounts->read($g_id);
3709			if(!$group['account_email']) continue;
3710			$args = explode('@', trim($group['account_email']));
3711			$args[] = $name;
3712			$completeMailString = call_user_func_array('imap_rfc822_write_address', $args);
3713			$results[] = array(
3714				'id' => $completeMailString,
3715				'label' => $completeMailString,
3716				'name'	=> $name,
3717				'title' => $group['account_email']
3718			);
3719		}
3720
3721		 // switch regular JSON response handling off
3722		Api\Json\Request::isJSONRequest(false);
3723
3724		//error_log(__METHOD__.__LINE__.array2string($jsArray));
3725		header('Content-Type: application/json; charset=utf-8');
3726		echo json_encode($results);
3727		exit();
3728	}
3729
3730	/**
3731	 * Get list of matching distribution lists when searching for email addresses
3732	 *
3733	 * The results are limited by config setting.  Default 10 each of group lists and normal lists
3734	 *
3735	 * @param String $_searchString
3736	 * @param Contacts $contacts_obj
3737	 * @return array
3738	 */
3739	protected static function get_lists($_searchString, &$contacts_obj)
3740	{
3741		$group_lists = array();
3742		$manual_lists = array();
3743		$lists = array_filter(
3744			$contacts_obj->get_lists(Acl::READ),
3745			function($element) use($_searchString) {
3746				return (stripos($element, $_searchString) !== false);
3747			}
3748		);
3749
3750		foreach($lists as $key => $list_name)
3751		{
3752			$type = $key > 0 ? 'manual' : 'group';
3753			$list = array(
3754				'id'	=> $key,
3755				'name'	=> $list_name,
3756				'label'	=> $list_name,
3757				'class' => 'mailinglist ' . "{$type}_list",
3758				'title' => lang('Mailinglist'),
3759				'data'	=> $key
3760			);
3761			${"${type}_lists"}[] = $list;
3762		}
3763		$config = Api\Config::read('mail');
3764		$limit = $config['address_list_limit'] ?: 10;
3765		$trim = function($list) use ($limit) {
3766			if(count($list) <= $limit) return $list;
3767			$list[$limit-1]['class'].= ' more_results';
3768			$list[$limit-1]['title'] .= '  (' . lang('%1 more', count($list) - $limit) . ')';
3769			return array_slice($list, 0, $limit);
3770		};
3771		return array_merge($trim($group_lists), $trim($manual_lists));
3772	}
3773	/**
3774	 * Merge the selected contact ID into the document given in $_REQUEST['document']
3775	 * and send it.
3776	 *
3777	 * @param int $contact_id
3778	 */
3779	public function ajax_merge($contact_id)
3780	{
3781		$response = Api\Json\Response::get();
3782		if(class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
3783		{
3784			$document_merge = new $_REQUEST['merge']();
3785		}
3786		else
3787		{
3788			$document_merge = new Api\Contacts\Merge();
3789		}
3790		$this->mail_bo->openConnection();
3791
3792		if(($error = $document_merge->check_document($_REQUEST['document'],'')))
3793		{
3794			$response->error($error);
3795			return;
3796		}
3797
3798		// Actually do the merge
3799		$folder = $merged_mail_id = null;
3800		try
3801		{
3802			$results = $this->mail_bo->importMessageToMergeAndSend(
3803				$document_merge, Vfs::PREFIX . $_REQUEST['document'],
3804				// Send an extra non-numeric ID to force actual send of document
3805				// instead of save as draft
3806				array((int)$contact_id, ''),
3807				$folder,$merged_mail_id
3808			);
3809
3810			// Also save as infolog
3811			if($merged_mail_id && $_REQUEST['to_app'] && isset($GLOBALS['egw_info']['user']['apps'][$_REQUEST['to_app']]))
3812			{
3813				$rowid = mail_ui::generateRowID($this->mail_bo->profileID, $folder, $merged_mail_id, true);
3814				$data = mail_integration::get_integrate_data($rowid);
3815				if($data && $_REQUEST['to_app'] == 'infolog')
3816				{
3817					$bo = new infolog_bo();
3818					$entry = $bo->import_mail($data['addresses'],$data['subject'],$data['message'],$data['attachments'],$data['date']);
3819					if($_REQUEST['info_type'] && isset($bo->enums['type'][$_REQUEST['info_type']]))
3820					{
3821						$entry['info_type'] = $_REQUEST['info_type'];
3822					}
3823					$bo->write($entry);
3824				}
3825			}
3826		}
3827		catch (Exception $e)
3828		{
3829			$contact = $document_merge->contacts->read((int)$contact_id);
3830			//error_log(__METHOD__.' ('.__LINE__.') '.' ID:'.$val.' Data:'.array2string($contact));
3831			$email = ($contact['email'] ? $contact['email'] : $contact['email_home']);
3832			$nfn = ($contact['n_fn'] ? $contact['n_fn'] : $contact['n_given'].' '.$contact['n_family']);
3833			$response->error(lang('Sending mail to "%1" failed', "$nfn <$email>").
3834				"\n".$e->getMessage()
3835			);
3836		}
3837
3838		if($results['success'])
3839		{
3840			$response->data(implode(',',$results['success']));
3841		}
3842		if($results['failed'])
3843		{
3844			$response->error(implode(',',$results['failed']));
3845		}
3846	}
3847
3848	/**
3849	 * Method to do encryption on given mail object
3850	 *
3851	 * @param Api\Mailer $mail
3852	 * @param string $type encryption type
3853	 * @param array|string $recipients list of recipients
3854	 * @param string $sender email of sender
3855	 * @param string $passphrase = '', SMIME Private key passphrase
3856	 *
3857	 * @return boolean returns true if successful and false if passphrase required
3858	 * @throws Api\Exception\WrongUserinput if no certificate found
3859	 */
3860	protected function _encrypt($mail, $type, $recipients, $sender, $passphrase='')
3861	{
3862		$AB = new addressbook_bo();
3863		 // passphrase of sender private key
3864		$params['passphrase'] = $passphrase;
3865
3866		try
3867		{
3868			$sender_cert = $AB->get_smime_keys($sender);
3869			if (!$sender_cert)	throw new Exception(lang("S/MIME Encryption failed because no certificate has been found for sender address: %1", $sender));
3870			$params['senderPubKey'] = $sender_cert[strtolower($sender)];
3871
3872			if (isset($sender) && ($type == Mail\Smime::TYPE_SIGN || $type == Mail\Smime::TYPE_SIGN_ENCRYPT))
3873			{
3874				$acc_smime = Mail\Smime::get_acc_smime($this->mail_bo->profileID, $params['passphrase']);
3875				$params['senderPrivKey'] = $acc_smime['pkey'];
3876				$params['extracerts'] = $acc_smime['extracerts'];
3877			}
3878
3879			if (isset($recipients) && ($type == Mail\Smime::TYPE_ENCRYPT || $type == Mail\Smime::TYPE_SIGN_ENCRYPT))
3880			{
3881				$params['recipientsCerts'] = $AB->get_smime_keys($recipients);
3882				foreach ($recipients as &$recipient)
3883				{
3884					if (!$params['recipientsCerts'][strtolower($recipient)]) $missingCerts []= $recipient;
3885				}
3886				if (is_array($missingCerts)) throw new Exception ('S/MIME Encryption failed because no certificate has been found for following addresses: '. implode ('|', $missingCerts));
3887			}
3888
3889			return $mail->smimeEncrypt($type, $params);
3890		}
3891		catch(Api\Exception\WrongUserinput $e)
3892		{
3893			throw new $e;
3894		}
3895	}
3896
3897	/**
3898	 * Builds attachments from provided UIDs and add them to sessionData
3899	 *
3900	 * @param string|array $_ids series of message ids
3901	 * @param int $_serverID compose current profileID
3902	 *
3903	 * @return array returns an array of attachments
3904	 *
3905	 * @throws Exception throws exception on cross account attempt
3906	 */
3907	function _get_uids_as_attachments ($_ids, $_serverID)
3908	{
3909		$ids = is_array($_ids) ? $_ids : explode(',', $_ids);
3910		if (is_array($ids) && $_serverID)
3911		{
3912			$parts = mail_ui::splitRowID($ids[0]);
3913			if ($_serverID != $parts['profileID'])
3914			{
3915				throw new Exception(lang('Cross account forward attachment is not allowed!'));
3916			}
3917		}
3918		foreach ($ids as &$id)
3919		{
3920			$parts = mail_ui::splitRowID($id);
3921			$mail_bo    = $this->mail_bo;
3922			$mail_bo->openConnection();
3923			$mail_bo->reopen($parts['folder']);
3924			$headers	= $mail_bo->getMessageEnvelope($parts['msgUID'], null,false,$parts['folder']);
3925			$this->addMessageAttachment($parts['msgUID'], null, $parts['folder'],
3926					$mail_bo->decode_header(($headers['SUBJECT']?$headers['SUBJECT']:lang('no subject'))).'.eml',
3927					'MESSAGE/RFC822', $headers['SIZE'] ? $headers['SIZE'] : lang('unknown'));
3928			$mail_bo->closeConnection();
3929		}
3930		return $this->sessionData['attachments'];
3931	}
3932}
3933