You are here

MailhandlerPhpImapRetrieve.class.php in Mailhandler 6.2

Definition of MailhandlerPhpImapRetrieve class.

File

modules/mailhandler_php_imap/plugins/mailhandler/retrieve/MailhandlerPhpImapRetrieve.class.php
View source
<?php

/**
 * @file
 * Definition of MailhandlerPhpImapRetrieve class.
 */

/**
 * Retrieve messages using PHP IMAP library.
 */
class MailhandlerPhpImapRetrieve extends MailhandlerRetrieve {
  private $suppress_errors = FALSE;

  /**
   * Connect to mailbox and run message retrieval.
   *
   * @param object $mailbox
   *   The mailbox to retrieve from.
   * @param string|null $filter_name
   *   (optional) Mailhandler filter to restrict what messages are retrieved.
   *
   * @return array
   *   Retrieved messages.
   */
  function retrieve($mailbox, $filter_name = 'MailhandlerFilters') {
    extract($mailbox->settings);
    if (!($result = $this
      ->open_mailbox($mailbox))) {
      mailhandler_report('error', 'Unable to connect to %mail. Please check the <a href="@mailbox-edit">connection settings</a> for this mailbox.', array(
        '%mail' => $mailbox->mail,
        '@mailbox-edit' => url(MAILHANDLER_MENU_PREFIX . '/mailhandler/list/' . $mailbox->mail . '/edit'),
      ));
      $this
        ->report_errors($mailbox);
    }
    $new = $this
      ->get_unread_messages($result, $mailbox);
    $messages = array();
    $retrieved = 0;
    while ($new && (!$limit || $retrieved < $limit)) {
      if (!($message = $this
        ->retrieve_message($result, $mailbox, array_shift($new), $filter_name))) {
        continue;
      }
      $messages[] = $message;
      $retrieved++;
    }
    mailhandler_report('status', 'Mailbox %mail was checked and contained %retrieved messages.', array(
      '%mail' => $mailbox->admin_title,
      '%retrieved' => $retrieved,
    ));
    $this
      ->close_mailbox($result, $mailbox);
    return $messages;
  }

  /**
   * Test connection to a mailbox.
   *
   * @param object $mailbox
   *   The mailbox to test.
   *
   * @return array
   *   Test results.
   */
  function test($mailbox) {
    extract($mailbox->settings);
    $ret = array();
    $this->suppress_errors = TRUE;
    $is_local = $type == 'local';
    $folder_is_set = !empty($folder) && $folder != 'INBOX';
    $connect_is_set = !empty($domain) && !empty($port) && !empty($name) && !empty($pass);
    if ($is_local && $folder_is_set || !$is_local && $connect_is_set) {
      if (!($result = $this
        ->open_mailbox($mailbox))) {
        $errors = imap_errors();
        foreach ($errors as $error) {
          $ret[] = array(
            'severity' => 'error',
            'message' => t($error),
          );
        }
        $ret[] = array(
          'severity' => 'error',
          'message' => t('Mailhandler could not access the mailbox using these settings'),
        );
        return $ret;
      }
      $ret[] = array(
        'severity' => 'status',
        'message' => t('Mailhandler was able to connect to the mailbox.'),
      );
      $box = $this
        ->mailbox_string($mailbox);
      $box = ltrim($box, '/');
      $status = imap_status($result, $box, SA_MESSAGES);
      if ($status) {
        $ret[] = array(
          'severity' => 'status',
          'message' => t('There are @messages messages in the mailbox folder.', array(
            '@messages' => $status->messages,
          )),
        );
      }
      else {
        $ret[] = array(
          'severity' => 'warning',
          'message' => t('Mailhandler could not open the mailbox.'),
        );
      }
      $this
        ->close_mailbox($result, $mailbox);
    }
    return $ret;
  }

