<?php
/**
 * @category 	Atmail
 * @package 	Models
 * @subpackage	Composer
 * @copyright 	Copyright (c) 2009-2011 ATMAIL. All rights reserved
 * @license 	See http://atmail.com/license.php for license agreement
 * @author		Ben Duncan ben@staff.atmail.com
 * @author		Atmail (http://atmail.com)
 */
class compose
{
	// Compose model is used as a generic model for sending email messages ( Used by mail module and API module )
	
	private $_account;
	private $_tableNames;
		
	public function __construct(Array $args)
	{

		// For internal logging of messages sent
		require_once('log.php');
					
		// Required for HTML2Text conversion
		require_once('class.html2text.inc');
		
		if( !isset($args['Account']) )
		{
			throw new Atmail_Exception('Account field required in arguments array');
		}
		else
		{
			$this->_account = $args['Account'];			
		}
			
		if( !isset($args['view']) )
		{
			throw new Atmail_Exception('view object required in arguments array. TODO:// until freed of this requirement');
		}
		else
		{
			$this->view = $args['view'];
		}
			
		$email = explode('@', $this->_account);
		
		if(!empty($email['1']))
		{
			$this->_domain = $email['1'];
		}
		else
		{			
			$this->_domain = '';	
		}


		// Get a copy of the Zend helper object for use with plugins
		$this->_helper = isset($args['helper']) ? $args['helper'] : null;
		
		$this->dbAdapter = Zend_Registry::get('dbAdapter');
		$this->log = Zend_Registry::get('log');
		
		// Setup our mail environment
		$this->session = new Zend_Session_Namespace('defaultNamespace');
		$this->userData = Zend_Auth::getInstance()->getStorage()->read();
		$this->userData = Atmail_Password::processUser($this->userData);
		
		$this->_globalConfig = Zend_Registry::get('config')->global;
		$this->view->UserSettings = Zend_Registry::get('UserSettings');
		$this->view->Signature = $this->view->UserSettings['Signature'];
		$this->_currentConfig = array();

		// Init the object that communicates to the IMAP server ( IMAP append, IMAP flags for replies, etc)
		$this->_mailStoreConfig = array_merge($this->_globalConfig, $this->userData, $this->_currentConfig);
		$this->_mailStoreConfig['UseSSL'] = $this->view->UserSettings['UseSSL'];
		$this->AtmailMailStorage = Atmail_Mail_Storage::getInstance($this->_mailStoreConfig);
		$this->AtmailMailStorageMain = &$this->AtmailMailStorage->getMailStore($this->_mailStoreConfig['namespaceName']);

		
	}
	
