PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/ampacheapi/AmpacheApi.lib.php

https://gitlab.com/x33n/ampache
PHP | 394 lines | 215 code | 60 blank | 119 comment | 38 complexity | 1011ce18139da6537244c2046a39ae34 MD5 | raw file
  1. <?php
  2. /* vim:set softtabstop=4 shiftwidth=4 expandtab: */
  3. /**
  4. *
  5. * LICENSE: GNU General Public License, version 2 (GPLv2)
  6. * Copyright 2012 - 2015 Ampache.org
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License v2
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. *
  21. */
  22. class AmpacheApi
  23. {
  24. // General Settings
  25. private $server;
  26. private $username;
  27. private $password;
  28. private $api_secure;
  29. // Handshake variables
  30. private $handshake;
  31. private $handshake_time; // Used to figure out how stale our data is
  32. // Response variables
  33. private $api_session;
  34. private $raw_response;
  35. // Constructed variables
  36. private $api_url;
  37. private $api_state = 'UNCONFIGURED';
  38. private $api_auth;
  39. // XML Parser variables
  40. private $XML_currentTag;
  41. private $XML_subTag;
  42. private $XML_parser;
  43. private $XML_results;
  44. private $XML_position = 0;
  45. protected $XML_grabtags = array();
  46. protected $XML_skiptags = array('root');
  47. protected $XML_parenttags = array('artist','album','song','tag','video','playlist','result',
  48. 'auth','version','update','add','clean','songs',
  49. 'artists','albums','tags','videos','api','playlists','catalogs');
  50. // Library static version information
  51. protected static $LIB_version = '350001';
  52. private static $API_version = '';
  53. private $_debug_callback = null;
  54. private $_debug_output = false;
  55. /**
  56. * Constructor
  57. *
  58. * If enough information is provided then we will attempt to connect right
  59. * away, otherwise we will simply return an object that can be reconfigured
  60. * and manually connected.
  61. */
  62. public function __construct($config = array())
  63. {
  64. // See if we are setting debug first
  65. if (isset($config['debug'])) {
  66. $this->_debug_output = $config['debug'];
  67. }
  68. if (isset($config['debug_callback'])) {
  69. $this->_debug_callback = $config['debug_callback'];
  70. }
  71. // If we got something, then configure!
  72. if (is_array($config) && count($config)) {
  73. $this->configure($config);
  74. }
  75. // If we've been READY'd then go ahead and attempt to connect
  76. if ($this->state() == 'READY') {
  77. $this->connect();
  78. }
  79. }
  80. /**
  81. * _debug
  82. *
  83. * Make debugging all nice and pretty.
  84. */
  85. private function _debug($source, $message, $level = 5)
  86. {
  87. if ($this->_debug_output) {
  88. echo "$source :: $message\n";
  89. }
  90. if (!is_null($this->_debug_callback)) {
  91. call_user_func($this->_debug_callback, 'AmpacheApi', "$source :: $message", $level);
  92. }
  93. }
  94. /**
  95. * connect
  96. *
  97. * This attempts to connect to the Ampache instance.
  98. */
  99. public function connect()
  100. {
  101. $this->_debug('CONNECT', "Using $this->username / $this->password");
  102. // Set up the handshake
  103. $results = array();
  104. $timestamp = time();
  105. $passphrase = hash('sha256', $timestamp . $this->password);
  106. $options = array(
  107. 'timestamp' => $timestamp,
  108. 'auth' => $passphrase,
  109. 'version' => self::$LIB_version,
  110. 'user' => $this->username
  111. );
  112. $data = $this->send_command('handshake', $options);
  113. foreach ($data as $value) {
  114. $results = array_merge($results, $value);
  115. }
  116. if (!$results['auth']) {
  117. $this->set_state('error');
  118. return false;
  119. }
  120. $this->api_auth = $results['auth'];
  121. $this->set_state('connected');
  122. // Define when we pulled this, it is not wine, it does
  123. // not get better with age
  124. $this->handshake_time = time();
  125. $this->handshake = $results;
  126. }
  127. /**
  128. * configure
  129. *
  130. * This function takes an array of elements and configures the AmpacheApi
  131. * object. It doesn't really do anything fancy, but it's a separate function
  132. * so it can be called both from the constructor and directly.
  133. */
  134. public function configure($config = array())
  135. {
  136. $this->_debug('CONFIGURE', 'Checking passed config options');
  137. if (!is_array($config)) {
  138. trigger_error('AmpacheApi::configure received a non-array value');
  139. return false;
  140. }
  141. // FIXME: Is the scrubbing of these variables actually sane? I'm pretty
  142. // sure password at least shouldn't be messed with like that.
  143. if (isset($config['username'])) {
  144. $this->username = htmlentities($config['username'], ENT_QUOTES, 'UTF-8');
  145. }
  146. if (isset($config['password'])) {
  147. $this->password = htmlentities($config['password'], ENT_QUOTES, 'UTF-8');
  148. }
  149. if (isset($config['api_secure'])) {
  150. // This should be a boolean response
  151. $this->api_secure = $config['api_secure'] ? true : false;
  152. }
  153. $protocol = $this->api_secure ? 'https://' : 'http://';
  154. if (isset($config['server'])) {
  155. // Replace any http:// in the URL with ''
  156. $config['server'] = str_replace($protocol, '', $config['server']);
  157. $this->server = htmlentities($config['server'], ENT_QUOTES, 'UTF-8');
  158. }
  159. $this->api_url = $protocol . $this->server . '/server/xml.server.php';
  160. // See if we have enough to authenticate, if so change the state
  161. if ($this->username AND $this->password AND $this->server) {
  162. $this->set_state('ready');
  163. }
  164. return true;
  165. }
  166. /**
  167. * set_state
  168. *
  169. * This sets the current state of the API, it is used mostly internally but
  170. * the state can be accessed externally so it could be used to check and see
  171. * where the API is at, at this moment
  172. */
  173. public function set_state($state)
  174. {
  175. // Very simple for now, maybe we'll do something more with this later
  176. $this->api_state = strtoupper($state);
  177. }
  178. /**
  179. * state
  180. *
  181. * This returns the state of the API.
  182. */
  183. public function state()
  184. {
  185. return $this->api_state;
  186. }
  187. /**
  188. * info
  189. *
  190. * Returns the information gathered by the handshake.
  191. * Not raw so we can format it if we want?
  192. */
  193. public function info()
  194. {
  195. if ($this->state() != 'CONNECTED') {
  196. throw new Exception('AmpacheApi::info API in non-ready state, unable to return info');
  197. }
  198. return $this->handshake;
  199. }
  200. /**
  201. * send_command
  202. *
  203. * This sends an API command with options to the currently connected
  204. * host.
  205. */
  206. public function send_command($command, $options = array())
  207. {
  208. $this->_debug('SEND COMMAND', $command . ' ' . json_encode($options));
  209. if ($this->state() != 'READY' AND $this->state() != 'CONNECTED') {
  210. throw new Exception('AmpacheApi::send_command API in non-ready state, unable to send');
  211. }
  212. $command = trim($command);
  213. if (!$command) {
  214. throw new Exception('AmpacheApi::send_command no command specified');
  215. }
  216. if (!$this->validate_command($command)) {
  217. throw new Exception('AmpacheApi::send_command Invalid/Unknown command ' . $command . ' issued');
  218. }
  219. $url = $this->api_url . '?action=' . urlencode($command);
  220. foreach ($options as $key => $value) {
  221. $key = trim($key);
  222. if (!$key) {
  223. // Nonfatal, don't need to throw an exception
  224. trigger_error('AmpacheApi::send_command unable to append empty variable to command');
  225. continue;
  226. }
  227. $url .= '&' . urlencode($key) . '=' . urlencode($value);
  228. }
  229. // If auth is set then we append it so callers don't have to.
  230. if ($this->api_auth) {
  231. $url .= '&auth=' . urlencode($this->api_auth) . '&username=' . urlencode($this->username);
  232. }
  233. $this->_debug('COMMAND URL', $url);
  234. $data = file_get_contents($url);
  235. $this->raw_response = $data;
  236. $this->parse_response($data);
  237. return $this->get_response();
  238. }
  239. /**
  240. * validate_command
  241. *
  242. * This takes the specified command and checks it against the known
  243. * commands for the current version of Ampache. If no version is known yet
  244. * it should return FALSE for everything except ping and handshake.
  245. */
  246. public function validate_command($command)
  247. {
  248. // FIXME: actually do something
  249. return true;
  250. }
  251. /**
  252. * parse_response
  253. *
  254. * This takes an XML document and dumps it into $this->results. Before
  255. * it does that it will clean up anything that was there before, so I hope
  256. * you didn't want any of that.
  257. */
  258. public function parse_response($response)
  259. {
  260. // Reset the results
  261. $this->XML_results = array();
  262. $this->XML_position = 0;
  263. $this->XML_create_parser();
  264. if (!xml_parse($this->XML_parser, $response)) {
  265. $errorcode = xml_get_error_code($this->XML_parser);
  266. throw new Exception('AmpacheApi::parse_response was unable to parse XML document. Error ' . $errorcode . ' line ' . xml_get_current_line_number($this->XML_parser) . ': ' . xml_error_string($errorcode));
  267. }
  268. xml_parser_free($this->XML_parser);
  269. $this->_debug('PARSE RESPONSE', json_encode($this->XML_results));
  270. return true;
  271. }
  272. /**
  273. * get_response
  274. *
  275. * This returns the last data we parsed.
  276. */
  277. public function get_response()
  278. {
  279. return $this->XML_results;
  280. }
  281. ////////////////////////// XML PARSER FUNCTIONS ////////////////////////////
  282. /**
  283. * XML_create_parser
  284. * This creates the xml parser and sets the options
  285. */
  286. public function XML_create_parser()
  287. {
  288. $this->XML_parser = xml_parser_create();
  289. xml_parser_set_option($this->XML_parser,XML_OPTION_CASE_FOLDING,false);
  290. xml_set_object($this->XML_parser,$this);
  291. xml_set_element_handler($this->XML_parser,'XML_start_element','XML_end_element');
  292. xml_set_character_data_handler($this->XML_parser,'XML_cdata');
  293. } // XML_create_parser
  294. /**
  295. * XML_cdata
  296. * This is called for the content of the XML tag
  297. */
  298. public function XML_cdata($parser,$cdata)
  299. {
  300. $cdata = trim($cdata);
  301. if (!$this->XML_currentTag || !$cdata) { return false; }
  302. if ($this->XML_subTag) {
  303. $this->XML_results[$this->XML_position][$this->XML_currentTag][$this->XML_subTag] = $cdata;
  304. } else {
  305. $this->XML_results[$this->XML_position][$this->XML_currentTag] = $cdata;
  306. }
  307. } // XML_cdata
  308. public function XML_start_element($parser,$tag,$attributes)
  309. {
  310. // Skip it!
  311. if (in_array($tag,$this->XML_skiptags)) { return false; }
  312. if (!in_array($tag,$this->XML_parenttags) OR $this->XML_currentTag) {
  313. $this->XML_subTag = $tag;
  314. } else {
  315. $this->XML_currentTag = $tag;
  316. }
  317. if (count($attributes)) {
  318. if (!$this->XML_subTag) {
  319. $this->XML_results[$this->XML_position][$this->XML_currentTag]['self'] = $attributes;
  320. } else {
  321. $this->XML_results[$this->XML_position][$this->XML_currentTag][$this->XML_subTag]['self'] = $attributes;
  322. }
  323. }
  324. } // start_element
  325. public function XML_end_element($parser,$tag)
  326. {
  327. if ($tag != $this->XML_currentTag) {
  328. $this->XML_subTag = false;
  329. } else {
  330. $this->XML_currentTag = false;
  331. $this->XML_position++;
  332. }
  333. } // end_element
  334. }