function engagements_handle_incoming_sms

7.x engagements.module engagements_handle_incoming_sms($vars = array())
6.x engagements.module engagements_handle_incoming_sms($vars = array())

This catches incoming sms messages from POST, but can also be used by our "sms_get_all_messages" function, but it is also used by the sms_get_all_messages to save/update information.

TODO: Have a way to handle incoming calls, too, so we can charge correctly.

3 calls to engagements_handle_incoming_sms()
engagements.module in modules/engagements/engagements.module
This is the primary module file for the engagements module.
engagements_mass_sms_perform_batch_operation in modules/engagements/engagements.module
engagements_sms_get_all_messages in modules/engagements/engagements.module
Retrieve all messages, update the ones which don't have prices associated with them yet.
2 string references to 'engagements_handle_incoming_sms'
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 1651
This is the primary module file for the engagements module.

Code

function engagements_handle_incoming_sms($vars = array()) 
 {

  $signing_key = NULL;

  // If we did not supply a set of variables, use the POST superglobal
  if (count($vars) === 0) {
    // Meaning, we are NOT calling this manually from cron, but rather this is
    // being sent to us from the webhook.  So, we will want to verify using the signing key.
    $vars = $_POST;
    $signing_key = variable_get('sms_signing_key', 'none');
  }

  watchdog("engagements_sms", "Received from vars (possibly POST): <pre>" . print_r($vars, TRUE) . '</pre>
                                <br>Headers:<pre>' . print_r(getallheaders(), TRUE) . "</pre>", array(), WATCHDOG_DEBUG);


  if ($signing_key) {
    // We want to make sure this was sent from SendGrid and not a forgery.
    require_once (fp_get_module_path('engagements', TRUE, FALSE) . '/lib/signalwire/vendor/autoload.php');
    // See: https://www.twilio.com/docs/usage/webhooks/webhooks-security
    // see: https://developer.signalwire.com/guides/webhook-security/

    $validator = new Twilio\Security\RequestValidator($signing_key);
    $headers = getallheaders();
    $signature = $headers ['X-Twilio-Signature'];
    $url = base_url() . "/engagements-handle-incoming-sms";

    // Remove unrelated variables which might be in our vars array.
    unset($vars ['current_student_id']);
    unset($vars ['advising_student_id']);
    ksort($vars); // needs to be in alphabetical order by keys

    $result = $validator->validate($signature, $url, $vars);
    watchdog('engagements_sms', "Validator result with $signature, $url and vars...: <pre>" . print_r(intval($result), TRUE) . "</pre>", array(), WATCHDOG_DEBUG);

    // reject if intval($result) === 0    
    if (intval($result) === 0) {
      // As this was some sort of hacking attempt, we don't want signalwire to keep trying to notify us of the message,
      // so send back 200 and exit.
      watchdog('engagements_sms', "Tried to receive signalwire sms, but could not verify signature. Signature given: $signature", array(), WATCHDOG_ERROR);

      // Email php error contact to let them know this happened.
      // Dev: comment this out when we know it is working correctly?
      // smtp_mail(variable_get("notify_php_error_email_address", ""), "Error validating incoming SMS", "Tried to receive signalwire sms, but could not verify signature. Signature given: $signature.");


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

  } // if signing_key


  // If we made it here, then it's proven to be from SignalWire, and we can continue.

  // Allow other modules would like to handle in incoming SMS or even make changes to what is in $vars.
  invoke_hook('engagements_handle_incoming_sms_init', array(&$vars));


  $message_sid = $vars ['MessageSid'];
  $from_mobile = engagements_convert_to_valid_phone_number($vars ['From']);
  $to_mobile = engagements_convert_to_valid_phone_number($vars ['To']);
  $system_name = variable_get("system_name", "FlightPath");

  $body = $vars ['Body'];

  $num_media = intval($vars ['NumMedia']);
  $num_segments = @intval($vars ['NumSegments']);
  $price = 0.0042; // We know this is an SMS, so this is the price per segment inbound.
  if ($num_media > 0) {
    $price += 0.01; // Contained at least one piece of media, so price increases.  This is SignalWire's price.
  }
  $direction = "inbound";
  $fp_price = engagements_get_fp_price($price, $direction, $num_segments);

  $date_sent_ts = time(); // the time we received it.
  if (isset($vars ['date_sent_ts'])) {
    $date_sent_ts = $vars ['date_sent_ts'];
  }

  if (isset($vars ['direction'])) {
    $direction = $vars ['direction'];
  }

  $content = NULL;
  $content_created = array();

  $link = $alert_link = $media_filenames = "";

  // Figure out what user this came from/to!
  $from_cwid = $from_user_id = $to_user_id = "";
  $user_id = db_result(db_query("SELECT user_id FROM user_attributes WHERE `name` = 'mobile_phone' AND `value` = ?", array($from_mobile)));
  if ($user_id) {
    $from_cwid = db_get_cwid_from_user_id($user_id);
    $from_user_id = $user_id;
  }

  $to_cwid = "";
  $user_id = db_result(db_query("SELECT user_id FROM user_attributes WHERE `name` = 'mobile_phone' AND `value` = ?", array($to_mobile)));
  if ($user_id) {
    $to_cwid = db_get_cwid_from_user_id($user_id);
    $to_user_id = $user_id;
  }


  $dir = "received";
  $fac_cwid = $to_cwid;
  $stu_cwid = $from_cwid;
  if (strstr($direction, 'outbound')) {
    $dir = 'sent';
    $fac_cwid = $from_cwid;
    $stu_cwid = $to_cwid;
  }

  // Did we receive STOP?
  if ($dir == 'received') {
    if (trim(strtoupper($body)) === 'STOP') {
      engagements_handle_sms_stop($from_user_id, $from_cwid, $from_mobile, $to_mobile);
    }
    // UNSTOP or SUBSCRIBE?
    if (trim(strtoupper($body)) === 'UNSTOP' || trim(strtoupper($body)) === 'SUBSCRIBE' || trim(strtoupper($body)) === 'START') {
      engagements_handle_sms_unstop($from_user_id, $from_cwid, $from_mobile, $to_mobile);
    }

    // We do not return.  Go ahead and let it create an engagement below, so the
    // advisor can see that this happened.

  }

  //////////////////////
  // Get the message and any files attached.
  $media_filenames = "";
  $files = array();

  // Handle retreiving and saving media.
  if ($num_media > 0) { // Do we have any media?
    for ($t = 0; $t < $num_media; $t++) {
      $media_url = $vars ["MediaUrl$t"];
      $media_type = $vars ["MediaContentType$t"];

      $ext = engagements_get_file_extension_from_mime_type($media_type);
      if (!$ext || $ext == "html" || $ext == "htm") {
        continue; // skip if its not a type we care about.
      }

      // Create a new random filename to save it as.
      $filename = 'sms__' . $from_mobile . '_' . time() . '_' . mt_rand(1, 9999) . '.' . $ext;
      $tmp_name = sha1($filename . mt_rand(1, 9999) . time() . mt_rand(1, 9999));

      $file_contents = file_get_contents($media_url);

      if ($media_type == "text/plain") {
        // The body for whatever reason has been sent as a kookie media attachment.  Add it to the body and skip adding it to media.
        $body .= $file_contents;
        continue;
      }


      // Save to our temporary location on the server.
      $test = file_put_contents(sys_get_temp_dir() . '/' . $tmp_name, $file_contents);
      if (!$test) {
        watchdog('engagements_sms', 'Unable to write media file at temp location: ' . sys_get_temp_dir(), array(), 'error');
        fpm('Unable to write media file at temp location:' . sys_get_temp_dir() . '.  Permissions issue?');
        continue;
      }


      $file = array(
        'name' => $filename,
        'type' => $media_type,
        'tmp_name' => $tmp_name,
      );

      $files [] = $file; // Add to our "files" array, for attaching to content later.

      $media_filenames .= $filename . ',';
    } // for t (media)

    $media_filenames = rtrim($media_filenames, ',');
  } // if num media > 0

  $tbody = trim($body);

  // if we have media, add to the end of body.
  if ($media_filenames) {
    $body .= "~~MEDIA~~$media_filenames~~END_MEDIA~~";
    $tbody .= "\n\n[SMS includes attached image(s)]";
  }

  $tbody = filter_markup($tbody, 'plain');
  $tbody = nl2br($tbody);
  ///////////////////////////////////

  if (trim($stu_cwid) != "") {
    // create an engagement content node for this student!

    $content = new stdClass();
    $content->type = 'engagement';
    $content->cid = "new";
    $content->published = 1;
    $content->delete_flag = 0;
    $content->title = "Engagement: txt_msg re: student $stu_cwid"; // required

    $content->field__activity_datetime ['value'] = date('Y-m-d H:i:s', $date_sent_ts);
    $content->field__faculty_id ['value'] = $fac_cwid;
    $content->field__student_id ['value'] = $stu_cwid;
    $content->field__engagement_type ['value'] = 'txt_msg';
    $content->field__direction ['value'] = $dir;
    $content->field__from_sms_phone ['value'] = $from_mobile;
    $content->field__to_sms_phone ['value'] = $to_mobile;

    content_save($content);
    $cid = $content->cid;

    foreach ($files as $file) {
      // This function will handle encryption of files automatically.
      $fid = content_add_new_uploaded_file($file, $cid, FALSE);
    }

    $content->field__engagement_msg ['value'] = $body;
    $content->field__external_msg_id ['value'] = $message_sid;

    $content->field__visibility ['value'] = 'public';

    content_save($content);

    $content_created ['engagement'] = $content;

  } // We have a student to attach this to.



  if ($dir != "sent") { // meaning, we RECEIVED this message.
    $already_notified_users = array();

    $student_name = fp_get_student_name($stu_cwid, TRUE);
    $base_url = $GLOBALS ['fp_system_settings']['base_url'];
    $link = $base_url . "/engagements?current_student_id=$stu_cwid";

    $phones = engagements_get_from_phones();
    $desc = "";
    $to = $to_mobile;
    $pretty_to_number = engagements_convert_to_pretty_phone_number($to);
    $desc = @$phones ['lines'][$to]['description'];
    if (!$desc) {
      $desc = @$phones ['lines'][$from_mobile]['description'];
    }
    if ($desc) {
      $desc = " $desc";
    }

    if (trim($stu_cwid) != "") {
      // Create a new "activity record" that the student has sent a txt message    
      $acontent = new stdClass();
      $acontent->type = 'activity_record';
      $acontent->cid = "new";
      $acontent->published = 1;
      $acontent->delete_flag = 0;
      $acontent->title = t('Student sent a text message to ') . $pretty_to_number . " / " . $desc;
      $acontent->field__student_id ['value'] = $stu_cwid;
      $acontent->field__activity_type ['value'] = 'comment';
      content_save($acontent);

      $content_created ['activity_record'] = $acontent;

      // Notify the advisor(s).      

      $tmsg = "$student_name has submitted a text message to $system_name 
                    (To: $pretty_to_number / $desc).
                    <br><br>\n\n
                    To view/respond, visit the student's Engagements tab by logging in and following this link:<br>\n 
                    <a href='$link'>$link</a>";

      // If we have enabled sending the full contents, then send that (stripped of HTML):
      if (intval(variable_get('engagements_send_incoming_sms_contents_in_email', FALSE)) === 1) {
        $tmsg = "$student_name has submitted a text message to $system_name 
                    (To: $pretty_to_number / $desc).
                    <br><br>\n\n
                    The filtered contents of the message are as follows:
                    <blockquote>\n\n
                      $tbody
                    </blockquote>
                    To view/respond to the full message, visit the student's Engagements tab by logging in and following this link:<br>\n 
                    <a href='$link'>$link</a>";

      }


      $advisors = advise_get_advisors_for_student($stu_cwid);
      foreach ($advisors as $c => $afaculty_id) {
        $account_user_id = db_get_user_id_from_cwid($afaculty_id, 'faculty');
        if ($account_user_id) {
          notify_send_notification_to_user($account_user_id, $tmsg, $content->cid, 'engagement');
          $already_notified_users [] = $account_user_id;
        }
      } // foreach advisors

    }




    // Notify other users who might have been assigned to receive notifications 
    $to_be_notified = engagements_get_users_to_be_notified_for_sms_on_number($to_mobile);
    foreach ($to_be_notified as $account_user_id) {
      if (in_array($account_user_id, $already_notified_users)) {
        continue;
      }

      if (trim($stu_cwid) == "") {
        // Meaning, we couldn't find this student in our system!
        // Create an alert for the recipient which does show the full txt message.  The notification provides a link to the alert.
        $alert = new stdClass();
        $alert->type = 'alert';
        $alert->cid = "new";
        $alert->published = 1;
        $alert->delete_flag = 0;


        $from_mobile_pretty = engagements_convert_to_pretty_phone_number($from_mobile);
        $alert->title = t('Unrecognized sender (@fmb) sent a text message to ', array("@fmb" => $from_mobile_pretty)) . $pretty_to_number . " / " . $desc;
        $alert->field__alert_msg ['value'] = $body;
        $alert->field__alert_status ['value'] = 'open';
        $alert->field__visibility ['value'] = 'faculty';
        $alert->field__target_faculty_id ['value'] = db_get_cwid_from_user_id($account_user_id);
        $alert->field__student_id ['value'] = "N/A";
        $alert->field__exclude_advisor ['value'] = 1;

        content_save($alert);

        $content_created ['alerts'][$alert->cid] = $alert;

        // If there were any files attached, save them with the ALERT here, since this is from an unknown sender.
        foreach ($files as $file) {
          // This function will handle encryption of files automatically.
          $fid = content_add_new_uploaded_file($file, $alert->cid, FALSE);
        }


        $alert_link = $base_url . "/content/$alert->cid?content_crumbs=alerts";

        $tmsg = "A text message has been received for $system_name. (To: $pretty_to_number / $desc).<br><br> No student could be found which is
                  associated with the sending number ($from_mobile).<br><br>Message SID: $message_sid.
                  <br><br>To view the full message, view the Alert by logging in and clicking here: 
                  <a href='$alert_link'>$alert_link</a>";


        // If we have enabled sending the full contents, then send that (stripped of HTML):
        if (intval(variable_get('engagements_send_incoming_sms_contents_in_email', FALSE)) === 1) {
          $tmsg = "A text message has been received for $system_name. 
                      (To: $pretty_to_number / $desc).
                      <br><br>\n\n
                      No student could be found which is associated with the sending number ($from_mobile).
                      <br><br>Message SID: $message_sid.  The filtered contents of the message are as follows:
                      <blockquote>\n\n
                        $tbody
                      </blockquote>
                      To view the full message, view the Alert by logging in and clicking here: 
                      <a href='$alert_link'>$alert_link</a>";

        }



        notify_send_notification_to_user($account_user_id, $tmsg, $alert->cid, 'alert');
      }
      else {
        // We DO have a student this came from.  Let this user know normally.
        $tmsg = "$student_name has submitted a text message to $system_name (To: $pretty_to_number / $desc).
                  <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>";


        // If we have enabled sending the full contents, then send that (stripped of HTML):
        if (intval(variable_get('engagements_send_incoming_sms_contents_in_email', FALSE)) === 1) {
          $tmsg = "$student_name has submitted a text message to $system_name 
                      (To: $pretty_to_number / $desc).
                      <br><br>\n\n
                      The filtered contents of the message are as follows:
                      <blockquote>\n\n
                        $tbody
                      </blockquote>
                      To view/respond to the full message, 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');
      }
    } //foreach to_be_notified


  } // dir != sent  (we received this text)








  // Write our data to our sms_history table.
  db_query(
  "INSERT INTO sms_history (`message_sid`, sw_type, `body`, `from_number`, `to_number`, `sw_price`, `fp_price`, `from_cwid`, `to_cwid`, `updated`, `direction`, 
                              `media_filenames`, `date_sent`, price_processed, num_segments, delivery_status, err_code, err_message, err_friendly_message)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ", 
  array($message_sid, 'sms', $body, $from_mobile, $to_mobile, $price, $fp_price, $from_cwid, $to_cwid, time(), $direction,
    $media_filenames, $date_sent_ts, 1, $num_segments, @$vars ['display_status'], @$vars ['err_code'], @$vars ['err_message'], @$vars ['err_friendly_message'])
  );
  $sms_history_mid = db_insert_id();


  // Allow other modules would like to handle in incoming SMS or even make changes to what is in $vars.
  invoke_hook('engagements_handle_incoming_sms_post_save', array(&$vars, $sms_history_mid, $content_created));



}