Artist.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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/sanitize.php');
  17. require_once($install_path . '/data/Album.php');
  18. require_once($install_path . '/data/Track.php');
  19. require_once($install_path . '/data/Server.php');
  20. require_once($install_path . '/utils/linkeddata.php');
  21. require_once($install_path . '/data/Tag.php');
  22. /**
  23. * Represents artist data
  24. *
  25. * General artist attributes are accessible as public variables.
  26. * Lists of tracks and albums are only generated when requested.
  27. */
  28. class Artist {
  29. public $name, $mbid, $streamable, $bio_content, $bio_published, $bio_summary, $image_small, $image_medium, $image_large, $flattr_uid;
  30. public $id;
  31. private $query, $album_query;
  32. /**
  33. * Artist constructor
  34. *
  35. * @param string $name The name of the artist to load
  36. * @param string $mbid The mbid of the artist (optional)
  37. * @param boolean $recache Whether the artist cache should be cleared before loading the artist
  38. */
  39. function __construct($name, $mbid = false, $recache = false) {
  40. global $adodb;
  41. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  42. $mbidquery = '';
  43. if ($mbid) {
  44. $mbidquery = 'mbid = ' . $adodb->qstr($mbid) . ' OR ';
  45. }
  46. $this->query = 'SELECT name, mbid, streamable, bio_published, bio_content, bio_summary, image_small, image_medium, image_large, homepage, hashtag, flattr_uid FROM Artist WHERE '
  47. . $mbidquery
  48. . 'lower(name) = lower(' . $adodb->qstr($name) . ')';
  49. if($recache) {
  50. $this->clearCache();
  51. }
  52. $row = $adodb->CacheGetRow(1200, $this->query);
  53. if (!$row) {
  54. throw new Exception('No such artist' . $name);
  55. } else {
  56. $this->name = $row['name'];
  57. $this->mbid = $row['mbid'];
  58. $this->streamable = $row['streamable'];
  59. $this->bio_published = $row['bio_published'];
  60. $this->bio_content = strip_tags($row['bio_content'], '<p><a><li><ul><ol><br><b><em><strong><i>');
  61. $this->bio_summary = strip_tags($row['bio_summary']. '<p><a><li><ul><ol><br><b><em><strong><i>');
  62. $this->image_small = $row['image_small'];
  63. $this->image_medium = $row['image_medium'];
  64. $this->image_large = $row['image_large'];
  65. $this->homepage = $row['homepage'];
  66. $this->hashtag = $row['hashtag'];
  67. $this->flattr_uid = $row['flattr_uid'];
  68. $this->id = identifierArtist(null, $this->name, null, null, null, null, $this->mbid, null);
  69. $this->album_query = 'SELECT name, image FROM Album WHERE artist_name = '. $adodb->qstr($this->name);
  70. }
  71. }
  72. /**
  73. * Retrieves the artist's albums
  74. *
  75. * @return array Album objects
  76. */
  77. function getAlbums() {
  78. global $adodb;
  79. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  80. $res = $adodb->CacheGetAll(600, $this->album_query);
  81. foreach ($res as &$row) {
  82. $albums[] = new Album($row['name'], $this->name);
  83. }
  84. return $albums;
  85. }
  86. /**
  87. * Clear the album cache, should be called after creating a new album
  88. */
  89. function clearAlbumCache() {
  90. global $adodb;
  91. $adodb->CacheFlush($this->album_query);
  92. }
  93. /**
  94. * Retrieves the artist's tracks
  95. *
  96. * @return array Track objects
  97. */
  98. function getTracks() {
  99. global $adodb;
  100. $adodb->SetFetchMode(ADODB_FETCH_ASSOC);
  101. $res = $adodb->CacheGetAll(600, 'SELECT name FROM Track WHERE artist_name = '
  102. . $adodb->qstr($this->name));
  103. foreach ($res as &$row) {
  104. $tracks[] = new Track($row['name'], $this->name);
  105. }
  106. return $tracks;
  107. }
  108. /**
  109. * Get this artist's top tracks
  110. *
  111. * @param int $limit The number of tracks to return
  112. * @param int $offset Skip this number of rows before returning tracks
  113. * @param bool $streamable Only return streamable tracks
  114. * @param int $begin Only use scrobbles with time higher than this timestamp
  115. * @param int $end Only use scrobbles with time lower than this timestamp
  116. * @param int $cache Caching period in seconds
  117. * @return array An array of tracks ((artist, track, freq, listeners, artisturl, trackurl) ..) or empty array in case of failure
  118. */
  119. function getTopTracks($limit = 20, $offset = 0, $streamable = False, $begin = null, $end = null, $cache = 600) {
  120. return Server::getTopTracks($limit, $offset, $streamable, $begin, $end, $this->name, null, $cache);
  121. }
  122. /**
  123. * Get this artist's top listeners
  124. *
  125. * @param int $limit Amount of results to return
  126. * @param int $offset Skip this many items before returning results
  127. * @param int $streamable Only return results for streamable tracks
  128. * @param int $begin Only use scrobbles with time higher than this timestamp
  129. * @param int $end Only use scrobbles with time lower than this timestamp
  130. * @param int $cache Caching period in seconds
  131. * @return array ((userid, freq, username, userurl) ..)
  132. */
  133. function getTopListeners($limit = 20, $offset = 0, $streamable = False, $begin = null, $end = null, $cache = 600) {
  134. return Server::getTopListeners($limit, $offset, $streamable, $begin, $end, $this->name, null, $cache);
  135. }
  136. /**
  137. * Gives the URL for this artist
  138. *
  139. * @param string $component Type of page
  140. * @return string URL of this artist
  141. */
  142. function getURL($component = '') {
  143. return Server::getArtistURL($this->name, $component);
  144. }
  145. /**
  146. * Gives the URL to the management interface for this artist
  147. *
  148. * @return string URL for this artist's management interface
  149. */
  150. function getManagementURL() {
  151. return Server::getArtistManagementURL($this->name);
  152. }
  153. /**
  154. * Gives the URL for manages to add a new album to this artist
  155. *
  156. * @return string URL for adding albums to this artist
  157. */
  158. function getAddAlbumURL() {
  159. return Server::getAddAlbumURL($this->name);
  160. }
  161. /**
  162. * Add a list of tags to an artist
  163. *
  164. * @param string $tags A comma seperated list of tags
  165. * @param int $userid The user adding these tags
  166. */
  167. function addTags($tags, $userid) {
  168. global $adodb;
  169. $tags = explode(',', strtolower($tags));
  170. foreach ($tags as $tag) {
  171. $tag = trim($tag);
  172. if (strlen($tag) == 0) {
  173. continue;
  174. }
  175. try {
  176. $adodb->Execute('INSERT INTO Tags (tag, artist, userid) VALUES ('
  177. . $adodb->qstr($tag) . ', '
  178. . $adodb->qstr($this->name) . ', '
  179. . $userid . ')');
  180. } catch (Exception $ex) {}
  181. }
  182. }
  183. /**
  184. * Get the top tags for an artist, ordered by tag count
  185. * (including any tags for the artist's albums and tracks)
  186. *
  187. * @param int $limit The number of tags to return (default is 10)
  188. * @param int $offset The position of the first tag to return (default is 0)
  189. * @param int $cache Caching period of query in seconds (default is 600)
  190. * @return array Tag details ((tag, freq) .. )
  191. */
  192. function getTopTags($limit=10, $offset=0, $cache=600) {
  193. return Tag::_getTagData($cache, $limit, $offset, null, $this->name);
  194. }
  195. /**
  196. * Get a specific user's tags for this artist.
  197. *
  198. * @param int $userid Get tags for this user
  199. * @param int $limit The number of tags to return (default is 10)
  200. * @param int $offset The position of the first tag to return (default is 0)
  201. * @param int $cache Caching period of query in seconds (default is 600)
  202. * @return array Tag details ((tag, freq) .. )
  203. */
  204. function getTags($userid, $limit=10, $offset=0, $cache=600) {
  205. if(isset($userid)) {
  206. return Tag::_getTagData($cache, $limit, $offset, $userid, $this->name);
  207. }
  208. }
  209. /**
  210. * Clear cached database query for this artist
  211. */
  212. function clearCache() {
  213. global $adodb;
  214. $adodb->CacheFlush($this->query);
  215. }
  216. /**
  217. * Set an artist's biography summary
  218. *
  219. * @param string $bio_summary The new biography summary to enter into the database.
  220. */
  221. function setBiographySummary($bio_summary) {
  222. global $adodb;
  223. $adodb->Execute('UPDATE Artist SET bio_summary = ' . $adodb->qstr($bio_summary) . ' WHERE name = ' . $adodb->qstr($this->name));
  224. $this->bio_summary = $bio_summary;
  225. $adodb->CacheFlush($this->query);
  226. }
  227. /**
  228. * Set an artist's full biography
  229. *
  230. * @param string $bio The new biography to enter into the database.
  231. */
  232. function setBiography($bio) {
  233. global $adodb;
  234. $adodb->Execute('UPDATE Artist SET bio_content = ' . $adodb->qstr($bio) . ' WHERE name = ' . $adodb->qstr($this->name));
  235. $this->bio_content = $bio;
  236. $adodb->CacheFlush($this->query);
  237. }
  238. /**
  239. * Set an artist's homepage
  240. *
  241. * @param string $homepage The artist's homepage
  242. */
  243. function setHomepage($homepage) {
  244. global $adodb;
  245. $adodb->Execute('UPDATE Artist SET homepage = ' . $adodb->qstr($homepage) . ' WHERE name = ' . $adodb->qstr($this->name));
  246. $this->homepage = $homepage;
  247. $adodb->CacheFlush($this->query);
  248. }
  249. /**
  250. * Set a URL to an image of this artist
  251. *
  252. * @param string $image_url A URL linking directly to an image file.
  253. */
  254. function setImage($image_url) {
  255. global $adodb;
  256. $adodb->Execute('UPDATE Artist SET image_medium = ' . $adodb->qstr($image_url) . ' WHERE name = ' . $adodb->qstr($this->name));
  257. $this->image_medium = $image_url;
  258. $adodb->CacheFlush($this->query);
  259. }
  260. /**
  261. * Set an identi.ca hashtag, used to display dents from on the artist page
  262. *
  263. * @param string $hashtag An identi.ca hashtag.
  264. */
  265. function setHashtag($hashtag) {
  266. global $adodb;
  267. $adodb->Execute('UPDATE Artist SET hashtag = ' . $adodb->qstr($hashtag) . ' WHERE name = ' . $adodb->qstr($this->name));
  268. $this->hashtag = $hashtag;
  269. $adodb->CacheFlush($this->query);
  270. }
  271. /**
  272. * Set a flattr user id for to allow this artist to be tipped
  273. *
  274. * @param string $flattr_uid A flattr username to associate with this artist
  275. */
  276. function setFlattr($flattr_uid) {
  277. global $adodb;
  278. $adodb->Execute('UPDATE Artist SET flattr_uid = ' . $adodb->qstr($flattr_uid) . ' WHERE name = ' . $adodb->qstr($this->name));
  279. $this->flattr_uid = $flattr_uid;
  280. $adodb->CacheFlush($this->query);
  281. }
  282. /**
  283. * Get streamable status for this artist
  284. *
  285. * @return bool True if artist have any streamable tracks
  286. */
  287. function isStreamable() {
  288. global $adodb;
  289. return $this->streamable;
  290. }
  291. /**
  292. * Finds out which users manage this artist.
  293. *
  294. * @return array User objects who manage this artist.
  295. */
  296. function getManagers() {
  297. global $adodb;
  298. $managers = array();
  299. $res = $adodb->Execute('SELECT userid FROM Manages WHERE lower(artist)=lower(' . $adodb->qstr($this->name) . ') AND authorised=1');
  300. foreach($res as $row) {
  301. $managers[] = User::new_from_uniqueid_number($row['userid']);
  302. }
  303. return $managers;
  304. }
  305. /**
  306. * Returns the number of listeners this artist has in total.
  307. *
  308. * @return int The number of people who've listened to this artist.
  309. */
  310. function getListenerCount() {
  311. global $adodb;
  312. $row = $adodb->CacheGetRow(600, 'SELECT COUNT(DISTINCT userid) AS listeners FROM Scrobbles WHERE'
  313. . ' lower(artist) = lower(' . $adodb->qstr($this->name) . ')');
  314. return $row['listeners'];
  315. }
  316. /**
  317. * Retrieves a list of similar artist names
  318. *
  319. * @param int $limit Number of artists to return
  320. * @return array Artists and their similarity measure (between 0 and 1), sorted from most to least similar
  321. */
  322. function getSimilar($limit = 10) {
  323. global $adodb;
  324. $similarArtists = array();
  325. // Find this artist's tags
  326. $tmpTags = $adodb->CacheGetAll(86400, 'SELECT lower(tag) as ltag, count(tag) as num FROM Tags WHERE artist = ' . $adodb->qstr($this->name) . ' GROUP BY ltag ORDER BY num DESC');
  327. $tagCount = $adodb->CacheGetOne(86400, 'SELECT count(artist) FROM Tags WHERE artist = ' . $adodb->qstr($this->name));
  328. // Narrow down similar artists to ones that at least share the most common tag and get hold of their other tags
  329. $otherArtists = $adodb->CacheGetAll(86400, 'SELECT artist, lower(tag) as ltag, count(tag) as num FROM Tags INNER JOIN Artist ON Artist.name = Tags.artist WHERE Artist.streamable = 1 AND artist in '
  330. . '(SELECT distinct(artist) FROM Tags WHERE lower(tag) = ' . $adodb->qstr($tmpTags[0]['ltag']) . ') '
  331. . 'GROUP BY artist, ltag ORDER BY num DESC LIMIT 1000');
  332. $totalTags = array();
  333. // Normalise tag proportions
  334. foreach ($otherArtists as &$commonArtist) {
  335. if (!array_key_exists($commonArtist['artist'], $totalTags)) {
  336. $totalTags[$commonArtist['artist']] = $commonArtist['num'];
  337. }
  338. $totalTags[$commonArtist['artist']] += $commonArtist['num'];
  339. }
  340. foreach ($otherArtists as &$commonArtist) {
  341. $commonArtist['num'] /= $totalTags[$commonArtist['artist']];
  342. }
  343. $tags = array();
  344. foreach ($tmpTags as &$tag) {
  345. $tags[$tag['ltag']] = $tag['num'] / $tagCount;
  346. }
  347. $mostSimilar = 1;
  348. // Calculate similarity
  349. foreach ($otherArtists as &$commonArtist) {
  350. if (!array_key_exists($commonArtist['artist'], $similarArtists)) {
  351. $similarArtists[$commonArtist['artist']] = array('artist' => $commonArtist['artist'], 'similarity' => 0);
  352. }
  353. if (array_key_exists($commonArtist['ltag'], $tags)) {
  354. $sdiff = (1 - abs($tags[$commonArtist['ltag']] - $commonArtist['num'])) * $tags[$commonArtist['ltag']];
  355. } else {
  356. $sdiff = 0;
  357. }
  358. $similarArtists[$commonArtist['artist']]['similarity'] += $sdiff;
  359. if ($similarArtists[$commonArtist['artist']]['similarity'] > $mostSimilar) {
  360. $mostSimilar = $similarArtists[$commonArtist['artist']]['similarity'];
  361. }
  362. }
  363. // Normalise similarity metric
  364. foreach ($similarArtists as &$artist) {
  365. $artist['similarity'] /= $mostSimilar;
  366. }
  367. // Sort artists by similarity
  368. $tmp = array();
  369. foreach ($similarArtists as &$ar) {
  370. $tmp[] = &$ar['similarity'];
  371. }
  372. array_multisort($tmp, SORT_DESC, $similarArtists);
  373. $similarWithMeta = array();
  374. $sizes = array('xx-large', 'x-large', 'large', 'medium', 'small', 'x-small', 'xx-small');
  375. $i = 0;
  376. foreach ($similarArtists as $artist) {
  377. if ($artist['artist'] != $this->name) {
  378. $similarWithMeta[$i]['artist'] = $artist['artist'];
  379. $similarWithMeta[$i]['similarity'] = $artist['similarity'];
  380. $similarWithMeta[$i]['url'] = Server::getArtistURL($artist['artist']);
  381. $similarWithMeta[$i]['size'] = $sizes[(int) ($i/($limit/count($sizes)))];
  382. $i++;
  383. if ($i >= $limit) {
  384. break;
  385. }
  386. }
  387. }
  388. sort($similarWithMeta);
  389. return $similarWithMeta;
  390. }
  391. }