Server.php 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084
  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. foreach ($data as &$i) {
  214. $row = sanitize($i);
  215. $row['artisturl'] = Server::getArtistURL($row['artist']);
  216. $result[] = $row;
  217. }
  218. return $result;
  219. }
  220. /**
  221. * Retrieves a list of popular tracks
  222. *
  223. * @param int $limit The number of tracks to return
  224. * @param int $offset Skip this number of rows before returning tracks
  225. * @param bool $streamable Only return streamable tracks
  226. * @param int $begin Only use scrobbles with time higher than this timestamp
  227. * @param int $end Only use scrobbles with time lower than this timestamp
  228. * @param int $artist Only return results from this artist
  229. * @param int $userid Only return results from this userid
  230. * @param int $cache Caching period in seconds
  231. * @return array Tracks ((artist, track, freq, listeners, artisturl, trackurl) ..) or empty array in case of failure
  232. */
  233. static function getTopTracks($limit = 20, $offset = 0, $streamable = False, $begin = null, $end = null, $artist = null, $userid = null, $cache = 600) {
  234. global $adodb;
  235. $query = 'SELECT s.artist, s.track, count(s.track) AS freq, count(DISTINCT s.userid) AS listeners FROM Scrobbles s';
  236. if ($streamable) {
  237. $query .= ' WHERE ROW(s.artist, s.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  238. $andquery = True;
  239. } else {
  240. if($begin || $end || $userid || $artist) {
  241. $query .= ' WHERE';
  242. $andquery = False;
  243. }
  244. }
  245. if($begin) {
  246. //change time resolution to full hours (for easier caching)
  247. $begin = $begin - ($begin % 3600);
  248. $andquery ? $query .= ' AND' : $andquery = True ;
  249. $query .= ' s.time>' . (int)$begin;
  250. }
  251. if($end) {
  252. //change time resolution to full hours (for easier caching)
  253. $end = $end - ($end % 3600);
  254. $andquery ? $query .= ' AND' : $andquery = True ;
  255. $query .= ' s.time<' . (int)$end;
  256. }
  257. if($userid) {
  258. $andquery ? $query .= ' AND' : $andquery = True ;
  259. $query .= ' s.userid=' . (int)$userid;
  260. }
  261. if($artist) {
  262. $andquery ? $query .= ' AND' : $andquery = True;
  263. $query .= ' lower(s.artist)=lower(' . $adodb->qstr($artist) . ')';
  264. }
  265. $query .= ' GROUP BY s.track, s.artist ORDER BY freq DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  266. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  267. try {
  268. $data = $adodb->CacheGetAll($cache, $query);
  269. } catch (Exception $e) {
  270. return array();
  271. }
  272. $result = array();
  273. foreach ($data as &$i) {
  274. $row = sanitize($i);
  275. $row['artisturl'] = Server::getArtistURL($row['artist']);
  276. $row['trackurl'] = Server::getTrackURL($row['artist'], null, $row['track']);
  277. $result[] = $row;
  278. }
  279. return $result;
  280. }
  281. /**
  282. * Get a list of users with the most listens
  283. *
  284. * @param int $limit Amount of results to return
  285. * @param int $offset Skip this many items before returning results
  286. * @param int $streamable Only return results for streamable tracks
  287. * @param int $begin Only use scrobbles with time higher than this timestamp
  288. * @param int $end Only use scrobbles with time lower than this timestamp
  289. * @param string $artist Filter results by this artist
  290. * @param string $track Filter result by this track (need $artist to be set)
  291. * @param int $cache Caching period in seconds
  292. * @return array ((userid, freq, username, userurl) ..)
  293. */
  294. static function getTopListeners($limit = 10, $offset = 0, $streamable = True, $begin = null, $end = null, $artist = null, $track = null, $cache = 600) {
  295. global $adodb;
  296. $params = array();
  297. $query = 'SELECT s.userid, COUNT(*) as freq FROM Scrobbles s';
  298. if ($streamable) {
  299. $query .= ' WHERE ROW(s.artist, s.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  300. $andquery = True;
  301. } else {
  302. if($begin || $end || $artist) {
  303. $query .= ' WHERE';
  304. $andquery = False;
  305. }
  306. }
  307. if($begin) {
  308. //change time resolution to full hours (for easier caching)
  309. $begin = $begin - ($begin % 3600);
  310. $andquery ? $query .= ' AND' : $andquery = True ;
  311. $query .= ' s.time > ?';
  312. $params[] = (int)$begin;
  313. }
  314. if($end) {
  315. //change time resolution to full hours (for easier caching)
  316. $end = $end - ($end % 3600);
  317. $andquery ? $query .= ' AND' : $andquery = True ;
  318. $query .= ' s.time < ?';
  319. $params[] = (int)$end;
  320. }
  321. if($artist) {
  322. $andquery ? $query .= ' AND' : $andquery = True;
  323. $query .= ' lower(s.artist)=lower(?)';
  324. $params[] = $artist;
  325. if($track) {
  326. $andquery ? $query .= ' AND' : $andquery = True;
  327. $query .= ' lower(s.track)=lower(?)';
  328. $params[] = $track;
  329. }
  330. }
  331. $query .= ' GROUP BY s.userid ORDER BY freq DESC LIMIT ? OFFSET ?';
  332. $params[] = (int)$limit;
  333. $params[] = (int)$offset;
  334. try {
  335. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  336. $res = $adodb->CacheGetAll($cache, $query, $params);
  337. }catch (Exception $e) {
  338. return array();
  339. }
  340. foreach($res as &$row) {
  341. $row['username'] = uniqueid_to_username($row['userid']);
  342. $row['userurl'] = Server::getUserURL($row['username']);
  343. $result[] = $row;
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Retrieves a list of loved tracks
  349. *
  350. * @param int $limit The number of tracks to return
  351. * @param int $offset Skip this number of rows before returning tracks
  352. * @param bool $streamable Only return streamable tracks
  353. * @param int $artist Only return results from this artist
  354. * @param int $userid Only return results from this userid
  355. * @param int $cache Caching period in seconds
  356. * @return array Tracks ((artist, track, freq, listeners, artisturl, trackurl) ..) or empty array in case of failure
  357. */
  358. static function getLovedTracks($limit = 20, $offset = 0, $streamable = False, $artist = null, $userid = null, $cache = 600) {
  359. global $adodb;
  360. $query = 'SELECT lt.artist, lt.track, max(lt.time) as time, count(lt.track) AS freq FROM Loved_Tracks lt';
  361. if ($streamable) {
  362. $query .= ' WHERE ROW(lt.artist, lt.track) IN (SELECT artist_name, name FROM Track WHERE streamable=1)';
  363. $andquery = True;
  364. } else {
  365. if($userid || $artist) {
  366. $query .= ' WHERE';
  367. $andquery = False;
  368. }
  369. }
  370. if($userid) {
  371. $andquery ? $query .= ' AND' : $andquery = True ;
  372. $query .= ' lt.userid=' . (int)$userid;
  373. }
  374. if($artist) {
  375. $andquery ? $query .= ' AND' : $andquery = True;
  376. $query .= ' lower(lt.artist)=lower(' . $adodb->qstr($artist) . ')';
  377. }
  378. $query .= ' GROUP BY lt.track, lt.artist ORDER BY freq DESC, time DESC LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset;
  379. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  380. try {
  381. $data = $adodb->CacheGetAll($cache, $query);
  382. } catch (Exception $e) {
  383. return array();
  384. }
  385. $result = array();
  386. foreach ($data as &$i) {
  387. $row = sanitize($i);
  388. $row['artisturl'] = Server::getArtistURL($row['artist']);
  389. $row['trackurl'] = Server::getTrackURL($row['artist'], null, $row['track']);
  390. $result[] = $row;
  391. }
  392. return $result;
  393. }
  394. /**
  395. * Get a list of users
  396. *
  397. * @param string $alpha Search for user names starting with this string
  398. */
  399. static function getUserList($alpha) {
  400. global $adodb;
  401. $alpha .= '%';
  402. $query = 'SELECT username from Users where username LIKE ' . $adodb->qstr($alpha) . ' ORDER BY username ASC';
  403. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  404. $data = $adodb->CacheGetAll(7200, $query);
  405. if (!$data) {
  406. throw new Exception('ERROR ' . $query);
  407. }
  408. return $data;
  409. }
  410. /**
  411. * Retrieves a list of the currently playing tracks
  412. *
  413. * @param int $number The maximum number of tracks to return
  414. * @param string $username The name of the user to retrieve playing tracks for
  415. * @return array Now playing data or null in case of failure
  416. */
  417. static function getNowPlaying($number = 1, $username = false) {
  418. global $adodb;
  419. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  420. try {
  421. if ($username) {
  422. $data = $adodb->CacheGetAll(1, 'SELECT
  423. ss.userid,
  424. n.artist,
  425. n.track,
  426. n.album,
  427. client,
  428. api_key,
  429. n.mbid,
  430. t.license
  431. FROM Now_Playing n
  432. LEFT OUTER JOIN Scrobble_Sessions ss
  433. ON n.sessionid=ss.sessionid
  434. LEFT OUTER JOIN Track t
  435. ON lower(n.artist) = lower(t.artist_name)
  436. AND lower(n.album) = lower(t.album_name)
  437. AND lower(n.track) = lower(t.name)
  438. AND lower(n.mbid) = lower(t.mbid)
  439. WHERE ss.userid= ' . username_to_uniqueid($username) . '
  440. ORDER BY t.streamable DESC, n.expires DESC LIMIT ' . (int)($number));
  441. } else {
  442. $data = $adodb->CacheGetAll(60, 'SELECT
  443. ss.userid,
  444. n.artist,
  445. n.track,
  446. n.album,
  447. client,
  448. n.mbid,
  449. t.license
  450. FROM Now_Playing n
  451. LEFT OUTER JOIN Scrobble_Sessions ss
  452. ON n.sessionid=ss.sessionid
  453. LEFT OUTER JOIN Track t
  454. ON lower(n.artist) = lower(t.artist_name)
  455. AND lower(n.album) = lower(t.album_name)
  456. AND lower(n.track) = lower(t.name)
  457. AND lower(n.mbid) = lower(t.mbid)
  458. ORDER BY t.streamable DESC, n.expires DESC LIMIT ' . (int)($number));
  459. }
  460. } catch (Exception $e) {
  461. return null;
  462. }
  463. $result = array();
  464. foreach ($data as &$i) {
  465. $row = sanitize($i);
  466. $client = getClientData($row['client'], $row['api_key']);
  467. $row['clientcode'] = $client['code'];
  468. $row['clientapi_key'] = $client['code'];
  469. $row['clientname'] = $client['name'];
  470. $row['clienturl'] = $client['url'];
  471. $row['clientfree'] = $client['free'];
  472. $row['username'] = uniqueid_to_username($row['userid']);
  473. $row['userurl'] = Server::getUserURL($row['username']);
  474. $row['artisturl'] = Server::getArtistURL($row['artist']);
  475. $row['trackurl'] = Server::getTrackURL($row['artist'], $row['album'], $row['track']);
  476. if ($username) {
  477. $row['loved'] = $adodb->CacheGetOne(60, 'SELECT Count(*) FROM Loved_Tracks WHERE artist='
  478. . $adodb->qstr($row['artist'])
  479. . ' AND track=' . $adodb->qstr($row['track'])
  480. . ' AND userid=' . $row['userid']);
  481. }
  482. // 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.
  483. $row['albumart'] = $base_url . 'themes/' . $default_theme . '/images/qm50.png';
  484. $row['licenseurl'] = $row['license'];
  485. $row['license'] = simplify_license($row['licenseurl']);
  486. $result[] = $row;
  487. }
  488. return $result;
  489. }
  490. /**
  491. * Gets the URL to a user's profile page
  492. *
  493. * The get*URL functions are implemented here rather than in their respective
  494. * objects so that we can produce URLs without needing to build whole objects.
  495. *
  496. * @param string $username The user name we want a URL for
  497. * @param string $component Type of URL to return
  498. * @param string $params Trailing get parameters
  499. * @return string URL to the user's profile
  500. */
  501. static function getUserURL ($username, $component = 'profile', $params = false) {
  502. global $friendly_urls, $base_url;
  503. if ($component == 'edit') {
  504. return $base_url . '/user-edit.php';
  505. } else if ($component == 'delete') {
  506. return $base_url . '/delete-profile.php';
  507. } else if ($friendly_urls) {
  508. if ($component == 'profile') {
  509. $component = '';
  510. } else {
  511. $component = "/{$component}";
  512. }
  513. return $base_url . '/user/' . rewrite_encode($username) . $component . ($params ? '?' . $params : null);
  514. } else {
  515. return $base_url . "/user-{$component}.php?user=" . rawurlencode($username) . ($params ? '&' . $params : null);
  516. }
  517. }
  518. /**
  519. * Gets the URL to a group's page
  520. *
  521. * @param string $groupname The group we want a URL for
  522. * @return string URL to the group's page
  523. */
  524. static function getGroupURL($groupname) {
  525. global $friendly_urls, $base_url;
  526. if ($friendly_urls) {
  527. return $base_url . '/group/' . rewrite_encode($groupname);
  528. } else {
  529. return $base_url . '/group.php?group=' . rawurlencode($groupname);
  530. }
  531. }
  532. /**
  533. * Gets the URL to an artist's page
  534. *
  535. * @param string $artist The artist we want a URL for
  536. * @param string $component Type of URL to return
  537. * @return string URL to the artist's page
  538. */
  539. static function getArtistURL($artist, $component = '') {
  540. global $friendly_urls, $base_url;
  541. if ($friendly_urls) {
  542. return $base_url . '/artist/' . rewrite_encode($artist) . '/' . $component;
  543. } else {
  544. if ($component) {
  545. return $base_url . '/artist-' . $component . '.php?artist=' . rawurlencode($artist);
  546. } else {
  547. return $base_url . '/artist.php?artist=' . rawurlencode($artist);
  548. }
  549. }
  550. }
  551. /**
  552. * Gives the URL to the management interface for an artist
  553. *
  554. * @param string $artist The artist we want a URL for
  555. * @return string URL for an artist's management interface
  556. */
  557. static function getArtistManagementURL($artist) {
  558. global $friendly_urls, $base_url;
  559. if ($friendly_urls) {
  560. return Server::getArtistURL($artist) . '/manage';
  561. } else {
  562. return $base_url . '/artist-manage.php?artist=' . rawurlencode($artist);
  563. }
  564. }
  565. /**
  566. * Gives the URL for managers to add a new album to an artist
  567. *
  568. * @param string $artist The artist we want a URL for
  569. * @return string URL for adding albums to an artist
  570. */
  571. static function getAddAlbumURL($artist) {
  572. global $friendly_urls, $base_url;
  573. if ($friendly_urls) {
  574. return Server::getArtistURL($artist) . '/album/add';
  575. } else {
  576. return $base_url . '/album-add.php?artist=' . rawurlencode($artist);
  577. }
  578. }
  579. /**
  580. * Gets the URL to an album's page
  581. *
  582. * @param string $artist The artist name of the album
  583. * @param string $album The name of the album
  584. * @return string URL to the album's page
  585. */
  586. static function getAlbumURL($artist, $album) {
  587. global $friendly_urls, $base_url;
  588. if ($friendly_urls) {
  589. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album);
  590. } else {
  591. return $base_url . '/album.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  592. }
  593. }
  594. /**
  595. * Gives the URL for managers to add a new track to an album
  596. *
  597. * @param string $artist The artist name of the album
  598. * @param string $album The name of the album
  599. * @return string URL for adding tracks to an album
  600. */
  601. static function getAddTrackURL($artist, $album) {
  602. global $friendly_urls, $base_url;
  603. if ($friendly_urls) {
  604. return Server::getAlbumURL($artist, $album) . '/track/add';
  605. } else {
  606. return $base_url . '/track-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  607. }
  608. }
  609. /**
  610. * Gets the URL to a track's page
  611. *
  612. * @param string $artist The artist name of the track
  613. * @param string $album The album name of this track (optional)
  614. * @param string $track The name of the track
  615. * @param string $component Type of page
  616. * @return string URL to the track's page
  617. */
  618. static function getTrackURL($artist, $album, $track, $component = '') {
  619. global $friendly_urls, $base_url;
  620. if($friendly_urls) {
  621. $trackurl = $base_url . '/artist/' . rewrite_encode($artist);
  622. if($album) {
  623. $trackurl .= '/album/' . rewrite_encode($album);
  624. }
  625. $trackurl .= '/track/' . rewrite_encode($track);
  626. if($component) {
  627. $trackurl .= '/' . $component;
  628. }
  629. } else {
  630. if($component) {
  631. $trackurl = $base_url . '/track-' . $component . '.php?artist=' . rawurlencode($artist)
  632. . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  633. } else {
  634. $trackurl = $base_url . '/track.php?artist=' . rawurlencode($artist)
  635. . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  636. }
  637. }
  638. return $trackurl;
  639. }
  640. /**
  641. * Gets the URL to a track's edit page
  642. *
  643. * @param string $artist The artist name of the track
  644. * @param string $album The album name of this track (optional)
  645. * @param string $track The name of the track
  646. * @return string URL to the track's edit page
  647. */
  648. static function getTrackEditURL($artist, $album, $track) {
  649. global $friendly_urls, $base_url;
  650. if ($friendly_urls && $album) {
  651. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album) . '/track/' . rewrite_encode($track) . '/edit';
  652. } else if ($friendly_urls) {
  653. return $base_url . '/artist/' . rewrite_encode($artist) . '/track/' . rewrite_encode($track) . '/edit';
  654. } else {
  655. return $base_url . '/track-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album) . '&track=' . rawurlencode($track);
  656. }
  657. }
  658. static function getAlbumEditURL($artist, $album) {
  659. global $friendly_urls, $base_url;
  660. if ($friendly_urls) {
  661. return $base_url . '/artist/' . rewrite_encode($artist) . '/album/' . rewrite_encode($album) . '/edit';
  662. } else {
  663. return $base_url . '/album-add.php?artist=' . rawurlencode($artist) . '&album=' . rawurlencode($album);
  664. }
  665. }
  666. /**
  667. * Gets the URL to a tag's page
  668. *
  669. * @param string $tag The name of the tag
  670. * @return string URL to the tag's page
  671. */
  672. static function getTagURL($tag) {
  673. global $friendly_urls, $base_url;
  674. if ($friendly_urls) {
  675. return $base_url . '/tag/' . rewrite_encode($tag);
  676. } else {
  677. return $base_url . '/tag.php?tag=' . rawurlencode($tag);
  678. }
  679. }
  680. static function getLocationDetails($name) {
  681. global $adodb;
  682. if (!$name) {
  683. return array();
  684. }
  685. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  686. $rv = $adodb->GetRow('SELECT p.latitude, p.longitude, p.country, c.country_name, c.wikipedia_en '
  687. . 'FROM Places p '
  688. . 'LEFT JOIN Countries c ON p.country=c.country '
  689. . 'WHERE p.location_uri=' . $adodb->qstr($name, 'text'));
  690. if ($rv) {
  691. if (!($rv['latitude'] && $rv['longitude'] && $rv['country'])) {
  692. $parser = ARC2::getRDFXMLParser();
  693. $parser->parse($name);
  694. $index = $parser->getSimpleIndex();
  695. $rv = array(
  696. 'latitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#lat'][0],
  697. 'longitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#long'][0],
  698. 'country' => strtoupper(substr($index[$name]['http://www.geonames.org/ontology#inCountry'][0], -2))
  699. );
  700. $adodb->Execute(sprintf('UPDATE Places SET latitude=%s, longitude=%s, country=%s WHERE location_uri=%s',
  701. $adodb->qstr($rv['latitude']),
  702. $adodb->qstr($rv['longitude']),
  703. $adodb->qstr($rv['country']),
  704. $adodb->qstr($name)));
  705. }
  706. } else {
  707. $parser = ARC2::getRDFXMLParser();
  708. $parser->parse($name);
  709. $index = $parser->getSimpleIndex();
  710. $rv = array(
  711. 'latitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#lat'][0],
  712. 'longitude' => $index[$name]['http://www.w3.org/2003/01/geo/wgs84_pos#long'][0],
  713. 'country' => strtoupper(substr($index[$name]['http://www.geonames.org/ontology#inCountry'][0], -2))
  714. );
  715. $adodb->Execute(sprintf('INSERT INTO Places (location_uri, latitude, longitude, country) VALUES (%s, %s, %s, %s)',
  716. $adodb->qstr($name),
  717. $adodb->qstr($rv['latitude']),
  718. $adodb->qstr($rv['longitude']),
  719. $adodb->qstr($rv['country'])));
  720. }
  721. return $rv;
  722. }
  723. /**
  724. * Log in to the radio server
  725. *
  726. * @param string $station The station to be played
  727. * @param string $username The user to associate this session with (optional)
  728. * @param string $session_id Allows for a custom session id to be set, allowing for compatibility with webservices
  729. * @return string Session key to be used for streaming
  730. */
  731. static function getRadioSession($station, $username = false, $session_id = false) {
  732. global $adodb;
  733. if (!$session_id) {
  734. $session_id = md5(mt_rand() . time());
  735. }
  736. // Remove any previous station for this session id
  737. $adodb->Execute('DELETE FROM Radio_Sessions WHERE session = ' . $adodb->qstr($session_id));
  738. if ($username) {
  739. $sql = 'INSERT INTO Radio_Sessions(username, session, url, expires) VALUES ('
  740. . $adodb->qstr($username) . ','
  741. . $adodb->qstr($session_id) . ','
  742. . $adodb->qstr($station) . ','
  743. . (int)(time() + 86400) . ')';
  744. } else {
  745. $sql = 'INSERT INTO Radio_Sessions(session, url, expires) VALUES ('
  746. . $adodb->qstr($session_id) . ','
  747. . $adodb->qstr($station) . ','
  748. . (int)(time() + 86400) . ')';
  749. }
  750. $res = $adodb->Execute($sql);
  751. return $session_id;
  752. }
  753. /**
  754. * Log in to web services
  755. *
  756. * @param string $username The user to create a session for
  757. * @return string The web service session key
  758. */
  759. static function getWebServiceSession($username) {
  760. global $adodb;
  761. $sk = md5(mt_rand() . time());
  762. $token = md5(mt_rand() . time());
  763. $adodb->Execute('INSERT INTO Auth(token, sk, expires, username) VALUES ('
  764. . $adodb->qstr($token) . ', '
  765. . $adodb->qstr($sk) . ', '
  766. . (int)(time() + 86400) . ', '
  767. . $adodb->qstr($username) . ')');
  768. return $sk;
  769. }
  770. /**
  771. * Get scrobble session ID for a user.
  772. *
  773. * Gets the most recent scrobble session ID for userid,
  774. * or creates a new session ID if it can't find one.
  775. *
  776. * @param int userid (required) User ID.
  777. * @param string api_key (optional) Client API key (32 characters)
  778. * @param int expire_limit (optional) Amount of time in seconds before session will expire (defaults to 86400 = 24 hours)
  779. * @return string Scrobble session ID
  780. */
  781. static function getScrobbleSession($userid, $api_key = null, $expire_limit = 86400) {
  782. //TODO Add code to remove expired sessions (this is currently only done in gnukebox)
  783. global $adodb;
  784. $query = 'SELECT sessionid FROM Scrobble_Sessions WHERE userid = ? AND expires > ?';
  785. $params = array( (int) $userid, time());
  786. if (strlen($api_key) == 32) {
  787. $query .= ' AND api_key=?';
  788. $params[] = $api_key;
  789. } elseif (strlen($api_key) == 3) {
  790. // api_key is really a 3 char client code (2.0-scrobble-proxy.php sends client code in api_key)
  791. $query .= ' AND client=?';
  792. $client_id = $api_key;
  793. $params[] = $client_id;
  794. // we dont want to insert a 3 char code as api_key in db
  795. $api_key = null;
  796. }
  797. $sessionid = $adodb->GetOne($query, $params);
  798. if (!$sessionid) {
  799. $sessionid = md5(mt_rand() . time());
  800. $expires = time() + (int) $expire_limit;
  801. $query = 'INSERT INTO Scrobble_Sessions(userid, sessionid, client, expires, api_key) VALUES (?,?,?,?,?)';
  802. $params = array($userid, $sessionid, $client_id, $expires, $api_key);
  803. try {
  804. $adodb->Execute($query, $params);
  805. } catch (Exception $e) {
  806. return null;
  807. }
  808. }
  809. return $sessionid;
  810. }
  811. /**
  812. * Get all artists
  813. *
  814. * @return array Artists ordered by name
  815. */
  816. static function getAllArtists() {
  817. global $adodb;
  818. $sql = 'SELECT * from Artist ORDER by name';
  819. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  820. try {
  821. $res = $adodb->CacheGetAll(86400, $sql);
  822. } catch (Exception $e) {
  823. return null;
  824. }
  825. $result = array();
  826. foreach ($res as &$i) {
  827. $row = sanitize($i);
  828. $row['artisturl'] = Server::getArtistURL($row['name']);
  829. $result[] = $row;
  830. }
  831. return $result;
  832. }
  833. /**
  834. * Search for users, artists or tags
  835. *
  836. * Does a lower-case search of %search_term%
  837. *
  838. * @param string $search_term
  839. * @param string $search_type Type of search, artist|user|tag
  840. * @param int $limit How many items to return
  841. * @param bool $streamable Only return streamable artists
  842. * @return array Results
  843. */
  844. static function search($search_term, $search_type, $limit = 40, $streamable = false) {
  845. global $adodb;
  846. if ($search_term) {
  847. switch ($search_type) {
  848. case 'artist':
  849. $table = 'Artist';
  850. $search_fields[] = 'name';
  851. $data_fields[] = 'name';
  852. $data_fields[] = 'bio_summary';
  853. $data_fields[] = 'streamable';
  854. $data_fields[] = 'image_small';
  855. $data_fields[] = 'image_medium';
  856. $data_fields[] = 'image_large';
  857. break;
  858. case 'user':
  859. $table = 'Users';
  860. $search_fields[] = 'username';
  861. $search_fields[] = 'fullname';
  862. $data_fields[] = 'username';
  863. $data_fields[] = 'fullname';
  864. $data_fields[] = 'bio';
  865. break;
  866. case 'tag':
  867. $table = 'Tags';
  868. $search_fields[] = 'tag';
  869. $data_fields[] = 'tag';
  870. break;
  871. default:
  872. return array();
  873. }
  874. $sql = 'SELECT DISTINCT ';
  875. for ($i = 0; $i < count($data_fields); $i++) {
  876. $sql .= $data_fields[$i];
  877. if ($i < count($data_fields) - 1) {
  878. $sql .= ', ';
  879. }
  880. }
  881. $sql .= ' FROM ' . $table . ' WHERE ';
  882. for ($i = 0; $i < count($search_fields); $i++) {
  883. if ($i > 0) {
  884. $sql .= ' OR ';
  885. }
  886. $sql .= 'LOWER(' . $search_fields[$i] . ') LIKE LOWER(' . $adodb->qstr('%' . $search_term . '%') . ')';
  887. }
  888. if ($streamable) {
  889. $sql .= " AND streamable = 1 ";
  890. }
  891. $sql .= 'LIMIT ' . $limit;
  892. $res = $adodb->CacheGetAll(600, $sql);
  893. $result = array();
  894. foreach ($res as &$i) {
  895. $row = sanitize($i);
  896. switch ($search_type) {
  897. case 'artist':
  898. $row['url'] = Server::getArtistURL($row['name']);
  899. break;
  900. case 'user':
  901. $row['url'] = Server::getUserURL($row['username']);
  902. break;
  903. case 'tag':
  904. $row['url'] = Server::getTagURL($row['tag']);
  905. break;
  906. }
  907. $result[] = $row;
  908. }
  909. }
  910. return $result;
  911. }
  912. /**
  913. * Create a random authentication token and return it
  914. *
  915. * @return string Token.
  916. */
  917. static function getAuthToken() {
  918. global $adodb;
  919. $key = md5(time() . rand());
  920. $expires = (int) (time() + 3600);
  921. $query = 'INSERT INTO Auth(token, expires) VALUES(?,?)';
  922. $params = array($key, $expires);
  923. try {
  924. $adodb->Execute($query, $params);
  925. return $key;
  926. } catch (Exception $e) {
  927. reportError($e->getMessage(), $e->getTraceAsString());
  928. }
  929. }
  930. }