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