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 */
22
23declare(strict_types=0);
24
25namespace Ampache\Repository\Model;
26
27use Ampache\Module\Authorization\Access;
28use Ampache\Module\System\Dba;
29use Ampache\Config\AmpConfig;
30use Ampache\Module\System\Core;
31use PDOStatement;
32
33/**
34 * This class handles playlists in ampache. it references the playlist* tables
35 */
36class Playlist extends playlist_object
37{
38    protected const DB_TABLENAME = 'playlist';
39
40    /* Variables from the database */
41    public $genre;
42    public $date;
43    public $last_update;
44    public $last_duration;
45
46    public $link;
47    public $f_link;
48    public $f_date;
49    public $f_last_update;
50
51    /* Generated Elements */
52    public $items = array();
53
54    /**
55     * Constructor
56     * This takes a playlist_id as an optional argument and gathers the information
57     * if not playlist_id is passed returns false (or if it isn't found
58     * @param integer $object_id
59     */
60    public function __construct($object_id)
61    {
62        $info = $this->get_info($object_id);
63
64        foreach ($info as $key => $value) {
65            $this->$key = $value;
66        }
67    } // Playlist
68
69    public function getId(): int
70    {
71        return (int) $this->id;
72    }
73
74    /**
75     * garbage_collection
76     *
77     * Clean dead items out of playlists
78     */
79    public static function garbage_collection()
80    {
81        foreach (array('song', 'video') as $object_type) {
82            Dba::write("DELETE FROM `playlist_data` USING `playlist_data` LEFT JOIN `" . $object_type . "` ON `" . $object_type . "`.`id` = `playlist_data`.`object_id` WHERE `" . $object_type . "`.`file` IS NULL AND `playlist_data`.`object_type`='" . $object_type . "';");
83        }
84        Dba::write("DELETE FROM `playlist` USING `playlist` LEFT JOIN `playlist_data` ON `playlist_data`.`playlist` = `playlist`.`id` WHERE `playlist_data`.`object_id` IS NULL;");
85    }
86
87    /**
88     * build_cache
89     * This is what builds the cache from the objects
90     * @param array $ids
91     */
92    public static function build_cache($ids)
93    {
94        if (!empty($ids)) {
95            $idlist     = '(' . implode(',', $ids) . ')';
96            $sql        = "SELECT * FROM `playlist` WHERE `id` IN $idlist";
97            $db_results = Dba::read($sql);
98
99            while ($row = Dba::fetch_assoc($db_results)) {
100                parent::add_to_cache('playlist', $row['id'], $row);
101            }
102        }
103    } // build_cache
104
105    /**
106     * get_playlists
107     * Returns a list of playlists accessible by the user.
108     * @param integer $user_id
109     * @param string $playlist_name
110     * @param boolean $like
111     * @param boolean $includePublic
112     * @return integer[]
113     */
114    public static function get_playlists($user_id = null, $playlist_name = '', $like = true, $includePublic = true)
115    {
116        if (!$user_id) {
117            $user_id = Core::get_global('user')->id;
118        }
119        $key = ($includePublic)
120            ? 'playlistids'
121            : 'accessibleplaylistids';
122        if (empty($playlist_name)) {
123            if (parent::is_cached($key, $user_id)) {
124                return parent::get_from_cache($key, $user_id);
125            }
126        }
127        $is_admin = (Access::check('interface', 100, $user_id) || $user_id == -1);
128        $sql      = "SELECT `id` FROM `playlist` ";
129        $params   = array();
130
131        if (!$is_admin) {
132            $sql .= ($includePublic)
133                ? "WHERE (`user` = ? OR `type` = 'public') "
134                : "WHERE (`user` = ?) ";
135            $params[] = $user_id;
136        }
137        if ($playlist_name !== '') {
138            $playlist_name = (!$like) ? "= '" . $playlist_name . "'" : "LIKE '%" . $playlist_name . "%' ";
139            $sql .= (!$is_admin) ? "AND `name` " . $playlist_name : "WHERE `name` " . $playlist_name;
140        }
141        $sql .= "ORDER BY `name`";
142        //debug_event(self::class, 'get_playlists query: ' . $sql, 5);
143
144        $db_results = Dba::read($sql, $params);
145        $results    = array();
146        while ($row = Dba::fetch_assoc($db_results)) {
147            $results[] = (int)$row['id'];
148        }
149
150        if (empty($playlist_name)) {
151            parent::add_to_cache($key, $user_id, $results);
152        }
153
154        return $results;
155    } // get_playlists
156
157    /**
158     * get_playlist_array
159     * Returns a list of playlists accessible by the user with formatted name.
160     * @param integer $user_id
161     * @return integer[]
162     */
163    public static function get_playlist_array($user_id = null)
164    {
165        if (!$user_id) {
166            $user_id = Core::get_global('user')->id;
167        }
168        $key = 'playlistarray';
169        if (parent::is_cached($key, $user_id)) {
170            return parent::get_from_cache($key, $user_id);
171        }
172        $is_admin = (Access::check('interface', 100, $user_id) || $user_id == -1);
173        $sql      = "SELECT `id`, IF(`user` = ?, `name`, CONCAT(`name`, ' (', `username`, ')')) AS `name` FROM `playlist` ";
174        $params   = array($user_id);
175
176        if (!$is_admin) {
177            $sql .= "WHERE (`user` = ? OR `type` = 'public') ";
178            $params[] = $user_id;
179        }
180        $sql .= "ORDER BY `name`";
181        //debug_event(self::class, 'get_playlists query: ' . $sql, 5);
182
183        $db_results = Dba::read($sql, $params);
184        $results    = array();
185        while ($row = Dba::fetch_assoc($db_results)) {
186            $results[$row['id']] = $row['name'];
187        }
188
189        parent::add_to_cache($key, $user_id, $results);
190
191        return $results;
192    } // get_playlist_array
193
194    /**
195     * get_details
196     * Returns a keyed array of playlist id and name accessible by the user.
197     * @param string $type
198     * @param integer $user_id
199     * @return array
200     */
201    public static function get_details($type = 'playlist', $user_id = 0)
202    {
203        if (!$user_id) {
204            $user_id = Core::get_global('user')->id ?: -1;
205        }
206
207        $sql        = "SELECT `id`, `name` FROM `$type` WHERE (`user` = ? OR `type` = 'public') ORDER BY `name`";
208        $results    = array();
209        $db_results = Dba::read($sql, array($user_id));
210        while ($row = Dba::fetch_assoc($db_results)) {
211            $results[$row['id']] = $row['name'];
212        }
213
214        return $results;
215    } // get_playlists
216
217    /**
218     * get_smartlists
219     * Returns a list of searches accessible by the user.
220     * @param integer $user_id
221     * @param string $playlist_name
222     * @param boolean $like
223     * @return array
224     */
225    public static function get_smartlists($user_id = null, $playlist_name = '', $like = true)
226    {
227        if (!$user_id) {
228            $user_id = Core::get_global('user')->id;
229        }
230        $key = 'smartlists';
231        if (empty($playlist_name)) {
232            if (parent::is_cached($key, $user_id)) {
233                return parent::get_from_cache($key, $user_id);
234            }
235        }
236        $is_admin = (Access::check('interface', 100, $user_id) || $user_id == -1);
237        $sql      = "SELECT CONCAT('smart_', `id`) AS `id` FROM `search`";
238        $params   = array();
239
240        if (!$is_admin) {
241            $sql .= "WHERE (`user` = ? OR `type` = 'public') ";
242            $params[] = $user_id;
243        }
244        if ($playlist_name !== '') {
245            $playlist_name = (!$like) ? "= '" . $playlist_name . "'" : "LIKE '%" . $playlist_name . "%' ";
246            $sql .= (!$is_admin) ? "AND `name` " . $playlist_name : "WHERE `name` " . $playlist_name;
247        }
248        $sql .= "ORDER BY `name`";
249        //debug_event(self::class, 'get_smartlists ' . $sql, 5);
250
251        $db_results = Dba::read($sql, $params);
252        $results    = array();
253        while ($row = Dba::fetch_assoc($db_results)) {
254            $results[] = $row['id'];
255        }
256
257        if (empty($playlist_name)) {
258            parent::add_to_cache($key, $user_id, $results);
259        }
260
261        return $results;
262    } // get_smartlists
263
264    /**
265     * format
266     * This takes the current playlist object and gussies it up a little
267     * bit so it is presentable to the users
268     * @param boolean $details
269     */
270    public function format($details = true)
271    {
272        parent::format($details);
273        $this->link   = AmpConfig::get('web_path') . '/playlist.php?action=show_playlist&playlist_id=' . $this->id;
274        $this->f_link = '<a href="' . $this->link . '">' . scrub_out($this->f_name) . '</a>';
275
276        $this->f_date        = $this->date ? get_datetime((int)$this->date) : T_('Unknown');
277        $this->f_last_update = $this->last_update ? get_datetime((int)$this->last_update) : T_('Unknown');
278    } // format
279
280    /**
281     * get_items
282     * This returns an array of playlist medias that are in this playlist.
283     * Because the same media can be on the same playlist twice they are
284     * keyed by the uid from playlist_data
285     * @return array
286     */
287    public function get_items()
288    {
289        $results = array();
290
291        $sql        = "SELECT `id`, `object_id`, `object_type`, `track` FROM `playlist_data` WHERE `playlist`= ? ORDER BY `track`";
292        $db_results = Dba::read($sql, array($this->id));
293
294        while ($row = Dba::fetch_assoc($db_results)) {
295            $results[] = array(
296                'object_type' => $row['object_type'],
297                'object_id' => $row['object_id'],
298                'track' => $row['track'],
299                'track_id' => $row['id']
300            );
301        } // end while
302
303        return $results;
304    } // get_items
305
306    /**
307     * get_random_items
308     * This is the same as before but we randomize the buggers!
309     * @param string $limit
310     * @return array
311     */
312    public function get_random_items($limit = '')
313    {
314        $results = array();
315
316        $limit_sql = $limit ? 'LIMIT ' . (string)($limit) : '';
317
318        $sql        = "SELECT `object_id`, `object_type` FROM `playlist_data` WHERE `playlist` = ? ORDER BY RAND() $limit_sql";
319        $db_results = Dba::read($sql, array($this->id));
320
321        while ($row = Dba::fetch_assoc($db_results)) {
322            $results[] = array(
323                'object_type' => $row['object_type'],
324                'object_id' => $row['object_id']
325            );
326        } // end while
327
328        return $results;
329    } // get_random_items
330
331    /**
332     * get_songs
333     * This is called by the batch script, because we can't pass in Dynamic objects they pulled once and then their
334     * target song.id is pushed into the array
335     */
336    public function get_songs()
337    {
338        $results = array();
339
340        $sql         = "SELECT * FROM `playlist_data` WHERE `playlist` = ? AND `object_type` = 'song' AND `object_id` IS NOT NULL ORDER BY `track`";
341        $db_results  = Dba::read($sql, array($this->id));
342
343        while ($row = Dba::fetch_assoc($db_results)) {
344            $results[] = $row['object_id'];
345        } // end while
346
347        return $results;
348    } // get_songs
349
350    /**
351     * get_media_count
352     * This simply returns a int of how many media elements exist in this playlist
353     * For now let's consider a dyn_media a single entry
354     * @param string $type
355     * @return string|null
356     */
357    public function get_media_count($type = '')
358    {
359        $params = array($this->id);
360        $sql    = "SELECT COUNT(`id`) FROM `playlist_data` WHERE `playlist` = ?";
361        if (!empty($type)) {
362            $sql .= " AND `object_type` = ?";
363            $params[] = $type;
364        }
365        $db_results = Dba::read($sql, $params);
366
367        $results = Dba::fetch_row($db_results);
368
369        return $results['0'];
370    } // get_media_count
371
372    /**
373    * get_total_duration
374    * Get the total duration of all songs.
375    * @return integer
376    */
377    public function get_total_duration()
378    {
379        $songs  = $this->get_songs();
380        $idlist = '(' . implode(',', $songs) . ')';
381        if ($idlist == '()') {
382            return 0;
383        }
384        $sql        = "SELECT SUM(`time`) FROM `song` WHERE `id` IN $idlist";
385        $db_results = Dba::read($sql);
386        $results    = Dba::fetch_row($db_results);
387
388        return (int) $results['0'];
389    } // get_total_duration
390
391    /**
392     * update
393     * This function takes a key'd array of data and runs updates
394     * @param array $data
395     * @return integer
396     */
397    public function update(array $data)
398    {
399        if (isset($data['name']) && $data['name'] != $this->name) {
400            $this->update_name($data['name']);
401        }
402        if (isset($data['pl_type']) && $data['pl_type'] != $this->type) {
403            $this->update_type($data['pl_type']);
404        }
405        if (isset($data['pl_user']) && $data['pl_user'] != $this->user) {
406            $this->update_user($data['pl_user']);
407        }
408        // reformat after an update
409        $this->format();
410
411        return $this->id;
412    } // update
413
414    /**
415     * update_type
416     * This updates the playlist type, it calls the generic update_item function
417     * @param string $new_type
418     */
419    private function update_type($new_type)
420    {
421        if ($this->_update_item('type', $new_type, 50)) {
422            $this->type = $new_type;
423        }
424    } // update_type
425
426    /**
427     * update_user
428     * This updates the playlist type, it calls the generic update_item function
429     * @param int $new_user
430     */
431    private function update_user($new_user)
432    {
433        if ($this->_update_item('user', $new_user, 50)) {
434            $this->type     = $new_user;
435            $this->username = User::get_username($new_user);
436            $sql            = "UPDATE `playlist` SET `playlist`.`username` = ? WHERE `playlist`.`user` = ?;";
437            Dba::write($sql, array($this->username, $new_user));
438        }
439    } // update_type
440
441    /**
442     * update_name
443     * This updates the playlist name, it calls the generic update_item function
444     * @param string $new_name
445     */
446    private function update_name($new_name)
447    {
448        if ($this->_update_item('name', $new_name, 50)) {
449            $this->name = $new_name;
450        }
451    } // update_name
452
453    /**
454     * update_last_update
455     * This updates the playlist last update, it calls the generic update_item function
456     */
457    private function update_last_update()
458    {
459        $last_update = time();
460        if ($this->_update_item('last_update', $last_update, 50)) {
461            $this->last_update = $last_update;
462        }
463        $this->set_last($this->get_total_duration(), 'last_duration');
464    } // update_last_update
465
466    /**
467     * _update_item
468     * This is the generic update function, it does the escaping and error checking
469     * @param string $field
470     * @param string|integer $value
471     * @param integer $level
472     * @return PDOStatement|boolean
473     */
474    private function _update_item($field, $value, $level)
475    {
476        if (Core::get_global('user')->id != $this->user && !Access::check('interface', $level)) {
477            return false;
478        }
479
480        $sql = "UPDATE `playlist` SET `$field` = ? WHERE `id` = ?";
481
482        return Dba::write($sql, array($value, $this->id));
483    } // update_item
484
485    /**
486     * update_track_number
487     * This takes a playlist_data.id and a track (int) and updates the track value
488     * @param integer $track_id
489     * @param integer $index
490     */
491    public function update_track_number($track_id, $index)
492    {
493        $sql = "UPDATE `playlist_data` SET `track` = ? WHERE `id` = ?";
494        Dba::write($sql, array($index, $track_id));
495    } // update_track_number
496
497    /**
498     * Regenerate track numbers to fill gaps.
499     */
500    public function regenerate_track_numbers()
501    {
502        $items = $this->get_items();
503        $index = 1;
504        foreach ($items as $item) {
505            $this->update_track_number($item['track_id'], $index);
506            $index++;
507        }
508
509        $this->update_last_update();
510    }
511
512    /**
513     * add_songs
514     * @param array $song_ids
515     * This takes an array of song_ids and then adds it to the playlist
516     */
517    public function add_songs($song_ids = array())
518    {
519        $medias = array();
520        foreach ($song_ids as $song_id) {
521            $medias[] = array(
522                'object_type' => 'song',
523                'object_id' => $song_id,
524            );
525        }
526        $this->add_medias($medias);
527    } // add_songs
528
529    /**
530     * add_medias
531     * @param array $medias
532     * @return bool
533     */
534    public function add_medias($medias)
535    {
536        if (empty($medias)) {
537            return false;
538        }
539        /* We need to pull the current 'end' track and then use that to
540         * append, rather then integrate take end track # and add it to
541         * $song->track add one to make sure it really is 'next'
542         */
543        debug_event(self::class, "add_medias to: " . $this->id, 5);
544        $unique     = (bool) AmpConfig::get('unique_playlist');
545        $track_data = $this->get_songs();
546        $base_track = count($track_data);
547        $count      = 0;
548        $sql        = "INSERT INTO `playlist_data` (`playlist`, `object_id`, `object_type`, `track`) VALUES ";
549        $values     = array();
550        foreach ($medias as $data) {
551            if ($unique && in_array($data['object_id'], $track_data)) {
552                debug_event(self::class, "Can't add a duplicate " . $data['object_type'] . " (" . $data['object_id'] . ") when unique_playlist is enabled", 3);
553            } else {
554                $count++;
555                $track = $base_track + $count;
556                $sql .= "(?, ?, ?, ?), ";
557                $values[] = $this->id;
558                $values[] = $data['object_id'];
559                $values[] = $data['object_type'];
560                $values[] = $track;
561            } // if valid id
562        } // end foreach medias
563        Dba::write(rtrim($sql, ', '), $values);
564        debug_event(self::class, "Added $count tracks to playlist: " . $this->id, 5);
565        $this->update_last_update();
566
567        return true;
568    }
569
570    /**
571     * create
572     * This function creates an empty playlist, gives it a name and type
573     * @param string $name
574     * @param string $type
575     * @param integer $user_id
576     * @return string|null
577     */
578    public static function create($name, $type, $user_id = null)
579    {
580        if ($user_id === null) {
581            $user_id = Core::get_global('user')->id ?: -1;
582        }
583        // get the public_name/username
584        $username = User::get_username($user_id);
585        // check for duplicates
586        $results    = array();
587        $sql        = "SELECT `id` FROM `playlist` WHERE `name` = ? AND `user` = ? AND `type` = ?";
588        $db_results = Dba::read($sql, array($name, $user_id, $type));
589
590        while ($row = Dba::fetch_assoc($db_results)) {
591            $results[] = $row['id'];
592        }
593        // return the duplicate ID
594        if (!empty($results)) {
595            return $results[0];
596        }
597
598        $date = time();
599        $sql  = "INSERT INTO `playlist` (`name`, `user`, `username`, `type`, `date`, `last_update`) VALUES (?, ?, ?, ?, ?, ?)";
600        Dba::write($sql, array($name, $user_id, $username, $type, $date, $date));
601
602        return Dba::insert_id();
603    } // create
604
605    /**
606     * set_items
607     * This calls the get_items function and sets it to $this->items which is an array in this object
608     */
609    public function set_items()
610    {
611        $this->items = $this->get_items();
612    } // set_items
613
614    /**
615     * set_last
616     *
617     * @param integer $count
618     * @param string $column
619     */
620    private function set_last($count, $column)
621    {
622        if ($this->id && in_array($column, array('last_count', 'last_duration')) && $count >= 0) {
623            $sql = "UPDATE `playlist` SET `" . Dba::escape($column) . "` = " . $count . " WHERE `id` = " . Dba::escape($this->id);
624            Dba::write($sql);
625        }
626    }
627
628    /**
629     * delete_all
630     *
631     * this deletes all tracks from a playlist, you specify the playlist.id here
632     * @return boolean
633     */
634    public function delete_all()
635    {
636        $sql = "DELETE FROM `playlist_data` WHERE `playlist_data`.`playlist` = ?";
637        Dba::write($sql, array($this->id));
638        debug_event(self::class, 'Delete all tracks from: ' . $this->id, 5);
639
640        $this->update_last_update();
641
642        return true;
643    } // delete_all
644
645    /**
646     * delete_song
647     * @param integer $object_id
648     * this deletes a single track, you specify the playlist_data.id here
649     * @return boolean
650     */
651    public function delete_song($object_id)
652    {
653        $sql = "DELETE FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`object_id` = ? LIMIT 1";
654        Dba::write($sql, array($this->id, $object_id));
655        debug_event(self::class, 'Delete object_id: ' . $object_id . ' from ' . $this->id, 5);
656
657        $this->update_last_update();
658
659        return true;
660    } // delete_track
661
662    /**
663     * delete_track
664     * this deletes a single track, you specify the playlist_data.id here
665     * @param integer $object_id
666     * @return boolean
667     */
668    public function delete_track($object_id)
669    {
670        $sql = "DELETE FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`id` = ? LIMIT 1";
671        Dba::write($sql, array($this->id, $object_id));
672        debug_event(self::class, 'Delete item_id: ' . $object_id . ' from ' . $this->id, 5);
673
674        $this->update_last_update();
675
676        return true;
677    } // delete_track
678
679    /**
680     * delete_track_number
681     * this deletes a single track by it's track #, you specify the playlist_data.track here
682     * @param integer $track
683     * @return boolean
684     */
685    public function delete_track_number($track)
686    {
687        $sql = "DELETE FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`track` = ? LIMIT 1";
688        Dba::write($sql, array($this->id, $track));
689        debug_event(self::class, 'Delete track: ' . $track . ' from ' . $this->id, 5);
690
691        $this->update_last_update();
692
693        return true;
694    } // delete_track_number
695
696    /**
697     * set_by_track_number
698     * this deletes a single track by it's track #, you specify the playlist_data.track here
699     * @param integer $object_id
700     * @param integer $track
701     * @return boolean
702     */
703    public function set_by_track_number($object_id, $track)
704    {
705        $sql = "UPDATE `playlist_data` SET `object_id` = ? WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`track` = ?";
706        Dba::write($sql, array($object_id, $this->id, $track));
707        debug_event(self::class, 'Set track ' . $track . ' to ' . $object_id . ' for playlist: ' . $this->id, 5);
708
709        $this->update_last_update();
710
711        return true;
712    } // delete_track_number
713
714    /**
715     * has_item
716     * look for the track id or the object id in a playlist
717     * @param integer $object
718     * @param integer $track
719     * @return boolean
720     */
721    public function has_item($object = null, $track = null)
722    {
723        $results = array();
724        if ($object) {
725            $sql        = "SELECT `object_id` FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`object_id` = ? LIMIT 1";
726            $db_results = Dba::read($sql, array($this->id, $object));
727            $results    = Dba::fetch_assoc($db_results);
728        } elseif ($track) {
729            $sql        = "SELECT `track` FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? AND `playlist_data`.`track` = ? LIMIT 1";
730            $db_results = Dba::read($sql, array($this->id, $track));
731            $results    = Dba::fetch_assoc($db_results);
732        }
733        if (isset($results['object_id']) || isset($results['track'])) {
734            debug_event(self::class, 'has_item results: ' . $results['object_id'], 5);
735
736            return true;
737        }
738
739        return false;
740    } // has_item
741
742    /**
743     * has_search
744     * Look for a saved smartlist with the same name as this playlist that the user can access
745     * @param int $playlist_user
746     * @return int
747     */
748    public function has_search($playlist_user): int
749    {
750        // search for your own playlist
751        $sql        = "SELECT `id`, `name` FROM `search` WHERE `user` = ?";
752        $db_results = Dba::read($sql, array($playlist_user));
753        while ($row = Dba::fetch_assoc($db_results)) {
754            if ($row['name'] == $this->name) {
755                return (int)$row['id'];
756            }
757        }
758        // look for public ones
759        $sql        = "SELECT `id`, `name` FROM `search` WHERE (`type`='public' OR `user` = ?)";
760        $db_results = Dba::read($sql, array(Core::get_global('user')->id));
761        while ($row = Dba::fetch_assoc($db_results)) {
762            if ($row['name'] == $this->name) {
763                return (int)$row['id'];
764            }
765        }
766
767        return 0;
768    } // has_search
769
770    /**
771     * delete
772     * This deletes the current playlist and all associated data
773     */
774    public function delete()
775    {
776        $sql = "DELETE FROM `playlist_data` WHERE `playlist` = ?";
777        Dba::write($sql, array($this->id));
778
779        $sql = "DELETE FROM `playlist` WHERE `id` = ?";
780        Dba::write($sql, array($this->id));
781
782        $sql = "DELETE FROM `object_count` WHERE `object_type`='playlist' AND `object_id` = ?";
783        Dba::write($sql, array($this->id));
784
785        return true;
786    } // delete
787
788    /**
789     * Sort the tracks and save the new position
790     */
791    public function sort_tracks()
792    {
793        /* First get all of the songs in order of their tracks */
794        $sql = "SELECT `list`.`id` FROM `playlist_data` AS `list` LEFT JOIN `song` ON `list`.`object_id` = `song`.`id` LEFT JOIN `album` ON `song`.`album` = `album`.`id` LEFT JOIN `artist` ON `album`.`album_artist` = `artist`.`id` WHERE `list`.`playlist` = ? ORDER BY `artist`.`name` ASC, `album`.`name` ASC, `album`.`year` ASC, `album`.`disk` ASC, `song`.`track` ASC, `song`.`title` ASC, `song`.`track` ASC";
795
796
797        $count      = 1;
798        $db_results = Dba::query($sql, array($this->id));
799        $results    = array();
800
801        while ($row = Dba::fetch_assoc($db_results)) {
802            $new_data          = array();
803            $new_data['id']    = $row['id'];
804            $new_data['track'] = $count;
805            $results[]         = $new_data;
806            $count++;
807        } // end while results
808        if (!empty($results)) {
809            $sql = "INSERT INTO `playlist_data` (`id`, `track`) VALUES ";
810            foreach ($results as $data) {
811                $sql .= "(" . Dba::escape($data['id']) . ", " . Dba::escape($data['track']) . "), ";
812            } // foreach re-ordered results
813
814            //replace the last comma
815            $sql = substr_replace($sql, "", -2);
816            $sql .= "ON DUPLICATE KEY UPDATE `track`=VALUES(`track`)";
817
818            // do this in one go
819            Dba::write($sql);
820        }
821        $this->update_last_update();
822
823        return true;
824    } // sort_tracks
825
826    /**
827     * Migrate an object associate stats to a new object
828     * @param string $object_type
829     * @param integer $old_object_id
830     * @param integer $new_object_id
831     * @return PDOStatement|boolean
832     */
833    public static function migrate($object_type, $old_object_id, $new_object_id)
834    {
835        $sql    = "UPDATE `playlist_data` SET `object_id` = ? WHERE `object_id` = ? AND `object_type` = ?;";
836        $params = array($new_object_id, $old_object_id, $object_type);
837
838        return Dba::write($sql, $params);
839    }
840}
841