  /**
   * Purge (mark as read or delete) a message.
   *
   * @param object $mailbox
   *   Mailbox configuration.
   * @param array $message
   *   Message to purge.
   */
  function purge_message($mailbox, $message) {
    if (!isset($message['imap_uid'])) {
      return;
    }
    if (!($result = $this
      ->open_mailbox($mailbox))) {
      mailhandler_report('error', 'Unable to connect to %mail. Following errors may provide details.', array(
        '%mail' => $mailbox->mail,
      ));
      $this
        ->report_errors($mailbox);
    }
    if ($mailbox->settings['delete_after_read']) {
      imap_delete($result, $message['imap_uid'], FT_UID);
    }
    elseif (!isset($mailbox->settings['flag_after_read']) || $mailbox->settings['flag_after_read']) {
      imap_setflag_full($result, (string) $message['imap_uid'], '\\Seen', FT_UID);
    }
    $this
      ->close_mailbox($result, $mailbox);
  }

  /**
   * Establish IMAP stream connection to specified mailbox.
   *
   * @param array $mailbox
   *   Mailbox configuration.
   *
   * @return resource|false
   *   IMAP stream.
   */
  function open_mailbox($mailbox) {
    extract($mailbox->settings);
    if (!function_exists('imap_open')) {
      mailhandler_report('error', 'The PHP IMAP extension must be enabled in order to use Mailhandler.');
    }
    $box = $this
      ->mailbox_string($mailbox);
    if ($type != 'local') {
      $result = imap_open($box, $name, $pass, NULL, 1);
    }
    else {

      // This is a local mbox.
      // Change HOME to work around php_imap access restrictions.
      $orig_home = getenv('HOME');
      if (strpos($box, '/') === 0) {
        $new_home = '/';
        $box = ltrim($box, '/');
      }
      else {

        // This is hackish, but better than using $_SERVER['DOCUMENT_ROOT']
        $new_home = realpath(drupal_get_path('module', 'node') . '/../../');
      }
      if (!putenv("HOME={$new_home}")) {
        mailhandler_report('error', 'Could not set home directory to %home.', array(
          '%home' => $new_home,
        ));
      }
      $result = imap_open($box, '', '', NULL, 1);

      // Restore HOME directory.
      putenv("HOME={$orig_home}");
    }
    return $result;
  }

  /**
   * Constructs a mailbox string based on mailbox object
   */
  function mailbox_string($mailbox) {
    extract($mailbox->settings);
    switch ($type) {
      case 'imap':
        return '{' . $domain . ':' . $port . $extraimap . '}' . $folder;
      case 'pop3':
        return '{' . $domain . ':' . $port . '/pop3' . $extraimap . '}' . $folder;
      case 'local':
        $box = $folder;
        if ($readonly) {

          // Copy mbox to avoid modifying original.
          $source = $box;
          $destination = file_directory_temp();
          $replace = FILE_EXISTS_REPLACE;
          $box = realpath($destination . '/' . basename($source));
        }
        return $box;
    }
  }

  /**
   * Returns an array of parts as file objects
   *
   * @param $stream
   * @param $msg_number
   * @return
   *   An array of message parts (text body, html body, and attachments).
   */
  function get_parts($stream, $msg_number) {
    $parts = array(
      'text_body' => '',
      'html_body' => '',
      'attachments' => array(),
    );

    // Load structure.
    if (!($structure = imap_fetchstructure($stream, $msg_number))) {
      mailhandler_report('error', 'Could not fetch structure for message number %msg_number', array(
        '%msg_number' => $msg_number,
      ));
    }

    // Get message parts.
    if ($structure->type == TYPEMULTIPART) {

      // Multi-part message.
      foreach ($structure->parts as $index => $sub_structure) {
        $this
          ->get_part($stream, $msg_number, $sub_structure, $index + 1, $parts);
      }
    }
    else {

      // Single-part message.
      $this
        ->get_part($stream, $msg_number, $structure, FALSE, $parts);
    }
    return $parts;
  }

