1<?php
2/*
3 * vim:set softtabstop=4 shiftwidth=4 expandtab:
4 *
5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
6 * Copyright 2001 - 2020 Ampache.org
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21
22declare(strict_types=0);
23
24namespace Ampache\Repository\Model;
25
26use Ampache\Module\Artist\Tag\ArtistTagUpdaterInterface;
27use Ampache\Module\Label\LabelListUpdaterInterface;
28use Ampache\Module\Statistics\Stats;
29use Ampache\Module\System\Dba;
30use Ampache\Module\Util\Recommendation;
31use Ampache\Config\AmpConfig;
32use Ampache\Module\Util\VaInfo;
33use Ampache\Repository\AlbumRepositoryInterface;
34use Ampache\Repository\LabelRepositoryInterface;
35use Ampache\Repository\SongRepositoryInterface;
36use Ampache\Repository\UserActivityRepositoryInterface;
37use PDOStatement;
38
39class Artist extends database_object implements library_item, GarbageCollectibleInterface
40{
41    protected const DB_TABLENAME = 'artist';
42
43    /* Variables from DB */
44
45    /**
46     * @var integer $id
47     */
48    public $id;
49
50    /**
51     * @var string $name
52     */
53    public $name;
54
55    /**
56     * @var string $summary
57     */
58    public $summary;
59
60    /**
61     * @var string $placeformed
62     */
63    public $placeformed;
64
65    /**
66     * @var integer $yearformed
67     */
68    public $yearformed;
69
70    /**
71     * @var integer $last_update
72     */
73    public $last_update;
74
75    /**
76     * @var integer $songs
77     */
78    public $songs;
79
80    /**
81     * @var integer $albums
82     */
83    public $albums;
84
85    /**
86     * @var string $prefix
87     */
88    public $prefix;
89
90    /**
91     * @var string $mbid
92     */
93    public $mbid; // MusicBrainz ID
94
95    /**
96     * @var integer $catalog_id
97     */
98    public $catalog_id;
99
100    /**
101     * @var integer $time
102     */
103    public $time;
104
105    /**
106     * @var integer $user
107     */
108    public $user;
109
110    /**
111     * @var boolean $manual_update
112     */
113    public $manual_update;
114
115    /**
116     * @var array $tags
117     */
118    public $tags;
119
120    /**
121     * @var string $f_tags
122     */
123    public $f_tags;
124
125    /**
126     * @var array $labels
127     */
128    public $labels;
129
130    /**
131     * @var string $f_labels
132     */
133    public $f_labels;
134
135    /**
136     * @var integer $object_cnt
137     */
138    public $object_cnt;
139
140    /**
141     * @var integer $total_count
142     */
143    private $total_count;
144
145    /**
146     * @var string $f_name // Prefix + Name, generated
147     */
148    public $f_name;
149
150    /**
151     * @var string $link
152     */
153    public $link;
154
155    /**
156     * @var string $f_link
157     */
158    public $f_link;
159
160    /**
161     * @var string $f_time
162     */
163    public $f_time;
164
165    // Constructed vars
166    /**
167     * @var boolean $_fake
168     */
169    public $_fake = false; // Set if construct_from_array() used
170
171    /**
172     * @var integer $album_count
173     */
174    private $album_count;
175
176    /**
177     * @var integer $album_group_count
178     */
179    private $album_group_count;
180
181    /**
182     * @var integer $song_count
183     */
184    private $song_count;
185
186    /**
187     * @var array $_mapcache
188     */
189    private static $_mapcache = array();
190
191    /**
192     * Artist
193     * Artist class, for modifying an artist
194     * Takes the ID of the artist and pulls the info from the db
195     * @param integer|null $artist_id
196     * @param integer $catalog_init
197     */
198    public function __construct($artist_id = null, $catalog_init = 0)
199    {
200        /* If they failed to pass in an id, just run for it */
201        if ($artist_id === null) {
202            return false;
203        }
204
205        $this->catalog_id = $catalog_init;
206        /* Get the information from the db */
207        $info = $this->get_info($artist_id);
208
209        foreach ($info as $key => $value) {
210            $this->$key = $value;
211        } // foreach info
212
213        // set the full name
214        $this->f_name = trim(trim((string) $info['prefix']) . ' ' . trim((string) $info['name']));
215        // make sure the int values are cast to integers
216        $this->object_cnt        = (int)$this->total_count;
217        $this->time              = (int)$this->time;
218        $this->album_count       = (int)$this->album_count;
219        $this->album_group_count = (int)$this->album_group_count;
220        $this->song_count        = (int)$this->song_count;
221
222        return true;
223    } // constructor
224
225    public function getId(): int
226    {
227        return (int) $this->id;
228    }
229
230    public function isNew(): bool
231    {
232        return $this->getId() === 0;
233    }
234
235    /**
236     * construct_from_array
237     * This is used by the metadata class specifically but fills out a Artist object
238     * based on a key'd array, it sets $_fake to true
239     * @param array $data
240     * @return Artist
241     */
242    public static function construct_from_array($data)
243    {
244        $artist = new Artist(0);
245        foreach ($data as $key => $value) {
246            $artist->$key = $value;
247        }
248
249        // Ack that this is not a real object from the DB
250        $artist->_fake = true;
251
252        return $artist;
253    } // construct_from_array
254
255    /**
256     * garbage_collection
257     *
258     * This cleans out unused artists
259     */
260    public static function garbage_collection()
261    {
262        Dba::write("DELETE FROM `artist` WHERE `artist`.`id` NOT IN (SELECT `song`.`artist` FROM `song` WHERE `song`.`artist` IS NOT NULL) AND `artist`.`id` NOT IN (SELECT `album`.`album_artist` FROM `album` WHERE `album`.`album_artist` IS NOT NULL) AND `artist`.`id` NOT IN (SELECT `wanted`.`artist` FROM `wanted` WHERE `wanted`.`artist` IS NOT NULL) AND `artist`.`id` NOT IN (SELECT `clip`.`artist` FROM `clip` WHERE `clip`.`artist` IS NOT NULL);");
263    }
264
265    /**
266     * this attempts to build a cache of the data from the passed albums all in one query
267     * @param integer[] $ids
268     * @param boolean $extra
269     * @param string $limit_threshold
270     * @return boolean
271     */
272    public static function build_cache($ids, $extra = false, $limit_threshold = '')
273    {
274        if (empty($ids)) {
275            return false;
276        }
277        $idlist     = '(' . implode(',', $ids) . ')';
278        $sql        = "SELECT * FROM `artist` WHERE `id` IN $idlist";
279        $db_results = Dba::read($sql);
280
281        while ($row = Dba::fetch_assoc($db_results)) {
282            parent::add_to_cache('artist', $row['id'], $row);
283        }
284
285        // If we need to also pull the extra information, this is normally only used when we are doing the human display
286        if ($extra && (AmpConfig::get('show_played_times'))) {
287            $sql = "SELECT `song`.`artist` FROM `song` WHERE `song`.`artist` IN $idlist";
288
289            //debug_event("artist.class", "build_cache sql: " . $sql, 5);
290            $db_results = Dba::read($sql);
291
292            while ($row = Dba::fetch_assoc($db_results)) {
293                $row['object_cnt'] = (!empty($limit_threshold))
294                    ? Stats::get_object_count('artist', $row['artist'], $limit_threshold)
295                    : $row['total_count'];
296                parent::add_to_cache('artist_extra', $row['artist'], $row);
297            }
298        } // end if extra
299
300        return true;
301    } // build_cache
302
303    /**
304     * get_from_name
305     * This gets an artist object based on the artist name
306     * @param string $name
307     * @return Artist
308     */
309    public static function get_from_name($name)
310    {
311        $sql        = "SELECT `id` FROM `artist` WHERE `name` = ? OR LTRIM(CONCAT(COALESCE(`artist`.`prefix`, ''), ' ', `artist`.`name`)) = ? ";
312        $db_results = Dba::read($sql, array($name, $name));
313
314        $row = Dba::fetch_assoc($db_results);
315
316        return new Artist($row['id']);
317    } // get_from_name
318
319    /**
320     * get_time
321     *
322     * Get time for an artist's songs.
323     * @param integer $artist_id
324     * @return integer
325     */
326    public static function get_time($artist_id)
327    {
328        $params     = array($artist_id);
329        $sql        = "SELECT SUM(`song`.`time`) AS `time` from `song` WHERE `song`.`artist` = ?";
330        $db_results = Dba::read($sql, $params);
331        $results    = Dba::fetch_assoc($db_results);
332        // album artists that don't have any songs
333        if ((int) $results['time'] == 0) {
334            $sql        = "SELECT SUM(`album`.`time`) AS `time` from `album` WHERE `album`.`album_artist` = ?";
335            $db_results = Dba::read($sql, $params);
336            $results    = Dba::fetch_assoc($db_results);
337        }
338
339        return (int) $results['time'];
340    }
341
342    /**
343     * get_song_count
344     *
345     * Get count for an artist's songs.
346     * @param integer $artist_id
347     * @return integer
348     */
349    public static function get_song_count($artist_id)
350    {
351        $params     = array($artist_id);
352        $sql        = "SELECT COUNT(`song`.`id`) AS `song_count` from `song` WHERE `song`.`artist` = ?";
353        $db_results = Dba::read($sql, $params);
354        $results    = Dba::fetch_assoc($db_results);
355
356        return (int) $results['song_count'];
357    }
358
359    /**
360     * get_album_count
361     *
362     * Get count for an artist's albums.
363     * @param integer $artist_id
364     * @return integer
365     */
366    public static function get_album_count($artist_id)
367    {
368        $params     = array($artist_id);
369        $sql        = "SELECT COUNT(DISTINCT `album`.`id`) AS `album_count` FROM `album` LEFT JOIN `catalog` ON `catalog`.`id` = `album`.`catalog` WHERE `album`.`album_artist` = ? AND `catalog`.`enabled` = '1'";
370        $db_results = Dba::read($sql, $params);
371        $results    = Dba::fetch_assoc($db_results);
372
373        return (int) $results['album_count'];
374    }
375
376    /**
377     * get_album_group_count
378     *
379     * Get count for an artist's albums.
380     * @param integer $artist_id
381     * @return integer
382     */
383    public static function get_album_group_count($artist_id)
384    {
385        $params     = array($artist_id);
386        $sql        = "SELECT COUNT(DISTINCT CONCAT(COALESCE(`album`.`prefix`, ''), `album`.`name`, COALESCE(`album`.`album_artist`, ''), COALESCE(`album`.`mbid`, ''), COALESCE(`album`.`year`, ''))) AS `album_count` FROM `album` LEFT JOIN `catalog` ON `catalog`.`id` = `album`.`catalog` WHERE `album`.`album_artist` = ? AND `catalog`.`enabled` = '1'";
387        $db_results = Dba::read($sql, $params);
388        $results    = Dba::fetch_assoc($db_results);
389
390        return (int) $results['album_count'];
391    }
392
393    /**
394     * get_id_arrays
395     *
396     * Get each id from the artist table with the minimum detail required for subsonic
397     * @param array $catalogs
398     * @return array
399     */
400    public static function get_id_arrays($catalogs = array())
401    {
402        $group_column = (AmpConfig::get('album_group')) ? '`artist`.`album_group_count`' : '`artist`.`album_count`';
403        if (!empty($catalogs)) {
404            $sql        = "SELECT DISTINCT `artist`.`id`, LTRIM(CONCAT(COALESCE(`artist`.`prefix`, ''), ' ', `artist`.`name`)) AS `f_name`, `artist`.`name`, $group_column AS `album_count`, `artist`.`song_count` FROM `artist` LEFT JOIN `catalog_map` ON `catalog_map`.`object_type` = 'artist' AND `catalog_map`.`object_id` = `artist`.`id` WHERE `catalog_map`.`catalog_id` = ? ORDER BY `artist`.`name`";
405            $db_results = Dba::read($sql, $catalogs);
406        } else {
407            $sql        = "SELECT DISTINCT `artist`.`id`, LTRIM(CONCAT(COALESCE(`artist`.`prefix`, ''), ' ', `artist`.`name`)) AS `f_name`, `artist`.`name`, $group_column AS `album_count`, `artist`.`song_count` FROM `artist` ORDER BY `artist`.`name`";
408            $db_results = Dba::read($sql);
409        }
410        $results = array();
411
412        while ($row = Dba::fetch_assoc($db_results, false)) {
413            $results[] = $row;
414        }
415
416        return $results;
417    }
418
419    /**
420     * get_id_array
421     *
422     * Get info from the artist table with the minimum detail required for subsonic
423     * @param integer $artist_id
424     * @return array
425     */
426    public static function get_id_array($artist_id)
427    {
428        $group_column = (AmpConfig::get('album_group')) ? '`artist`.`album_group_count`' : '`artist`.`album_count`';
429        $sql          = "SELECT DISTINCT `artist`.`id`, LTRIM(CONCAT(COALESCE(`artist`.`prefix`, ''), ' ', `artist`.`name`)) AS `f_name`, `artist`.`name`, $group_column AS `album_count`, `artist`.`song_count` FROM `artist` WHERE `artist`.`id` = ? ORDER BY `artist`.`name`";
430        $db_results   = Dba::read($sql, array($artist_id));
431        $row          = Dba::fetch_assoc($db_results, false);
432
433        return $row;
434    }
435
436    /**
437     * get_child_ids
438     *
439     * Get each album id for the artist
440     * @return int[]
441     */
442    public function get_child_ids()
443    {
444        $sql        = "SELECT DISTINCT `album`.`id` FROM `album` LEFT JOIN `catalog` ON `catalog`.`id` = `album`.`catalog` WHERE `album`.`album_artist` = ? AND `catalog`.`enabled` = '1'";
445        $db_results = Dba::read($sql, array($this->id));
446        $results    = array();
447
448        while ($row = Dba::fetch_assoc($db_results, false)) {
449            $results[] = (int)$row['id'];
450        }
451
452        return $results;
453    }
454
455    /**
456     * format
457     * this function takes an array of artist
458     * information and formats the relevant values
459     * so they can be displayed in a table for example
460     * it changes the title into a full link.
461     * @param boolean $details
462     * @param string $limit_threshold
463     * @return boolean
464     */
465    public function format($details = true, $limit_threshold = '')
466    {
467        // If this is a memory-only object, we're done here
468        if (!$this->id) {
469            return true;
470        }
471        $this->songs  = $this->song_count;
472        $this->albums = (AmpConfig::get('album_group')) ? $this->album_group_count : $this->album_count;
473        $this->link   = ($this->catalog_id)
474            ? AmpConfig::get('web_path') . '/artists.php?action=show&catalog=' . $this->catalog_id . '&artist=' . $this->id
475            : AmpConfig::get('web_path') . '/artists.php?action=show&artist=' . $this->id;
476        $this->f_link = "<a href=\"" . $this->link . "\" title=\"" . scrub_out($this->f_name) . "\">" . scrub_out($this->f_name) . "</a>";
477
478        if ($details) {
479            $min   = sprintf("%02d", (floor($this->time / 60) % 60));
480            $sec   = sprintf("%02d", ($this->time % 60));
481            $hours = floor($this->time / 3600);
482
483            $this->f_time = ltrim((string)$hours . ':' . $min . ':' . $sec, '0:');
484            $this->tags   = Tag::get_top_tags('artist', $this->id);
485            $this->f_tags = Tag::get_display($this->tags, true, 'artist');
486
487            if (AmpConfig::get('label')) {
488                $this->labels   = $this->getLabelRepository()->getByArtist((int) $this->id);
489                $this->f_labels = Label::get_display($this->labels, true);
490            }
491        }
492
493        return true;
494    } // format
495
496    /**
497     * Get item keywords for metadata searches.
498     * @return array
499     */
500    public function get_keywords()
501    {
502        $keywords                = array();
503        $keywords['mb_artistid'] = array(
504            'important' => false,
505            'label' => T_('Artist MusicBrainzID'),
506            'value' => $this->mbid
507        );
508        $keywords['artist'] = array(
509            'important' => true,
510            'label' => T_('Artist'),
511            'value' => $this->f_name
512        );
513
514        return $keywords;
515    }
516
517    /**
518     * Get item fullname.
519     * @return string
520     */
521    public function get_fullname()
522    {
523        return $this->f_name;
524    }
525
526    /**
527     * Get parent item description.
528     * @return array|null
529     */
530    public function get_parent()
531    {
532        return null;
533    }
534
535    /**
536     * Get item childrens.
537     * @return array
538     */
539    public function get_childrens()
540    {
541        $medias = array();
542        $albums = $this->getAlbumRepository()->getByArtist($this->id);
543        foreach ($albums as $album_id) {
544            $medias[] = array(
545                'object_type' => 'album',
546                'object_id' => $album_id
547            );
548        }
549
550        return array('album' => $medias);
551    }
552
553    /**
554     * Search for item childrens.
555     * @param string $name
556     * @return array
557     */
558    public function search_childrens($name)
559    {
560        $search                    = array();
561        $search['type']            = "album";
562        $search['rule_0_input']    = $name;
563        $search['rule_0_operator'] = 4;
564        $search['rule_0']          = "title";
565        $search['rule_1_input']    = $this->name;
566        $search['rule_1_operator'] = 4;
567        $search['rule_1']          = "artist";
568        $albums                    = Search::run($search);
569
570        $childrens = array();
571        foreach ($albums as $album_id) {
572            $childrens[] = array(
573                'object_type' => 'album',
574                'object_id' => $album_id
575            );
576        }
577
578        return $childrens;
579    }
580
581    /**
582     * Get all childrens and sub-childrens medias.
583     * @param string $filter_type
584     * @return array
585     */
586    public function get_medias($filter_type = null)
587    {
588        $medias = array();
589        if ($filter_type === null || $filter_type == 'song') {
590            $songs = $this->getSongRepository()->getByArtist($this->id);
591            foreach ($songs as $song_id) {
592                $medias[] = array(
593                    'object_type' => 'song',
594                    'object_id' => $song_id
595                );
596            }
597        }
598
599        return $medias;
600    }
601
602    /**
603     * get_catalogs
604     *
605     * Get all catalog ids related to this item.
606     * @return integer[]
607     */
608    public function get_catalogs()
609    {
610        return array($this->catalog_id);
611    }
612
613    /**
614     * Get item's owner.
615     * @return integer|null
616     */
617    public function get_user_owner()
618    {
619        return $this->user;
620    }
621
622    /**
623     * Get default art kind for this item.
624     * @return string
625     */
626    public function get_default_art_kind()
627    {
628        return 'default';
629    }
630
631    /**
632     * get_description
633     * @return string
634     */
635    public function get_description()
636    {
637        return $this->summary;
638    }
639
640    /**
641     * display_art
642     * @param integer $thumb
643     * @param boolean $force
644     */
645    public function display_art($thumb = 2, $force = false)
646    {
647        $artist_id = null;
648        $type      = null;
649
650        if (Art::has_db($this->id, 'artist') || $force) {
651            $artist_id = $this->id;
652            $type      = 'artist';
653        }
654
655        if ($artist_id !== null && $type !== null) {
656            Art::display($type, $artist_id, $this->get_fullname(), $thumb, $this->link);
657        }
658    }
659
660    /**
661     * check
662     *
663     * Checks for an existing artist; if none exists, insert one.
664     * @param string $name
665     * @param string $mbid
666     * @param boolean $readonly
667     * @return integer|null
668     */
669    public static function check($name, $mbid = '', $readonly = false)
670    {
671        $trimmed = Catalog::trim_prefix(trim((string)$name));
672        $name    = $trimmed['string'];
673        $prefix  = $trimmed['prefix'];
674        // If Ampache support multiple artists per song one day, we should also handle other artists here
675        $trimmed = Catalog::trim_featuring($name);
676        $name    = $trimmed[0];
677
678        // If Ampache support multiple artists per song one day, we should also handle other artists here
679        $mbid = Catalog::trim_slashed_list($mbid);
680
681        if (!$name) {
682            $name   = T_('Unknown (Orphaned)');
683            $prefix = null;
684        }
685        if ($name == 'Various Artists') {
686            $mbid = '';
687        }
688
689        if (isset(self::$_mapcache[$name][$prefix][$mbid])) {
690            return self::$_mapcache[$name][$prefix][$mbid];
691        }
692
693        $artist_id = 0;
694        $exists    = false;
695        $matches   = array();
696
697        // check for artists by mbid and split-mbid
698        if ($mbid !== '') {
699            $sql     = 'SELECT `id` FROM `artist` WHERE `mbid` = ?';
700            $matches = VaInfo::get_mbid_array($mbid);
701            foreach ($matches as $mbid_string) {
702                $db_results = Dba::read($sql, array($mbid_string));
703
704                if (!$exists) {
705                    $row       = Dba::fetch_assoc($db_results);
706                    $artist_id = (int)$row['id'];
707                    $exists    = ($artist_id > 0);
708                    $mbid      = ($exists)
709                        ? $mbid_string
710                        : $mbid;
711                }
712            }
713            // try the whole string if it didn't work
714            if (!$exists) {
715                $db_results = Dba::read($sql, array($mbid));
716
717                if ($row = Dba::fetch_assoc($db_results)) {
718                    $artist_id = (int)$row['id'];
719                    $exists    = ($artist_id > 0);
720                }
721            }
722        }
723        // search by the artist name and build an array
724        if (!$exists) {
725            $sql        = 'SELECT `id`, `mbid` FROM `artist` WHERE `name` LIKE ?';
726            $db_results = Dba::read($sql, array($name));
727            $id_array   = array();
728            while ($row = Dba::fetch_assoc($db_results)) {
729                $key            = $row['mbid'] ?: 'null';
730                $id_array[$key] = $row['id'];
731            }
732            if (count($id_array)) {
733                if ($mbid !== '') {
734                    $matches = VaInfo::get_mbid_array($mbid);
735                    foreach ($matches as $mbid_string) {
736                        // reverse search artist id if it's still not found for some reason
737                        if (isset($id_array[$mbid_string])) {
738                            $artist_id = (int)$id_array[$mbid_string];
739                            $exists    = ($artist_id > 0);
740                            $mbid      = ($exists)
741                                ? $mbid_string
742                                : $mbid;
743                        }
744                        // update empty artists that match names
745                        if (isset($id_array['null']) && !$readonly) {
746                            $sql = 'UPDATE `artist` SET `mbid` = ? WHERE `id` = ?';
747                            Dba::write($sql, array($mbid_string, $id_array['null']));
748                        }
749                    }
750                    if (isset($id_array['null'])) {
751                        if (!$readonly) {
752                            $sql = 'UPDATE `artist` SET `mbid` = ? WHERE `id` = ?';
753                            Dba::write($sql, array($mbid, $id_array['null']));
754                        }
755                        $artist_id = (int)$id_array['null'];
756                        $exists    = true;
757                    }
758                } else {
759                    // Pick one at random
760                    $artist_id = array_shift($id_array);
761                    $exists    = true;
762                }
763            }
764        }
765        // cache and return the result
766        if ($exists) {
767            self::$_mapcache[$name][$prefix][$mbid] = $artist_id;
768
769            return (int)$artist_id;
770        }
771        // if all else fails, insert a new artist, cache it and return the id
772        $sql  = 'INSERT INTO `artist` (`name`, `prefix`, `mbid`) ' . 'VALUES(?, ?, ?)';
773        $mbid = (!empty($matches)) ? $matches[0] : $mbid; // TODO only use primary mbid until multi-artist is ready
774
775        $db_results = Dba::write($sql, array($name, $prefix, $mbid));
776        if (!$db_results) {
777            return null;
778        }
779
780        $artist_id = (int) Dba::insert_id();
781        debug_event(self::class, "check album: created {{$artist_id}}", 4);
782        // map the new id
783        Catalog::update_map(0, 'artist', $artist_id);
784
785        self::$_mapcache[$name][$prefix][$mbid] = $artist_id;
786
787        return $artist_id;
788    }
789
790    /**
791     * update
792     * This takes a key'd array of data and updates the current artist
793     * @param array $data
794     * @return integer
795     */
796    public function update(array $data)
797    {
798        // Save our current ID
799        $name        = isset($data['name']) ? $data['name'] : $this->name;
800        $mbid        = isset($data['mbid']) ? $data['mbid'] : $this->mbid;
801        $summary     = isset($data['summary']) ? $data['summary'] : $this->summary;
802        $placeformed = isset($data['placeformed']) ? $data['placeformed'] : $this->placeformed;
803        $yearformed  = isset($data['yearformed']) ? $data['yearformed'] : $this->yearformed;
804
805        $current_id = $this->id;
806
807        // Check if name is different than current name
808        if ($this->name != $name) {
809            $updated   = false;
810            $artist_id = self::check($name, $mbid, true);
811
812            // If it's changed we need to update
813            if ($artist_id !== null && $artist_id !== $this->id) {
814                $time  = time();
815                $songs = $this->getSongRepository()->getByArtist($this->id);
816                foreach ($songs as $song_id) {
817                    Song::update_artist($artist_id, $song_id, $this->id);
818                    Song::update_utime($song_id, $time);
819                }
820                $updated    = true;
821                $current_id = $artist_id;
822                Stats::migrate('artist', $this->id, $artist_id);
823                Useractivity::migrate('artist', $this->id, $artist_id);
824                Recommendation::migrate('artist', $this->id, $artist_id);
825                Share::migrate('artist', $this->id, $artist_id);
826                Shoutbox::migrate('artist', $this->id, $artist_id);
827                Tag::migrate('artist', $this->id, $artist_id);
828                Userflag::migrate('artist', $this->id, $artist_id);
829                Label::migrate('artist', $this->id, $artist_id);
830                Rating::migrate('artist', $this->id, $artist_id);
831                Art::duplicate('artist', $this->id, $artist_id);
832                Wanted::migrate('artist', $this->id, $artist_id);
833                Catalog::migrate_map('artist', $this->id, $artist_id);
834            } // end if it changed
835
836            // clear out the old data
837            if ($updated) {
838                self::garbage_collection();
839                Stats::garbage_collection();
840                Rating::garbage_collection();
841                Userflag::garbage_collection();
842                Label::garbage_collection();
843                $this->getUseractivityRepository()->collectGarbage();
844                self::update_artist_counts($current_id);
845            } // if updated
846        } else {
847            if ($this->mbid != $mbid) {
848                $sql = 'UPDATE `artist` SET `mbid` = ? WHERE `id` = ?';
849                Dba::write($sql, array($mbid, $current_id));
850            }
851        }
852
853        // Update artist name (if we don't want to use the MusicBrainz name)
854        $trimmed = Catalog::trim_prefix(trim((string)$name));
855        $name    = $trimmed['string'];
856        if ($name != '' && $name != $this->name) {
857            $sql = 'UPDATE `artist` SET `name` = ? WHERE `id` = ?';
858            Dba::write($sql, array($name, $current_id));
859        }
860
861        $this->update_artist_info($summary, $placeformed, $yearformed, true);
862
863        $this->name = $name;
864        $this->mbid = $mbid;
865
866        $override_childs = false;
867        if ($data['overwrite_childs'] == 'checked') {
868            $override_childs = true;
869        }
870
871        $add_to_childs = false;
872        if ($data['add_to_childs'] == 'checked') {
873            $add_to_childs = true;
874        }
875
876        if (isset($data['edit_tags'])) {
877            $this->getArtistTagUpdater()->updateTags(
878                $this,
879                $data['edit_tags'],
880                $override_childs,
881                $add_to_childs,
882                true
883            );
884        }
885
886        if (AmpConfig::get('label') && isset($data['edit_labels'])) {
887            $this->getLabelListUpdater()->update(
888                $data['edit_labels'],
889                (int) $this->id,
890                true
891            );
892        }
893
894        return $current_id;
895    } // update
896
897    /**
898     * Update artist information.
899     * @param string $summary
900     * @param string $placeformed
901     * @param integer $yearformed
902     * @param boolean $manual
903     * @return PDOStatement|boolean
904     */
905    public function update_artist_info($summary, $placeformed, $yearformed, $manual = false)
906    {
907        // set null values if missing
908        $summary     = (empty($summary)) ? null : $summary;
909        $placeformed = (empty($placeformed)) ? null : $placeformed;
910        $yearformed  = ((int)$yearformed == 0) ? null : Catalog::normalize_year($yearformed);
911
912        $sql     = "UPDATE `artist` SET `summary` = ?, `placeformed` = ?, `yearformed` = ?, `last_update` = ?, `manual_update` = ? WHERE `id` = ?";
913        $sqlret  = Dba::write($sql, array($summary, $placeformed, $yearformed, time(), (int)$manual, $this->id));
914
915        $this->summary     = $summary;
916        $this->placeformed = $placeformed;
917        $this->yearformed  = $yearformed;
918
919        return $sqlret;
920    }
921
922    /**
923     * Update artist associated user.
924     * @param integer $user
925     * @return PDOStatement|boolean
926     */
927    public function update_artist_user($user)
928    {
929        $sql = "UPDATE `artist` SET `user` = ? WHERE `id` = ?";
930
931        return Dba::write($sql, array($user, $this->id));
932    }
933
934    /**
935     * update_artist_counts
936     *
937     * @param integer $artist_id
938     */
939    public static function update_artist_counts($artist_id)
940    {
941        if ($artist_id > 0) {
942            $params = array($artist_id);
943            // artist.time
944            $sql = "UPDATE `artist`, (SELECT sum(`song`.`time`) as `time`, `song`.`artist` FROM `song` WHERE `song`.`artist` = ? GROUP BY `song`.`artist`) AS `song` SET `artist`.`time` = `song`.`time` WHERE `artist`.`id` = `song`.`artist`;";
945            Dba::write($sql, $params);
946            // artist.total_count
947            $sql = "UPDATE `artist`, (SELECT COUNT(`object_count`.`object_id`) AS `total_count`, `object_id` FROM `object_count` WHERE `object_count`.`object_id` = ? AND `object_count`.`object_type` = 'artist' AND `object_count`.`count_type` = 'stream' GROUP BY `object_count`.`object_id`) AS `object_count` SET `artist`.`total_count` = `object_count`.`total_count` WHERE `artist`.`id` = `object_count`.`object_id`;";
948            Dba::write($sql, $params);
949            // artist.album_count
950            $sql = "UPDATE `artist`, (SELECT COUNT(DISTINCT `album`.`id`) AS `album_count`, `album_artist` FROM `album` LEFT JOIN `catalog` ON `catalog`.`id` = `album`.`catalog` WHERE `album`.`album_artist` = ? AND `catalog`.`enabled` = '1' GROUP BY `album_artist`) AS `album` SET `artist`.`album_count` = `album`.`album_count` WHERE `artist`.`id` = `album`.`album_artist`;";
951            Dba::write($sql, $params);
952            // artist.album_group_count
953            $sql = "UPDATE `artist`, (SELECT COUNT(DISTINCT CONCAT(COALESCE(`album`.`prefix`, ''), `album`.`name`, COALESCE(`album`.`album_artist`, ''), COALESCE(`album`.`mbid`, ''), COALESCE(`album`.`year`, ''))) AS `album_group_count`, `album_artist` FROM `album` LEFT JOIN `catalog` ON `catalog`.`id` = `album`.`catalog` WHERE `album`.`album_artist` = ? AND `catalog`.`enabled` = '1' GROUP BY `album_artist`) AS `album` SET `artist`.`album_group_count` = `album`.`album_group_count` WHERE `artist`.`id` = `album`.`album_artist`;";
954            Dba::write($sql, $params);
955            // artist.song_count
956            $sql = "UPDATE `artist`, (SELECT COUNT(`song`.`id`) AS `song_count`, `artist` FROM `song` LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` WHERE `song`.`artist` = ? AND `catalog`.`enabled` = '1' GROUP BY `artist`) AS `song` SET `artist`.`song_count` = `song`.`song_count` WHERE `artist`.`id` = `song`.`artist`;";
957            Dba::write($sql, $params);
958        }
959    }
960
961    /**
962     * Update artist last_update time.
963     * @param integer $object_id
964     */
965    public static function set_last_update($object_id)
966    {
967        $sql = "UPDATE `artist` SET `last_update` = ? WHERE `id` = ?";
968        Dba::write($sql, array(time(), $object_id));
969    }
970
971    /**
972     * @deprecated
973     */
974    private function getLabelRepository(): LabelRepositoryInterface
975    {
976        global $dic;
977
978        return $dic->get(LabelRepositoryInterface::class);
979    }
980
981    /**
982     * @deprecated
983     */
984    private function getLabelListUpdater(): LabelListUpdaterInterface
985    {
986        global $dic;
987
988        return $dic->get(LabelListUpdaterInterface::class);
989    }
990
991    /**
992     * @deprecated
993     */
994    private function getAlbumRepository(): AlbumRepositoryInterface
995    {
996        global $dic;
997
998        return $dic->get(AlbumRepositoryInterface::class);
999    }
1000
1001    /**
1002     * @deprecated
1003     */
1004    private function getArtistTagUpdater(): ArtistTagUpdaterInterface
1005    {
1006        global $dic;
1007
1008        return $dic->get(ArtistTagUpdaterInterface::class);
1009    }
1010
1011    /**
1012     * @deprecated
1013     */
1014    private function getSongRepository(): SongRepositoryInterface
1015    {
1016        global $dic;
1017
1018        return $dic->get(SongRepositoryInterface::class);
1019    }
1020
1021    /**
1022     * @deprecated
1023     */
1024    private function getUseractivityRepository(): UserActivityRepositoryInterface
1025    {
1026        global $dic;
1027
1028        return $dic->get(UserActivityRepositoryInterface::class);
1029    }
1030}
1031