PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/esl-1.1/libraries/ESLconnection.php

https://github.com/robertleeplummerjr/bluebox
PHP | 384 lines | 183 code | 62 blank | 139 comment | 28 complexity | 2b6993227bef3c1db0fd6c0a616fc3f9 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct access allowed.');
  2. /**
  3. * ESLconnection.php - This provides ESLconnection when the native ESL
  4. * extension is not avaliable
  5. *
  6. * @author K Anderson
  7. * @license LGPL
  8. * @package Esl
  9. */
  10. class ESLconnection extends FS_Socket {
  11. private $eventQueue = array();
  12. private $sentCommand = FALSE;
  13. private $authenticated = FALSE;
  14. private $eventLock = FALSE;
  15. private $asyncExecute = FALSE;
  16. /**
  17. * Initializes a new instance of ESLconnection, and connects to the host
  18. * $host on the port $port, and supplies $password to freeswitch.
  19. */
  20. public function __construct($host = NULL, $port = NULL, $auth = NULL, $options = array()) {
  21. try {
  22. // attempt to open the socket
  23. $this->connect($host, $port, $options);
  24. // get the initial header
  25. $event = $this->recvEvent();
  26. // did we get the request for auth?
  27. if ($event->getHeader('Content-Type') != 'auth/request') {
  28. $this->_throwError("unexpected header recieved during authentication: " . $event->getType());
  29. }
  30. // send our auth
  31. $event = $this->sendRecv("auth {$auth}");
  32. // was our auth accepted?
  33. $reply = $event->getHeader('Reply-Text');
  34. if (!strstr($reply, '+OK')) {
  35. $this->_throwError("connection refused: {$reply}");
  36. }
  37. // we are authenticated!
  38. $this->authenticated = TRUE;
  39. return TRUE;
  40. } catch (Exception $e) {
  41. return FALSE;
  42. }
  43. }
  44. public function __destruct() {
  45. // cleanly exit
  46. $this->disconnect();
  47. }
  48. /**
  49. * Returns the UNIX file descriptor for the connection object, if the
  50. * connection object is connected. This is the same file descriptor that was
  51. * passed to new($fd) when used in outbound mode.
  52. */
  53. public function socketDescriptor() {
  54. try {
  55. return $this->getStatus();
  56. } catch (Exception $e) {
  57. return FALSE;
  58. }
  59. }
  60. /**
  61. * Test if the connection object is connected. Returns 1 if connected,
  62. * 0 otherwise.
  63. */
  64. public function connected() {
  65. if ($this->validateConnection() && $this->authenticated) {
  66. return TRUE;
  67. }
  68. return FALSE;
  69. }
  70. /**
  71. * When FS connects to an "Event Socket Outbound" handler, it sends a
  72. * "CHANNEL_DATA" event as the first event after the initial connection.
  73. * getInfo() returns an ESLevent that contains this Channel Data.
  74. * getInfo() returns NULL when used on an "Event Socket Inbound" connection.
  75. */
  76. public function getInfo() {
  77. $this->_throwError("has not implemented this yet!");
  78. }
  79. /**
  80. * Sends a command to FreeSwitch. Does not wait for a reply. You should
  81. * immediately call recvEvent or recvEventTimed in a loop until you get
  82. * the reply. The reply event will have a header named "content-type" that
  83. * has a value of "api/response" or "command/reply". To automatically wait
  84. * for the reply event, use sendRecv() instead of send().
  85. */
  86. public function send($command) {
  87. if (empty($command)) {
  88. $this->_throwError("requires non-blank command to send.");
  89. }
  90. // send the command out of the socket
  91. try {
  92. return $this->sendCmd($command);
  93. } catch (Exception $e) {
  94. return FALSE;
  95. }
  96. }
  97. /**
  98. * Internally sendRecv($command) calls send($command) then recvEvent(), and
  99. * returns an instance of ESLevent. recvEvent() is called in a loop until it
  100. * receives an event with a header named "content-type" that has a value of
  101. * "api/response" or "command/reply", and then returns it as an instance of
  102. * ESLevent. Any events that are received by recvEvent() prior to the reply
  103. * event are queued up, and will get returned on subsequent calls to
  104. * recvEvent() in your program.
  105. */
  106. public function sendRecv($command) {
  107. // setup an array of content-types to wait for
  108. $waitFor = array('api/response', 'command/reply');
  109. // set a flag so recvEvent ignores the event queue
  110. $this->sentCommand = TRUE;
  111. // send the command
  112. $this->send($command);
  113. // collect and queue all the events
  114. do {
  115. $event = $this->recvEvent();
  116. $this->eventQueue[] = $event;
  117. } while (!in_array($event->getHeader('Content-Type'), $waitFor));
  118. // clear the flag so recvEvent uses the event queue
  119. $this->sentCommand = FALSE;
  120. // the last queued event was of the content-type we where waiting for,
  121. // so pop one off
  122. return array_pop($this->eventQueue);
  123. }
  124. /**
  125. * Send an API command to the FreeSWITCH server. This method blocks further
  126. * execution until the command has been executed. api($command, $args) is
  127. * identical to sendRecv("api $command $args").
  128. */
  129. public function api() {
  130. $args = func_get_args();
  131. $command = array_shift($args);
  132. $command = 'api ' .$command .' ' .implode(' ', $args);
  133. return $this->sendRecv($command);
  134. }
  135. /**
  136. * Send a background API command to the FreeSWITCH server to be executed in
  137. * it's own thread. This will be executed in it's own thread, and is
  138. * non-blocking. bgapi($command, $args) is identical to
  139. * sendRecv("bgapi $command $args")
  140. */
  141. public function bgapi() {
  142. $args = func_get_args();
  143. $command = array_shift($args);
  144. $command = 'bgapi ' .$command .' ' .implode(' ', $args);
  145. return $this->sendRecv($command);
  146. }
  147. public function sendEvent($event) {
  148. $this->_throwError("does not implement this becuase there is no info on it in the docs!");
  149. //$command = 'sendevent ' .$event->name . ' ' .$event->serialize();
  150. }
  151. /**
  152. * Returns the next event from FreeSwitch. If no events are waiting, this
  153. * call will block until an event arrives. If any events were queued during
  154. * a call to sendRecv(), then the first one will be returned, and removed
  155. * from the queue. Otherwise, then next event will be read from the
  156. * connection.
  157. */
  158. public function recvEvent() {
  159. // if we are not waiting for an event and the event queue is not empty
  160. // shift one off
  161. if (!$this->sentCommand && !empty($this->eventQueue)) {
  162. return array_shift($this->eventQueue);
  163. }
  164. // wait for the first line
  165. $this->setBlocking();
  166. do {
  167. $line = $this->readLine();
  168. // if we timeout while waiting return NULL
  169. $streamMeta = $this->getMetaData();
  170. if (!empty($streamMeta['timed_out'])) {
  171. return NULL;
  172. }
  173. } while (empty($line));
  174. // save our first line
  175. $response = array($line);
  176. // keep reading the buffer untill we get a new line
  177. $this->setNonBlocking();
  178. do {
  179. $line = $response[] = $this->readLine();
  180. } while ($line != "\n");
  181. // build a new event from our response
  182. $event = new ESLevent($response);
  183. // if the response contains a content-length ...
  184. if ($contentLen = $event->getHeader('Content-Length')) {
  185. // ... add the content to this event
  186. $this->setBlocking();
  187. while ($contentLen > 0) {
  188. // our fread stops every 8192 so break up the reads into the
  189. // appropriate chunks
  190. if ($contentLen > 8192) {
  191. $getLen = 8192;
  192. } else {
  193. $getLen = $contentLen;
  194. }
  195. $event->addBody($this->getContent($getLen));
  196. $contentLen = $contentLen - $getLen;
  197. }
  198. }
  199. $contentType = $event->getHeader('Content-Type');
  200. if ($contentType == 'text/disconnect-notice') {
  201. $this->disconnect();
  202. return FALSE;
  203. }
  204. // return our ESLevent object
  205. return $event;
  206. }
  207. /**
  208. * Similar to recvEvent(), except that it will block for at most milliseconds.
  209. * A call to recvEventTimed(0) will return immediately. This is useful for
  210. * polling for events.
  211. */
  212. public function recvEventTimed($milliseconds) {
  213. // set the stream timeout to the users preference
  214. $this->setTimeOut(0, $milliseconds);
  215. // try to get an event
  216. $event = $this->recvEvent();
  217. // restore the stream time out
  218. $this->restoreTimeOut();
  219. // return the results (null or event object)
  220. return $event;
  221. }
  222. /**
  223. * Specify event types to listen for. Note, this is not a filter out but
  224. * rather a "filter in," that is, when a filter is applied only the filtered
  225. * values are received. Multiple filters on a socket connection are allowed.
  226. */
  227. public function filter($header, $value) {
  228. return $this->sendRecv('filter ' .$header .' ' .$value);
  229. }
  230. /**
  231. * $event_type can have the value "plain" or "xml". Any other value
  232. * specified for $event_type gets replaced with "plain". See the event FS
  233. * wiki socket event command for more info.
  234. */
  235. public function events($event_type, $value) {
  236. $event_type = strtolower($event_type);
  237. if ($event_type == 'xml') {
  238. $event = $this->sendRecv('event ' .$event_type .' ' .$value);
  239. } else {
  240. $event = $this->sendRecv('event plain ' .$value);
  241. }
  242. return $event;
  243. }
  244. /**
  245. * Execute a dialplan application, and wait for a response from the server.
  246. * On socket connections not anchored to a channel (THIS!),
  247. * all three arguments are required -- $uuid specifies the channel to
  248. * execute the application on. Returns an ESLevent object containing the
  249. * response from the server. The getHeader("Reply-Text") method of this
  250. * ESLevent object returns the server's response. The server's response will
  251. * contain "+OK [Success Message]" on success or "-ERR [Error Message]"
  252. * on failure.
  253. */
  254. public function execute($app, $arg, $uuid) {
  255. $command = 'sendmsg';
  256. if (!empty($uuid)) {
  257. $command .= " {$uuid}";
  258. }
  259. $command .= "\ncall-command: execute\n";
  260. if (!empty($app)) {
  261. $command .= "execute-app-name: {$app}\n";
  262. }
  263. if (!empty($arg)) {
  264. $command .= "execute-app-arg: {$arg}\n";
  265. }
  266. if ($this->eventLock) {
  267. $command .= "event-lock: true\n";
  268. }
  269. if ($this->asyncExecute) {
  270. $command .= "async: true\n";
  271. }
  272. return $this->sendRecv($command);
  273. }
  274. /**
  275. * Same as execute, but doesn't wait for a response from the server. This
  276. * works by causing the underlying call to execute() to append "async: true"
  277. * header in the message sent to the channel.
  278. */
  279. public function executeAsync($app, $arg, $uuid) {
  280. $currentAsync = $this->asyncExecute;
  281. $this->asyncExecute = TRUE;
  282. $response = $this->execute($app, $arg, $uuid);
  283. $this->asyncExecute = $currentAsync;
  284. return $response;
  285. }
  286. public function setAsyncExecute($value = NULL) {
  287. $this->asyncExecute = !empty($value);
  288. return TRUE;
  289. }
  290. /**
  291. * Force sync mode on for a socket connection. This command has no effect on
  292. * outbound socket connections that are not set to "async" in the dialplan,
  293. * since these connections are already set to sync mode. $value should be 1
  294. * to force sync mode, and 0 to not force it.
  295. */
  296. public function setEventLock($value = NULL) {
  297. $this->eventLock = !empty($value);
  298. return TRUE;
  299. }
  300. /**
  301. * Close the socket connection to the FreeSWITCH server.
  302. */
  303. public function disconnect() {
  304. // if we are connected cleanly exit
  305. if ($this->connected()) {
  306. $this->send('exit');
  307. $this->authenticated = FALSE;
  308. }
  309. // disconnect the socket
  310. return parent::disconnect();
  311. }
  312. /**
  313. * Throws an error
  314. *
  315. * @return void
  316. */
  317. private function _throwError($errorMessage)
  318. {
  319. message::set("ESL {$errorMessage}", 'alert');
  320. throw new ESLException("ESL {$errorMessage}");
  321. }
  322. }