Server.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086
  1. <?php
  2. /* GNU FM -- a free network service for sharing your music listening habits
  3. Copyright (C) 2009 Free Software Foundation, Inc
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU Affero General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Affero General Public License for more details.
  12. You should have received a copy of the GNU Affero General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. require_once($install_path . '/database.php');
  16. require_once($install_path . '/data/Artist.php');
  17. // require_once($install_path . '/data/Group.php');
  18. require_once($install_path . '/data/Track.php');
  19. require_once($install_path . '/data/User.php');
  20. require_once($install_path . '/data/sanitize.php');
  21. require_once($install_path . '/utils/linkeddata.php');
  22. require_once($install_path . '/utils/arc/ARC2.php');
  23. require_once($install_path . '/utils/resolve-external.php');
  24. require_once($install_path . '/utils/licenses.php');
  25. require_once($install_path . '/utils/rewrite-encode.php');
  26. require_once($install_path . '/temp-utils.php'); // this is extremely dodgy and shameful
  27. require_once($install_path . '/data/clientcodes.php');
  28. /**
  29. * Provides access to server-wide data
  30. *
  31. * All methods are statically accessible
  32. */
  33. class Server {
  34. /**
  35. * Retrieves a list of recent scrobbles
  36. *
  37. * @param int $number The number of scrobbles to return
  38. * @param int $userid The user id to return scrobbles for
  39. * @param int $offset Amount of entries to skip before returning scrobbles
  40. * @param int $from Only return scrobbles with time higher than this timestamp
  41. * @param int $to Only return scrobbles with time lower than this timestamp
  42. * @return array Scrobbles or null in case of failure
  43. */
  44. static function getRecentScrobbles($number = 10, $userid = false, $offset = 0, $from = false, $to = false) {
  45. global $adodb;
  46. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  47. if ($userid) {
  48. $params = array();
  49. $query = 'SELECT s.*, lt.userid as loved FROM Scrobbles s LEFT JOIN Loved_Tracks lt ON (s.track=lt.track AND s.artist=lt.artist AND s.userid=lt.userid) WHERE s.userid=?';
  50. $params[] = $userid;
  51. if ($from) {
  52. $query .= ' AND s.time>?';
  53. $params[] = (int) $from;
  54. }
  55. if ($to) {
  56. $query .= ' AND s.time<?';
  57. $params[] = (int) $to;
  58. }
  59. } else {
  60. $params = array();
  61. $query = 'SELECT s.* FROM Scrobbles s';
  62. if ($from) {
  63. $query .= ' WHERE s.time>?';
  64. $params[] = (int) $from;
  65. if ($to) {
  66. $query .= ' AND s.time<?';
  67. $params[] = (int) $to;
  68. }
  69. } else {
  70. if ($to) {
  71. $query .= ' WHERE s.time<?';
  72. $params[] = (int) $to;
  73. }
  74. }
  75. }
  76. $query .= ' ORDER BY s.time DESC LIMIT ? OFFSET ?';
  77. $params[] = (int) $number;
  78. $params[] = (int) $offset;
  79. try {
  80. $res = $adodb->CacheGetAll(60, $query, $params);
  81. } catch (Exception $e) {
  82. reportError($e->getMessage(), $e->getTraceAsString());
  83. return null;
  84. }
  85. if($userid) {
  86. $username = uniqueid_to_username($userid);
  87. $userurl = Server::getUserURL($username);
  88. }
  89. $result = array();
  90. foreach ($res as &$i) {
  91. $row = sanitize($i);
  92. if(!$userid) {
  93. $row['username'] = uniqueid_to_username($row['userid']);
  94. $row['userurl'] = Server::getUserURL($row['username']);
  95. } else {
  96. $row['username'] = $username;
  97. $row['userurl'] = $userurl;
  98. }
  99. if ($row['album']) {
  100. $row['albumurl'] = Server::getAlbumURL($row['artist'], $row['album']);
  101. }
  102. $row['artisturl'] = Server::getArtistURL($row['artist']);
  103. $row['trackurl'] = Server::getTrackURL($row['artist'], $row['album'], $row['track']);
  104. $row['timehuman'] = human_timestamp($row['time']);
  105. $row['timeiso'] = date('c', (int)$row['time']);
  106. $row['id'] = identifierScrobbleEvent($row['username'], $row['artist'], $row['track'], $row['album'], $row['time'], $row['mbid'], $row['artist_mbid'], $row['album_mbid']);
  107. $row['id_artist'] = identifierArtist($row['username'], $row['artist'], $row['track'], $row['album'], $row['time'], $row['mbid'], $row['artist_mbid'], $row['album_mbid']);
  108. $row['id_track'] = identifierTrack($row['username'], $row['artist'], $row['track'], $row['album'], $row['time'], $row['mbid'], $row['artist_mbid'], $row['album_mbid']);
  109. $row['id_album'] = identifierAlbum($row['username'], $row['artist'], $row['track'], $row['album'], $row['time'], $row['mbid'], $row['artist_mbid'], $row['album_mbid']);
  110. if (!$row['album_image']) {
  111. $row['album_image'] = false;
  112. } else {
  113. $row['album_image'] = resolve_external_url($row['album_image']);
  114. }
  115. if ($row['artwork_license'] == 'amazon') {
  116. $row['album_image'] = str_replace('SL160', 'SL50', $row['album_image']);
  117. }
  118. $row['licenseurl'] = $row['license'];
  119. $row['license'] = simplify_license($row['licenseurl']);
  120. $result[] = $row;
  121. }
  122. return $result;
  123. }
  124. /**
  125. * Retrieves a list of popular artists
  126. *
  127. * @param int $limit The number of artists to return
  128. * @param int $offset Skip this number of rows before returning artists
  129. * @param bool $streamable Only return streamable artists
  130. * @param int $begin Only use scrobbles with time higher than this timestamp
  131. * @param int $end Only use scrobbles with time lower than this timestamp
  132. * @param int $userid Only return results from this userid
  133. * @param int $cache Caching period in seconds
  134. * @return array An array of artists ((artist, freq, artisturl) ..) or empty array in case of failure
  135. */
  136. static function getTopArtists($limit = 21, $offset = 0, $streamable = False, $begin = null, $end = null, $userid = null, $cache = 600) {
  137. global $adodb;
  138. $query = ' SELECT artist, COUNT(artist) as freq FROM Scrobbles s';
  139. if ($streamable) {
  140. $query .= ' INNER JOIN Artist a ON s.artist=a.name WHERE a.streamable=1';
  141. $andquery = True;
  142. } else {
  143. if($begin || $end || $userid) {
  144. $query .= ' WHERE';
  145. $andquery = False;
  146. }
  147. }
  148. if($begin) {
  149. //change time resolution to full hours (for easier caching)
  150. $begin = $begin - ($begin % 3600);
  151. $andquery ? $query .= ' AND' : $andquery = True ;
  152. $query .= ' time>' . (int)$begin;
  153. }
  154. if($end) {
  155. //change time resolution to full hours (for easier caching)
  156. $end = $end - ($end % 3600);
  157. $andquery ? $query .= ' AND' : $andquery = True ;
  158. $query .= ' time<' . (int)$end;
  159. }
  160. if($userid) {
  161. $andquery ? $query .= ' AND' : $andquery = True ;
  162. $query .= ' userid=' . (int)$userid;
  163. }
  164. $query .= ' GROUP BY artist ORDER BY freq DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  165. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  166. try {
  167. $data = $adodb->CacheGetAll($cache, $query);
  168. } catch (Exception $e) {
  169. return array();
  170. }
  171. $result = array();
  172. foreach ($data as &$i) {
  173. $row = sanitize($i);
  174. $row['artisturl'] = Server::getArtistURL($row['artist']);
  175. $result[] = $row;
  176. }
  177. return $result;
  178. }
  179. /**
  180. * Retrieves a list of loved artists
  181. *
  182. * @param int $limit The number of artists to return
  183. * @param int $offset Skip this number of rows before returning artists
  184. * @param bool $streamable Only return streamable artists
  185. * @param int $userid Only return results from this userid
  186. * @param int $cache Caching period in seconds
  187. * @return array Artists ((artist, freq, artisturl) ..) or empty array in case of failure
  188. */
  189. static function getLovedArtists($limit = 20, $offset = 0, $streamable = False, $userid = null, $cache = 600) {
  190. global $adodb;
  191. $query = ' SELECT artist, COUNT(artist) as freq FROM Loved_Tracks lt INNER JOIN Artist a ON a.name=lt.artist';
  192. if ($streamable) {
  193. $query .= ' WHERE a.streamable=1';
  194. $andquery = True;
  195. } else {
  196. if ($userid) {
  197. $query .= ' WHERE';
  198. $andquery = False;
  199. }
  200. }
  201. if ($userid) {
  202. $andquery ? $query .= ' AND' : null;
  203. $query .= ' userid=' . (int)$userid;
  204. }
  205. $query .= ' GROUP BY artist ORDER BY freq DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  206. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  207. try {
  208. $data = $adodb->CacheGetAll($cache, $query);
  209. } catch (Exception $e) {
  210. return array();
  211. }
  212. $result = array();
  213. if (!empty($data)) {
  214. foreach ($data as &$i) {
  215. $row = sanitize($i);
  216. $row['artisturl'] = Server::getArtistURL($row['artist']);
  217. $result[] = $row;
  218. }
  219. }
  220. return $result;
  221. }
  222. /**
  223. * Retrieves a list of popular tracks
  224. *
  225. * @param int $limit The number of tracks to return
  226. * @param int $offset Skip this number of rows before returning tracks
  227. * @param bool $streamable Only return streamable tracks
  228. * @param int $begin Only use scrobbles with time higher than this timestamp
  229. * @param int $end Only use scrobbles with time lower than this timestamp
  230. * @param int $artist Only return results from this artist
  231. * @param int $userid Only return results from this userid
  232. * @param int $cache Caching period in seconds
  233. * @return array Tracks ((artist, track, freq, listeners, artisturl, trackurl) ..) or empty array in case of failure
  234. */
  235. static function getTopTracks($limit = 20, $offset = 0, $streamable = False, $begin = null, $end = null, $artist = null, $userid = null, $cache = 600) {
  236. global $adodb;
  237. $query = 'SELECT s.artist, s.track, count(s.track) AS freq, count(DISTINCT s.userid) AS listeners FROM Scrobbles s';
  238. if ($streamable) {
  239. $query .= ' WHERE ROW(s.artist, s.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  240. $andquery = True;
  241. } else {
  242. if($begin || $end || $userid || $artist) {
  243. $query .= ' WHERE';
  244. $andquery = False;
  245. }
  246. }
  247. if($begin) {
  248. //change time resolution to full hours (for easier caching)
  249. $begin = $begin - ($begin % 3600);
  250. $andquery ? $query .= ' AND' : $andquery = True ;
  251. $query .= ' s.time>' . (int)$begin;
  252. }
  253. if($end) {
  254. //change time resolution to full hours (for easier caching)
  255. $end = $end - ($end % 3600);
  256. $andquery ? $query .= ' AND' : $andquery = True ;
  257. $query .= ' s.time<' . (int)$end;
  258. }
  259. if($userid) {
  260. $andquery ? $query .= ' AND' : $andquery = True ;
  261. $query .= ' s.userid=' . (int)$userid;
  262. }
  263. if($artist) {
  264. $andquery ? $query .= ' AND' : $andquery = True;
  265. $query .= ' lower(s.artist)=lower(' . $adodb->qstr($artist) . ')';
  266. }
  267. $query .= ' GROUP BY s.track, s.artist ORDER BY freq DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  268. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  269. try {
  270. $data = $adodb->CacheGetAll($cache, $query);
  271. } catch (Exception $e) {
  272. return array();
  273. }
  274. $result = array();
  275. foreach ($data as &$i) {
  276. $row = sanitize($i);
  277. $row['artisturl'] = Server::getArtistURL($row['artist']);
  278. $row['trackurl'] = Server::getTrackURL($row['artist'], null, $row['track']);
  279. $result[] = $row;
  280. }
  281. return $result;
  282. }
  283. /**
  284. * Get a list of users with the most listens
  285. *
  286. * @param int $limit Amount of results to return
  287. * @param int $offset Skip this many items before returning results
  288. * @param int $streamable Only return results for streamable tracks
  289. * @param int $begin Only use scrobbles with time higher than this timestamp
  290. * @param int $end Only use scrobbles with time lower than this timestamp
  291. * @param string $artist Filter results by this artist
  292. * @param string $track Filter result by this track (need $artist to be set)
  293. * @param int $cache Caching period in seconds
  294. * @return array ((userid, freq, username, userurl) ..)
  295. */
  296. static function getTopListeners($limit = 10, $offset = 0, $streamable = True, $begin = null, $end = null, $artist = null, $track = null, $cache = 600) {
  297. global $adodb;
  298. $params = array();
  299. $query = 'SELECT s.userid, COUNT(*) as freq FROM Scrobbles s';
  300. if ($streamable) {
  301. $query .= ' WHERE ROW(s.artist, s.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  302. $andquery = True;
  303. } else {
  304. if($begin || $end || $artist) {
  305. $query .= ' WHERE';
  306. $andquery = False;
  307. }
  308. }
  309. if($begin) {
  310. //change time resolution to full hours (for easier caching)
  311. $begin = $begin - ($begin % 3600);
  312. $andquery ? $query .= ' AND' : $andquery = True ;
  313. $query .= ' s.time > ?';
  314. $params[] = (int)$begin;
  315. }
  316. if($end) {
  317. //change time resolution to full hours (for easier caching)
  318. $end = $end - ($end % 3600);
  319. $andquery ? $query .= ' AND' : $andquery = True ;
  320. $query .= ' s.time < ?';
  321. $params[] = (int)$end;
  322. }
  323. if($artist) {
  324. $andquery ? $query .= ' AND' : $andquery = True;
  325. $query .= ' lower(s.artist)=lower(?)';
  326. $params[] = $artist;
  327. if($track) {
  328. $andquery ? $query .= ' AND' : $andquery = True;
  329. $query .= ' lower(s.track)=lower(?)';
  330. $params[] = $track;
  331. }
  332. }
  333. $query .= ' GROUP BY s.userid ORDER BY freq DESC LIMIT ? OFFSET ?';
  334. $params[] = (int)$limit;
  335. $params[] = (int)$offset;
  336. try {
  337. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  338. $res = $adodb->CacheGetAll($cache, $query, $params);
  339. }catch (Exception $e) {
  340. return array();
  341. }
  342. foreach($res as &$row) {
  343. $row['username'] = uniqueid_to_username($row['userid']);
  344. $row['userurl'] = Server::getUserURL($row['username']);
  345. $result[] = $row;
  346. }
  347. return $result;
  348. }
  349. /**
  350. * Retrieves a list of loved tracks
  351. *
  352. * @param int $limit The number of tracks to return
  353. * @param int $offset Skip this number of rows before returning tracks
  354. * @param bool $streamable Only return streamable tracks
  355. * @param int $artist Only return results from this artist
  356. * @param int $userid Only return results from this userid
  357. * @param int $cache Caching period in seconds
  358. * @return array Tracks ((artist, track, freq, listeners, artisturl, trackurl) ..) or empty array in case of failure
  359. */
  360. static function getLovedTracks($limit = 20, $offset = 0, $streamable = False, $artist = null, $userid = null, $cache = 600) {
  361. global $adodb;
  362. $query = 'SELECT lt.artist, lt.track, max(lt.time) as time, count(lt.track) AS freq FROM Loved_Tracks lt';
  363. if ($streamable) {
  364. $query .= ' WHERE ROW(lt.artist, lt.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  365. $andquery = True;
  366. } else {
  367. if($userid || $artist) {
  368. $query .= ' WHERE';
  369. $andquery = False;
  370. }
  371. }
  372. if($userid) {
  373. $andquery ? $query .= ' AND' : $andquery = True ;
  374. $query .= ' lt.userid=' . (int)$userid;
  375. }
  376. if($artist) {
  377. $andquery ? $query .= ' AND' : $andquery = True;
  378. $query .= ' lower(lt.artist)=lower(' . $adodb->qstr($artist) . ')';
  379. }
  380. $query .= ' GROUP BY lt.track, lt.artist ORDER BY freq DESC, time DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  381. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  382. try {
  383. $data = $adodb->CacheGetAll($cache, $query);
  384. } catch (Exception $e) {
  385. return array();
  386. }
  387. $result = array();
  388. foreach ($data as &$i) {
  389. $row = sanitize($i);
  390. $row['artisturl'] = Server::getArtistURL($row['artist']);
  391. $row['trackurl'] = Server::getTrackURL($row['artist'], null, $row['track']);
  392. $result[] = $row;
  393. }
  394. return $result;
  395. }
  396. /**
  397. * Get a list of users
  398. *
  399. * @param string $alpha Search for user names starting with this string
  400. */
  401. static function getUserList($alpha) {
  402. global $adodb;
  403. $alpha .= '%';
  404. $query = 'SELECT username from Users where username LIKE ' . $adodb->qstr($alpha) . ' ORDER BY username ASC';
  405. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  406. $data = $adodb->CacheGetAll(7200, $query);
  407. if (!$data) {
  408. throw new Exception('ERROR ' . $query);
  409. }
  410. return $data;
  411. }
  412. /**
  413. * Retrieves a list of the currently playing tracks
  414. *
  415. * @param int $number The maximum number of tracks to return
  416. * @param string $username The name of the user to retrieve playing tracks for
  417. * @return array Now playing data or null in case of failure
  418. */
  419. static function getNowPlaying($number = 1, $username = false) {
  420. global $adodb;
  421. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  422. try {
  423. if ($username) {
  424. $data = $adodb->CacheGetAll(1, 'SELECT
  425. ss.userid,
  426. n.artist,
  427. n.track,
  428. n.album,
  429. client,
  430. api_key,
  431. n.mbid,
  432. t.license
  433. FROM Now_Playing n
  434. LEFT OUTER JOIN Scrobble_Sessions ss
  435. ON n.sessionid=ss.sessionid
  436. LEFT OUTER JOIN Track t
  437. ON lower(n.artist) = lower(t.artist_name)
  438. AND lower(n.album) = lower(t.album_name)
  439. AND lower(n.track) = lower(t.name)
  440. AND lower(n.mbid) = lower(t.mbid)
  441. WHERE ss.userid= ' . username_to_uniqueid($username) . '
  442. ORDER BY t.streamable DESC, n.expires DESC LIMIT ' . (int)($number));
  443. } else {
  444. $data = $adodb->CacheGetAll(60, 'SELECT
  445. ss.userid,
  446. n.artist,
  447. n.track,
  448. n.album,
  449. client,
  450. n.mbid,
  451. t.license
  452. FROM Now_Playing n
  453. LEFT OUTER JOIN Scrobble_Sessions ss
  454. ON n.sessionid=ss.sessionid
  455. LEFT OUTER JOIN Track t
  456. ON lower(n.artist) = lower(t.artist_name)
  457. AND lower(n.album) = lower(t.album_name)
  458. AND lower(n.track) = lower(t.name)
  459. AND lower(n.mbid) = lower(t.mbid)
  460. ORDER BY t.streamable DESC, n.expires DESC LIMIT ' . (int)($number));
  461. }
  462. } catch (Exception $e) {
  463. return null;
  464. }
  465. $result = array();
  466. foreach ($data as &$i) {
  467. $row = sanitize($i);
  468. $client = getClientData($row['client'], $row['api_key']);
  469. $row['clientcode'] = $client['code'];
  470. $row['clientapi_key'] = $client['code'];
  471. $row['clientname'] = $client['name'];
  472. $row['clienturl'] = $client['url'];
  473. $row['clientfree'] = $client['free'];
  474. $row['username'] = uniqueid_to_username($row['userid']);
  475. $row['userurl'] = Server::getUserURL($row['username']);
  476. $row['artisturl'] = Server::getArtistURL($row['artist']);
  477. $row['trackurl'] = Server::getTrackURL($row['artist'], $row['album'], $row['track']);
  478. if ($username) {
  479. $row['loved'] = $adodb->CacheGetOne(60, 'SELECT Count(*) FROM Loved_Tracks WHERE artist='
  480. . $adodb->qstr($row['artist'])
  481. . ' AND track=' . $adodb->qstr($row['track'])
  482. . ' AND userid=' . $row['userid']);
  483. }
  484. // We really want to get an image URI from the database and only fall back to qm50.png if we can't find an image.
  485. $row['albumart'] = $base_url . 'themes/' . $default_theme . '/images/qm50.png';
  486. $row['licenseurl'] = $row['license'];
  487. $row['license'] = simplify_license($row['licenseurl']);
  488. $result[] = $row;
  489. }
  490. return $result;
  491. }
  492. /**
  493. * Gets the URL to a user's profile page
  494. *
  495. * The get*URL functions are implemented here rather than in their respective
  496. * objects so that we can produce URLs without needing to build whole objects.
  497. *
  498. * @param string $username The user name we want a URL for
  499. * @param string $component Type of URL to return
  500. * @param string $params Trailing get parameters
  501. * @return string URL to the user's profile
  502. */
  503. static function getUserURL ($username, $component = 'profile', $params = false) {
  504. global $friendly_urls, $base_url;
  505. if ($component == 'edit') {
  506. return $base_url . '/user-edit.php';
  507. } else if ($component == 'delete') {
  508. return $base_url . '/delete-profile.php';
  509. } else if ($friendly_urls) {
  510. if ($component == 'profile') {
  511. $component = '';
  512. } else {
  513. $component = "/{$component}";
  514. }
  515. return $base_url . '/user/' . rewrite_encode($username) . $component . ($params ? '?' . $params : null);
  516. } else {
  517. return $base_url . "/user-{$component}.php?user=" . rawurlencode($username) . ($params ? '&' . $params : null);
  518. }
  519. }
  520. /**
  521. * Gets the URL to a group's page
  522. *
  523. * @param string $groupname The group we want a URL for
  524. * @return string URL to the group's page
  525. */
  526. static function getGroupURL($groupname) {
  527. global $friendly_urls, $base_url;
  528. if ($friendly_urls) {
  529. return $base_url . '/group/' . rewrite_encode($groupname);
  530. } else {
  531. return $base_url . '/group.php?group=' . rawurlencode($groupname);
  532. }
  533. }
  534. /**
  535. * Gets the URL to an artist's page
  536. *
  537. * @param string $artist The artist we want a URL for
  538. * @param string $component Type of URL to return
  539. * @return string URL to the artist's page
  540. */
  541. static function getArtistURL($artist, $component = '') {
  542. global $friendly_urls, $base_url;
  543. if ($friendly_urls) {
  544. return $base_url . '/artist/' . rewrite_encode($artist) . '/' . $component;
  545. } else {
  546. if ($component) {
  547. return $base_url . '/artist-' . $component . '.php?artist=' . rawurlencode($artist);
  548. } else {
  549. return $base_url . '/artist.php?artist=' . rawurlencode($artist);
  550. }
  551. }
  552. }
  553. /**
  554. * Gives the URL to the management interface for an artist
  555. *
  556. * @param string $artist The artist we want a URL for
  557. * @return string URL for an artist's management interface
  558. */
  559. static function getArtistManagementURL($artist) {
  560. global $friendly_urls, $base_url;
  561. if ($friendly_urls) {
  562. return Server::getArtistURL($artist) . '/manage';
  563. } else {
  564. return $base_url . '/artist-manage.php?artist=' . rawurlencode($artist);
  565. }
  566. }
  567. /**
  568. * Gives the URL for managers to add a new album to an artist
  569. *
  570. * @param string $artist The artist we want a URL for
  571. * @return string URL for adding albums to an artist
  572. */
  573. static function getAddAlbumURL($artist) {
  574. global $friendly_urls, $base_url;
  575. if ($friendly_urls) {
  576. return Server::getArtistURL($artist) . '/album/add';
  577. } else {
  578. return $base_url . '/album-add.php?artist=' . rawurlencode($artist);
  579. }
  580. }
  581. /**
  582. * Gets the URL to an album's page
  583. *
  584. * @param string $artist The artist name of the album
  585. * @param string $album The name of the album
  586. * @return string URL to the album's page
  587. */
  588. static function getAlbumURL($artist, $album) {
  589. global $friendly_urls, $base_url;
  590. if ($friendly_urls) {
  591. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album);
  592. } else {
  593. return $base_url . '/album.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  594. }
  595. }
  596. /**
  597. * Gives the URL for managers to add a new track to an album
  598. *
  599. * @param string $artist The artist name of the album
  600. * @param string $album The name of the album
  601. * @return string URL for adding tracks to an album
  602. */
  603. static function getAddTrackURL($artist, $album) {
  604. global $friendly_urls, $base_url;
  605. if ($friendly_urls) {
  606. return Server::getAlbumURL($artist, $album) . '/track/add';
  607. } else {
  608. return $base_url . '/track-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  609. }
  610. }
  611. /**
  612. * Gets the URL to a track's page
  613. *
  614. * @param string $artist The artist name of the track
  615. * @param string $album The album name of this track (optional)
  616. * @param string $track The name of the track
  617. * @param string $component Type of page
  618. * @return string URL to the track's page
  619. */
  620. static function getTrackURL($artist, $album, $track, $component = '') {
  621. global $friendly_urls, $base_url;
  622. if($friendly_urls) {
  623. $trackurl = $base_url . '/artist/' . rewrite_encode($artist);
  624. if($album) {
  625. $trackurl .= '/album/' . rewrite_encode($album);
  626. }
  627. $trackurl .= '/track/' . rewrite_encode($track);
  628. if($component) {
  629. $trackurl .= '/' . $component;
  630. }
  631. } else {
  632. if($component) {
  633. $trackurl = $base_url . '/track-' . $component . '.php?artist=' . rawurlencode($artist)
  634. . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  635. } else {
  636. $trackurl = $base_url . '/track.php?artist=' . rawurlencode($artist)
  637. . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  638. }
  639. }
  640. return $trackurl;
  641. }
  642. /**
  643. * Gets the URL to a track's edit page
  644. *
  645. * @param string $artist The artist name of the track
  646. * @param string $album The album name of this track (optional)
  647. * @param string $track The name of the track
  648. * @return string URL to the track's edit page
  649. */
  650. static function getTrackEditURL($artist, $album, $track) {
  651. global $friendly_urls, $base_url;
  652. if ($friendly_urls && $album) {
  653. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album) . '/track/' . rewrite_encode($track) . '/edit';
  654. } else if ($friendly_urls) {
  655. return $base_url . '/artist/' . rewrite_encode($artist) . '/track/' . rewrite_encode($track) . '/edit';
  656. } else {
  657. return $base_url . '/track-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  658. }
  659. }
  660. static function getAlbumEditURL($artist, $album) {
  661. global $friendly_urls, $base_url;
  662. if ($friendly_urls) {
  663. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album) . '/edit';
  664. } else {
  665. return $base_url . '/album-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  666. }
  667. }
  668. /**
  669. * Gets the URL to a tag's page
  670. *
  671. * @param string $tag The name of the tag
  672. * @return string URL to the tag's page
  673. */
  674. static function getTagURL($tag) {
  675. global $friendly_urls, $base_url;
  676. if ($friendly_urls) {
  677. return $base_url . '/tag/' . rewrite_encode($tag);
  678. } else {
  679. return $base_url . '/tag.php?tag=' . rawurlencode($tag);
  680. }
  681. }
  682. static function getLocationDetails($name) {
  683. global $adodb;
  684. if (!$name) {
  685. return array();
  686. }
  687. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  688. $rv = $adodb->GetRow('SELECT p.latitude, p.longitude, p.country, c.country_name, c.wikipedia_en '
  689. . 'FROM Places p '
  690. . 'LEFT JOIN Countries c ON p.country=c.country '
  691. . 'WHERE p.location_uri=' . $adodb->qstr($name, 'text'));
  692. if ($rv) {
  693. if (!($rv['latitude'] && $rv['longitude'] && $rv['country'])) {
  694. $parser = ARC2::getRDFXMLParser();
  695. $parser->parse($name);
  696. $index = $parser->getSimpleIndex();
  697. $rv = array(
  698. 'latitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#lat'][0],
  699. 'longitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#long'][0],
  700. 'country' => strtoupper(substr($index[$name]['http://www.geonames.org/ontology#inCountry'][0], -2))
  701. );
  702. $adodb->Execute(sprintf('UPDATE Places SET latitude=%s, longitude=%s, country=%s WHERE location_uri=%s',
  703. $adodb->qstr($rv['latitude']),
  704. $adodb->qstr($rv['longitude']),
  705. $adodb->qstr($rv['country']),
  706. $adodb->qstr($name)));
  707. }
  708. } else {
  709. $parser = ARC2::getRDFXMLParser();
  710. $parser->parse($name);
  711. $index = $parser->getSimpleIndex();
  712. $rv = array(
  713. 'latitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#lat'][0],
  714. 'longitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#long'][0],
  715. 'country' => strtoupper(substr($index[$name]['http://www.geonames.org/ontology#inCountry'][0], -2))
  716. );
  717. $adodb->Execute(sprintf('INSERT INTO Places (location_uri, latitude, longitude, country) VALUES (%s, %s, %s, %s)',
  718. $adodb->qstr($name),
  719. $adodb->qstr($rv['latitude']),
  720. $adodb->qstr($rv['longitude']),
  721. $adodb->qstr($rv['country'])));
  722. }
  723. return $rv;
  724. }
  725. /**
  726. * Log in to the radio server
  727. *
  728. * @param string $station The station to be played
  729. * @param string $username The user to associate this session with (optional)
  730. * @param string $session_id Allows for a custom session id to be set, allowing for compatibility with webservices
  731. * @return string Session key to be used for streaming
  732. */
  733. static function getRadioSession($station, $username = false, $session_id = false) {
  734. global $adodb;
  735. if (!$session_id) {
  736. $session_id = md5(mt_rand() . time());
  737. }
  738. // Remove any previous station for this session id
  739. $adodb->Execute('DELETE FROM Radio_Sessions WHERE session = ' . $adodb->qstr($session_id));
  740. if ($username) {
  741. $sql = 'INSERT INTO Radio_Sessions(username, session, url, expires) VALUES ('
  742. . $adodb->qstr($username) . ','
  743. . $adodb->qstr($session_id) . ','
  744. . $adodb->qstr($station) . ','
  745. . (int)(time() + 86400) . ')';
  746. } else {
  747. $sql = 'INSERT INTO Radio_Sessions(session, url, expires) VALUES ('
  748. . $adodb->qstr($session_id) . ','
  749. . $adodb->qstr($station) . ','
  750. . (int)(time() + 86400) . ')';
  751. }
  752. $res = $adodb->Execute($sql);
  753. return $session_id;
  754. }
  755. /**
  756. * Log in to web services
  757. *
  758. * @param string $username The user to create a session for
  759. * @return string The web service session key
  760. */
  761. static function getWebServiceSession($username) {
  762. global $adodb;
  763. $sk = md5(mt_rand() . time());
  764. $token = md5(mt_rand() . time());
  765. $adodb->Execute('INSERT INTO Auth(token, sk, expires, username) VALUES ('
  766. . $adodb->qstr($token) . ', '
  767. . $adodb->qstr($sk) . ', '
  768. . (int)(time() + 86400) . ', '
  769. . $adodb->qstr($username) . ')');
  770. return $sk;
  771. }
  772. /**
  773. * Get scrobble session ID for a user.
  774. *
  775. * Gets the most recent scrobble session ID for userid,
  776. * or creates a new session ID if it can't find one.
  777. *
  778. * @param int userid (required) User ID.
  779. * @param string api_key (optional) Client API key (32 characters)
  780. * @param int expire_limit (optional) Amount of time in seconds before session will expire (defaults to 86400 = 24 hours)
  781. * @return string Scrobble session ID
  782. */
  783. static function getScrobbleSession($userid, $api_key = null, $expire_limit = 86400) {
  784. //TODO Add code to remove expired sessions (this is currently only done in gnukebox)
  785. global $adodb;
  786. $query = 'SELECT sessionid FROM Scrobble_Sessions WHERE userid = ? AND expires > ?';
  787. $params = array( (int) $userid, time());
  788. if (strlen($api_key) == 32) {
  789. $query .= ' AND api_key=?';
  790. $params[] = $api_key;
  791. } elseif (strlen($api_key) == 3) {
  792. // api_key is really a 3 char client code (2.0-scrobble-proxy.php sends client code in api_key)
  793. $query .= ' AND client=?';
  794. $client_id = $api_key;
  795. $params[] = $client_id;
  796. // we dont want to insert a 3 char code as api_key in db
  797. $api_key = null;
  798. }
  799. $sessionid = $adodb->GetOne($query, $params);
  800. if (!$sessionid) {
  801. $sessionid = md5(mt_rand() . time());
  802. $expires = time() + (int) $expire_limit;
  803. $query = 'INSERT INTO Scrobble_Sessions(userid, sessionid, client, expires, api_key) VALUES (?,?,?,?,?)';
  804. $params = array($userid, $sessionid, $client_id, $expires, $api_key);
  805. try {
  806. $adodb->Execute($query, $params);
  807. } catch (Exception $e) {
  808. return null;
  809. }
  810. }
  811. return $sessionid;
  812. }
  813. /**
  814. * Get all artists
  815. *
  816. * @return array Artists ordered by name
  817. */
  818. static function getAllArtists() {
  819. global $adodb;
  820. $sql = 'SELECT * from Artist ORDER by name';
  821. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  822. try {
  823. $res = $adodb->CacheGetAll(86400, $sql);
  824. } catch (Exception $e) {
  825. return null;
  826. }
  827. $result = array();
  828. foreach ($res as &$i) {
  829. $row = sanitize($i);
  830. $row['artisturl'] = Server::getArtistURL($row['name']);
  831. $result[] = $row;
  832. }
  833. return $result;
  834. }
  835. /**
  836. * Search for users, artists or tags
  837. *
  838. * Does a lower-case search of %search_term%
  839. *
  840. * @param string $search_term
  841. * @param string $search_type Type of search, artist|user|tag
  842. * @param int $limit How many items to return
  843. * @param bool $streamable Only return streamable artists
  844. * @return array Results
  845. */
  846. static function search($search_term, $search_type, $limit = 40, $streamable = false) {
  847. global $adodb;
  848. if ($search_term) {
  849. switch ($search_type) {
  850. case 'artist':
  851. $table = 'Artist';
  852. $search_fields[] = 'name';
  853. $data_fields[] = 'name';
  854. $data_fields[] = 'bio_summary';
  855. $data_fields[] = 'streamable';
  856. $data_fields[] = 'image_small';
  857. $data_fields[] = 'image_medium';
  858. $data_fields[] = 'image_large';
  859. break;
  860. case 'user':
  861. $table = 'Users';
  862. $search_fields[] = 'username';
  863. $search_fields[] = 'fullname';
  864. $data_fields[] = 'username';
  865. $data_fields[] = 'fullname';
  866. $data_fields[] = 'bio';
  867. break;
  868. case 'tag':
  869. $table = 'Tags';
  870. $search_fields[] = 'tag';
  871. $data_fields[] = 'tag';
  872. break;
  873. default:
  874. return array();
  875. }
  876. $sql = 'SELECT DISTINCT ';
  877. for ($i = 0; $i < count($data_fields); $i++) {
  878. $sql .= $data_fields[$i];
  879. if ($i < count($data_fields) - 1) {
  880. $sql .= ', ';
  881. }
  882. }
  883. $sql .= ' FROM ' . $table . ' WHERE ';
  884. for ($i = 0; $i < count($search_fields); $i++) {
  885. if ($i > 0) {
  886. $sql .= ' OR ';
  887. }
  888. $sql .= 'LOWER(' . $search_fields[$i] . ') LIKE LOWER(' . $adodb->qstr('%' . $search_term . '%') . ')';
  889. }
  890. if ($streamable) {
  891. $sql .= " AND streamable = 1 ";
  892. }
  893. $sql .= 'LIMIT ' . $limit;
  894. $res = $adodb->CacheGetAll(600, $sql);
  895. $result = array();
  896. foreach ($res as &$i) {
  897. $row = sanitize($i);
  898. switch ($search_type) {
  899. case 'artist':
  900. $row['url'] = Server::getArtistURL($row['name']);
  901. break;
  902. case 'user':
  903. $row['url'] = Server::getUserURL($row['username']);
  904. break;
  905. case 'tag':
  906. $row['url'] = Server::getTagURL($row['tag']);
  907. break;
  908. }
  909. $result[] = $row;
  910. }
  911. }
  912. return $result;
  913. }
  914. /**
  915. * Create a random authentication token and return it
  916. *
  917. * @return string Token.
  918. */
  919. static function getAuthToken() {
  920. global $adodb;
  921. $key = md5(time() . rand());
  922. $expires = (int) (time() + 3600);
  923. $query = 'INSERT INTO Auth(token, expires) VALUES(?,?)';
  924. $params = array($key, $expires);
  925. try {
  926. $adodb->Execute($query, $params);
  927. return $key;
  928. } catch (Exception $e) {
  929. reportError($e->getMessage(), $e->getTraceAsString());
  930. }
  931. }
  932. }