/**
  * Compose a message.
  *
  * @param int $job_id
  *   ID of the Job associated with this message.
  * @param int $event_queue_id
  *   ID of the EventQueue.
  * @param string $hash
  *   Hash of the EventQueue.
  * @param string $contactId
  *   ID of the Contact.
  * @param string $email
  *   Destination address.
  * @param string $recipient
  *   To: of the recipient.
  * @param bool $test
  *   Is this mailing a test?.
  * @param $contactDetails
  * @param $attachments
  * @param bool $isForward
  *   Is this mailing compose for forward?.
  * @param string $fromEmail
  *   Email address of who is forwardinf it.
  *
  * @param null $replyToEmail
  *
  * @return Mail_mime               The mail object
  */
 public function compose($job_id, $event_queue_id, $hash, $contactId, $email, &$recipient, $test, $contactDetails, &$attachments, $isForward = FALSE, $fromEmail = NULL, $replyToEmail = NULL)
 {
     $config = CRM_Core_Config::singleton();
     $this->getTokens();
     if ($this->_domain == NULL) {
         $this->_domain = CRM_Core_BAO_Domain::getDomain();
     }
     list($verp, $urls, $headers) = $this->getVerpAndUrlsAndHeaders($job_id, $event_queue_id, $hash, $email, $isForward);
     //set from email who is forwarding it and not original one.
     if ($fromEmail) {
         unset($headers['From']);
         $headers['From'] = "<{$fromEmail}>";
     }
     if ($replyToEmail && $fromEmail != $replyToEmail) {
         $headers['Reply-To'] = "{$replyToEmail}";
     }
     if ($contactDetails) {
         $contact = $contactDetails;
     } elseif ($contactId === 0) {
         //anonymous user
         $contact = array();
         CRM_Utils_Hook::tokenValues($contact, $contactId, $job_id);
     } else {
         $params = array(array('contact_id', '=', $contactId, 0, 0));
         list($contact) = CRM_Contact_BAO_Query::apiQuery($params);
         //CRM-4524
         $contact = reset($contact);
         if (!$contact || is_a($contact, 'CRM_Core_Error')) {
             CRM_Core_Error::debug_log_message(ts('CiviMail will not send email to a non-existent contact: %1', array(1 => $contactId)));
             // setting this because function is called by reference
             //@todo test not calling function by reference
             $res = NULL;
             return $res;
         }
         // also call the hook to get contact details
         CRM_Utils_Hook::tokenValues($contact, $contactId, $job_id);
     }
     $pTemplates = $this->getPreparedTemplates();
     $pEmails = array();
     foreach ($pTemplates as $type => $pTemplate) {
         $html = $type == 'html' ? TRUE : FALSE;
         $pEmails[$type] = array();
         $pEmail =& $pEmails[$type];
         $template =& $pTemplates[$type]['template'];
         $tokens =& $pTemplates[$type]['tokens'];
         $idx = 0;
         if (!empty($tokens)) {
             foreach ($tokens as $idx => $token) {
                 $token_data = $this->getTokenData($token, $html, $contact, $verp, $urls, $event_queue_id);
                 array_push($pEmail, $template[$idx]);
                 array_push($pEmail, $token_data);
             }
         } else {
             array_push($pEmail, $template[$idx]);
         }
         if (isset($template[$idx + 1])) {
             array_push($pEmail, $template[$idx + 1]);
         }
     }
     $html = NULL;
     if (isset($pEmails['html']) && is_array($pEmails['html']) && count($pEmails['html'])) {
         $html =& $pEmails['html'];
     }
     $text = NULL;
     if (isset($pEmails['text']) && is_array($pEmails['text']) && count($pEmails['text'])) {
         $text =& $pEmails['text'];
     }
     // push the tracking url on to the html email if necessary
     if ($this->open_tracking && $html) {
         array_push($html, "\n" . '<img src="' . $config->userFrameworkResourceURL . "extern/open.php?q={$event_queue_id}\" width='1' height='1' alt='' border='0'>");
     }
     $message = new Mail_mime("\n");
     $useSmarty = defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY ? TRUE : FALSE;
     if ($useSmarty) {
         $smarty = CRM_Core_Smarty::singleton();
         // also add the contact tokens to the template
         $smarty->assign_by_ref('contact', $contact);
     }
     $mailParams = $headers;
     if ($text && ($test || $contact['preferred_mail_format'] == 'Text' || $contact['preferred_mail_format'] == 'Both' || $contact['preferred_mail_format'] == 'HTML' && !array_key_exists('html', $pEmails))) {
         $textBody = implode('', $text);
         if ($useSmarty) {
             $textBody = $smarty->fetch("string:{$textBody}");
         }
         $mailParams['text'] = $textBody;
     }
     if ($html && ($test || ($contact['preferred_mail_format'] == 'HTML' || $contact['preferred_mail_format'] == 'Both'))) {
         $htmlBody = implode('', $html);
         if ($useSmarty) {
             $htmlBody = $smarty->fetch("string:{$htmlBody}");
         }
         $mailParams['html'] = $htmlBody;
     }
     if (empty($mailParams['text']) && empty($mailParams['html'])) {
         // CRM-9833
         // something went wrong, lets log it and return null (by reference)
         CRM_Core_Error::debug_log_message(ts('CiviMail will not send an empty mail body, Skipping: %1', array(1 => $email)));
         $res = NULL;
         return $res;
     }
     $mailParams['attachments'] = $attachments;
     $mailingSubject = CRM_Utils_Array::value('subject', $pEmails);
     if (is_array($mailingSubject)) {
         $mailingSubject = implode('', $mailingSubject);
     }
     $mailParams['Subject'] = $mailingSubject;
     $mailParams['toName'] = CRM_Utils_Array::value('display_name', $contact);
     $mailParams['toEmail'] = $email;
     // Add job ID to mailParams for external email delivery service to utilise
     $mailParams['job_id'] = $job_id;
     CRM_Utils_Hook::alterMailParams($mailParams, 'civimail');
     // CRM-10699 support custom email headers
     if (!empty($mailParams['headers'])) {
         $headers = array_merge($headers, $mailParams['headers']);
     }
     //cycle through mailParams and set headers array
     foreach ($mailParams as $paramKey => $paramValue) {
         //exclude values not intended for the header
         if (!in_array($paramKey, array('text', 'html', 'attachments', 'toName', 'toEmail'))) {
             $headers[$paramKey] = $paramValue;
         }
     }
     if (!empty($mailParams['text'])) {
         $message->setTxtBody($mailParams['text']);
     }
     if (!empty($mailParams['html'])) {
         $message->setHTMLBody($mailParams['html']);
     }
     if (!empty($mailParams['attachments'])) {
         foreach ($mailParams['attachments'] as $fileID => $attach) {
             $message->addAttachment($attach['fullPath'], $attach['mime_type'], $attach['cleanName']);
         }
     }
     //pickup both params from mail params.
     $toName = trim($mailParams['toName']);
     $toEmail = trim($mailParams['toEmail']);
     if ($toName == $toEmail || strpos($toName, '@') !== FALSE) {
         $toName = NULL;
     } else {
         $toName = CRM_Utils_Mail::formatRFC2822Name($toName);
     }
     $headers['To'] = "{$toName} <{$toEmail}>";
     $headers['Precedence'] = 'bulk';
     // Will test in the mail processor if the X-VERP is set in the bounced email.
     // (As an option to replace real VERP for those that can't set it up)
     $headers['X-CiviMail-Bounce'] = $verp['bounce'];
     //CRM-5058
     //token replacement of subject
     $headers['Subject'] = $mailingSubject;
     CRM_Utils_Mail::setMimeParams($message);
     $headers = $message->headers($headers);
     //get formatted recipient
     $recipient = $headers['To'];
     // make sure we unset a lot of stuff
     unset($verp);
     unset($urls);
     unset($params);
     unset($contact);
     unset($ids);
     return $message;
 }