From 0613768a63cffe1d99f5fd57c2d9b9461fb4de3f Mon Sep 17 00:00:00 2001 From: Effy Elden Date: Fri, 10 Mar 2017 20:53:00 +1100 Subject: [PATCH 1/3] CRM-20238 - Create hook for inbound SMS --- CRM/SMS/Provider.php | 29 ++++++++++++++++++++--------- CRM/Utils/Hook.php | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CRM/SMS/Provider.php b/CRM/SMS/Provider.php index 9d914dce0d9c..68c6e2508009 100644 --- a/CRM/SMS/Provider.php +++ b/CRM/SMS/Provider.php @@ -203,9 +203,17 @@ public function retrieve($name, $type, $abort = TRUE, $default = NULL, $location * @throws CRM_Core_Exception */ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { - $formatFrom = $this->formatPhone($this->stripPhone($from), $like, "like"); - $escapedFrom = CRM_Utils_Type::escape($formatFrom, 'String'); - $fromContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $escapedFrom . '"'); + $fromContactID = NULL; + $toContactID = NULL; + // call hook_civicrm_inboundSMS + CRM_Utils_Hook::inboundSMS($from, $fromContactID, $to, $toContactID, $body, $trackID); + + if (!$fromContactID) { + // find sender by phone number if $fromContactID not set by hook + $formatFrom = $this->formatPhone($this->stripPhone($from), $like, "like"); + $escapedFrom = CRM_Utils_Type::escape($formatFrom, 'String'); + $fromContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $escapedFrom . '"'); + } if (!$fromContactID) { // unknown mobile sender -- create new contact @@ -236,12 +244,15 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { $fromContactID = $fromContact->id; } - if ($to) { - $to = CRM_Utils_Type::escape($to, 'String'); - $toContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $to . '"'); - } - else { - $toContactID = $fromContactID; + if (!($toContactID)) { + // find recipient if $toContactID not set by hook + if ($to) { + $to = CRM_Utils_Type::escape($to, 'String'); + $toContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $to . '"'); + } + else { + $toContactID = $fromContactID; + } } if ($fromContactID) { diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index 602028a3788e..e6e130f6a88c 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2299,4 +2299,27 @@ public static function geocoderFormat($geoProvider, &$values, $xml) { ); } + /** + * This hook is called before an inbound SMS is processed. + * + * @param string $from + * The phone number the message is from, as set by SMS provider + * @param int $fromContactID + * Set to override default matching + * @param string $to + * The optional phone number the message is to, as set by SMS provider + * @param int $toContactID + * Set to override default matching + * @param string $body + * The body text of the message + * @param string $trackID + * The tracking ID of the message + * + * @return mixed + */ + public static function inboundSMS(&$from, &$fromContactID = NULL, &$to, &$toContactID = NULL, &$body, &$trackID) { + return self::singleton() + ->invoke(6, $from, $fromContactID, $to, $toContactID, $body, $trackID, 'civicrm_inboundSMS'); + } + } From caed3ddc97f346a68e51450dfa46b6d33e3ed2fe Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Sat, 13 May 2017 08:42:00 +1000 Subject: [PATCH 2/3] CRM-20238 Switch to using an Object based style as suggested by Tim and Paramatise a little Further use of new Message object --- CRM/SMS/Message.php | 68 ++++++++++++++++++++++++++++++++++++++++++++ CRM/SMS/Provider.php | 49 ++++++++++++++++--------------- CRM/Utils/Hook.php | 24 ++++------------ 3 files changed, 100 insertions(+), 41 deletions(-) create mode 100644 CRM/SMS/Message.php diff --git a/CRM/SMS/Message.php b/CRM/SMS/Message.php new file mode 100644 index 000000000000..0d4289bf699d --- /dev/null +++ b/CRM/SMS/Message.php @@ -0,0 +1,68 @@ +from = $from; + $message->to = $to; + $message->body = $body; + $message->trackID = $trackID; // call hook_civicrm_inboundSMS - CRM_Utils_Hook::inboundSMS($from, $fromContactID, $to, $toContactID, $body, $trackID); + CRM_Utils_Hook::inboundSMS($message); - if (!$fromContactID) { + if (!$message->fromContactID) { // find sender by phone number if $fromContactID not set by hook - $formatFrom = $this->formatPhone($this->stripPhone($from), $like, "like"); - $escapedFrom = CRM_Utils_Type::escape($formatFrom, 'String'); - $fromContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $escapedFrom . '"'); + $formatFrom = '%' . $this->formatPhone($this->stripPhone($message->from), $like, "like"); + $message->fromContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE '%1'", array( + 1 => array($formatFrom, 'String'))); } - if (!$fromContactID) { + if (!$message->fromContactID) { // unknown mobile sender -- create new contact // use fake @mobile.sms email address for new contact since civi // requires email or name for all contacts @@ -223,7 +226,7 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'); $phoneloc = array_search('Home', $locationTypes); $phonetype = array_search('Mobile', $phoneTypes); - $stripFrom = $this->stripPhone($from); + $stripFrom = $this->stripPhone($message->from); $contactparams = array( 'contact_type' => 'Individual', 'email' => array( @@ -241,41 +244,41 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { ), ); $fromContact = CRM_Contact_BAO_Contact::create($contactparams, FALSE, TRUE, FALSE); - $fromContactID = $fromContact->id; + $message->fromContactID = $fromContact->id; } - if (!($toContactID)) { + if (!($message->toContactID)) { // find recipient if $toContactID not set by hook - if ($to) { - $to = CRM_Utils_Type::escape($to, 'String'); - $toContactID = CRM_Core_DAO::singleValueQuery('SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE "%' . $to . '"'); + if ($message->to) { + $message->toContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE '%1'", array( + 1 => array('%' . $message->to, 'String'))); } else { - $toContactID = $fromContactID; + $message->toContactID = $fromContactID; } } - if ($fromContactID) { + if ($message->fromContactID) { $actStatusIDs = array_flip(CRM_Core_OptionGroup::values('activity_status')); $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Inbound SMS'); // note: lets not pass status here, assuming status will be updated by callback $activityParams = array( - 'source_contact_id' => $toContactID, - 'target_contact_id' => $fromContactID, + 'source_contact_id' => $message->toContactID, + 'target_contact_id' => $message->fromContactID, 'activity_type_id' => $activityTypeID, 'activity_date_time' => date('YmdHis'), 'status_id' => $actStatusIDs['Completed'], - 'details' => $body, - 'phone_number' => $from, + 'details' => $message->body, + 'phone_number' => $message->from, ); - if ($trackID) { - $trackID = CRM_Utils_Type::escape($trackID, 'String'); + if ($message->trackID) { + $trackID = CRM_Utils_Type::escape($message->trackID, 'String'); $activityParams['result'] = $trackID; } $result = CRM_Activity_BAO_Activity::create($activityParams); - CRM_Core_Error::debug_log_message("Inbound SMS recorded for cid={$fromContactID}."); + CRM_Core_Error::debug_log_message("Inbound SMS recorded for cid={$message->fromContactID}."); return $result; } } diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index e6e130f6a88c..e69cbb77ab18 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2302,24 +2302,12 @@ public static function geocoderFormat($geoProvider, &$values, $xml) { /** * This hook is called before an inbound SMS is processed. * - * @param string $from - * The phone number the message is from, as set by SMS provider - * @param int $fromContactID - * Set to override default matching - * @param string $to - * The optional phone number the message is to, as set by SMS provider - * @param int $toContactID - * Set to override default matching - * @param string $body - * The body text of the message - * @param string $trackID - * The tracking ID of the message - * - * @return mixed - */ - public static function inboundSMS(&$from, &$fromContactID = NULL, &$to, &$toContactID = NULL, &$body, &$trackID) { - return self::singleton() - ->invoke(6, $from, $fromContactID, $to, $toContactID, $body, $trackID, 'civicrm_inboundSMS'); + * @param CRM_SMS_Message Object $message + * An SMS message recieved + * @return mixed + */ + public static function inboundSMS(&$message) { + return self::singleton()->invoke(array('message'), $message, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, 'civicrm_inboundSMS'); } } From d20cd5d41468ab958760d3a68980847e4db6e153 Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Sat, 13 May 2017 12:28:14 +1000 Subject: [PATCH 3/3] Add Tests of processInbound function with hook --- CRM/SMS/Provider.php | 11 +++++------ tests/phpunit/CRM/SMS/ProviderTest.php | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CRM/SMS/Provider.php b/CRM/SMS/Provider.php index 9119b7c1ddd0..bc7476766f27 100644 --- a/CRM/SMS/Provider.php +++ b/CRM/SMS/Provider.php @@ -214,7 +214,7 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { if (!$message->fromContactID) { // find sender by phone number if $fromContactID not set by hook $formatFrom = '%' . $this->formatPhone($this->stripPhone($message->from), $like, "like"); - $message->fromContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE '%1'", array( + $message->fromContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE %1", array( 1 => array($formatFrom, 'String'))); } @@ -247,14 +247,14 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { $message->fromContactID = $fromContact->id; } - if (!($message->toContactID)) { + if (!$message->toContactID) { // find recipient if $toContactID not set by hook if ($message->to) { - $message->toContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE '%1'", array( + $message->toContactID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_phone JOIN civicrm_contact ON civicrm_contact.id = civicrm_phone.contact_id WHERE !civicrm_contact.is_deleted AND phone LIKE %1", array( 1 => array('%' . $message->to, 'String'))); } else { - $message->toContactID = $fromContactID; + $message->toContactID = $message->fromContactID; } } @@ -273,8 +273,7 @@ public function processInbound($from, $body, $to = NULL, $trackID = NULL) { 'phone_number' => $message->from, ); if ($message->trackID) { - $trackID = CRM_Utils_Type::escape($message->trackID, 'String'); - $activityParams['result'] = $trackID; + $activityParams['result'] = CRM_Utils_Type::escape($message->trackID, 'String'); } $result = CRM_Activity_BAO_Activity::create($activityParams); diff --git a/tests/phpunit/CRM/SMS/ProviderTest.php b/tests/phpunit/CRM/SMS/ProviderTest.php index 217c071ba2fe..23c509f94a73 100644 --- a/tests/phpunit/CRM/SMS/ProviderTest.php +++ b/tests/phpunit/CRM/SMS/ProviderTest.php @@ -79,6 +79,27 @@ public function testProcessInboundNoTo() { $this->assertEquals($contact['id'], $activity['target_contact_id'][0]); } + /** + * CRM-20238 Add test of ProcessInbound function where no To number is passed into the function but the toContactId gets set in a hook + */ + public function testProcessInboundSetToContactIDUsingHook() { + $provider = new testSMSProvider(); + $this->hookClass->setHook('civicrm_inboundSMS', array($this, 'smsHookTest')); + $result = $provider->processInbound('+61412345678', 'This is a test message', NULL, '12345'); + $this->assertEquals('This is a test message', $result->details); + $this->assertEquals('+61412345678', $result->phone_number); + $this->assertEquals('12345', $result->result); + $contact = $this->callAPISuccess('contact', 'getsingle', array('phone' => '+61487654321')); + $activity = $this->callAPISuccess('activity', 'getsingle', array('id' => $result->id, 'return' => array('source_contact_id', 'target_contact_id', 'assignee_contact_id'))); + $this->assertEquals($contact['id'], $activity['source_contact_id']); + } + + + public function smsHookTest(&$message) { + $testSourceContact = $this->individualCreate(array('phone' => array(1 => array('phone' => '+61487654321')))); + $message->toContactID = $testSourceContact; + } + } /**