	// Group methods
	public function sendMessage($requestParams) {

		$status = array();
		
		// Call preEmailSend plugin hook which can override settings used to create the email
		$this->_helper->pluginCall('preEmailSend');

		//recipient fields may contain \" for quotes in recipient fields so decode here (jQuery or Browser encoding/serializing data before POST)
		foreach( array( 'emailTo', 'emailCc', 'emailBcc') as $recipientField )
		{
			$requestParams[ $recipientField ] = stripslashes( $requestParams[$recipientField] );
		}
		
		// UTF-8 is used as the default character set application wide
		$this->charset = 'UTF-8';
		
		// Force all input to UTF-8 to avoid character set issues
		if( $this->charset != 'UTF-8' )
		{
			foreach( $requestParams as &$param )
			{
				$param = iconv('UTF-8', strtoupper($this->charset).'//IGNORE', $param);
			}
		}
		
		// Detect if the compose is a "quick reply" or a normal compose tab (or API)
		if( array_key_exists('inlineComposer', $requestParams) && ($requestParams['inlineComposer'] == true || $requestParams['inlineComposer'] == 'true' || $requestParams['inlineComposer'] == 1 ) )
		{
			$fromInlineComposerForm = true;
		}
		else
		{
			$fromInlineComposerForm = false;
		}

		// Set a simple var if any errors detected so far
		$noErrorYet = true;

		// Decide if to enable SMTP auth or regular connection, based on the system settings
		if( !empty($this->_globalConfig['smtpauth_username']) && !empty($this->_globalConfig['smtpauth_password']) && !empty($this->_globalConfig['smtp_auth']) )
		{
			$config = array(
							'auth' => 'login',
							'username' => $this->_globalConfig['smtpauth_username'],
							'password' => $this->_globalConfig['smtpauth_password']
	   		);
		}
		else
		{
			$config = array( );
		}

		if(PHP_OS == "Darwin")
		{
			$config['name'] = php_uname('n');
		}

		$transportClass = $this->_helper->pluginCall("smtpTransportClass");
		if (empty($transportClass))
		{
			$transportClass = "Atmail_Mail_Transport_Smtp";
		}

		$transport = new $transportClass($this->_globalConfig['smtphost'], $config);
		$this->_helper->pluginCall("postCreateSmtpTransport", $transport);
		$newMessage = new Atmail_Mail( $this->charset );
		$newMessage->setMessageId(true);

		// Set the from, first to the ReplyTo defined in the settings, else the account login name
		// First process sender details and compile complete from and optional replyto address
		// At this stage dont accept custom replyTo or emailFrom from the compose form, take from userSettings

		//format propper replyto address if short Account
		if( strpos($this->view->UserSettings['Account'], '@') !== false )
		{
			$requestParams['fromAddress'] = $this->view->UserSettings['Account'];
		}
		else
		{
			$configDovecot = Zend_Registry::get('config')->dovecot;
			if( !isset($configDovecot['default_domain']) || $configDovecot['default_domain'] == '' )
			{
				throw new Exception( $this->view->translate('Unable to send, Administrator needs to set Default Domain in Admin > Services > POP3/IMAP') );
			}
			$requestParams['fromAddress'] = $this->view->UserSettings['Account'] . '@' . $configDovecot['default_domain'];
		}

		if( isset($this->view->UserSettings['RealName']) && strlen($this->view->UserSettings['RealName']) > 0 )
		{
			// ZF does not quote the realname, so lets do it
			$requestParams['fromRealname'] = $this->view->UserSettings['RealName'];
			$requestParams['fromRealname'] = trim($requestParams['fromRealname'], "\"' \t\n\r\0\x0B");
			$requestParams['fromRealname'] = '"' . $requestParams['fromRealname'] . '"';
		}
		else
		{
			$requestParams['fromRealname'] = '';
		}

		if( isset($this->view->UserSettings['ReplyTo']) && strlen($this->view->UserSettings['ReplyTo']) > 0 )
		{
			$requestParams['replyToAddress'] = $this->view->UserSettings['ReplyTo'];
		}
		else
		{
			$requestParams['replyToAddress'] = '';
		}

		if( strlen($requestParams['fromRealname']) > 0 )
		{
			// Optionally encode the users name in the header
			if( containsUTF8($requestParams['fromRealname']) )
			{
				$requestParams['fromRealname'] = Atmail_MIMEWords::encode_mimeword($requestParams['fromRealname'], "B", $this->charset);
			}
			
			//TODO: check if ZF wraps in ""
			$newMessage->setFrom( $requestParams['fromAddress'], $requestParams['fromRealname'] );
		}
		else
		{
			$newMessage->setFrom( $requestParams['fromAddress']);
		}


		if( strlen( $requestParams['replyToAddress']) > 0 )
		{
			if( strlen($requestParams['fromRealname']) > 0 )
			{
				//not natively supported in current ZF version
				$newMessage->setReplyTo( $requestParams['replyToAddress'], $requestParams['fromRealname'] );
			}
			else
			{
				$newMessage->setReplyTo( $requestParams['replyToAddress']);
			}
		}

		$status['final_recipients'] = array();
		$rcptLog = array();
		foreach (array('emailTo', 'emailCc', 'emailBcc') as $addressType)
		{
			//decode posted form data ready for processing
			$requestParams[$addressType] = html_entity_decode($requestParams[$addressType], ENT_COMPAT, 'UTF-8');

			if ($requestParams[$addressType] != '')
			{
				$addressObjectsArray = getProcessedRecipientObjects( $requestParams[$addressType] );

				foreach ($addressObjectsArray as $addressObject)
				{
					$personal = '';
					if (is_string($addressObject))
					{
						preg_match('/(.*?)<(.*?)>/', $addressObject, $m);
						$personal = trim($m[1]);
						$mail = $m[2];
					}
					else
					{
						$personal = $addressObject->personal;
						$mail = $addressObject->mailbox . '@' . $addressObject->host;
					}
					// make sure we only quote once and remember quotes are LEGAL
					$personal = trim($personal, '"');
					if(strlen($personal) > 0)
					{
						$personal = '"' . $personal . '"';
					}

					$status['final_recipients'][] = (strlen($personal)>0?$personal . ' <':'') . $mail . (strlen($personal)>0?'>':'');
					
					//add the cleaned up recipient to the message
					if( $addressType == 'emailBcc' )
					{
						$newMessage->addBcc($mail, (strlen($personal)>0?$personal:null) );
					}
					elseif( $addressType == 'emailCc' )
					{
						$newMessage->addCc($mail, (strlen($personal)>0?$personal:null) );
					}
					else
					{
						$newMessage->addTo($mail, (strlen($personal)>0?$personal:null) );
					}
					
					// Create an array of all recipients for logging
					$status['rcptLog'][] = $mail;
				}
			}
		}

		// Set the X-Mailer to the Atmail version
		$newMessage->addHeader('X-Mailer', "Atmail " . $this->_globalConfig['version'] );

		// Set in-reply-to header
		if( $fromInlineComposerForm )
		{
			$newMessage->addHeader('in-reply-to', $requestParams['relatedMessageMessageId'] );
		}
		elseif( array_key_exists('relatedMessageMessageId', $requestParams) && strlen($requestParams['relatedMessageMessageId']) > 0 )
		{
			$newMessage->addHeader('in-reply-to', $requestParams['relatedMessageMessageId'] );
		}

		// Set the subject field
		$newMessage->setSubject( $requestParams['emailSubject'] );

		// Add reply request if defined
		if( array_key_exists('readReceiptRequested', $requestParams) && $requestParams['readReceiptRequested'] == 'true' )
		{
			$newMessage->addHeader('X-Confirm-Reading-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
			$newMessage->addHeader('Disposition-Notification-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
			$newMessage->addHeader('Return-Receipt-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
		}

		// Add the HTML and text parts
		if( empty($requestParams['emailBodyHtml']) )	{
			
			// Next, append the footer
			//if( strpos($requestParams['emailBodyText'], substr($this->_globalConfig['footer_msg'], -1024)) === false )
			//	$requestParams['emailBodyText'] .= "\n\n" . strip_tags($this->_globalConfig['footer_msg']);
			
			$newMessage->setBodyText($requestParams['emailBodyText'] );			
			
		} elseif( !empty($requestParams['emailBodyHtml']) ) {
			
			// Append the global footer (HTML)
			if( strpos($requestParams['emailBodyHtml'], substr($this->_globalConfig['footer_msg'], -1024)) === false )
				$requestParams['emailBodyHtml'] .= $this->_globalConfig['footer_msg'];
			
			// If a HTML message is defined, append this into the HTML and create a HTML2Text copy
			// Check/wrap HTML body with <html><body></html></body>
			if( !preg_match("'<body[^>]*>(.*?)</body>'i", $requestParams['emailBodyHtml'] ) )
			{
				$requestParams['emailBodyHtml'] = '<body>' . $requestParams['emailBodyHtml'] . '</body>';
			}
			if( !preg_match("'<html[^>]*>(.*?)</html>'i", $requestParams['emailBodyHtml'] ) )
			{
				$requestParams['emailBodyHtml'] = '<html>' . $requestParams['emailBodyHtml'] . '</html>';
			}

			$newMessage->setBodyHtml($requestParams['emailBodyHtml']);
			
			// Create a plain text version of the HTML email (multipart MIME)
			$html2text = new html2text($requestParams['emailBodyHtml']);
			$emailBodyText = $html2text->get_text();
			$newMessage->setBodyText($emailBodyText);
			
		} else {
			
			$error = "HTML or Text part must be defined";
			
		}

		//Handle attachments
		if( $noErrorYet && isset($requestParams['attachments']) )
		{
            messageHandling::handleAttachmentsSubmitted($newMessage, $requestParams, 'attach');
		}

		if( $noErrorYet )
		{
			// Send the message
			try
			{
				$this->_helper->pluginCall("preEmailSend", $newMessage);
				$newMessage->send($transport);
			}
			catch (Exception $e)
			{
				$this->_helper->pluginCall("emailSendFailed", $e);
				$noErrorYet = false;
				$error = $e->getMessage();

				if( $error == 'Blocked' )
				{
					$error = $this->view->translate('The server has rejected the email address. If you feel this address is legitimate please contact your administrator.');				
				}
				else if( $error == 'No recipient forward path has been supplied' )
				{
					$error = $this->view->translate('Email address not valid. Please use an email address which looks like user@example.com');
				}
			}
		}

		//If succeeded, then transport will contain raw message parts needed for append
		if( $noErrorYet )
		{
			// If we are running the Webmail client, log the action to the DB - If server, the log-daemon process will log the action for us
			if($this->_globalConfig['install_type'] != 'server')
			{

				$log = new logEntry($this->view->UserSettings['Account']);

				foreach($rcptLog as $rcpt)
				{
					$log->insert("Log_SendMail", array('LogIP' => $_SERVER['REMOTE_ADDR'], 'EmailTo' => $rcpt, 'Account' => $this->view->UserSettings['Account']));
				}
			}

		}
	
	
		// Once the email has been sent, append to the remote servers Sent folder
		//TODO: confirm APPENDUID response handled properly
		$error = '';
		try
		{
			$sentFolderName = $this->AtmailMailStorageMain->getMainFolderName('Sent');
			$msg = $transport->getRawMessage($newMessage);

			$this->AtmailMailStorageMain->appendMessage($msg, $sentFolderName );
			unset($newMessage);
			unset($transport);
			// TODO: Move to mail controller
			//jQuery::evalScript("$('.sendEmail, .saveAsDraft, .addAttachment').enableActionSimpler();");
		}
		catch ( Exception $e)
		{
			$noErrorYet = false;
			$error = $e->getMessage();
			$status['appendError'] = 1;
		}	
	
		// Append the error to the status array
		$status['error'] = $error;
	
		// If no errors, cleanup temp attachment directory
		if( empty($status['error']) ) {

			//Handle attachments cleanup and removal
			if( isset($requestParams['attachments']) )
			{
                messageHandling::handleAttachmentsSubmitted(null, $requestParams, 'remove');
			}
				
		}

		return $status;
	
	}

	public function savedraft( $requestParams )
	{

		//return null for now until bug causing browser to call save draft with no params is removed
		if( count($requestParams) < 8 )
		{

			return;

		}

		//TODO: store headers of folder and uids of messages being replied to or forwarded so can load into composer ready for sent flagging
		$newDraftMessage = new Atmail_Mail('utf-8');

		// Set the from, first to the ReplyTo defined in the settings, else the account login name
		if( !empty($this->view->UserSettings['ReplyTo']) && !empty($this->view->UserSettings['RealName']) )
			$newDraftMessage->setFrom($this->view->UserSettings['ReplyTo'], $this->view->UserSettings['RealName']);
		else if( !empty($this->view->UserSettings['ReplyTo']) && empty($this->view->UserSettings['RealName']) )
			$newDraftMessage->setFrom($this->view->UserSettings['ReplyTo']);
		else if( !empty($this->view->UserSettings['RealName']) )
			$newDraftMessage->setFrom($this->view->UserSettings['Account'], $this->view->UserSettings['RealName']);
		else
			$newDraftMessage->setFrom($this->view->UserSettings['Account']);

		// Loop through the to, cc, bcc parts
		$rfc822 = new Mail_RFC822;
		$requestParams['emailTo'] = html_entity_decode($requestParams['emailTo'], ENT_COMPAT, 'UTF-8');
		if( strlen($requestParams['emailTo']) > 3 )
		{

			$emailTo = $rfc822->parseAddressList(stripslashes($requestParams['emailTo']), null, false);
			foreach($emailTo as $email)
			{

				$newDraftMessage->addTo($email->mailbox . '@' . $email->host, str_replace('"','',$email->personal));

			}

		}

		$requestParams['emailCc'] = html_entity_decode($requestParams['emailCc'], ENT_COMPAT, 'UTF-8');
		if( strlen($requestParams['emailCc']) > 3 )
		{

			$emailCc = $rfc822->parseAddressList(stripslashes($requestParams['emailCc']), null, false);
			foreach($emailCc as $email)
			{

				$newDraftMessage->addCc($email->mailbox . '@' . $email->host, str_replace('"','',$email->personal));

			}

		}

		//need to handle adding Bcc header to email differently because they are normally omitted from headers and transparently used for sending blind copies of the email
		$requestParams['emailBcc'] = html_entity_decode($requestParams['emailBcc'], ENT_COMPAT, 'UTF-8');
		if( strlen($requestParams['emailBcc']) > 3 )
		{

			$bccsProcessed = array();
			$emailBcc = $rfc822->parseAddressList(stripslashes($requestParams['emailBcc']), null, false);
			foreach($emailBcc as $email)
			{

				//$newDraftMessage->addBcc($email->mailbox . '@' . $email->host, str_replace('"','',$email->personal));
				$bccsProcessed[] = (strlen(str_replace('"','',$email->personal))>0?str_replace('"','',$email->personal) . ' <':'') . $email->mailbox . '@' . $email->host . (strlen(str_replace('"','',$email->personal))>0?'>':'');

			}
//Zend_Registry::get('log')->debug( "\n" . print_r($bccsProcessed, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$bccsProcessed \n");

			if( count($bccsProcessed) > 0 )
			{

				$newDraftMessage->addHeader('X-Atmail-Bcc', implode(", ", $bccsProcessed) );

			}

		}
		
		if(array_key_exists('sendAsText', $requestParams) && $requestParams['sendAsText'] == 1) {

			$newDraftMessage->setBodyText($requestParams['emailBodyHtml']);

		} else {

			// Add the HTML and text parts
			$html2text = new html2text($requestParams['emailBodyHtml']);
			$emailBodyText = $html2text->get_text();
			$newDraftMessage->setBodyText($emailBodyText);
			$newDraftMessage->setBodyHtml($requestParams['emailBodyHtml']);
		}
		
		// Set the subject and misc fields

		//check if this is a reply and if so add in-reply-to header and also flag message-id as replied to
		if( isset($requestParams['relatedMessageMessageId']) )
			$newDraftMessage->addHeader('in-reply-to', $requestParams['relatedMessageMessageId'] );
		elseif( isset($requestParams['relatedMessageMessageId']) )
			$newDraftMessage->addHeader('in-reply-to', $requestParams['relatedMessageMessageId'] );

		$newDraftMessage->setSubject($requestParams['emailSubject']);


		$newDraftMessage->setDate();
		$newDraftMessage->setMessageId(true);

		//add reply requested if set
		if( array_key_exists('readReceiptRequested', $requestParams) && $requestParams['readReceiptRequested'] == 'true' )
		{
			
			$newDraftMessage->addHeader('X-Confirm-Reading-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
			$newDraftMessage->addHeader('Disposition-Notification-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
			$newDraftMessage->addHeader('Return-Receipt-To', (strlen($requestParams['replyToAddress'])>0?$requestParams['replyToAddress']:$requestParams['fromAddress']) );
		
		}
		
		//Handle attachments
		if( isset($requestParams['attachments']) )
		{
            messageHandling::handleAttachmentsSubmitted($newDraftMessage, $requestParams, 'attach');
		}

		//CONSIDER: remove attachment tmp files so only exist in appended IMAP Draft for more security (will require more complex different attachment handling)
		$currentFolder = $this->AtmailMailStorageMain->getCurrentFolder();


		$transport = new Atmail_Mail_Transport_Smtp();
		try {
			$draftsFolderNameGlobal = $this->AtmailMailStorageMain->getMainFolderName('Drafts');
			$this->AtmailMailStorageMain->selectFolder($draftsFolderNameGlobal);
		} catch( Exception $e ) {
			
			Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": Drafts folder not found so failing saving of draft: " . $e->getMessage());
			
			$rootFolderGlobalUTF7 = $this->AtmailMailStorageMain->getMainFolderName();
			$rootFolderGlobalUTF7UrlencodedDouble = urlencode( urlencode( $rootFolderGlobalUTF7 ) );
			$this->AtmailMailStorageMain->selectFolder( $rootFolderGlobalUTF7 );
			$error = $e->getMessage();
			if( strpos($error, 'exist') )
				$error = "Drafts folder does not exist";
			elseif( strpos($error, 'literal') !== 0 )
				$error = "Over quota";
			return array('status' => 'failed', 'message' => $error );
			
		}
		
		try {
			
			$this->_helper->pluginCall("aboutToSaveDraft", $newDraftMessage);
			$relatedDraftUID = $this->AtmailMailStorageMain->appendMessage($transport->getRawMessage($newDraftMessage), $draftsFolderNameGlobal);
			
		} catch( Exception $e ) {
			
			$error = $e->getMessage();
			if( strpos($error, 'literal') !== 0 )
				$error = "Over quota";
			return array('status' => 'failed', 'message' => $error );
			
		}

		$incrementDraftsUITotal = true;
		//if relatedDraftUID is set then delete old draft and push new UID to UI relatedDraftUID else just push new relatedDraftUID to UI
		if( isset($requestParams['relatedDraftUID']) && strlen($requestParams['relatedDraftUID']) > 0 )
		{
			$this->AtmailMailStorageMain->selectFolder($draftsFolderNameGlobal);
			$this->AtmailMailStorageMain->removeMessage( $requestParams['relatedDraftUID'] );
			$incrementDraftsUITotal = false;
		}
		$newMessageMessageId = $newDraftMessage->getMessageId();
		//$savedDraftUIDArray = $this->AtmailMailStorageMain->search(array(array('field' => 'HEADER message-id', 'value' => $newMessageMessageId)) );

		//switch back to currently selected folder
		$this->AtmailMailStorageMain->selectFolder( $currentFolder );

		//$relatedDraftUID = $savedDraftUIDArray[0];
		//if(isset($relatedDraftUID['UID']))
		//{
		//	$relatedDraftUID = $relatedDraftUID['UID'];
		//}
		return array('incrementDraftsUITotal' => $incrementDraftsUITotal, 'relatedDraftUID' => $relatedDraftUID);
		
	}


}
