PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/api/business/voip_manager.class.php

https://github.com/patrickdemond/beartooth
PHP | 312 lines | 141 code | 36 blank | 135 comment | 26 complexity | 1d736941f5d5e1140cad3bb4a0efb5f5 MD5 | raw file
  1. <?php
  2. /**
  3. * voip_manager.class.php
  4. *
  5. * @author Patrick Emond <emondpd@mcmaster.ca>
  6. * @filesource
  7. */
  8. namespace beartooth\business;
  9. use cenozo\lib, cenozo\log, beartooth\util;
  10. require_once SHIFT8_PATH.'/library/Shift8.php';
  11. /**
  12. * Manages VoIP communications.
  13. */
  14. class voip_manager extends \cenozo\singleton
  15. {
  16. /**
  17. * Constructor.
  18. *
  19. * @author Patrick Emond <emondpd@mcmaster.ca>
  20. * @access protected
  21. */
  22. protected function __construct()
  23. {
  24. $setting_manager = lib::create( 'business\setting_manager' );
  25. $this->enabled = true === $setting_manager->get_setting( 'voip', 'enabled' );
  26. $this->url = $setting_manager->get_setting( 'voip', 'url' );
  27. $this->username = $setting_manager->get_setting( 'voip', 'username' );
  28. $this->password = $setting_manager->get_setting( 'voip', 'password' );
  29. $this->prefix = $setting_manager->get_setting( 'voip', 'prefix' );
  30. }
  31. /**
  32. * Initializes the voip manager.
  33. *
  34. * This method should be called immediately after initial construction of the manager
  35. * @author Patrick Emond <emondpd@mcmaster.ca>
  36. * @throws exception\runtime, exception\voip
  37. * @access public
  38. */
  39. public function initialize()
  40. {
  41. if( !$this->enabled ) return;
  42. try
  43. {
  44. // create and connect to the shift8 AJAM interface
  45. $this->manager = new \Shift8( $this->url, $this->username, $this->password );
  46. if( !$this->manager->login() )
  47. throw lib::create( 'exception\runtime',
  48. 'Unable to connect to the Asterisk server.', __METHOD__ );
  49. // get the current SIP info
  50. $peer = lib::create( 'business\session' )->get_user()->name;
  51. $s8_event = $this->manager->getSipPeer( $peer );
  52. if( !is_null( $s8_event ) &&
  53. $peer == $s8_event->get( 'objectname' ) &&
  54. 'OK' == substr( $s8_event->get( 'status' ), 0, 2 ) )
  55. {
  56. $this->sip_info = array(
  57. 'status' => $s8_event->get( 'status' ),
  58. 'type' => $s8_event->get( 'channeltype' ),
  59. 'agent' => $s8_event->get( 'sip_useragent' ),
  60. 'ip' => $s8_event->get( 'address_ip' ),
  61. 'port' => $s8_event->get( 'address_port' ) );
  62. }
  63. }
  64. catch( \Shift8_Exception $e )
  65. {
  66. throw lib::create( 'exception\voip',
  67. 'Failed to initialize Asterisk AJAM interface.', __METHOD__, $e );
  68. }
  69. }
  70. /**
  71. * Reads the list of active calls from the server.
  72. *
  73. * @author Patrick Emond <emondpd@mcmaster.ca>
  74. * @throws exception\voip
  75. * @access public
  76. */
  77. public function rebuild_call_list()
  78. {
  79. $this->call_list = array();
  80. $events = $this->manager->getStatus();
  81. if( is_null( $events ) )
  82. throw lib::create( 'exception\voip', $this->manager->getLastError(), __METHOD__ );
  83. foreach( $events as $s8_event )
  84. if( 'Status' == $s8_event->get( 'event' ) )
  85. $this->call_list[] = lib::create( 'business\voip_call', $s8_event, $this->manager );
  86. }
  87. /**
  88. * Gets a user's active call. If the user isn't currently on a call then null is returned.
  89. *
  90. * @author Patrick Emond <emondpd@mcmaster.ca>
  91. * @param database\user $db_user Which user's call to retrieve. If this parameter is null then
  92. * the current user's call is returned.
  93. * @return voip_call
  94. * @access public
  95. */
  96. public function get_call( $db_user = NULL )
  97. {
  98. if( !$this->enabled ) return NULL;
  99. if( is_null( $this->call_list ) ) $this->rebuild_call_list();
  100. $peer = is_null( $db_user )
  101. ? lib::create( 'business\session' )->get_user()->name
  102. : $db_user->name;
  103. // build the call list
  104. $calls = array();
  105. foreach( $this->call_list as $voip_call )
  106. if( $peer == $voip_call->get_peer() ) return $voip_call;
  107. return NULL;
  108. }
  109. /**
  110. * Attempts to connect to a phone.
  111. *
  112. * @author Patrick Emond <emondpd@mcmaster.ca>
  113. * @param mixed $phone May be a database phone record or an explicit number
  114. * @return voip_call
  115. * @access public
  116. * @throws exception\argument, exception\runtime, exception\notice, exception\voip
  117. */
  118. public function call( $phone )
  119. {
  120. if( !$this->enabled ) return NULL;
  121. // validate the input
  122. if( !is_object( $phone ) )
  123. {
  124. $number = $phone;
  125. }
  126. else
  127. {
  128. $db_phone = $phone;
  129. if( !is_object( $db_phone ) )
  130. throw lib::create( 'exception\argument', 'db_phone', $db_phone, __METHOD__ );
  131. $number = $db_phone->number;
  132. }
  133. // check that the phone number has exactly 10 digits
  134. $digits = preg_replace( '/[^0-9]/', '', $number );
  135. if( 10 != strlen( $digits ) )
  136. throw lib::create( 'exception\runtime',
  137. 'Tried to connect to phone number which does not have exactly 10 digits.', __METHOD__ );
  138. // make sure the user isn't already in a call
  139. if( !is_null( $this->get_call() ) )
  140. throw lib::create( 'exception\notice',
  141. 'Unable to connect call since you already appear to be in a call.', __METHOD__ );
  142. // originate call (careful, the online API has the arguments in the wrong order)
  143. $peer = lib::create( 'business\session' )->get_user()->name;
  144. $channel = 'SIP/'.$peer;
  145. $context = 'users';
  146. $extension = $this->prefix.$digits;
  147. $priority = 1;
  148. if( !$this->manager->originate( $channel, $context, $extension, $priority ) )
  149. throw lib::create( 'exception\voip', $this->manager->getLastError(), __METHOD__ );
  150. // rebuild the call list and return (what should be) the peer's only call
  151. $this->rebuild_call_list();
  152. return $this->get_call();
  153. }
  154. /**
  155. * Opens a listen-only connection to an existing call
  156. *
  157. * @author Patrick Emond <emondpd@mcmaster.ca>
  158. * @param voip_call $voip_call The call to spy on
  159. * @access public
  160. */
  161. public function spy( $voip_call )
  162. {
  163. $peer = lib::create( 'business\session' )->get_user()->name;
  164. $channel = 'SIP/'.$peer;
  165. // play sound in local channel
  166. if( !$this->manager->originate(
  167. $channel, // channel
  168. 'default', // context
  169. 'chanspy', // extension
  170. 1, // priority
  171. false, // application
  172. false, // data
  173. 30000, // timeout
  174. false, // callerID
  175. 'ActionID=Spy,'. // variables
  176. 'ToChannel='.$voip_call->get_channel() ) )
  177. {
  178. throw lib::create( 'exception\voip', $this->manager->getLastError(), __METHOD__ );
  179. }
  180. // rebuild the call list and return (what should be) the peer's only call
  181. $this->rebuild_call_list();
  182. return $this->get_call();
  183. }
  184. /**
  185. * Flushes a user's details using a sip prune command.
  186. *
  187. * @author Patrick Emond <emondpd@mcmaster.ca>
  188. * @param database\user $db_user Which user to flush.
  189. * @access public
  190. */
  191. public function sip_prune( $db_user )
  192. {
  193. if( !$this->enabled || is_null( $db_user ) ) return;
  194. // there is no way to send a sip prune command to asterisk using AMI so we need to use the CLI
  195. $output = array();
  196. $return_value = 0;
  197. exec( 'asterisk -rx "sip prune realtime peer '.$db_user->name.'"', $output, $return_value );
  198. if( 0 != $return_value ) log::err( $output[0] );
  199. }
  200. /**
  201. * Determines whether a SIP connection is detected with the client
  202. *
  203. * @author Patrick Emond <emondpd@mcmaster.ca>
  204. * @return boolean
  205. * @access public
  206. */
  207. public function get_sip_enabled()
  208. {
  209. return $this->enabled && is_array( $this->sip_info ) && 0 < count( $this->sip_info );
  210. }
  211. /**
  212. * Whether VOIP is enabled.
  213. *
  214. * @author Patrick Emond <emondpd@mcmaster.ca>
  215. * @return boolean
  216. * @access public
  217. */
  218. public function get_enabled() { return $this->enabled; }
  219. /**
  220. * Gets the dialing prefix to use when placing external calls
  221. *
  222. * @author Patrick Emond <emondpd@mcmaster.ca>
  223. * @return string
  224. * @access public
  225. */
  226. public function get_prefix() { return $this->prefix; }
  227. /**
  228. * The asterisk manager object
  229. * @var Shift8 object
  230. * @access private
  231. */
  232. private $manager = NULL;
  233. /**
  234. * The current SIP information (empty array if there is no connection found)
  235. * @var array
  236. * @access private
  237. */
  238. private $sip_info = NULL;
  239. /**
  240. * An array of all currently active calls.
  241. *
  242. * @var array( voip_call )
  243. * @access private
  244. */
  245. private $call_list = NULL;
  246. /**
  247. * Whether VOIP is enabled.
  248. * @var string
  249. * @access private
  250. */
  251. private $enabled = false;
  252. /**
  253. * The url that asterisk's AJAM is running on
  254. * @var string
  255. * @access private
  256. */
  257. private $url = '';
  258. /**
  259. * Which username to use when connecting to the manager
  260. * @var string
  261. * @access private
  262. */
  263. private $username = '';
  264. /**
  265. * Which password to use when connecting to the manager
  266. * @var string
  267. * @access private
  268. */
  269. private $password = '';
  270. /**
  271. * The dialing prefix to use when making external calls.
  272. * @var string
  273. * @access private
  274. */
  275. private $prefix = '';
  276. }