is anybody (or has any one been succesful in) creating a hook between the mailhandler module and the image module?

it would turn drupal into a fantastic moblog platform.

cheers

Comments

bruno’s picture

Yes. I have been working on it but put it on hold until I have successfully converted image.module to use the new file API. You may have a look into sandbox/bruno/image-mailhandler. There is a modified mailhandler module there. It needs an updated image module which implements the _mailhandler() hook.

simonaustin’s picture

bruno, I'm curious about what you have done here. Would it be able to handle file attachments? What would need to be done to get file attachments to work?

I'm interested in this project. I found some code of your originally on kollum.drupal.org. I would like to develop this code to handle file attachments. Please let me know if you have any ideas/comments on how this should be done.

javanaut’s picture

I have implemented a slightly different approach. I have created a mailhandler hook for the filestore module that handles all attachment types. It is not complete or robust, but I will include it below. I realize that some attachment types warrant special treatment, and the only special treatment that my current implementation gives to images is to append an img tag to the bottom of the node body. Automatic thumbnailing would be a useful addition that the image module could contribute to. Maybe we need some sort of content-type-to-module mapping? For now, I just hard-code image-related behavior if content-type starts with "image".

Here's the code I'm working on (just add to filestore.module):

/**
 * @name Store Mail Attachments
 *
 * mailhandler hook to parse out attachments and create filestore nodes for each of them.
 *
 * @param $node An object containing the new node or comment.
 * @param $stream The number of the opened imap stream.
 * @param $msg_number The number of the current message in the mailbox.
 * @param $header An object containing email headers. See imap_header()
 * @param $mailbox Array containing info about the mailbox the message is from.
 *
 * @author Mark Howell (javanaut)
 */
function filestore_mailhandler($node, $stream, $msg_number, $header, $mailbox) {
 
 /* there should be some form of security here.  We can tell who the poster is by the
  * $node->uid value.  Mailhandler even sets $node->name for convenience.  Unfortunately,
  * the user_access(..) function doesn't take an argument of which user to test against.
  * Currently, any user who can post blog entries by mail can also post files using this
  * function.  Filestore permissions are ignored.  Caveat Emptor.
  */
  
  $files = filestore_extract_mime_attachments($stream, $msg_number);

  // don't run this unless it's been specifically enabled.
  if(sizeof($files) && module_exist('filestore')) {
    
    foreach($files as $file) {
      
      // create a new node of type filestore
      $newnode->uid = $node->uid;
      $newnode->title = $node->title;
      $newnode->filename = $file['filename'];
      $newnode->version = 1;
      $newnode->fileauthor = $node->name;
      $newnode->teaser = $file['filename'];
      $newnode->body = $file['filename'];
      $newnode->type = 'filestore';
      
      if(isset($file['filename'])) {
        $newnode->title = $file['filename'];
      }
      $newnode->filetype = $file['content_type'];

      if (!$newnode->filetype) {
        $newnode->filetype = "application/octet-stream";
      }
      else if ($newnode->filetype == "application/x-octet-stream") {
        $newnode->filetype = "application/octet-stream";
      }
      if (eregi("\\.exe\$|\\.com\$|\\.pif\$", $newnode->filename)) {
        $newnode->filetype = "application/octet-stream";
      }
      if (!_filestore_mimetype_allowed($newnode->filetype)) {
        drupal_set_message("Rejected file '$newnode->filename' because it is not one of the accepted MIME types ($newnode->filetype) (msg: ".$msg_number.", part: ".$file['part'].").");
        
        // just skip the offending file
        unset($newnode);
        continue;
      }
      else {
        $newnode->filedata = $file['data'];
      
        if ($newnode->filetype == "application/octet-stream") {
           if (!ereg("[^\001-\177]", $filedata)) {
              $newnode->filetype = "text/plain";
           }
        }
        drupal_set_message("Accepted file '$newnode->filename' of acceptable MIME type ($newnode->filetype) (msg: ".$msg_number.", part: ".$file['part'].").");
      }

      if ($newnode->filedata) {
        $newnode->filesize = strlen(base64_decode($newnode->filedata));
        $newnode->nid = node_save($newnode);
        $node->body .= theme_filestore_attachment($newnode);
        //$node->body .= theme("filestore_attachment", $newnode);
        
      }
      unset($newnode);
    }
  }
  return $node;
}

/**
 * Formats an HTML referenceto a filestore node that is absolutely linked to the filestore source.
 * (Currently only handles img tag for content_type of image/*
 * @author Mark Howell (javanaut)
 */
function theme_filestore_attachment($node) {
  // this is only for filestore nodes.
  if($node->type != 'filestore' || !$node->filetype) {
    return "";
  }
  if(strncmp('image', $node->filetype, 5) === 0) {
    return "<img src=\"" . url("filestore/download/$node->nid", null, null, 1) . "\" border=\"0\" />\n";
  }
  return "";
}

/**
 * This is an attachment grabber.
 * The original code was mostly acquired from php.net or linked to from the documentation.
 * It has since been heavily reincarnated.
 * @param $mbox The number of the opened imap stream.
 * @param $msgno The number of the current message in the mailbox.
 * @param $prefix A string designating the parent MIME Part (such as 2.2 or 1.4.2)
 * @param $depth A variable used to limit the amount of recursion (not currently used)
 * @param $parts An array of imap_fetchstructure(..) type objects.
 * @author Mark Howell (javanaut)
 */
