function engagements_handle_incoming_mailgun_email

7.x engagements.module engagements_handle_incoming_mailgun_email()
2 string references to 'engagements_handle_incoming_mailgun_email'
engagements.module in modules/engagements/engagements.module
This is the primary module file for the engagements module.
engagements_menu in modules/engagements/engagements.module
Implement hook_menu

File

modules/engagements/engagements.module, line 204
This is the primary module file for the engagements module.

Code

function engagements_handle_incoming_mailgun_email() {
  watchdog('debug', 'From Post: <pre>' . print_r($_POST, TRUE) . '</pre>', array(), WATCHDOG_DEBUG);
  watchdog('debug', 'From Files: <pre>' . print_r($_FILES, TRUE) . '</pre>', array(), WATCHDOG_DEBUG);

  // See Mailgun's Securing Web hooks documentation to know what to do with "signature" and "token", to confirm
  //       it came from MailGun.  https://www.mailgun.com/blog/email/your-guide-to-webhooks/#chapter-5
  // This code shows how we verify....
  //     if (isset($event->signature) && hash_hmac('sha256', $event->signature->timestamp.$event->signature->token, $key) === $event->signature->signature) {
  // In this case, $key is our mailgun api key. Configured in the Engagements - email settings section.
  // This should be the "HTTP webhook signing Key".  Go to mailgun dashboard.  Lower right will be a link to the API Keys

  $timestamp = fp_trim(@$_POST ['timestamp']);
  $token = fp_trim(@$_POST ['token']);
  $signature = fp_trim(@$_POST ['signature']);
  $api_key = variable_get('engagements_mailgun_api_key', 'NONE SET');

  $confirm_hash = hash_hmac('sha256', $timestamp . $token, $api_key);

  // Before continuing, verify that the signature is correct (that we really came from MailGun)
  if (!$signature || strlen($signature) == 0 || $confirm_hash !== $signature) {
    watchdog('engagements_mailgun', "Tried to receive mailgun email, but could not verify signature. timestamp: $timestamp, token: $token, signature: $signature, confirm_hash = $confirm_hash", array(), WATCHDOG_ERROR);

    // As this was some sort of hacking attempt, we don't want mailgun to keep trying to notify us of the message,
    // so send back 200 and exit.

    // To tell MailGun not to retry this message, we need to deliver a 200 response.  Otherwise they will keep trying for 3 days.
    http_response_code(200);
    exit;
  }


  // If we are here, it means this email is valid and we can continue.



  $sender = $_POST ['sender'];
  $recipient = $_POST ['recipient'];
  $body_html = $_POST ['body-html'];
  $body_plain = $_POST ['body-plain'];
  $body_stripped_html = $_POST ['stripped-html'];
  $body_stripped_text = $_POST ['stripped-text'];
  $subject = $_POST ['subject'];
  $timestamp = intval($_POST ['timestamp']); // in UTC already?  
  $date = $_POST ['Date'];

  $body = $body_stripped_text;
  if ($body_plain) {
    $body = $body_plain;
  }
  if ($body_html) {
    $body = $body_html;
  }



  // Sometimes, emails contain odd encoding.  Ex:  =C2=A0, which is apparently a non-breaking space.  Let's convert just in case.
  // From: https://stackoverflow.com/questions/12682208/parsing-email-body-with-7bit-content-transfer-encoding-php
  // and: https://github.com/geerlingguy/Imap/blob/c4b54e52576bf71a93045d2a4581589eab9a00b6/JJG/Imap.php#L398
  // Manually convert common encoded characters into their UTF-8 equivalents.
  $characters = array(
    '=20' => ' ', // space.
    '=2C' => ',', // comma.
    '=E2=80=99' => "'", // single quote.
    '=C2=A0' => ' ', // non-breaking space.      
    '=0A' => "\r\n", // line break.
    '=0D' => "\r\n", // carriage return.
    '=A0' => ' ', // non-breaking space.
    "=\r\n" => '', // joined line.
    '=E2=80=A6' => '&hellip;', // ellipsis.
    '=E2=80=A2' => '&bull;', // bullet.
    '=E2=80=93' => '&ndash;', // en dash.
    '=E2=80=94' => '&mdash;', // em dash.
  );

  // Loop through the encoded characters and replace any that are found.
  foreach ($characters as $key => $value) {
    $body = str_replace($key, $value, $body);
  }




  // This will map any inline images in the body of the email to their attachment names.
  // Example, this is in the body_html where an inline image was:
  // <img title="Inline image" alt="Inline image" src="cid:285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com" class="yahoo-inline-image" draggable="false" style="max-width: 800px; width: 100%;" data-id="<285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com>">
  // Notice the src.
  // Now, in the content-id-map, we see this:
  // {"<06d5a647-5600-086a-12e2-29a3dc32a7c8@yahoo.com>":"attachment-3",
  //      "<138bab3e-5b05-91d2-133b-adbba379dd23@yahoo.com>":"attachment-2",
  //      "<285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com>":"attachment-1"}
  // Notice that 285ac......@yahoo.com says it matches "attachment-1".  That is the same src= for our inline image tag.
  // 
  // Note: From gmail, the content-id-map has no useful information.  Very strange.  But what we DO have is the "alt" tag.
  // See: <img src="cid:ii_lwy9bb9j1" alt="um actually.png" width="399" height="398">
  // We can use THAT to figure out the inline image, since [name] in the FILES states the filename.
  $content_id_map = fp_trim(@$_POST ['content-id-map']);

  // Our attachments are in $_FILES...
  /*
   * Array
    (
    [attachment-1] => Array
        (
            [name] => The_More_You_Know_0-0_screenshot.jpg
            [full_path] => The_More_You_Know_0-0_screenshot.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpV7mds1
            [error] => 0
            [size] => 33147
        )

    [attachment-2] => Array
        (
            [name] => ATS notes.rtf
            [full_path] => ATS notes.rtf
            [type] => application/rtf
            [tmp_name] => /tmp/phph4nE9m
            [error] => 0
            [size] => 10728
        )

    [attachment-3] => Array
        (
            [name] => jetstream script.odt
            [full_path] => jetstream script.odt
            [type] => application/vnd.oasis.opendocument.text
            [tmp_name] => /tmp/phpbbQK2W
            [error] => 0
            [size] => 32185
        )

    )
   */

  // Curiously, mailgun one time sent me the same email twice.  We didn't have attachments with the first POST, but we did on the second.  We should
  // always overwrite any email we've already got with a new one, if such comes in.
  // We can't use the Message-Id to identify unique messages!
  // Note: from Gmail, the Message-Id is blank!  In that case, we can hash the date, sender, recipient, subject, message, etc, to create our own ID.

  $external_msg_id = hash('sha256', $sender . $subject . $date . $recipient . $body . $body_html . $body_plain . $body_stripped_text . $content_id_map);


  // Create the email entry in FlightPath:

  // Match the sender's email to a student.
  $student_user_id = db_get_user_id_from_email($sender, 'student');
  if ($student_user_id) {

    watchdog('engagements_mailgun', "Found from student $student_user_id, creating engagement content.", array(), WATCHDOG_DEBUG);

    // create this as an engagement for this student.
    $account = fp_load_user($student_user_id);
    $student_cwid = $account->cwid;

    // TODO: If another engagement with external_msg_id exists, then load that instead of
    // TODO: creating a new one.  ((Is this necessary?))
    $content = new stdClass();
    $content->type = 'engagement';
    $content->cid = "new";


    $content->published = 1;
    $content->delete_flag = 0;
    $content->title = $subject; // required

    $content->field__activity_datetime ['value'] = date('Y-m-d H:i:s', $timestamp);
    $content->field__student_id ['value'] = $student_cwid;
    $content->field__engagement_type ['value'] = 'email';
    $content->field__direction ['value'] = 'received';

    $content->field__engagement_msg ['value'] = filter_markup($body, 'basic');
    $content->field__external_msg_id ['value'] = $external_msg_id;

    content_save($content);
    $cid = $content->cid; // We need the cid to already be created in order to save attachments

    // add attachments
    $attachments = array();
    if ($content_id_map) {
      $map = json_decode($content_id_map, TRUE);
      watchdog('debug', 'map is: <pre>' . print_r($map, TRUE) . "</pre>", array(), WATCHDOG_DEBUG);

      $sender_machine_readable = fp_get_machine_readable($sender);

      foreach ($_FILES as $xid => $details) {
        $filename_prefix = "email__" . $sender_machine_readable;

        $file = array(
          'name' => $details ['name'],
          'type' => $details ['type'],
          'tmp_name' => $details ['tmp_name'],
        );


        $fid = content_add_new_uploaded_file($file, $cid, TRUE, CONTENT_ENCRYPTED_FILE, FALSE, $filename_prefix);

        if (!$fid) {
          watchdog('engagements_mailgun', 'Unable to save email attachment file: @fn', array('@fn' => $file ['name']), WATCHDOG_ERROR);
          $body .= "\n\n[ATTACHMENT UNABLE TO SAVE. Check with system administrator.]";
          continue;
        }

        // If we are here, it means we DID save the file.
        // Modify body as necessary.  Replace embedded <img> tag with a message stating that for
        // security, the image is encrypted and stored with attachments?
        if (strstr($body, "<img")) {

          // Go through and replace elements from our map with names...  Helps fix a bug with Yahoo where the preg_replace breaks.
          $c = 1;
          foreach ($map as $e => $v) {
            $body = str_replace($e, $v, $body);
            $body = preg_replace("/<img[^>]+\>/i", "<div class='email-inline-img-replace'>Inline image #$c securely encrypted - See attachments</div> ", $body, 1);
            $c++;
          }

          // Get any others left not replaced in the foreach above.
          $body = preg_replace("/<img[^>]+\>/i", "<div class='email-inline-img-replace'>Inline image securely encrypted - See attachments</div> ", $body);
        }



        // Add to our list of attachments
        $attachments [] = array('fid' => $fid);


      } // foreach FILES

    } // if content_id_map




    // save attachements along with the engagement, if there are any
    if (count($attachments) > 0) {

      $fid_line = "";
      foreach ($attachments as $adetails) {
        $fid = $adetails ['fid'];
        $fid_line .= $fid . ",";
      }
      $fid_line = rtrim($fid_line, ",");
      $content->field__attachment ['value'] = $fid_line;
    }


    // Re-add body, as it may have changed because of attachments.      
    $content->field__engagement_msg ['value'] = filter_markup($body, 'basic');
    $content->field__visibility ['value'] = 'public';

    content_save($content);

    // if received, create an activity record.
    // Create a new "activity record" that the student has sent an email message

    $acontent = new stdClass();
    $acontent->type = 'activity_record';
    $acontent->cid = "new";
    $acontent->published = 1;
    $acontent->delete_flag = 0;
    $acontent->title = t('Student replied to email message.');
    $acontent->field__student_id ['value'] = $student_cwid;
    $acontent->field__activity_type ['value'] = 'mail';
    content_save($acontent);

    // Notify the advisor(s).
    // get list of all advisors for this student, and send notifications.
    $advisors = advise_get_advisors_for_student($student_cwid);
    foreach ($advisors as $c => $afaculty_id) {
      $account_user_id = db_get_user_id_from_cwid($afaculty_id, 'faculty');
      if ($account_user_id) {
        $student_name = fp_get_student_name($student_cwid, TRUE);
        $base_url = $GLOBALS ['fp_system_settings']['base_url'];
        $link = $base_url . "/engagements?current_student_id=$student_cwid";
        $tmsg = "";
        $tmsg .= "$student_name has submitted an email to FlightPath.<br><br>\n\n
                    To view, visit the student's Engagements tab by logging in and following this link:<br>\n 
                    <a href='$link'>$link</a>";

        notify_send_notification_to_user($account_user_id, $tmsg, $content->cid, 'engagement');
      }
    }
  } // if student_user_id    
  else {
    // The student_user_id could not be found!  This is possibly spam.
    // TODO: handle the rejection of this email?
  }







  // To tell MailGun not to retry this message, we need to deliver a 200 response.  Otherwise they will keep trying for 3 days.
  http_response_code(200);
  exit;

}