  /**
   * Gets a message part and adds it to $parts.
   *
   * @param $stream
   * @param $msg_number
   * @param $structure
   * @param $part_number
   * @param $parts
   */
  function get_part($stream, $msg_number, $structure, $part_number, &$parts) {

    // Get data.
    $data = '';
    if ($part_number) {
      $data = imap_fetchbody($stream, $msg_number, $part_number, FT_PEEK);
    }
    else {
      $data = imap_body($stream, $msg_number, FT_PEEK);
    }

    // Get params.
    $params = array();
    if ($structure->ifparameters) {
      foreach ($structure->parameters as $parameter) {
        $params[drupal_strtolower($parameter->attribute)] = $parameter->value;
      }
    }
    if ($structure->ifdparameters) {
      foreach ($structure->dparameters as $parameter) {
        $params[drupal_strtolower($parameter->attribute)] = $parameter->value;
      }
    }
    $charset = 'auto';
    if (isset($params['charset'])) {
      $charset = $params['charset'];
    }

    // Decode data.
    switch ($structure->encoding) {
      case ENCQUOTEDPRINTABLE:
        $data = quoted_printable_decode($data);

        // Properly decode most Microsoft encodings (Hotmail and Windows).
        $data = drupal_convert_to_utf8($data, $charset);
        break;
      case ENCBASE64:
        $data = base64_decode($data);
        break;
      case ENC7BIT:
      case ENC8BIT:
        $data = imap_utf8($data);

        // Properly decode most Microsoft encodings (Hotmail and Windows).
        $data = drupal_convert_to_utf8($data, $charset);
        break;
    }

    // Get attachments.
    foreach ($params as $attribute => $value) {
      switch ($attribute) {
        case 'filename':
        case 'name':

          // Prevent duplicates, if attachment has both name and filename param.
          if (isset($attachment) && $attachment->filename === $value) {
            break;
          }
          $attachment = new stdClass();
          $attachment->filename = $value;
          $attachment->data = $data;
          if (isset($structure->id)) {
            $attachment->id = $structure->id;
          }
          if (!($attachment->filemime = $this
            ->get_mime_type($structure))) {
            mailhandler_report('warning', 'Could not fetch mime type for message part. Defaulting to application/octet-stream.');
            $attachment->filemime = 'application/octet-stream';
          }
          $parts['attachments'][] = $attachment;
          break 2;
      }
    }

    // Get bodies.
    if (($structure->type == TYPETEXT || $structure->type == TYPEMESSAGE) && $data) {

      // Messages may be split in different parts because of inline attachments,
      // so append parts together with blank row.
      if (drupal_strtolower($structure->subtype) == 'plain') {
        $parts['text_body'] .= trim($data);
      }
      else {
        $parts['html_body'] .= $data;
      }
    }

    // Recurse for multi-part messages.
    if ($structure->type = TYPEMULTIPART && isset($structure->parts)) {
      foreach ($structure->parts as $index => $sub_structure) {
        $this
          ->get_part($stream, $msg_number, $sub_structure, $part_number . '.' . ($index + 1), $parts);
      }
    }
  }

  /**
   * Retrieve MIME type of the message structure.
   */
  function get_mime_type(&$structure) {
    static $primary_mime_type = array(
      'text',
      'multipart',
      'message',
      'application',
      'audio',
      'image',
      'video',
      'other',
    );
    $type_id = (int) $structure->type;
    if (isset($primary_mime_type[$type_id]) && !empty($structure->subtype)) {
      return $primary_mime_type[$type_id] . '/' . drupal_strtolower($structure->subtype);
    }
    return 'text/plain';
  }

