MurmurQuery.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. /**
  3. * Murmur Query Class
  4. *
  5. * Based on GT MURMUR PLUGIN, which allows us to query a Murmur server
  6. * without having to install PHP ICE on the web server.
  7. * @link http://www.gametracker.com/downloads/gtmurmurplugin.php
  8. *
  9. * The response is constructed using Channel Viewer Protocol.
  10. * @link http://mumble.sourceforge.net/Channel_Viewer_Protocol
  11. *
  12. * @author Edmundas Kondrašovas <[email protected]>
  13. * @license http://www.opensource.org/licenses/MIT
  14. * @copyright Copyright (c) 2011 Edmundas Kondrašovas <[email protected]>
  15. * @version 0.6
  16. *
  17. */
  18. class MurmurQuery
  19. {
  20. /* Packets */
  21. const Q_XML = "\x78\x6D\x6C";
  22. const Q_JSON = "\x6A\x73\x6F\x6E";
  23. private $users = array();
  24. private $channels = array();
  25. private $socket;
  26. private $host;
  27. private $port;
  28. private $timeout;
  29. private $format;
  30. private $response;
  31. private $status;
  32. private $raw;
  33. private $online = false;
  34. /**
  35. * Constructor
  36. *
  37. * @access public
  38. * @param string hostname
  39. * @param integer port (optional)
  40. * @param integer timeout in miliseconds (optional)
  41. * @param string format (optional)
  42. * @return void
  43. */
  44. public function __construct($host = '', $port = 27800, $timeout = 200, $format = 'json')
  45. {
  46. if(!empty($host))
  47. {
  48. $this->setup($host, $port, $timeout, $format);
  49. $this->query();
  50. }
  51. }
  52. /**
  53. * Set the parameters
  54. *
  55. * @access public
  56. * @param string/array hostname or settings array
  57. * @param integer port (optional)
  58. * @param integer timeout in miliseconds (optional)
  59. * @param string format (optional)
  60. * @return void
  61. */
  62. public function setup($host, $port = 27800, $timeout = 200, $format = 'json')
  63. {
  64. if(is_array($host))
  65. {
  66. $this->host = array_key_exists('host', $host) ? $host['host'] : '';
  67. $this->port = array_key_exists('port', $host) ? $host['port'] : $port;
  68. $this->timeout = array_key_exists('timeout', $host) ? $host['timeout'] : $timeout;
  69. $this->format = array_key_exists('format', $host) ? $host['format'] : $format;
  70. }
  71. else
  72. {
  73. $this->host = $host;
  74. $this->port = $port;
  75. $this->timeout = $timeout;
  76. $this->format = $format;
  77. }
  78. }
  79. /**
  80. * Set data format
  81. *
  82. * @access public
  83. * @param string data format
  84. * @return void
  85. */
  86. public function set_format($format = 'json')
  87. {
  88. $this->format = $format;
  89. }
  90. /**
  91. * Query the server
  92. *
  93. * @access public
  94. * @return void
  95. */
  96. public function query()
  97. {
  98. $this->_connect();
  99. $this->_send_query($this->format);
  100. $this->_catch_response();
  101. if(!empty($this->response)) $this->online = true;
  102. $this->_close();
  103. }
  104. /**
  105. * Get server status
  106. *
  107. * @access public
  108. * @param boolean return raw response
  109. * @return mixed json/xml if set to return raw response or array otherwise
  110. */
  111. public function get_status($raw = false)
  112. {
  113. return ($raw) ? $this->raw : $this->status;
  114. }
  115. /**
  116. * Get users
  117. *
  118. * @access public
  119. * @return array
  120. */
  121. public function get_users()
  122. {
  123. return $this->users;
  124. }
  125. /**
  126. * Get channels
  127. *
  128. * @access public
  129. * @return array
  130. */
  131. public function get_channels()
  132. {
  133. return $this->channels;
  134. }
  135. /**
  136. * Check if the server is online
  137. *
  138. * @access public
  139. * @return bool
  140. */
  141. public function is_online()
  142. {
  143. return $this->online;
  144. }
  145. /**
  146. * Establish a socket connection
  147. *
  148. * @access private
  149. * @return bool
  150. */
  151. private function _connect()
  152. {
  153. // We need timeout in seconds for fsockopen()
  154. $timeout = ($this->timeout < 1000) ? 1 : ceil($this->timeout / 1000);
  155. $this->socket = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
  156. if(!$this->socket) return false;
  157. return true;
  158. }
  159. /**
  160. * Send query to the server
  161. *
  162. * @access private
  163. * @param string query (should be one the constants defined)
  164. * @return void
  165. */
  166. private function _send_query($format)
  167. {
  168. $data = '';
  169. switch($format)
  170. {
  171. case 'json':
  172. $data = self::Q_JSON;
  173. break;
  174. case 'xml':
  175. $data = self::Q_XML;
  176. break;
  177. default:
  178. $data = self::Q_JSON;
  179. break;
  180. }
  181. if($this->socket)
  182. {
  183. @fwrite($this->socket, $data);
  184. stream_set_timeout($this->socket, 0, $this->timeout * 1000);
  185. }
  186. }
  187. /**
  188. * Receive response from the server
  189. *
  190. * @access private
  191. * @return void
  192. */
  193. private function _catch_response()
  194. {
  195. if($this->socket)
  196. {
  197. while($resp = @fread($this->socket, 1024)) $this->response .= $resp;
  198. stream_set_timeout($this->socket, 0, $this->timeout * 1000);
  199. $this->raw = $this->response;
  200. $this->status = $this->parse_response($this->response, $this->format);
  201. }
  202. }
  203. /**
  204. * Close socket connection
  205. *
  206. * @access private
  207. * @return void
  208. */
  209. private function _close()
  210. {
  211. if($this->socket) fclose($this->socket);
  212. $this->response = NULL;
  213. $this->data = NULL;
  214. $this->socket = NULL;
  215. }
  216. /**
  217. * Parse data returned from the server
  218. *
  219. * @access public
  220. * @param string xml/json
  221. * @param string format
  222. * @return array parsed data
  223. */
  224. public function parse_response($data, $format = 'json')
  225. {
  226. switch($format)
  227. {
  228. case 'json':
  229. $parsed_data = $this->_parse_json($data);
  230. break;
  231. case 'xml':
  232. $parsed_data = $this->_parse_xml($data);
  233. break;
  234. default:
  235. $parsed_data = $this->_parse_json($data);
  236. break;
  237. }
  238. return $parsed_data;
  239. }
  240. /**
  241. * Parse JSON
  242. *
  243. * @access private
  244. * @param string json
  245. * @return array parsed data
  246. */
  247. private function _parse_json($data)
  248. {
  249. $parsed_data = array();
  250. $decoded = json_decode($data, true);
  251. $this->_parse_channels($decoded);
  252. $parsed_data['channels'] = $this->channels;
  253. $parsed_data['users'] = $this->users;
  254. $parsed_data['original'] = $decoded;
  255. return $parsed_data;
  256. }
  257. /**
  258. * Parse XML
  259. *
  260. * @access private
  261. * @param string xml
  262. * @return array parsed data
  263. */
  264. // Does not work properly yet.
  265. private function _parse_xml($data)
  266. {
  267. $parsed_data = array();
  268. $decoded = simplexml_load_string($data);
  269. $this->_parse_channels($decoded);
  270. $parsed_data['channels'] = $this->channels;
  271. $parsed_data['users'] = $this->users;
  272. $parsed_data['original'] = $decoded;
  273. return $parsed_data;
  274. }
  275. /**
  276. * Parse the channels
  277. *
  278. * @access private
  279. * @param array channels
  280. * @return void
  281. */
  282. private function _parse_channels($channels)
  283. {
  284. if(array_key_exists('x_gtmurmur_error', $channels))
  285. return;
  286. // We'll have to deal with the root channel separately
  287. if(array_key_exists('root', $channels))
  288. {
  289. if(count($channels['root']['users']) > 0)
  290. {
  291. foreach($channels['root']['users'] as $user) $this->users[] = $user;
  292. }
  293. $tmp = $channels['root']['channels'];
  294. unset($channels['root']['users']);
  295. unset($channels['root']['channels']);
  296. $this->_parse_channels($tmp);
  297. }
  298. else
  299. {
  300. if(count($channels) > 0)
  301. {
  302. foreach($channels as $channel)
  303. {
  304. if(count($channel['users']) > 0)
  305. {
  306. foreach($channel['users'] as $user) $this->users[] = $user;
  307. }
  308. if($channel['users'] > 0) unset($channel['users']);
  309. $this->_parse_channels($channel['channels']);
  310. if($channel['channels'] > 0) unset($channel['channels']);
  311. $this->channels[] = $channel;
  312. }
  313. }
  314. }
  315. }
  316. }
  317. ?>