. * */ /** * Album Class * * This is the class responsible for handling the Album object * it is related to the album table in the database. * */ class Album extends database_object implements library_item { /* Variables from DB */ /** * @var int $id */ public $id; /** * @var string $name */ public $name; /** * @var int $album_artist */ public $album_artist; /** * @var int $disk */ public $disk; /** * @var int $year */ public $year; /** * @var string $prefix */ public $prefix; /** * @var string $mbid */ public $mbid; // MusicBrainz ID /** * @var string $mbid_group */ public $mbid_group; // MusicBrainz Release Group ID /** * @var string $release_type */ public $release_type; /** * @var int $catalog_id */ public $catalog_id; /** * @var int $song_count */ public $song_count; /** * @var string $artist_prefix */ public $artist_prefix; /** * @var string $artist_name */ public $artist_name; /** * @var int $artist_id */ public $artist_id; /** * @var array $tags */ public $tags; /** * @var string $full_name */ public $full_name; // Prefix + Name, generated /** * @var int $artist_count */ public $artist_count; /** * @var string $f_artist_name */ public $f_artist_name; /** * @var string $f_artist_link */ public $f_artist_link; /** * @var string $f_artist */ public $f_artist; /** * @var string $album_artist_name */ public $album_artist_name; /** * @var string $f_album_artist_name */ public $f_album_artist_name; /** * @var string $f_album_artist_link */ public $f_album_artist_link; /** * @var string $f_name */ public $f_name; /** * @var string $link */ public $link; /** * @var string $f_link */ public $f_link; /** * @var string $f_tags */ public $f_tags; /** * @var string $f_year */ public $f_year; /** * @var string $f_title */ public $f_title; /** * @var string $f_release_type */ public $f_release_type; // cached information /** * @var boolean $_fake */ public $_fake; /** * @var array $_songs */ public $_songs = array(); /** * @var array $_mapcache */ private static $_mapcache = array(); /** * @var array $album_suite */ public $album_suite = array(); /** * @var boolean $allow_group_disks */ public $allow_group_disks = false; /** * __construct * Album constructor it loads everything relating * to this album from the database it does not * pull the album or thumb art by default or * get any of the counts. * @param int|null $id */ public function __construct($id=null) { if (!$id) { return false; } /* Get the information from the db */ $info = $this->get_info($id); // Foreach what we've got foreach ($info as $key=>$value) { $this->$key = $value; } // Little bit of formatting here $this->full_name = trim(trim($info['prefix']) . ' ' . trim($info['name'])); // Looking for other albums with same mbid, ordering by disk ascending if ($this->disk && !empty($this->mbid) && AmpConfig::get('album_group')) { $this->album_suite = $this->get_album_suite(); } return true; } // constructor /** * construct_from_array * This is often used by the metadata class, it fills out an album object from a * named array, _fake is set to true * @param array $data * @return Album */ public static function construct_from_array(array $data) { $album = new Album(0); foreach ($data as $key=>$value) { $album->$key = $value; } $album->_fake = true; // Make sure that we tell em it's fake return $album; } // construct_from_array /** * gc * * Cleans out unused albums */ public static function gc() { Dba::write('DELETE FROM `album` USING `album` LEFT JOIN `song` ON `song`.`album` = `album`.`id` WHERE `song`.`id` IS NULL'); } /** * build_cache * This takes an array of object ids and caches all of their information * with a single query * @param array $ids * @return boolean */ public static function build_cache(array $ids) { // Nothing to do if they pass us nothing if (!is_array($ids) or !count($ids)) { return false; } $idlist = '(' . implode(',', $ids) . ')'; $sql = "SELECT * FROM `album` WHERE `id` IN $idlist"; $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { parent::add_to_cache('album', $row['id'], $row); } return true; } // build_cache /** * _get_extra_info * This pulls the extra information from our tables, this is a 3 table join, which is why we don't normally * do it * @return array */ private function _get_extra_info($limit_threshold = '') { if (!$this->id) { return array(); } if (parent::is_cached('album_extra', $this->id)) { return parent::get_from_cache('album_extra', $this->id); } // Calculation $sql = "SELECT " . "COUNT(DISTINCT(`song`.`artist`)) AS `artist_count`, " . "COUNT(`song`.`id`) AS `song_count`, " . "SUM(`song`.`time`) as `total_duration` " . "FROM `song` "; $sqlj = ''; if (AmpConfig::get('catalog_disable')) { $sqlj = "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } $suite_array = array(); if ($this->allow_group_disks) { $suite_array = $this->album_suite; } if (!count($suite_array)) { $suite_array[] = $this->id; } $idlist = '(' . implode(',', $suite_array) . ')'; $sqlw = "WHERE `song`.`album` IN $idlist "; if (AmpConfig::get('catalog_disable')) { $sqlw .= "AND `catalog`.`enabled` = '1' "; } $sql .= $sqlj . $sqlw; if (!count($this->album_suite)) { $sql .= "GROUP BY `song`.`album` "; } else { $sql .= "GROUP BY `song`.`artist` "; } $db_results = Dba::read($sql); $results = Dba::fetch_assoc($db_results); // Get associated information from first song only $sql = "SELECT " . "`song`.`catalog` as `catalog_id`," . "`artist`.`name` AS `artist_name`, " . "`artist`.`prefix` AS `artist_prefix`, " . "`artist`.`id` AS `artist_id` " . "FROM `song` INNER JOIN `artist` " . "ON `artist`.`id`=`song`.`artist` "; $sql .= $sqlj . $sqlw . "LIMIT 1"; $db_results = Dba::read($sql); $results = array_merge($results, Dba::fetch_assoc($db_results)); $art = new Art($this->id, 'album'); $art->get_db(); $results['has_art'] = make_bool($art->raw); $results['has_thumb'] = make_bool($art->thumb); if (AmpConfig::get('show_played_times')) { $results['object_cnt'] = Stats::get_object_count('album', $this->id, $limit_threshold); } parent::add_to_cache('album_extra', $this->id, $results); return $results; } // _get_extra_info public function can_edit($user = null) { if (!$user) { $user = $GLOBALS['user']->id; } if (!$user) { return false; } if ($this->user !== null && $user == $this->user) { return true; } if (Access::check('interface', 50, $user)) { return true; } if (!$this->album_artist) { return false; } if (!AmpConfig::get('upload_allow_edit')) { return false; } $owner = $this->get_user_owner(); return ($owner && $owner === $user); } /** * check * * Searches for an album; if none is found, insert a new one. * @param string $name * @param int $year * @param int $disk * @param string $mbid * @param string $mbid_group * @param string $album_artist * @param string $release_type * @param boolean $readonly * @return int|null */ public static function check($name, $year = 0, $disk = 0, $mbid = null, $mbid_group = null, $album_artist = null, $release_type = null, $readonly = false) { $trimmed = Catalog::trim_prefix(trim($name)); $name = $trimmed['string']; $prefix = $trimmed['prefix']; $album_artist = intval($album_artist); $album_artist = ($album_artist <= 0) ? null : $album_artist; $mbid = empty($mbid) ? null : $mbid; $mbid_group = empty($mbid_group) ? null : $mbid_group; $release_type = empty($release_type) ? null : $release_type; // Not even sure if these can be negative, but better safe than llama. $year = Catalog::normalize_year($year); $disk = abs(intval($disk)); if (!$name) { $name = T_('Unknown (Orphaned)'); $year = 0; $disk = 0; $album_artist = null; } if (isset(self::$_mapcache[$name][$disk][$mbid][$album_artist])) { return self::$_mapcache[$name][$disk][$mbid][$album_artist]; } $sql = 'SELECT `album`.`id` FROM `album` WHERE `album`.`name` = ? AND `album`.`disk` = ? '; $params = array($name, $disk); if ($mbid) { $sql .= 'AND `album`.`mbid` = ? '; $params[] = $mbid; } else { $sql .= 'AND `album`.`mbid` IS NULL '; } if ($prefix) { $sql .= 'AND `album`.`prefix` = ? '; $params[] = $prefix; } if ($album_artist) { $sql .= 'AND `album`.`album_artist` = ? '; $params[] = $album_artist; } $db_results = Dba::read($sql, $params); if ($row = Dba::fetch_assoc($db_results)) { $id = $row['id']; self::$_mapcache[$name][$disk][$mbid][$album_artist] = $id; return $id; } if ($readonly) { return null; } $sql = 'INSERT INTO `album` (`name`, `prefix`, `year`, `disk`, `mbid`, `mbid_group`, `release_type`, `album_artist`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; $db_results = Dba::write($sql, array($name, $prefix, $year, $disk, $mbid, $mbid_group, $release_type, $album_artist)); if (!$db_results) { return null; } $id = Dba::insert_id(); // Remove from wanted album list if any request on it if (!empty($mbid) && AmpConfig::get('wanted')) { try { Wanted::delete_wanted_release($mbid); } catch (Exception $e) { debug_event('wanted', 'Cannot process wanted releases auto-removal check: ' . $e->getMessage(), '1'); } } self::$_mapcache[$name][$disk][$mbid][$album_artist] = $id; return $id; } /** * get_songs * gets the songs for this album takes an optional limit * and an optional artist, if artist is passed it only gets * songs with this album + specified artist * @param int $limit * @param string $artist * @return int[] */ public function get_songs($limit = 0,$artist='') { $results = array(); $sql = "SELECT `song`.`id` FROM `song` "; if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } $sql .= "WHERE `song`.`album` = ? "; $params = array($this->id); if (strlen($artist)) { $sql .= "AND `artist` = ? "; $params[] = $artist; } if (AmpConfig::get('catalog_disable')) { $sql .= "AND `catalog`.`enabled` = '1' "; } $sql .= "ORDER BY `song`.`track`, `song`.`title`"; if ($limit) { $sql .= " LIMIT " . intval($limit); } $db_results = Dba::read($sql, $params); while ($r = Dba::fetch_assoc($db_results)) { $results[] = $r['id']; } return $results; } // get_songs /** * get_http_album_query_ids * return the html album parameters with all album suite ids * @param string $url_param_name * @return string */ public function get_http_album_query_ids($url_param_name) { if ($this->allow_group_disks) { $suite_array = $this->get_group_disks_ids(); } else { $suite_array = array($this->id); } return http_build_query(array($url_param_name => $suite_array)); } /** * get_group_disks_ids * return all album suite ids or current album if no albums * @return int[] */ public function get_group_disks_ids() { $suite_array = $this->album_suite; if (!count($suite_array)) { $suite_array[] = $this->id; } return $suite_array; } /** * get_album_suite * gets the album ids with the same musicbrainz identifier * @param int $catalog * return int[] */ public function get_album_suite($catalog = 0) { $results = array(); $catalog_where = ""; $catalog_join = "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog`"; if ($catalog) { $catalog_where .= " AND `catalog`.`id` = '$catalog'"; } if (AmpConfig::get('catalog_disable')) { $catalog_where .= " AND `catalog`.`enabled` = '1'"; } $sql = "SELECT DISTINCT `album`.`id`, `album`.`disk` FROM album LEFT JOIN `song` ON `song`.`album`=`album`.`id` $catalog_join " . "WHERE `album`.`mbid`='$this->mbid' $catalog_where ORDER BY `album`.`disk` ASC"; $db_results = Dba::read($sql); while ($r = Dba::fetch_assoc($db_results)) { $results[] = $r['id']; } return $results; } // get_album_suite /** * has_track * This checks to see if this album has a track of the specified title * @param string $title * @return array */ public function has_track($title) { $sql = "SELECT `id` FROM `song` WHERE `album` = ? AND `title` = ?"; $db_results = Dba::read($sql, array($this->id, $title)); $data = Dba::fetch_assoc($db_results); return $data; } // has_track /** * get_addtime_first_song * Get the add date of first added song. * @return int */ public function get_addtime_first_song() { $time = 0; $sql = "SELECT MIN(`addition_time`) FROM `song` WHERE `album` = ?"; $db_results = Dba::read($sql, array($this->id)); if ($data = Dba::fetch_row($db_results)) { $time = $data[0]; } return $time; } /** * format * This is the format function for this object. It sets cleaned up * albumĀ information with the base required * f_link, f_name */ public function format($details = true, $limit_threshold = '') { $web_path = AmpConfig::get('web_path'); if ($details) { /* Pull the advanced information */ $data = $this->_get_extra_info($limit_threshold); foreach ($data as $key=>$value) { $this->$key = $value; } if ($this->album_artist) { $Album_artist = new Artist($this->album_artist); $Album_artist->format(); $this->album_artist_name = $Album_artist->name; $this->f_album_artist_name = $Album_artist->f_name; $this->f_album_artist_link = "album_artist . "\" title=\"" . scrub_out($this->album_artist_name) . "\">" . $this->f_album_artist_name . ""; } $this->tags = Tag::get_top_tags('album', $this->id); $this->f_tags = Tag::get_display($this->tags, true, 'album'); } /* Truncate the string if it's to long */ $this->f_name = $this->full_name; $this->link = $web_path . '/albums.php?action=show&album=' . scrub_out($this->id); $this->f_link = "link . "\" title=\"" . scrub_out($this->full_name) . "\">" . scrub_out($this->f_name); // Looking if we need to combine or display disks if ($this->disk && (!$this->allow_group_disks || ($this->allow_group_disks && !AmpConfig::get('album_group')))) { $this->f_link .= " [" . T_('Disk') . " " . $this->disk . "]"; } $this->f_link .=""; $this->f_title = $this->full_name; if ($this->artist_count == '1') { $artist = trim(trim($this->artist_prefix) . ' ' . trim($this->artist_name)); $this->f_artist_name = $artist; $this->f_artist_link = "artist_id . "\" title=\"" . scrub_out($this->artist_name) . "\">" . $artist . ""; $this->f_artist = $artist; } else { $this->f_artist_link = "artist_count " . T_('Artists') . "\">" . T_('Various') . ""; $this->f_artist = T_('Various'); $this->f_artist_name = $this->f_artist; } if (!$this->year) { $this->f_year = "N/A"; } $this->f_release_type = ucwords($this->release_type); } // format /** * Get item keywords for metadata searches. * @return array */ public function get_keywords() { $keywords = array(); $keywords['mb_albumid'] = array('important' => false, 'label' => T_('Album MusicBrainzID'), 'value' => $this->mbid); $keywords['mb_albumid_group'] = array('important' => false, 'label' => T_('Release Group MusicBrainzID'), 'value' => $this->mbid_group); $keywords['artist'] = array('important' => true, 'label' => T_('Artist'), 'value' => (($this->artist_count < 2) ? $this->f_artist_name : '')); $keywords['album'] = array('important' => true, 'label' => T_('Album'), 'value' => $this->f_name); return $keywords; } /** * Get item fullname. * @return string */ public function get_fullname() { return $this->f_name; } /** * Get parent item description. * @return array|null */ public function get_parent() { if ($this->artist_count == 1) { return array('object_type' => 'artist', 'object_id' => $this->artist_id); } return null; } /** * Get item childrens. * @return array */ public function get_childrens() { return $this->get_medias(); } /** * Search for item childrens. * @param string $name * @return array */ public function search_childrens($name) { $search['type'] = "song"; $search['rule_0_input'] = $name; $search['rule_0_operator'] = 4; $search['rule_0'] = "title"; $search['rule_1_input'] = $this->name; $search['rule_1_operator'] = 4; $search['rule_1'] = "album"; $search['rule_2_input'] = $this->album_artist_name; $search['rule_2_operator'] = 4; $search['rule_2'] = "artist"; $songs = Search::run($search); $childrens = array(); foreach ($songs as $song) { $childrens[] = array( 'object_type' => 'song', 'object_id' => $song ); } return $childrens; } /** * Get all childrens and sub-childrens medias. * @param string $filter_type * @return array */ public function get_medias($filter_type = null) { $medias = array(); if (!$filter_type || $filter_type == 'song') { $songs = $this->get_songs(); foreach ($songs as $song_id) { $medias[] = array( 'object_type' => 'song', 'object_id' => $song_id ); } } return $medias; } /** * get_catalogs * * Get all catalog ids related to this item. * @return int[] */ public function get_catalogs() { return array($this->catalog_id); } /** * Get item's owner. * @return int|null */ public function get_user_owner() { if (!$this->album_artist) { return null; } $artist = new Artist($this->album_artist); return $artist->get_user_owner(); } /** * Get default art kind for this item. * @return string */ public function get_default_art_kind() { return 'default'; } public function get_description() { // Album description is not supported yet, always return artist description $artist = new Artist($this->artist_id); return $artist->get_description(); } public function display_art($thumb = 2, $force = false) { $id = null; $type = null; if (Art::has_db($this->id, 'album')) { $id = $this->id; $type = 'album'; } else { if (Art::has_db($this->artist_id, 'artist') || $force) { $id = $this->artist_id; $type = 'artist'; } } if ($id !== null && $type !== null) { $title = '[' . ($this->f_album_artist_name ?: $this->f_artist) . '] ' . $this->f_name; Art::display($type, $id, $title, $thumb, $this->link); } } /** * get_random_songs * gets a random number, and a random assortment of songs from this album * @return int[] */ public function get_random_songs() { $sql = "SELECT `song`.`id` FROM `song` "; if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } $sql .= "WHERE `song`.`album` = ? "; if (AmpConfig::get('catalog_disable')) { $sql .= "AND `catalog`.`enabled` = '1' "; } $sql .= "ORDER BY RAND()"; $db_results = Dba::read($sql, array($this->id)); $results = array(); while ($r = Dba::fetch_row($db_results)) { $results[] = $r['0']; } return $results; } // get_random_songs /** * update * This function takes a key'd array of data and updates this object * as needed * @param array $data * @return int */ public function update(array $data) { $year = isset($data['year']) ? $data['year'] : $this->year; $artist = isset($data['artist']) ? intval($data['artist']) : $this->artist_id; $album_artist = isset($data['album_artist']) ? intval($data['album_artist']) : $this->album_artist; $name = isset($data['name']) ? $data['name'] : $this->name; $disk = isset($data['disk']) ? $data['disk']: $this->disk; $mbid = isset($data['mbid']) ? $data['mbid'] : $this->mbid; $mbid_group = isset($data['mbid_group']) ? $data['mbid_group'] : $this->mbid_group; $release_type = isset($data['release_type']) ? $data['release_type'] : $this->release_type; $current_id = $this->id; $updated = false; $songs = null; if ($artist != $this->artist_id && $artist) { // Update every song $songs = $this->get_songs(); foreach ($songs as $song_id) { Song::update_artist($artist,$song_id); } $updated = true; Artist::gc(); } if (!empty($data['album_artist_name'])) { // Need to create new artist according the name $album_artist = Artist::check($data['album_artist_name']); } $album_id = self::check($name, $year, $disk, $mbid, $mbid_group, $album_artist, $release_type); if ($album_id != $this->id) { if (!is_array($songs)) { $songs = $this->get_songs(); } foreach ($songs as $song_id) { Song::update_album($album_id,$song_id); Song::update_year($year,$song_id); Song::write_id3_for_song($song_id); } $current_id = $album_id; $updated = true; Stats::migrate('album', $this->id, $album_id); Art::migrate('album', $this->id, $album_id); self::gc(); } else { Album::update_year($year, $album_id); Album::update_mbid_group($mbid_group, $album_id); Album::update_release_type($release_type, $album_id); } $this->year = $year; $this->mbid_group = $mbid_group; $this->release_type = $release_type; $this->name = $name; $this->disk = $disk; $this->mbid = $mbid; $this->album_artist = $album_artist; if ($updated && is_array($songs)) { foreach ($songs as $song_id) { Song::update_utime($song_id); } // foreach song of album Stats::gc(); Rating::gc(); Userflag::gc(); Useractivity::gc(); } // if updated $override_childs = false; if ($data['overwrite_childs'] == 'checked') { $override_childs = true; } $add_to_childs = false; if ($data['add_to_childs'] == 'checked') { $add_to_childs = true; } if (isset($data['edit_tags'])) { $this->update_tags($data['edit_tags'], $override_childs, $add_to_childs, $current_id, true); } return $current_id; } // update /** * update_tags * * Update tags of albums and/or songs * @param string $tags_comma * @param boolean $override_childs * @param boolean $add_to_childs * @param int|null $current_id */ public function update_tags($tags_comma, $override_childs, $add_to_childs, $current_id = null, $force_update = false) { if ($current_id == null) { $current_id = $this->id; } // When current_id not empty we force to overwrite current object Tag::update_tag_list($tags_comma, 'album', $current_id, $force_update ? true : $override_childs); if ($override_childs || $add_to_childs) { $songs = $this->get_songs(); foreach ($songs as $song_id) { Tag::update_tag_list($tags_comma, 'song', $song_id, $override_childs); } } } public function remove_from_disk() { $deleted = true; $song_ids = $this->get_songs(); foreach ($song_ids as $id) { $song = new Song($id); $deleted = $song->remove_from_disk(); if (!$deleted) { debug_event('album', 'Error when deleting the song `' . $id . '`.', 1); break; } } if ($deleted) { $sql = "DELETE FROM `album` WHERE `id` = ?"; $deleted = Dba::write($sql, array($this->id)); if ($deleted) { Art::gc('album', $this->id); Userflag::gc('album', $this->id); Rating::gc('album', $this->id); Shoutbox::gc('album', $this->id); Useractivity::gc('album', $this->id); } } return $deleted; } /** * Update album year. * @param int $year * @param int $album_id */ public static function update_year($year, $album_id) { self::update_field('year', $year, $album_id); } /** * Update album mbid group. * @param string $mbid_group * @param int $album_id */ public static function update_mbid_group($mbid_group, $album_id) { $mbid_group = (!empty($mbid_group)) ? $mbid_group : null; self::update_field('mbid_group', $mbid_group, $album_id); } /** * Update album release type. * @param string $release_type * @param int $album_id */ public static function update_release_type($release_type, $album_id) { $release_type = (!empty($release_type)) ? $release_type : null; self::update_field('release_type', $release_type, $album_id); } /** * Update an album field. * @param string $field * @param int $album_id * @return boolean */ private static function update_field($field, $value, $album_id) { $sql = "UPDATE `album` SET `" . $field . "` = ? WHERE `id` = ?"; return Dba::write($sql, array($value, $album_id)); } /** * get_random * * This returns a number of random albums. * @param int $count * @param boolean $with_art * @return int[] */ public static function get_random($count = 1, $with_art = false) { $results = array(); if (!$count) { $count = 1; } $sql = "SELECT DISTINCT `album`.`id` FROM `album` " . "LEFT JOIN `song` ON `song`.`album` = `album`.`id` "; if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; $where = "WHERE `catalog`.`enabled` = '1' "; } else { $where = "WHERE '1' = '1' "; } if ($with_art) { $sql .= "LEFT JOIN `image` ON (`image`.`object_type` = 'album' AND `image`.`object_id` = `album`.`id`) "; $where .="AND `image`.`id` IS NOT NULL "; } $sql .= $where; $sql .= "ORDER BY RAND() LIMIT " . intval($count); $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { $results[] = $row['id']; } return $results; } } //end of album class