  /**
   * Obtain the number of unread messages for an imap stream
   *
   * @param $result
   *   IMAP stream - as opened by imap_open
   * @param object $mailbox
   *   The mailbox to retrieve from.
   * @return array
   *   IMAP message numbers of unread messages.
   */
  function get_unread_messages($result, $mailbox) {
    $unread_messages = array();
    $number_of_messages = imap_num_msg($result);
    for ($i = 1; $i <= $number_of_messages; $i++) {
      $header = imap_header($result, $i);
      if ($header->Unseen == 'U' || $header->Recent == 'N') {
        $unread_messages[] = $i;
      }
    }
    return $unread_messages;
  }

  /**
   * Retrieve individual messages from an IMAP result.
   *
   * @param $result
   *   IMAP stream.
   * @param object $mailbox
   *   Mailbox to retrieve from.
   * @param int $msg_number
   *   IMAP message number.
   * @param string $filter_name
   *   Mailhandler Filter plugin to use.
   *
   * @return array|false
   *   Retrieved message, or FALSE if message cannot / should not be retrieved.
   */
  function retrieve_message($result, $mailbox, $msg_number, $filter_name) {
    extract($mailbox->settings);
    $header = imap_headerinfo($result, $msg_number);

    // Check to see if we should retrieve this message at all
    if ($filter = mailhandler_plugin_load_class('mailhandler', $filter_name, 'filters', 'handlers')) {
      if (!$filter
        ->fetch($header)) {
        return FALSE;
      }
    }

    // Initialize the subject in case it's missing.
    if (!isset($header->subject)) {
      $header->subject = '';
    }

    // Parse MIME parts.
    $parts = $this
      ->get_parts($result, $msg_number);
    $message = FALSE;

    // Is this an empty message with no body and no mimeparts?
    if (!empty($parts)) {
      $imap_uid = $type == 'pop3' ? $this
        ->fetch_uid($mailbox, $msg_number) : imap_uid($result, $msg_number);
      $message = compact('header', 'imap_uid');
      $message['body_text'] = $parts['text_body'] ? $parts['text_body'] : $parts['html_body'];
      $message['body_html'] = $parts['html_body'] ? $parts['html_body'] : $parts['text_body'];
      $message['mimeparts'] = $parts['attachments'];
    }
    return $message;
  }

  /**
   * Close a mailbox.
   */
  function close_mailbox($result, $mailbox) {
    $this
      ->report_errors($mailbox);
    imap_close($result, CL_EXPUNGE);
  }

  /**
   * Fetch UID for a message in a POP mailbox.
   *
   * Taken from PHP.net.
   */
  function fetch_uid($mailbox, $msg_number) {
    extract($mailbox->settings);
    $retval = 0;
    $fp = fsockopen($domain, $port);
    if ($fp > 0) {
      $buf = fgets($fp, 1024);
      fwrite($fp, "USER {$name}\r\n");
      $buf = fgets($fp, 1024);
      fwrite($fp, "PASS {$pass}\r\n");
      $buf = fgets($fp, 1024);
      fwrite($fp, "UIDL {$msg_number}\r\n");
      $retval = fgets($fp, 1024);
      fwrite($fp, "QUIT\r\n");
      $buf = fgets($fp, 1024);
      fclose($fp);
    }
    return drupal_substr($retval, 6, 30);
  }

  /**
   * Capture and report IMAP errors.
   */
  function report_errors($mailbox) {

    // Need to be able to suppress errors when we are testing from an AJAX call.
    // Otherwise we get nasty AJAX dialogs.
    if (!$this->suppress_errors) {
      $errors = imap_errors();
      if ($errors) {
        list(, $caller) = debug_backtrace(false);
        $function = $caller['function'];
        foreach ($errors as $error) {
          if ($error == "SECURITY PROBLEM: insecure server advertised AUTH=PLAIN" && $mailbox->settings['insecure']) {
            continue;
          }
          mailhandler_report('error', 'IMAP error in %function: %error', array(
            '%function' => $function,
            '%error' => $error,
          ));
        }
      }
    }
  }

}

Classes

Namesort descending Description
MailhandlerPhpImapRetrieve Retrieve messages using PHP IMAP library.