function filestore_extract_mime_attachments($mbox, $msgno, $prefix='', $depth=0, $parts=null) {
  $files = array();

  $types[0] = 'text';
  $types[1] = 'multipart';
  $types[2] = 'message';
  $types[3] = 'application';
  $types[4] = 'audio';
  $types[5] = 'image';
  $types[6] = 'video';
  $types[7] = 'other';

  $start=1; // if you're always looking to skip the first part...set this to 2

  if(!$prefix && !$parts) {
    $struct = imap_fetchstructure($mbox,$msgno);
    //print("Message Structure:\n&quot;); print_r($struct); print(&quot;");
    $parts = $struct->parts;
  }
  elseif(is_array($parts)) {
    $prefix .= ".";
  }
  else {
    $struct = imap_bodystruct($mbox,$msgno,$prefix);
    $parts = $struct->parts;
    $prefix .= ".";
  }
 
  $contentParts = count($parts);

  if ($contentParts >= $start) {
    
    for ($k=0;$k<sizeof($parts);$k++) {
      $content_type = $types[$parts[$k]->type] . "/" . strtolower($parts[$k]->subtype);
      if ($content_type == "message/rfc822" || (is_array($parts[$k]->parts) && sizeof($parts[$k]->parts) > 0)) {
        if(is_array($parts[$k]->parts)) {
          //drupal_set_message("RECURSING (with " . sizeof($parts[$k]->parts) . " parts): new depth=$depth, new prefix=$prefix" . ($k+$start));
          $files += filestore_extract_mime_attachments($mbox, $msgno, $prefix . ($k+$start), $depth, $parts[$k]->parts);
        }
        else {
          drupal_set_message("Error: empty rfc822 message?  There is no parts array here..skipping..");
          //$files += filestore_extract_mime_attachments($mbox, $msgno, $prefix . ($k+$start), $depth);
        }
      }
      elseif($parts[$k]->parameters[0]->value == 'us-ascii') {
        // skip it.
        //drupal_set_message("skipping attachment $prefix" . ($k+$start) . " because it is the <em>us-ascii</em> placeholder"); 
      }
      else {
        //drupal_set_message("appending attachment " . $parts[$k]->parameters[0]->value . " which is part " . $prefix . ($k+$start) . " and of content-type " . $content_type);
        $files[] = array('filename' => $parts[$k]->parameters[0]->value, 
                         'part' => $prefix . ($k+$start), 
                         'data' => imap_fetchbody($mbox,$msgno,$prefix . $k+$start),
                         'content_type' => $content_type
                        );
      }
    }
  }
  elseif ($depth++ < 10 && $types[$struct->type] == "message" && strtolower($struct->subtype) == "rfc822") {
    //drupal_set_message("RECURSING(outer): new depth=$depth, new prefix=$prefix" . ($k+$start));
    $files += filestore_extract_mime_attachments($mbox, $msgno, $prefix . $start, $depth);
  }

  return $files;
}

Please understand that this is not a finished project. Some attachment types require special treatment (i.e. winmail.dat attachments) and there are many one-off scenarios that will run any simple elegant solution. If your goal is a moblog site, then many special cases need to be handled. For example, Cingular (a U.S. wireless carrier) phones don't apparently send the photo as an attachment, but instead link to a web page that would have to be screen-scraped to get the image. There are many such one-off scenarios. Once I've tested this out a bit more and it proves to be robust, I'll submit a patch to the filestore project. Also, this code was developed against 4.4.0rc code. I have no idea what it will do on 4.3.0.

simonaustin’s picture

my goal isn't a moblog site, but rather having users be able to email in stories that can have files attached that can be downloaded. I don't even need the images to display. The files just have to be downloadable. I'm running 4.3 so I might need to make some modifications to the code that you supplied. I'll let you know my results.

moshe weitzman’s picture

since this issue was opened, we have come a long way with upload.module and image.inc/module. i think now would be a great time to implement this.

javanaut’s picture

I'm planning on revamping the moblog module's underbelly for 4.6. The current means of creating image and file nodes from mime attachments is admittedly ugly. As far as the moblog platform goes, a generic solution would be quite difficult considering some of the challenges that I've seen with the moblog module:

* Wildly variant message formats, from simple single image attachments to embedded xml linking to remote audio/video/image content.
* Unwanted attachments..when I post content from my own phone, there are spacer.gif's, dotted_line.gif's, etc. that accompany the actual content. There would need to be a means of filtering these out.

There are other challenges as well, but in general, I would prefer the main mime-content-to-node translation to be in a well supported module such as mailhandler (mainly because it could be used by many other modules). I haven't really pushed for developers to support a formalized API for creating nodes from amorphous data, because, in my eyes, the challenge is very large and any solution that I can currently think of would be either a compromise or ugly (or both). Besides, I'm no good at pushing a somewhat political agenda :P

I will put a bit of thought into this as moblog module gets revised for 4.6. In a perfect world (at this point), I would like the mime-to-file and mime-to-image-node code in mailhandler if possible such that simple attachments are handled without needing moblog module. In such a design, moblog module would call upon mailhandler to perform these operations. If further strangeness is required (like pulling files off of a phone carrier's server), this more bizarre code would go in moblog (core is clean, outermost has cruft/implementation-specific stuff). This would allow perfect-world scenarios to be handled without installing moblog, and moblog to use mailhandler for heavy lifting.

moshe weitzman’s picture

sounds good ... fyi, i did some mime decoding voodoo in upload_indexer.module

sanduhrs’s picture

Version: 4.3.x-1.x-dev » 4.6.x-1.x-dev

There's a new approach to send images (media) vie e-mail/mobile without any filtering (yet) at http://drupal.org/node/59122 .
It uses image.module and mailhandler.module (patched for 4.7).
Please have a look at it and post your opinion/feedback/suggestions/code.
Tia.

moshe weitzman’s picture

Status: Active » Closed (won't fix)

see new module described in comment before this one.