1<?php
2/**
3 * EGroupware Tracker - Handle incoming mails
4 *
5 * This class handles incoming mails in the async services.
6 * It is an addition for the eGW Tracker app by Ralf Becker
7 *
8 * @link http://www.egroupware.org
9 * @author Oscar van Eijk <oscar.van.eijk-AT-oveas.com>
10 * @package tracker
11 * @copyright (c) 2008 by Oscar van Eijk <oscar.van.eijk-AT-oveas.com>
12 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
13 * @version $Id$
14 */
15
16use EGroupware\Api;
17use EGroupware\Api\Link;
18
19use EGroupware\Api\Mail;
20
21class tracker_mailhandler extends tracker_bo
22{
23	/**
24	 * UID of the mailsender, 0 if not recognized
25	 *
26	 * @var int
27	 */
28	var $mailSender;
29
30	/**
31	 * Subject line of the incoming mail
32	 *
33	 * @var string
34	 */
35	var $mailSubject;
36
37	/**
38	 * Text from the mailbody (1st part)
39	 *
40	 * @var string
41	 */
42	var $mailBody;
43
44	/**
45	 * Identification of the mailbox
46	 *
47	 * @var string
48	 */
49	var $mailBox;
50
51	/**
52	 * List with all messages retrieved from the server
53	 *
54	 * @var array
55	 */
56	var $msgList = array();
57
58	/**
59	 * Mailbox stream
60	 *
61	 * @var int
62	 */
63	var $mbox;
64
65	/**
66	 * smtpObject for autoreplies and worwarding
67	 *
68	 * @var send object
69	 */
70	var $smtpMail;
71
72	/**
73	 * Ticket ID or 0 if not recognize
74	 *
75	 * @var int
76	 */
77	var $ticketId;
78
79	/**
80	 * User ID currently executing. Used in case we execute in fallback
81	 *
82	 * @var int
83	 */
84	var $originalUser;
85
86	/**
87	 * Supported mailservertypes, extracted from parent::mailservertypes
88	 *
89	 * @var array
90	 */
91	var $serverTypes = array();
92
93	/**
94	 * How much should be logged to the apache error-log
95	 *
96	 * 0 = Nothing
97	 * 1 = only errors
98	 * 2 = more debug info
99	 * 3 = complete debug info
100	 */
101	const LOG_LEVEL = 0;
102
103	/**
104	 * Constructor
105	 * @param array mailhandlingConfig - optional mailhandling config, to overrule the config loaded by parent
106	 */
107	function __construct($mailhandlingConfig=null)
108	{
109		parent::__construct();
110		if (!is_null($mailhandlingConfig)) $this->mailhandling = $mailhandlingConfig;
111		// In case we run in fallback, make sure the original user gets restored
112		$this->originalUser = $this->user;
113		foreach($this->mailservertypes as $typ)
114		{
115			$this->serverTypes[] = $typ[0];
116		}
117		if (($this->mailBox = self::get_mailbox(0)) === false)
118		{
119			return false;
120		}
121	}
122
123	/**
124	 * Destructor, close the stream if not done before.
125	 */
126	function __destruct()
127	{
128	}
129
130	/**
131	 * Compare 2 given mailbox settings (for a given set of properties)
132	 * @param defaultImap Object $reference reference
133	 * @param defaultImap Object $profile compare to reference
134	 * @return mixed false/array with the differences found; empty array when no differences for the predefined set of keys are found; false if either one is not of type defaultImap
135	 */
136	static function compareMailboxSettings($reference, $profile)
137	{
138		$diff = array();
139		if (!($reference instanceof Mail\Imap)) return false;
140		if (!($profile instanceof Mail\Imap)) return false;
141		//error_log(__METHOD__.__LINE__.' Reference:'.get_class($reference));
142		//error_log(__METHOD__.__LINE__.' Reference:'.array2string($reference));
143		//error_log(__METHOD__.__LINE__.' Profile:'.get_class($profile));
144		//error_log(__METHOD__.__LINE__.' Profile:'.array2string($profile));
145		if (get_class($reference) != get_class($profile)) return false;
146		if ($profile instanceof Mail\Imap)
147		{
148			if ($reference->ImapServerId != $profile->ImapServerId) $diff['ImapServerId']=array('reference'=>$reference->ImapServerId,'profile'=>$profile->ImapServerId);
149			try
150			{
151				if ($reference->acc_imap_host != $profile->acc_imap_host) $diff['acc_imap_host']=array('reference'=>$reference->acc_imap_host,'profile'=>$profile->acc_imap_host);
152				if ($reference->acc_imap_port != $profile->acc_imap_port) $diff['acc_imap_port']=array('reference'=>$reference->acc_imap_port,'profile'=>$profile->acc_imap_port);
153				if ($reference->acc_imap_username != $profile->acc_imap_username) $diff['acc_imap_username']=array('reference'=>$reference->acc_imap_username,'profile'=>$profile->acc_imap_username);
154				if ($reference->acc_imap_password != $profile->acc_imap_password) $diff['acc_imap_password']=array('reference'=>$reference->acc_imap_password,'profile'=>$profile->acc_imap_password);
155			}
156			catch(Exception $e)
157			{
158				unset($e);	// not used
159				if ($reference->hostspec != $profile->hostspec) $diff['acc_imap_host']=array('reference'=>$reference->hostspec,'profile'=>$profile->hostspec);
160				if ($reference->port != $profile->port) $diff['acc_imap_port']=array('reference'=>$reference->port,'profile'=>$profile->port);
161				if ($reference->username != $profile->username) $diff['acc_imap_username']=array('reference'=>$reference->username,'profile'=>$profile->username);
162				if ($reference->password != $profile->password) $diff['acc_imap_password']=array('reference'=>$reference->password,'profile'=>$profile->password);
163			}
164		}
165		return $diff;
166	}
167
168	/**
169	 * Compose the mailbox identification
170	 *
171	 * @return string mailbox identification as '{server[:port]/type}folder'
172	 */
173	function get_mailbox($queue = 0)
174	{
175		if (empty($this->mailhandling[$queue]['server']))
176		{
177			return false; // Or should we default to 'localhost'?
178		}
179		if ($this->mailhandling[$queue]['servertype']<=2)
180		{
181			try
182			{
183				$params['acc_imap_type'] = 'EGroupware\\Api\\Mail\\Imap';
184				$params['acc_id'] = 'tracker_'.trim($queue);
185				$params['acc_imap_host'] = $this->mailhandling[$queue]['server'];
186				$params['acc_imap_ssl'] = ($this->mailhandling[$queue]['servertype']==2?3:($this->mailhandling[$queue]['servertype']==1?2:0));
187				$params['acc_imap_port'] = $this->mailhandling[$queue]['serverport'];
188				$params['acc_imap_username'] = $this->mailhandling[$queue]['username'];
189				$params['acc_imap_password'] = $this->mailhandling[$queue]['password'];
190				$params['acc_sieve_enabled'] = false;
191				$params['acc_smtp_type'] = 'EGroupware\\Api\\Mail\\Smtp'; // needed, else the constructor fails
192				$eaaccount = new Mail\Account($params);
193				$icServer = $eaaccount->imapServer();
194				return $icServer;
195			}
196			catch (Exception $e)
197			{
198				error_log(__METHOD__.__LINE__.' Failed loading mail profile:'.$e->getMessage());
199				return false;
200			}
201		}
202	}
203
204	/**
205	 * Get all mails from the server. Invoked by the async timer
206	 *
207	 * @param int|string|array $queue Which tracker queue to check mail for (array('tr_tracker' => $queue)
208	 * @param boolean TestConnection=false
209	 * @return boolean true=run finished, false=an error occured
210	 */
211	function check_mail($queue = 0, $TestConnection=false)
212	{
213		$matches = null;
214		// new format with array('tr_tracker' => $queue)
215		if (is_array($queue))
216		{
217			$queue = $queue['tr_tracker'];
218		}
219		// remove quotes added by async-service
220		elseif(!is_numeric($queue) && preg_match('/([0-9]+)/', $queue, $matches))
221		{
222			$queue = $matches[1];
223		}
224		// Config for all passes null
225		if(!$queue) {
226			$queue = 0;
227		} else {
228			// Mailbox for all is pre-loaded, for others we have to change it
229			$this->mailBox = self::get_mailbox($queue);
230		}
231		if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__." for $queue on".' # Instance='.$GLOBALS['egw_info']['user']['domain']);
232
233		if ($this->mailBox === false)
234		{
235			if ($TestConnection) throw new Api\Exception\WrongUserinput(lang("incomplete server profile for mailhandling provided; Disabling mailhandling for Queue %1", $queue));
236			// this line should prevent adding garbage to mailhandlerconfig
237			if (!isset($this->mailhandling[$queue]) || empty($this->mailhandling[$queue]) || $this->mailhandling[$queue]['interval']==0) return false;
238			error_log(__METHOD__.','.__LINE__.lang("incomplete server profile for mailhandling provided; Disabling mailhandling for Queue %1", $queue.' # Instance='.$GLOBALS['egw_info']['user']['domain']));
239			$this->mailhandling[$queue]['interval']=0;
240			$this->save_config();
241			return false;
242		}
243		if ($this->mailBox instanceof Mail\Imap)
244		{
245			if (/*$this->mailhandling[$queue]['auto_reply'] ||*/ $this->mailhandling[$queue]['autoreplies'] || $this->mailhandling[$queue]['unrecognized_mails'])
246			{
247				if(is_object($this->smtpMail))
248				{
249					unset($this->smtpMail);
250				}
251				try
252				{
253					$this->smtpMail = new Api\Mailer('initbasic');
254					if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.array2string($this->smtpMail));
255				} catch(Exception $e) {
256					// ignore exception, but log it, to block the account and give a correct error-message to user
257					error_log(__METHOD__."(".__LINE__.") "." Could not initiate smtpMail for notification purpose:".$e->getMessage());
258					unset($this->smtpMail);
259				}
260
261			}
262			$rFP=Api\Cache::getCache(Api\Cache::INSTANCE,'email','rememberFailedProfile_'.trim($this->mailBox->ImapServerId));
263			if ($rFP && !empty($rFP))
264			{
265				$d = self::compareMailboxSettings($this->mailBox,$rFP);
266				if ($d===false || empty($d))
267				{
268					if ($TestConnection==false)
269					{
270						error_log(__METHOD__.','.__LINE__." ".lang("eGroupWare Tracker Mailhandling: could not connect previously, and profile did not change"));
271						error_log(__METHOD__.','.__LINE__." ".lang("refused to open mailbox: %1",array2string($this->mailBox)));
272						$previousInterval = $this->mailhandling[$queue]['interval'];
273						$this->mailhandling[$queue]['interval']=$this->mailhandling[$queue]['interval']*2;
274						$this->save_config();
275						Api\Cache::setCache(Api\Cache::INSTANCE,'email','rememberFailedProfile_'.trim($this->mailBox->ImapServerId),array(),$expiration=60*10);
276						if ($GLOBALS['egw_info']['server']['admin_mails'] && $this->smtpMail)
277						{
278							// notify admin(s) via email
279							$from    = 'eGroupWareTrackerMailHandling@'.$GLOBALS['egw_info']['server']['mail_suffix'];
280							$subject = lang("eGroupWare Tracker Mailhandling: could not connect previously, and profile did not change");
281							$body    = lang("refused to open mailbox therefore changed Interval from %1 to %2",$previousInterval,$this->mailhandling[$queue]['interval']);
282							$body    .= "\n";
283							$body    .= lang("Mailbox settings used: %1",array2string($this->mailBox));
284
285							$admin_mails = explode(',',$GLOBALS['egw_info']['server']['admin_mails']);
286							foreach($admin_mails as $to)
287							{
288								try {
289										$GLOBALS['egw']->send->msg('email',$to,$subject,$body,'','','',$from,$from);
290								}
291								catch(Exception $e) {
292									// ignore exception, but log it, to block the account and give a correct error-message to user
293									error_log(__METHOD__."('$to') ".$e->getMessage());
294								}
295							}
296						}
297						return false;
298					}
299				}
300			}
301			$mailobject	= Mail::getInstance(false,$this->mailBox->ImapServerId,false,$this->mailBox);
302			if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.'#'.array2string($this->mailBox));
303
304			$connectionFailed = false;
305			// connect
306			try
307			{
308				$mailobject->openConnection($this->mailBox->ImapServerId);
309				$_folderName = (!empty($this->mailhandling[$queue]['folder'])?$this->mailhandling[$queue]['folder']:'INBOX');
310				$mailobject->reopen($_folderName);
311			}
312			catch (Exception $e)
313			{
314				$connectionFailed=true;
315				$mailobjecterrorMessage = $e->getMessage();
316			}
317			if ($TestConnection===true)
318			{
319				if (self::LOG_LEVEL>0) error_log(__METHOD__.','.__LINE__." failed to open mailbox:".array2string($mailobject->icServer));
320				if ($connectionFailed) throw new Api\Exception\WrongUserinput(lang("failed to open mailbox: %1 -> disabled for automatic mailprocessing!",($mailobjecterrorMessage?$mailobjecterrorMessage:lang('could not connect'))));
321				return true;//everythig all right
322			}
323			if ($connectionFailed)
324			{
325				Api\Cache::setCache(Api\Cache::INSTANCE,'email','rememberFailedProfile_'.trim($this->mailBox->ImapServerId),$this->mailBox,$expiration=60*60*5);
326				if (self::LOG_LEVEL>0) error_log(__METHOD__.','.__LINE__." failed to open mailbox:".array2string($this->mailBox));
327				return false;
328			}
329			else
330			{
331				Api\Cache::setCache(Api\Cache::INSTANCE,'email','rememberFailedProfile_'.trim($this->mailBox->ImapServerId),array(),$expiration=60*10);
332			}
333			// load lang stuff for mailheaderInfoSection creation
334			Api\Translation::add_app('mail');
335			// retrieve list
336			if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__." Processing mailbox {$_folderName} with ServerID:".$mailobject->icServer->ImapServerId." for queue $queue\n".array2string($mailobject->icServer));
337			$_filter=array('status'=>array('UNSEEN','UNDELETED'));
338			if (!empty($this->mailhandling[$queue]['address']))
339			{
340				$_filter['type']='TO';
341				$_filter['string']=trim($this->mailhandling[$queue]['address']);
342			}
343			$_reverse=1;
344			$_rByUid = true;
345			$_sortResult = $mailobject->getSortedList($_folderName, $_sort=0, $_reverse, $_filter, $_rByUid, false);
346			$sortResult = $_sortResult['match']->ids;
347			if (self::LOG_LEVEL>1 && $sortResult) error_log(__METHOD__.__LINE__.'#'.array2string($sortResult));
348			$deletedCounter = 0;
349			$mailobject->reopen($_folderName);
350			foreach ((array)$sortResult as $uid)
351			{
352				if (empty($uid)) continue;
353				if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__.'# fetching Data for:'.array2string(array('uid'=>$uid,'folder'=>$_folderName)).' Mode:'.$this->htmledit.' SaveAsOption:'.$GLOBALS['egw_info']['user']['preferences']['mail']['saveAsOptions']);
354				if ($uid)
355				{
356					$this->user = $this->originalUser;
357					$htmlEditOrg = $this->htmledit; // preserve that, as an existing ticket may be of a different mode
358					if (self::process_message2($mailobject, $uid, $_folderName, $queue) && $this->mailhandling[$queue]['delete_from_server'])
359					{
360						try
361						{
362							$mailobject->deleteMessages($uid, $_folderName, 'move_to_trash');
363							$deletedCounter++;
364						}
365						catch (Exception $e)
366						{
367							error_log(__METHOD__.__LINE__." Failed to move Message (".array2string($uid).") from Folder $_folderName to configured TrashFolder Error:".$e->getMessage());
368						}
369					}
370					$this->htmledit = $htmlEditOrg;
371				}
372			}
373			// Expunge deleted mails, if any
374			if ($deletedCounter) // NOTE THERE MAY BE DELETED MESSAGES AFTER THE PROCESSING
375			{
376				$mailobject->reopen($_folderName);
377				$rv = $mailobject->compressFolder($_folderName);
378				if (self::LOG_LEVEL && PEAR::isError($rv)) error_log(__METHOD__." failed to expunge Message(s) from Folder: ".$_folderName.' due to:'.$rv->message);
379			}
380
381			// Close the connection
382			//$mailobject->closeConnection(); // not sure we should do that, as this seems to kill more then our connection
383
384			$this->user = $this->originalUser;
385			return true;
386		}
387	}
388
389	/**
390	 * determines the mime type of a eMail in accordance to the imap_fetchstructure
391	 * found at http://www.linuxscope.net/articles/mailAttachmentsPHP.html
392	 * by Kevin Steffer
393	 */
394	function get_mime_type(&$structure)
395	{
396	}
397
398	function get_part($stream, $msg_number, $mime_type, $structure = false, $part_number = false)
399	{
400	} // END OF FUNCTION
401
402	/**
403	 * Extract the latest reply from mail body message
404	 *
405	 *
406	 * @param {string} $mailBody string of mail body message
407	 *
408	 * @return {string} latest reply of mail body message
409	 *
410	 * @todo Find an optimize and accurate pattern/method to recognize content of mail message (e.g. Recognition/Classification algorithm like Perceptron or similar)
411	 */
412	function extract_latestReply ($mailBody)
413	{
414		$mailCntArray = preg_split("/(\r\n|\n|\r)/",$mailBody);
415		$oMInx = 0;
416		$noReplyMatch = true;
417		foreach (array_keys($mailCntArray) as $key)
418		{
419			if (preg_match("/-----.*".lang("original message")."---.*/i", $mailCntArray[$key]) && $oMInx === 0)
420			{
421				$oMInx = $key;
422			}
423			if (preg_match("/^>.*|\<\/blockquote\>/",$mailCntArray[$key]))
424			{
425				$noReplyMatch = false;
426				if ($oMInx > 0)
427				{
428					for ($i =  $oMInx; $i<$key; $i++)
429					{
430						unset ($mailCntArray[$i]);
431					}
432					unset ($mailCntArray[$i]);
433				}
434			}
435		}
436		// try to cleanup original part even if not finding ">" or "blockquote"
437		if ($noReplyMatch && $oMInx > 0)
438		{
439			foreach (array_keys($mailCntArray) as $key)
440			{
441				if ($key >= $oMInx) unset ($mailCntArray[$key]);
442			}
443		}
444		return join("\n", $mailCntArray);
445	}
446
447	/**
448	 * Retrieve and decode a bodypart
449	 *
450	 * @param int Message ID from the server
451	 * @param string The body part, defaults to "1"
452	 * @return string The decoded bodypart
453	 */
454	function get_mailbody ($mid, $section=false, $structure = false)
455	{
456	}
457
458	/**
459	 * Check if this is an automated message (bounce, autoreply...)
460	 * @TODO This is currently a very basic implementation, the intention is to implement more checks,
461	 * eg, filter failing addresses and remove them from CC.
462	 *
463	 * @param int $mid Message ID
464	 * @param array $msgHeader IMap header
465	 * @return boolean
466	 */
467	function is_automail($mid, $msgHeader)
468	{
469	}
470
471	/**
472	 * Check if this is an automated message (bounce, autoreply...)
473	 * @TODO This is currently a very basic implementation, the intention is to implement more checks,
474	 * eg, filter failing addresses and remove them from CC.
475	 *
476	 * @param object mailobject holding the server, and its connection
477	 * @param int message ID from the server
478	 * @param string subject the messages subject
479	 * @param array msgHeaders full headers retrieved for message
480	 * @param int queue the queue we are in
481	 * @return boolean status
482	 */
483	function is_automail2($mailobject, $uid, $subject, $msgHeaders, $queue=0)
484	{
485		// This array can be filled with checks that should be made.
486		// 'bounces' and 'autoreplies' (level 1) are the keys coded below, the level 2 arrays
487		// must match $msgHeader properties.
488		//
489		$autoMails = array(
490			 'bounces' => array(
491				 'subject' => array(
492				)
493				,'from' => array(
494					 'mailer-daemon'
495				)
496			)
497			,'autoreplies' => array(
498				 'subject' => array(
499					 'out of the office',
500					 'out of office',
501					 'autoreply'
502					)
503				,'auto-submitted' => array(
504					'auto-replied'
505				)
506			)
507		);
508
509		// Check for bounced messages
510		foreach ($autoMails['bounces'] as $_k => $_v) {
511			if (count($_v) == 0) {
512				continue;
513			}
514			$_re = '/(' . implode('|', $_v) . ')/i';
515			if (preg_match($_re, $msgHeader[strtoupper($_k)])) {
516				switch ($this->mailhandling[0]['bounces']) {
517					case 'delete' :		// Delete, whatever the overall delete setting is
518						$returnVal = $mailobject->deleteMessages($uid, $_folderName, 'move_to_trash');
519						break;
520					case 'forward' :	// Return the status of the forward attempt
521						$returnVal = $this->forward_message2($mailobject, $uid, $mailcontent['subject'], lang("automatic mails (bounces) are configured to be forwarded"), $queue);
522						if ($returnVal)
523						{
524							$mailobject->flagMessages('seen', $uid, $_folderName);
525							$mailobject->flagMessages('forwarded', $uid, $_folderName);
526						}
527					default :			// default: 'ignore'
528						break;
529				}
530				return true;
531			}
532		}
533
534		// Check for autoreplies
535		foreach ($autoMails['autoreplies'] as $_k => $_v) {
536			if (count($_v) == 0) {
537				continue;
538			}
539			$_re = '/(' . implode('|', $_v) . ')/i';
540			if (preg_match($_re, $msgHeader[strtoupper($_k)])) {
541				switch ($this->mailhandling[0]['autoreplies']) {
542					case 'delete' :		// Delete, whatever the overall delete setting is
543						$returnVal = $mailobject->deleteMessages($uid, $_folderName, 'move_to_trash');
544						break;
545					case 'forward' :	// Return the status of the forward attempt
546						$returnVal = $this->forward_message2($mailobject, $uid, $mailcontent['subject'], lang("automatic mails (replies) are configured to be forwarded"), $queue);
547						if ($returnVal)
548						{
549							$mailobject->flagMessages('seen', $uid, $_folderName);
550							$mailobject->flagMessages('forwarded', $uid, $_folderName);
551						}
552						break;
553					case 'process' :	// Process normally...
554						return false;	// ...so act as if it's no automail
555					default :			// default: 'ignore'
556						break;
557				}
558				return true;
559			}
560		}
561	}
562
563	/**
564	 * Decode a mail header
565	 *
566	 * @param string Pointer to the (possibly) encoded header that will be changes
567	 */
568	function decode_header (&$header)
569	{
570	}
571
572	/**
573	 * Process a messages from the mailbox
574	 *
575	 * @param int Message ID from the server
576	 * @param int queue tracking queue_id
577	 * @return boolean true=message successfully processed, false=message couldn't or shouldn't be processed
578	 */
579	function process_message ($mid, $queue)
580	{
581	}
582
583	/**
584	 * Process a messages from the mailbox
585	 *
586	 * @param int mailobject that holds connection to the server
587	 * @param int Message ID from the server
588	 * @param string _folderName the folder where the messages should reside in
589	 * @param int queue tracking queue_id
590	 * @return boolean true=message successfully processed, false=message couldn't or shouldn't be processed
591	 */
592	function process_message2 ($mailobject, $uid, $_folderName, $queue)
593	{
594		$senderIdentified = true;
595		$sR = $mailobject->getHeaders($_folderName, $_startMessage=1, 1, 'INTERNALDATE', true, array(), $uid, false);
596		$s = $sR['header'][$uid];
597		$subject_in = $mailobject->decode_subject($s['subject']);// we use the needed headers for determining beforehand, if we have a new ticket, or a comment
598		// FLAGS - control in case filter wont work
599		$flags = $s;//implicit with retrieved information on getHeaders
600		if ($flags['deleted'] || $flags['seen'])
601		{
602			return false; // Already seen or deleted (in case our filter did not work as intended)
603		}
604		// should do the same as checking only recent, but is more robust as recent is a flag with some sideeffects
605		// message should be marked/flagged as seen after processing
606		// (don't forget to flag the message if forwarded; as forwarded is not supported with all IMAP use Seen instead)
607		if (($flags['recent'] && $flags['seen']) ||
608			($flags['answered'] && $flags['seen']) || // is answered and seen
609			$flags['draft']) // is Draft
610		{
611			if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__.':'."UID:$uid in Folder $_folderName with".' Subject:'.$subject_in.
612				"\n Date:".$s['date'].
613	            "\n Flags:".print_r($flags,true).
614				"\n Stopped processing Mail ($uid). Not recent, new, or already answered, or draft");
615			return false;
616		}
617		$subject = Mail::adaptSubjectForImport($subject_in);
618		$tId = $this->get_ticketId($subject);
619		if ($tId)
620		{
621			$t = $this->read($tId);
622			$this->htmledit = $t['tr_edit_mode']=='html';
623		}
624		$addHeaderInfoSection = false;
625		if (isset($this->mailhandling[$queue]['mailheaderhandling']) && $this->mailhandling[$queue]['mailheaderhandling']>0)
626		{
627			//$tId == 0 will be new ticket, else will indicate comment
628			if ($this->mailhandling[$queue]['mailheaderhandling']==1) $addHeaderInfoSection=($tId == 0 ? true : false);
629			if ($this->mailhandling[$queue]['mailheaderhandling']==2) $addHeaderInfoSection=($tId == 0 ? false: true);
630			if ($this->mailhandling[$queue]['mailheaderhandling']==3) $addHeaderInfoSection=true;
631		}
632		if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__."# $uid with title:".$subject.($tId==0?' for new ticket':' for ticket:'.$tId).'. FetchMailHeader:'.$addHeaderInfoSectiont.' mailheaderhandling:'.$this->mailhandling[$queue]['mailheaderhandling']);
633		$mailcontent = $mailobject::get_mailcontent($mailobject,$uid,$partid='',$_folderName,$this->htmledit,$addHeaderInfoSection,(!($GLOBALS['egw_info']['user']['preferences']['mail']['saveAsOptions']==='text_only')));
634
635		// on we go, as everything seems to be in order. flagging the message
636		$rv = $mailobject->flagMessages('seen', $uid, $_folderName);
637		if ( PEAR::isError($rv)) error_log(__METHOD__.__LINE__." failed to flag Message $uid as Seen in Folder: ".$_folderName.' due to:'.$rv->message);
638
639		// this one adds the mail itself (as message/rfc822 (.eml) file) to the infolog as additional attachment
640		// this is done to have a simple archive functionality
641		if ($mailcontent && $GLOBALS['egw_info']['user']['preferences']['mail']['saveAsOptions']==='add_raw')
642		{
643			$message = $mailobject->getMessageRawBody($uid, $partid, $_folderName);
644			$headers = $mailobject->getMessageHeader($uid, $partid,true,false,$_folderName);
645			$subject = Mail::adaptSubjectForImport($headers['SUBJECT']);
646			$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
647			$tmpfile = fopen($attachment_file,'w');
648			fwrite($tmpfile,$message);
649			fclose($tmpfile);
650			$size = filesize($attachment_file);
651			$mailcontent['attachments'][] = array(
652					'name' => trim($subject).'.eml',
653					'mimeType' => 'message/rfc822',
654					'type' => 'message/rfc822',
655					'tmp_name' => $attachment_file,
656					'size' => $size,
657				);
658		}
659		if (self::LOG_LEVEL>1 && $mailcontent)
660		{
661			error_log(__METHOD__.__LINE__.'#'.array2string($mailcontent));
662			if (!empty($mailcontent['attachments'])) error_log(__METHOD__.__LINE__.'#'.array2string($mailcontent['attachments']));
663		}
664		if (!$mailcontent)
665		{
666			error_log(__METHOD__.__LINE__." Could not retrieve Content for message $uid in $_folderName for Server with ID:".$mailobject->icServer->ImapServerId." for Queue: $queue");
667			return false;
668		}
669		// prepare the data to be saved
670		// (use bo function connected to the ui interface mail import, so after preparing we need to adjust stuff)
671		$mailcontent['subject'] = Mail::adaptSubjectForImport($mailcontent['subject']);
672		$this->data = $this->prepare_import_mail(
673			$mailcontent['mailaddress'],
674			$mailcontent['subject'],
675			$mailcontent['message'],
676			$mailcontent['attachments'],
677			($tId?$tId:0),
678			$queue
679		);
680		if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.array2string($this->data));
681		if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.' Mailaddress:'.array2string($mailcontent['mailaddress']));
682		if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__.':'.$this->mailhandling[$queue]['unrecognized_mails'].':'.($this->data['tr_id']?$this->data['reply_creator']:$this->data['tr_creator']).' vs. '.array2string($this->user).' Ticket:'.$this->data['tr_id'].' Message:'.$this->data['msg']);
683
684		// handle auto - mails
685		if ($this->is_automail2($mailobject, $uid, $mailcontent['subject'], $mailcontent['headers'], $queue)) {
686			if (self::LOG_LEVEL>1) error_log(__METHOD__.' Automails will not be processed.');
687			return false;
688		}
689		if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.array2string($this->data['msg']).':'.$this->data['tr_creator'].'=='.$this->data['reply_creator'].'=='. $this->user);
690		// Handle unrecognized mails: we get a warning from prepare_import_mail, when mail is not recognized
691		// ToDo: Introduce a key, to be able to tell the error-condition
692		if (!empty($this->data['msg']) && (($this->data['tr_creator'] == $this->user) || ($this->data['tr_id'] && $this->data['reply_creator'] == $this->user)))
693		{
694			if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__.array2string($this->data['msg']).':'.$this->data['tr_creator'].'=='. $this->user);
695			if ($this->data['tr_id'] && $this->data['reply_creator'] == $this->user) unset($this->data['reply_creator']);
696			$senderIdentified = false;
697			$replytoAddress = $mailcontent['mailaddress'];
698			if (self::LOG_LEVEL>1) error_log(__METHOD__.__LINE__.' ReplyToAddress:'.$replytoAddress);
699			switch ($this->mailhandling[$queue]['unrecognized_mails'])
700			{
701				case 'ignore' :		// Do nothing
702					return false;
703				case 'delete' :		// Delete, whatever the overall delete setting is
704					$mailobject->deleteMessages($uid, $_folderName, 'move_to_trash');
705					return false;	// Prevent from a second delete attempt
706				case 'forward' :	// Return the status of the forward attempt
707					$returnVal = $this->forward_message2($mailobject, $uid, $mailcontent['subject'], $this->data['msg'], $queue);
708					if ($returnVal)
709					{
710						$mailobject->flagMessages('seen', $uid, $_folderName);
711						$mailobject->flagMessages('forwarded', $uid, $_folderName);
712					}
713					return $returnVal;
714				case 'default' :	// Save as default user; handled below
715				default :			// Duh ??
716					break;
717			}
718		}
719		else
720		{
721			$replytoAddress = ($this->data['tr_id']?$this->data['reply_creator']:$this->data['tr_creator']);
722		}
723
724		// do not fetch the possible ticketID (again), use what is returned by prepare_import_mail
725		$this->ticketId = $this->data['tr_id'];
726
727
728		if ($this->ticketId == 0) // Create new ticket?
729		{
730			if (empty($this->mailhandling[$queue]['default_tracker']))
731			{
732				return false; // Not allowed
733			}
734			if (!$senderIdentified) // Unknown user
735			{
736				if (empty($this->mailhandling[$queue]['unrec_mail']))
737				{
738					return false; // Not allowed for unknown users
739				}
740				$this->mailSender = $this->mailhandling[$queue]['unrec_mail']; // Ok, set default user
741			}
742		}
743		else
744		{
745			$this->mailSender = (!$senderIdentified?$this->mailhandling[$queue]['unrec_mail']:$this->data['reply_creator']);
746		}
747
748		if ($this->ticketId == 0)
749		{
750
751			$this->data['tr_tracker'] = $this->mailhandling[$queue]['default_tracker'];
752			$this->data['cat_id'] = $this->mailhandling[$queue]['default_cat'];
753			$this->data['tr_version'] = $this->mailhandling[$queue]['default_version'];
754			$this->data['tr_priority'] = 5;
755			if (!$senderIdentified && isset($this->mailSender))  $this->data['tr_creator'] = $this->user = $this->mailSender;
756			//error_log(__METHOD__.__LINE__.array2string($this->data));
757		}
758		else
759		{
760			// Extract latest reply from the mail message content and replace it for last comment
761			$this->data['reply_message'] = $this->extract_latestReply($this->data['reply_message']);
762
763			if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.array2string($this->data['reply_message']));
764			if (!$senderIdentified)
765			{
766				if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.':'.$this->data['tr_creator'].':'.$this->mailhandling[$queue]['unrec_mail'].':'.$this->user.':'.$this->mailSender.'#');
767				switch ($this->mailhandling[$queue]['unrec_reply'])
768				{
769					case 0 :
770						$this->user = (!empty($this->data['tr_creator'])?$this->data['tr_creator']:(!empty($this->mailhandling[$queue]['unrec_mail'])?$this->mailhandling[$queue]['unrec_mail']:$this->user));
771						break;
772					case 1 :
773						$this->user = 0;
774						break;
775					default :
776						$this->user = (!empty($this->mailhandling[$queue]['unrec_mail'])?$this->mailhandling[$queue]['unrec_mail']:0);
777						break;
778				}
779			}
780			else
781			{
782				$this->user = $this->mailSender;
783			}
784		}
785		if ($this->ticketId == 0 && (!isset($this->mailhandling[$queue]['auto_cc']) || $this->mailhandling[$queue]['auto_cc']==false))
786		{
787			unset($this->data['tr_cc']);
788		}
789
790		if ($this->data['popup']) unset($this->data['popup']);
791		// Save Current edition mode preventing mixed types
792		if ($this->data['tr_edit_mode'] == 'html' && !$this->htmledit)
793		{
794			$this->data['tr_edit_mode'] = 'html';
795		}
796		elseif ($this->data['tr_edit_mode'] == 'ascii' && $this->htmledit)
797		{
798			$this->data['tr_edit_mode'] = 'ascii';
799		}
800		else
801		{
802			$this->htmledit ? $this->data['tr_edit_mode'] = 'html' : $this->data['tr_edit_mode'] = 'ascii';
803		}
804		if (self::LOG_LEVEL>1 && $replytoAddress) error_log(__METHOD__.__LINE__.' Replytoaddress:'.array2string($replytoAddress).' Text:'.$this->mailhandling[$queue]['reply_text']);
805		// Save the ticket and let tracker_bo->save() handle the autorepl, if required
806		$saverv = $this->save(null,
807			(($this->mailhandling[$queue]['auto_reply'] == 2		// Always reply or
808			|| ($this->mailhandling[$queue]['auto_reply'] == 1	// only new tickets
809				&& $this->ticketId == 0)					// and this is a new one
810				) && (										// AND
811					$senderIdentified		 				// we know this user
812				|| (!$senderIdentified						// or we don't and
813				&& $this->mailhandling[$queue]['reply_unknown'] == 1 // don't care
814			))) == true
815				? array(
816					'reply_text' => $this->mailhandling[$queue]['reply_text'],
817					// UserID or mail address
818					'reply_to' => ($replytoAddress ? $replytoAddress : $this->user),
819				)
820				: null
821		);
822		// attachments must be saved/linked after saving the ticket
823		if (($saverv==0) && is_array($mailcontent['attachments']))
824		{
825			foreach ($mailcontent['attachments'] as $attachment)
826			{
827				//error_log(__METHOD__.__LINE__.'#'.$attachment['tmp_name'].'#'.$this->data['tr_id']);
828				if(is_readable($attachment['tmp_name']))
829				{
830					// Put it where it will be tied to the comment
831					if($this->ticketId)
832					{
833						$attachment['name'] = 'comments/.new/'.$attachment['name'];
834					}
835
836					//error_log(__METHOD__.__LINE__.'# trying to link '.$attachment['tmp_name'].'# to:'.$this->data['tr_id']);
837					Link::attach_file('tracker',$this->data['tr_id'],$attachment);
838				}
839			}
840			$this->comment_files($this->data['tr_id'],
841				$this->data['replies'][0]['reply_id'],
842				$this->data
843			);
844		}
845
846		return !$saverv;
847	}
848
849	/**
850	 * flag message after processing
851	 *
852	 */
853	function flagMessageAsSeen($mid, $messageHeader)
854	{
855	}
856
857	/**
858	 * Get an email address in plain format, no matter how the address was specified
859	 *
860	 * @param string $addr a string (probably) containing an email address
861	 */
862	function extract_mailaddress($addr='')
863	{
864	}
865
866	/**
867	 * Retrieve the user ID based on the mail address that was extracted from the mailheaders
868	 *
869	 * @param string $mail_addr ='' the mail address.
870	 */
871	function search_user($mail_addr='')
872	{
873	}
874
875	/**
876	 * Forward a mail that was not recognized
877	 *
878	 * @param int message ID from the server
879	 * @return boolean status
880	 */
881	function forward_message($mid=0, &$headers=null, $queue=0)
882	{
883	}
884
885	/**
886	 * Forward a mail that was not recognized
887	 *
888	 * @param object mailobject holding the server, and its connection
889	 * @param int message ID from the server
890	 * @param string subject the messages subject
891	 * @param array _message full retrieved message
892	 * @param int queue the queue we are in
893	 * @return boolean status
894	 */
895	function forward_message2($mailobject, $uid, $subject, $_message, $queue=0)
896	{
897		$this->smtpMail->ClearAddresses();
898		$this->smtpMail->clearParts();
899		$this->smtpMail->AddAddress(
900			$this->mailhandling[$queue]['forward_to'],
901			$this->mailhandling[$queue]['forward_to']
902		);
903
904		// Mailer Headers
905		$headers = array (
906			'X-EGroupware-type'		=> 'tracker-forward',
907			'X-EGroupware-Tracker'	=> $queue,
908			'X-EGroupware-Install'	=> $GLOBALS['egw_info']['server']['install_id'].
909				'@'.$GLOBALS['egw_info']['server']['default_domain'],
910			'Subject'				=> lang('[FWD]').' '.$subject
911		);
912
913		$notificationSender = !empty($this->notification[$queue]['sender']) ?
914				$this->notification[$queue]['sender'] : $this->notification[0]['sender'];
915
916		if (!empty($notificationSender))
917		{
918			$this->smtpMail->setFrom($notificationSender, $notificationSender);
919		}
920		else
921		{
922			$this->smtpMail->setFrom(
923				Api\Accounts::id2name($this->mailSender, 'account_email'),
924				Api\Accounts::id2name($this->mailSender, 'account_lid')
925			);
926		}
927
928		$this->smtpMail->addHeaders($headers);
929		$this->smtpMail->setBody(
930			lang("This message was forwarded to you from EGroupware-Tracker".
931					" Mailhandling: %1. \r\nSee attachment (original mail)".
932					" for further details\r\n %2", $queue, $_message)
933		);
934
935		$rawBody = $mailobject->getMessageRawBody(
936			$uid,
937			'',
938			!empty($this->mailhandling[$queue]['folder']) ?	$this->mailhandling[$queue]['folder'] : 'INBOX'
939		);
940
941		$this->smtpMail->AddStringAttachment($rawBody, $subject, 'message/rfc822');
942
943		if(!$error=$this->smtpMail->Send())
944		{
945			error_log(__METHOD__.__LINE__." Failed forwarding message via email.$error".print_r($this->smtpMail->ErrorInfo,true));
946			return false;
947		}
948		if (self::LOG_LEVEL>2) error_log(__METHOD__.__LINE__.array2string($this->smtpMail));
949		return true;
950	}
951
952	/**
953	 * Check if exist and if not start or stop an async job to check incoming mails
954	 *
955	 * @param int $queue ID of the queue to check email for
956	 * @param int $interval =1 >0=start, 0=stop
957	 */
958	static function set_async_job($queue=0, $interval=0)
959	{
960		$async = new Api\Asyncservice();
961		$job_id = 'tracker-check-mail' . ($queue ? '-'.$queue : '');
962
963		// Make sure an existing timer is cancelled
964		$async->cancel_timer($job_id);
965
966		if ($interval > 0)
967		{
968			if ($interval == 60)
969			{
970				$async->set_timer(array('hour' => '*'),$job_id,'tracker.tracker_mailhandler.check_mail',array('tr_tracker' => $queue));
971			}
972			else
973			{
974				$async->set_timer(array('min' => "*/$interval"),$job_id,'tracker.tracker_mailhandler.check_mail',array('tr_tracker' => $queue));
975			}
976		}
977	}
978}
979