'SMS Framework', 'description' => 'Control how your site uses SMS.', 'position' => 'right', 'page callback' => 'system_admin_menu_block_page', 'access arguments' => array('administer smsframework'), 'file' => 'system.admin.inc', 'file path' => drupal_get_path('module', 'system') ); $items['admin/smsframework/gateways'] = array( 'title' => 'Gateway configuration', 'description' => 'Configure gateways and chose the default gateway.', 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_admin_default_form', NULL), 'access arguments' => array('administer smsframework'), 'file' => 'sms.admin.inc', ); $items['admin/smsframework/gateways/%'] = array( 'title callback' => 'sms_admin_gateway_title', 'title arguments' => array(3), 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_admin_gateway_form', 3), 'access arguments' => array('administer smsframework'), 'type' => MENU_CALLBACK, 'file' => 'sms.admin.inc', ); $items['admin/smsframework/carriers'] = array( 'title' => 'Carrier configuration', 'description' => 'Configure supported carriers.', 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_carriers_admin_form'), 'access arguments' => array('administer smsframework'), 'file' => 'sms.admin.inc', ); $items['admin/smsframework/carriers/manage'] = array( 'title' => 'Manage', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/smsframework/carriers/add'] = array( 'title' => 'Add', 'description' => 'Configure supported carriers.', 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_carriers_edit_form'), 'access arguments' => array('administer smsframework'), 'file' => 'sms.admin.inc', 'type' => MENU_LOCAL_TASK, ); $items['admin/smsframework/carriers/%carrier'] = array( 'title' => 'Edit carrier', 'description' => 'Configure supported carriers.', 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_carriers_edit_form', 3), 'access arguments' => array('administer smsframework'), 'type' => MENU_CALLBACK, 'file' => 'sms.admin.inc', ); $items['admin/smsframework/carriers/delete/%carrier'] = array( 'title' => 'Edit carrier', 'description' => 'Configure supported carriers.', 'page callback' => 'drupal_get_form', 'page arguments' => array('sms_carriers_delete_form', 4), 'access arguments' => array('administer smsframework'), 'type' => MENU_CALLBACK, 'file' => 'sms.admin.inc', ); return $items; } /** * Implements hook_perm(). */ function sms_perm() { return array('administer smsframework'); } /** * Implements hook_theme(). */ function sms_theme() { $items['sms_admin_default_form'] = $items['sms_carriers_admin_form'] = array( 'arguments' => array('form' => NULL), 'file' => 'sms.admin.inc', ); return $items; } /** * Sends a message using the active gateway. * * @param $number * The destination number. * * @param $message * The text of the messsage to send. * * @param $options * An array of dditional properties as defined by gateway modules. */ function sms_send($number, $message, $options = array()) { $gateway = sms_default_gateway(); foreach (module_implements('sms_send') as $module) { $function = $module .'_sms_send'; $function($number, $message, $options, $gateway); } $response = NULL; if (function_exists($gateway['send'])) { $response = $gateway['send']($number, $message, $options); } $result = sms_handle_result($response, $number, $message); // Post process hook foreach (module_implements('sms_send_process') as $module) { $function = $module .'_sms_send_process'; $function('post process', $number, $message, $options, $gateway, $result); } return $result; } /** * Handle the response back from the sms gateway. */ function sms_handle_result($result, $number, $message) { if ($result['status']) { return TRUE; } else { $error_message = 'Sending SMS to %number failed.'; $variables['%number'] = $number; if ($result['message']) { $error_message .= ' The gateway said '. $result['message']; if (!empty($result['variables'])) { $variables = array_merge($variables, $result['variables']); } } watchdog('sms', $error_message, $variables, WATCHDOG_ERROR); return FALSE; } } /** * Callback for incoming messages. Allows gateways modules to pass messages in * a standard format for processing. * * @param $number * The sender's mobile number. * * @param $message * The content of the text message. */ function sms_incoming($number, $message, $options = array()) { if (module_exists('rules')) { rules_invoke_event('sms_incoming', $number, $message); } // Execute three phases module_invoke_all('sms_incoming', 'pre process', $number, $message, $options); module_invoke_all('sms_incoming', 'process', $number, $message, $options); module_invoke_all('sms_incoming', 'post process', $number, $message, $options); } /** * Callback for incoming message receipts. Allows gateways modules to pass * message receipts in a standard format for processing, and provides a basic * set of status codes for common code handling. * * Allowed message status codes are defined as constants at the top of this module. * * The gateway code and string will often be provided in the $options array as * 'gateway_message_status' and 'gateway_message_status_text'. * * @param string $number * The sender's mobile number. * * @param string $reference * Unique message reference code, as provided when message is sent. * * @param string $message_status * An SMS Framework message status code, as per the defined constants. * * @param array $options * Extended options passed by the receipt receiver. */ function sms_receipt($number, $reference, $message_status = SMS_GW_UNKNOWN_STATUS, $options = array()) { // Execute three phases module_invoke_all('sms_receipt', 'pre process', $number, $reference, $message_status, $options); module_invoke_all('sms_receipt', 'process', $number, $reference, $message_status, $options); module_invoke_all('sms_receipt', 'post process', $number, $reference, $message_status, $options); } /** * Returns the current default gateway. */ function sms_default_gateway() { return sms_gateways('gateway', variable_get('sms_default_gateway', 'log')); } /** * Implements hook_gateway_info(). */ function sms_gateway_info() { return array( 'log' => array( 'name' => t('Log only'), 'send' => 'sms_send_log', ), ); } /** * Log sms message. * * @param $number * Mobile numbers message sent to. * @param $message * Message sent. * @param $options * Associative array of options passed to gateway. */ function sms_send_log($number, $message, $options) { watchdog('sms', 'SMS message sent to %number with the text: @message', array('%number' => $number, '@message' => $message), WATCHDOG_INFO); return array('status' => TRUE); } /** * SMS gateway menutitle callback. */ function sms_admin_gateway_title($gateway_id) { $gateway = sms_gateways('gateway', $gateway_id); return sprintf('%s gateway', $gateway['name']); } /** * Get a list of all gateways * * @param $op * The format in which to return the list. When set to 'gateway' or 'name', * only the specified gateway is returned. When set to 'gateways' or 'names', * all gateways are returned. * * @param $gateway * A gateway identifier string that indicates the gateway to return. Leave at default * value (NULL) to return all gateways. * * @return * Either an array of all gateways or a single gateway, in a variable format. */ function sms_gateways($op = 'gateways', $gateway = NULL) { list($_gateways, $_names) = _gateways_build(); switch ($op) { case 'gateways': return $_gateways; case 'gateway': $return = $_gateways[$gateway]; $return['identifier'] = $gateway; return $return; case 'names': return $_names; case 'name': return $_names[$gateway]; } } function _gateways_build() { $_gateways = array(); $_names = array(); $gateway_array = module_invoke_all('gateway_info'); foreach ($gateway_array as $identifier => $info) { $info['configuration'] = variable_get('sms_'. $identifier .'_settings', ''); $_gateways[$identifier] = $info; $_names[$identifier] = $info['name']; } asort($_names); return array($_gateways, $_names); } /** * Form builder for send sms form. * * Generates a SMS sending form and adds gateway defined elements. The form * array that is returned can be merged with an existing form using * array_merge(). * * @param $required * Specify if the user is required to provide information for the fields. * * @see sms_send_form_submit_validate(). * @see sms_send_form_submit_submit(). */ function sms_send_form($required = FALSE) { $gateway = sms_default_gateway(); $form['number'] = array( '#type' => 'textfield', '#title' => t('Phone number'), '#size' => 40, '#maxlength' => 255, '#required' => $required, ); // Add gateway defined fields if (function_exists($gateway['send form'])) { $form['gateway']['#tree'] = TRUE; $form['gateway'] = array_merge($gateway['send form']($required), $form['gateway']); } return $form; } /** * Form validation handler for sms_send_form(). * * @see sms_send_form(). * @see sms_send_form_submit(). */ function sms_send_form_validate($form, &$form_state) { if (!sms_validate_number(sms_formatter($form_state['values']['number']), $form_state['values']['gateway'])) { form_set_error('number', t('You must enter a valid phone number.')); } } /** * Form submission handler for sms_send_form(). * * @see sms_send_form(). * @see sms_send_form_validate(). */ function sms_send_form_submit($form, &$form_state) { $form_state['values']['number'] = sms_formatter($form_state['values']['number']); sms_send($form_state['values']['number'], $form_state['values']['message'], $form_state['values']['gateway']); } /****************************************************************************** * SMS Carrier Functions * * @todo - consider moving this to email gateway, unless there is a reason to * have these functions without the email gateway? *****************************************************************************/ /** * Get a list of all carriers */ function sms_carriers($domain = NULL) { $default_carriers = module_invoke_all('sms_carriers'); $enabled_carriers = variable_get('sms_enabled_carriers', array()); // Load default carriers from code foreach ($default_carriers as $id => $carrier) { $carriers[$id] = array('name' => $carrier, 'type' => SMS_CARRIER_DEFAULT); } // Load overrideen carriers from database $result = db_query("SELECT name, domain FROM {sms_carriers}"); while ($carrier = db_fetch_array($result)) { if (in_array($carrier['domain'], array_keys($carriers))) { $type = SMS_CARRIER_OVERRIDDEN; } else { $type = SMS_CARRIER_NORMAL; } $carriers[$carrier['domain']] = array( 'name' => $carrier['name'], 'type' => $type, ); } foreach($enabled_carriers as $carrier) { if (is_array($carriers[$carrier])) { $carriers[$carrier]['status'] = 1; } } if ($domain) { $carriers[$domain]['domain'] = $domain; return $carriers[$domain]; } return $carriers; } /** * Load a single carrier */ function carrier_load($domain) { return sms_carriers($domain); } /** * Save a carrier. */ function carrier_save($domain, $edit) { if (!empty($domain)) { $carrier = carrier_load($domain); if ($carrier['type'] == SMS_CARRIER_DEFAULT) { $edit['status'] = 1; drupal_write_record('sms_carriers', $edit); } else if(!empty($edit['domain'])) { drupal_write_record('sms_carriers', $edit, 'domain'); // TODO: we need more logic to figure out when someone is changing the domain name } } else { $edit['status'] = 1; drupal_write_record('sms_carriers', $edit); } } /** * Implements hook_sms_carriers() */ function sms_sms_carriers() { return array( 'sms.3rivers.net' => t('3 River Wireless'), 'airtelkk.com' => t('Airtel (Karnataka, India)'), 'msg.acsalaska.com' => t('Alaska Communications Systems'), 'message.alltel.com' => t('Alltel Wireless'), 'txt.att.net' => t('AT&T/Cingular'), 'txt.bell.ca' => t('Bell Mobility (Canada)'), 'myboostmobile.com' => t('Boost Mobile'), 'mobile.celloneusa.com' => t('Cellular One (Dobson)'), 'south1.com' => t('Cellular South'), 'cwemail.com' => t('Centennial Wireless'), '139.com' => t('ChinaMobile (139)'), 'gocbw.com' => t('Cincinnati Bell Wireless'), 'cingularme.com' => t('Cingular (GoPhone prepaid/7-11 Speakout)'), 'ideasclaro-ca.com' => t('Claro (Nicaragua)'), 'mms.claro.com.uy' => t('Claro (Uruguay)'), 'comcel.com.co' => t('Comcel'), 'sms.mycricket.com' => t('Cricket'), 'sms.ctimovil.com.ar' => t('CTI'), 'emtelworld.net' => t('Emtel (Mauritius)'), 'smsmail.eplus.de' => t('E-Plus'), 'fido.ca' => t('Fido (Canada)'), 'msg.gci.net' => t('General Communications Inc.'), 'msg.globalstarusa.com' => t('Globalstar'), 'myhelio.com' => t('Helio'), 'ivctext.com' => t('Illinois Valley Cellular'), 'sms.mymeteor.ie' => t('Meteor (Ireland)'), 'sms.spicenepal.com' => t('Mero Mobile (Nepal)'), 'mymetropcs.com' => t('MetroPCS'), 'mobilinkworld.com' => t('Mobilink World'), 'sms.mobitel.lk' => t('Mobitel (Sri Lanka)'), 'movimensaje.com.ar' => t('Movicom'), 'movistar.com.co' => t('Movistar (Colombia)'), 'sms.co.za' => t('MTN (South Africa)'), 'text.mtsmobility.com' => t('MTS (Canada)'), 'nextel.net.ar' => t('Nextel (Argentina)'), 'orange.fr' => t('Orange (France)'), 'orange.pl' => t('Orange (Poland)'), 'orange.net' => t('Orange (UK)'), 'personal-net.com.ar' => t('Personal (Argentina)'), 'text.plusgsm.pl' => t('Plus GSM (Poland)'), 'qwestmp.com' => t('Qwest'), 'pcs.rogers.com' => t('Rogers (Canada)'), 'sms.sasktel.com' => t('Sasktel (Canada)'), 'mas.aw' => t('Setar Mobile email (Aruba)'), 'page.nextel.com' => t('Sprint (Nextel)'), 'messaging.sprintpcs.com' => t('Sprint (PCS)'), 'tms.suncom.com' => t('Suncom'), 'tmomail.net' => t('T-Mobile'), 'sms.t-mobile.at' => t('T-Mobile (Austria)'), 't-mobile-sms.de' => t('T-Mobile Germany'), 'msg.telus.com' => t('Telus Mobility (Canada)'), 'sms.thumbcellular.com' => t('Thumb Cellular'), 'sms.tigo.com.co' => t('Tigo (Formerly Ola)'), 'utext.com' => t('Unicel'), 'email.uscc.net' => t('US Cellular'), 'vtext.com' => t('Verizon'), 'vmobile.ca' => t('Virgin Mobile (Canada)'), 'vmobl.com' => t('Virgin Mobile'), 'vodafone-sms.de' => t('Vodafone Germany'), 'sms.ycc.ru' => t('YCC'), 'vmpix.com' => t('Virgin Mobile'), ); } /****************************************************************************** * HELPER FUNCTIONS *****************************************************************************/ /** * Formats a number for display. */ function sms_format_number(&$number, $options = array()) { $gateway = sms_default_gateway(); if ($gateway['format number'] && function_exists($gateway['format number'])) { return $gateway['format number']($number, $options); } else { return $number; } } /** * Converts various sms formats into a common format for use in this module. * * @param $number * The sms number * @param $format * Undefined - @todo: decide if this is needed. */ function sms_formatter($number, $format = 1) { // Remove non-number characters $number = preg_replace("/[^0-9]/", '', $number); /* @todo - the only length specification in the international numbering plan is that numbers should be a maximum of 15 digits. http://en.wikipedia.org/wiki/E.164 if (strlen($number) > 16) { if ($number[0] == 1) { $number = ltrim($number, 1); } else { return FALSE; } } elseif (strlen($number) < 10) { return FALSE; } */ return $number; } /** * Validates a phone number. Passes number to active gateway for further * validation if neccessary. */ function sms_validate_number(&$number, $options = array()) { if (!strlen($number)) { return t('The phone number is invalid.'); } // Allow the active gateway to provide number validation $gateway = sms_default_gateway(); if (function_exists($gateway['validate number']) && $error = $gateway['validate number']($number, $options)) { return $error; } } /** * Render a direction code * * @param $out bool Outgoing allowed or not * @param $in bool Incoming allowed or not * @return const The constant that defines this direction combination. Usually an integer value. */ function sms_dir($out, $in) { if ( $out && $in) { return SMS_DIR_ALL; } if ( $out && !$in) { return SMS_DIR_OUT; } if (!$out && $in) { return SMS_DIR_IN; } if (!$out && !$in) { return SMS_DIR_NONE; } } /** * Returns an array of E.164 international country calling codes * * @return array Associative array of country calling codes and country names. */ function sms_country_codes() { return array( 93 => "Afghanistan", 355 => "Albania", 213 => "Algeria", 376 => "Andorra", 244 => "Angola", 1264 => "Anguilla", 1268 => "Antigua & Barbuda", 54 => "Argentina", 374 => "Armenia", 297 => "Aruba", 61 => "Australia", 43 => "Austria", 994 => "Azerbaijan", 1242 => "Bahamas", 973 => "Bahrain", 880 => "Bangladesh", 1246 => "Barbados", 375 => "Belarus", 32 => "Belgium", 501 => "Belize", 229 => "Benin", 1441 => "Bermuda", 975 => "Bhutan", 591 => "Bolivia", 387 => "Bosnia-Herzegovina", 267 => "Botswana", 55 => "Brazil", 1284 => "British Virgin Islands", 673 => "Brunei", 359 => "Bulgaria", 226 => "Burkina Faso", 257 => "Burundi", 855 => "Cambodia", 237 => "Cameroon", 34 => "Canary Islands", 238 => "Cape Verde", 1345 => "Cayman Islands", 236 => "Central African Republic", 235 => "Chad", 56 => "Chile", 86 => "China", 57 => "Colombia", 269 => "Comoros", 242 => "Congo", 243 => "Democratic Republic Congo", 682 => "Cook Islands", 385 => "Croatia", 53 => "Cuba", 357 => "Cyprus", 420 => "Czech Republic", 45 => "Denmark", 253 => "Djibouti", 1767 => "Dominica", 670 => "East Timor", 593 => "Ecuador", 20 => "Egypt", 503 => "El Salvador", 240 => "Equatorial Guinea", 372 => "Estonia", 251 => "Ethiopia", 500 => "Falkland Islands", 298 => "Faroe Islands", 679 => "Fiji", 358 => "Finland", 33 => "France", 594 => "French Guiana", 689 => "French Polynesia", 241 => "Gabon", 220 => "Gambia", 995 => "Georgia", 49 => "Germany", 233 => "Ghana", 350 => "Gibraltar", 881 => "Global Mobile Satellite", 30 => "Greece", 299 => "Greenland", 1473 => "Grenada", 590 => "Guadeloupe", 1671 => "Guam", 502 => "Guatemala", 224 => "Guinea", 592 => "Guyana", 509 => "Haiti", 504 => "Honduras", 852 => "HongKong", 36 => "Hungary", 354 => "Iceland", 91 => "India", 62 => "Indonesia", 98 => "Iran", 964 => "Iraq", 353 => "Ireland", 972 => "Israel", 39 => "Italy / Vatican City State", 225 => "Ivory Coast", 1876 => "Jamaica", 81 => "Japan", 962 => "Jordan", 254 => "Kenya", 82 => "Korea (South)", 965 => "Kuwait", 996 => "Kyrgyzstan", 856 => "Lao", 371 => "Latvia", 961 => "Lebanon", 266 => "Lesotho", 231 => "Liberia", 218 => "Libya", 423 => "Liechtenstein", 370 => "Lithuania", 352 => "Luxembourg", 853 => "Macau", 389 => "Macedonia", 261 => "Madagascar", 265 => "Malawi", 60 => "Malaysia", 960 => "Maldives", 223 => "Mali", 356 => "Malta", 596 => "Martinique", 222 => "Mauritania", 230 => "Mauritius", 269 => "Mayotte Island (Comoros)", 52 => "Mexico", 373 => "Moldova", 377 => "Monaco (Kosovo)", 976 => "Mongolia", 382 => "Montenegro", 1664 => "Montserrat", 212 => "Morocco", 258 => "Mozambique", 95 => "Myanmar", 264 => "Namibia", 977 => "Nepal", 31 => "Netherlands", 599 => "Netherlands Antilles", 687 => "New Caledonia", 64 => "New Zealand", 505 => "Nicaragua", 227 => "Niger", 234 => "Nigeria", 47 => "Norway", 968 => "Oman", 92 => "Pakistan", 970 => "Palestine (+970)", 9725 => "Palestine (+9725)", 507 => "Panama", 675 => "Papua New Guinea", 595 => "Paraguay", 51 => "Peru", 63 => "Philippines", 48 => "Poland", 351 => "Portugal", 974 => "Qatar", 262 => "Reunion", 40 => "Romania", 7 => "Russia / Kazakhstan", 250 => "Rwanda", 1670 => "Saipan", 1684 => "Samoa (American)", 685 => "Samoa (Western)", 378 => "San Marino", 882 => "Satellite-Thuraya", 966 => "Saudi Arabia", 221 => "Senegal", 381 => "Serbia", 248 => "Seychelles", 232 => "Sierra Leone", 65 => "Singapore", 421 => "Slovakia", 386 => "Slovenia", 252 => "Somalia", 27 => "South Africa", 34 => "Spain", 94 => "Sri Lanka", 1869 => "St. Kitts And Nevis", 1758 => "St. Lucia", 1784 => "St. Vincent", 249 => "Sudan", 597 => "Suriname", 268 => "Swaziland", 46 => "Sweden", 41 => "Switzerland", 963 => "Syria", 886 => "Taiwan", 992 => "Tajikistan", 255 => "Tanzania", 66 => "Thailand", 228 => "Togo", 676 => "Tonga Islands", 1868 => "Trinidad and Tobago", 216 => "Tunisia", 90 => "Turkey", 993 => "Turkmenistan", 1649 => "Turks and Caicos Islands", 256 => "Uganda", 44 => "UK / Isle of Man / Jersey / Guernsey", 380 => "Ukraine", 971 => "United Arab Emirates", 598 => "Uruguay", 1 => "USA / Canada / Dominican Rep. / Puerto Rico", 998 => "Uzbekistan", 678 => "Vanuatu", 58 => "Venezuela", 84 => "Vietnam", 967 => "Yemen", 260 => "Zambia", 255 => "Zanzibar", 263 => "Zimbabwe", ); }