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\Module\Api; 26 27use Ampache\Module\Authorization\Access; 28use Ampache\Repository\Model\Album; 29use Ampache\Repository\Model\Bookmark; 30use Ampache\Repository\Model\Podcast; 31use Ampache\Module\Playback\Localplay\LocalPlay; 32use Ampache\Module\Util\InterfaceImplementationChecker; 33use Ampache\Config\AmpConfig; 34use Ampache\Repository\Model\Art; 35use Ampache\Repository\Model\Artist; 36use Ampache\Repository\Model\Catalog; 37use Ampache\Module\System\Dba; 38use Ampache\Repository\Model\Live_Stream; 39use Ampache\Repository\Model\Playlist; 40use Ampache\Repository\Model\Podcast_Episode; 41use Ampache\Repository\Model\Preference; 42use Ampache\Repository\Model\PrivateMsg; 43use Ampache\Repository\Model\Rating; 44use Ampache\Repository\Model\Search; 45use Ampache\Repository\Model\Share; 46use Ampache\Repository\AlbumRepositoryInterface; 47use Ampache\Repository\Model\User_Playlist; 48use Ampache\Repository\SongRepositoryInterface; 49use SimpleXMLElement; 50use Ampache\Repository\Model\Song; 51use Ampache\Repository\Model\Tag; 52use Ampache\Repository\Model\User; 53use Ampache\Repository\Model\Userflag; 54use Ampache\Repository\Model\Video; 55 56/** 57 * XML_Data Class 58 * 59 * This class takes care of all of the xml document stuff in Ampache these 60 * are all static calls 61 * 62 */ 63class Subsonic_Xml_Data 64{ 65 const API_VERSION = "1.13.0"; 66 67 const SSERROR_GENERIC = 0; 68 const SSERROR_MISSINGPARAM = 10; 69 const SSERROR_APIVERSION_CLIENT = 20; 70 const SSERROR_APIVERSION_SERVER = 30; 71 const SSERROR_BADAUTH = 40; 72 const SSERROR_TOKENAUTHNOTSUPPORTED = 41; 73 const SSERROR_UNAUTHORIZED = 50; 74 const SSERROR_TRIAL = 60; 75 const SSERROR_DATA_NOTFOUND = 70; 76 77 // Ampache doesn't have a global unique id but each items are unique per category. We use id pattern to identify item category. 78 const AMPACHEID_ARTIST = 100000000; 79 const AMPACHEID_ALBUM = 200000000; 80 const AMPACHEID_SONG = 300000000; 81 const AMPACHEID_SMARTPL = 400000000; 82 const AMPACHEID_VIDEO = 500000000; 83 const AMPACHEID_PODCAST = 600000000; 84 const AMPACHEID_PODCASTEP = 700000000; 85 const AMPACHEID_PLAYLIST = 800000000; 86 87 public static $enable_json_checks = false; 88 89 /** 90 * @param $artistid 91 * @return integer 92 */ 93 public static function getArtistId($artistid) 94 { 95 return $artistid + self::AMPACHEID_ARTIST; 96 } 97 98 /** 99 * @param $albumid 100 * @return integer 101 */ 102 public static function getAlbumId($albumid) 103 { 104 return $albumid + self::AMPACHEID_ALBUM; 105 } 106 107 /** 108 * @param $songid 109 * @return integer 110 */ 111 public static function getSongId($songid) 112 { 113 return $songid + self::AMPACHEID_SONG; 114 } 115 116 /** 117 * @param integer $videoid 118 * @return integer 119 */ 120 public static function getVideoId($videoid) 121 { 122 return $videoid + Subsonic_Xml_Data::AMPACHEID_VIDEO; 123 } 124 125 /** 126 * @param integer $plistid 127 * @return integer 128 */ 129 public static function getSmartPlId($plistid) 130 { 131 return $plistid + self::AMPACHEID_SMARTPL; 132 } 133 134 /** 135 * @param integer $podcastid 136 * @return integer 137 */ 138 public static function getPodcastId($podcastid) 139 { 140 return $podcastid + self::AMPACHEID_PODCAST; 141 } 142 143 /** 144 * @param integer $episode_id 145 * @return integer 146 */ 147 public static function getPodcastEpId($episode_id) 148 { 149 return $episode_id + self::AMPACHEID_PODCASTEP; 150 } 151 152 /** 153 * @param integer $plist_id 154 * @return integer 155 */ 156 public static function getPlaylistId($plist_id) 157 { 158 return $plist_id + self::AMPACHEID_PLAYLIST; 159 } 160 161 /** 162 * cleanId 163 * @param string $object_id 164 * @return integer 165 */ 166 private static function cleanId($object_id) 167 { 168 // Remove all al-, ar-, ... prefixes 169 $tpos = strpos((string)$object_id, "-"); 170 if ($tpos !== false) { 171 $object_id = substr((string) $object_id, $tpos + 1); 172 } 173 174 return (int) $object_id; 175 } 176 177 /** 178 * getAmpacheId 179 * @param string $object_id 180 * @return integer 181 */ 182 public static function getAmpacheId($object_id) 183 { 184 return (self::cleanId($object_id) % self::AMPACHEID_ARTIST); 185 } 186 187 /** 188 * getAmpacheIds 189 * @param array $object_ids 190 * @return array 191 */ 192 public static function getAmpacheIds($object_ids) 193 { 194 $ampids = array(); 195 foreach ($object_ids as $object_id) { 196 $ampids[] = self::getAmpacheId($object_id); 197 } 198 199 return $ampids; 200 } 201 202 /** 203 * getAmpacheIdArrays 204 * @param array $object_ids 205 * @return array 206 */ 207 public static function getAmpacheIdArrays($object_ids) 208 { 209 $ampidarrays = array(); 210 foreach ($object_ids as $object_id) { 211 $ampidarrays[] = array( 212 'object_id' => self::getAmpacheId($object_id), 213 'object_type' => self::getAmpacheType($object_id) 214 ); 215 } 216 217 return $ampidarrays; 218 } 219 220 /** 221 * @param string $artist_id 222 * @return boolean 223 */ 224 public static function isArtist($artist_id) 225 { 226 return (self::cleanId($artist_id) >= self::AMPACHEID_ARTIST && $artist_id < self::AMPACHEID_ALBUM); 227 } 228 229 /** 230 * @param string $album_id 231 * @return boolean 232 */ 233 public static function isAlbum($album_id) 234 { 235 return (self::cleanId($album_id) >= self::AMPACHEID_ALBUM && $album_id < self::AMPACHEID_SONG); 236 } 237 238 /** 239 * @param string $song_id 240 * @return boolean 241 */ 242 public static function isSong($song_id) 243 { 244 return (self::cleanId($song_id) >= self::AMPACHEID_SONG && $song_id < self::AMPACHEID_SMARTPL); 245 } 246 247 /** 248 * @param string $plist_id 249 * @return boolean 250 */ 251 public static function isSmartPlaylist($plist_id) 252 { 253 return (self::cleanId($plist_id) >= self::AMPACHEID_SMARTPL && $plist_id < self::AMPACHEID_VIDEO); 254 } 255 256 /** 257 * @param string $video_id 258 * @return boolean 259 */ 260 public static function isVideo($video_id) 261 { 262 $video_id = self::cleanId($video_id); 263 264 return (self::cleanId($video_id) >= self::AMPACHEID_VIDEO && $video_id < self::AMPACHEID_PODCAST); 265 } 266 267 /** 268 * @param string $podcast_id 269 * @return boolean 270 */ 271 public static function isPodcast($podcast_id) 272 { 273 return (self::cleanId($podcast_id) >= self::AMPACHEID_PODCAST && $podcast_id < self::AMPACHEID_PODCASTEP); 274 } 275 276 /** 277 * @param string $episode_id 278 * @return boolean 279 */ 280 public static function isPodcastEp($episode_id) 281 { 282 return (self::cleanId($episode_id) >= self::AMPACHEID_PODCASTEP && $episode_id < self::AMPACHEID_PLAYLIST); 283 } 284 285 /** 286 * @param string $plistid 287 * @return boolean 288 */ 289 public static function isPlaylist($plistid) 290 { 291 return (self::cleanId($plistid) >= self::AMPACHEID_PLAYLIST); 292 } 293 294 /** 295 * getAmpacheType 296 * @param string $object_id 297 * @return string 298 */ 299 public static function getAmpacheType($object_id) 300 { 301 if (self::isArtist($object_id)) { 302 return "artist"; 303 } elseif (self::isAlbum($object_id)) { 304 return "album"; 305 } elseif (self::isSong($object_id)) { 306 return "song"; 307 } elseif (self::isSmartPlaylist($object_id)) { 308 return "search"; 309 } elseif (self::isVideo($object_id)) { 310 return "video"; 311 } elseif (self::isPodcast($object_id)) { 312 return "podcast"; 313 } elseif (self::isPodcastEp($object_id)) { 314 return "podcast_episode"; 315 } elseif (self::isPlaylist($object_id)) { 316 return "playlist"; 317 } 318 319 return ""; 320 } 321 322 /** 323 * createFailedResponse 324 * @param string $function 325 * @return SimpleXMLElement 326 */ 327 public static function createFailedResponse($function = '') 328 { 329 $version = self::API_VERSION; 330 $response = self::createResponse($version, 'failed'); 331 debug_event(self::class, 'API fail in function ' . $function . '-' . $version, 3); 332 333 return $response; 334 } 335 336 /** 337 * createSuccessResponse 338 * @param string $function 339 * @return SimpleXMLElement 340 */ 341 public static function createSuccessResponse($function = '') 342 { 343 $version = self::API_VERSION; 344 $response = self::createResponse($version); 345 debug_event(self::class, 'API success in function ' . $function . '-' . $version, 5); 346 347 return $response; 348 } 349 350 /** 351 * createResponse 352 * @param string $version 353 * @param string $status 354 * @return SimpleXMLElement 355 */ 356 public static function createResponse($version, $status = 'ok') 357 { 358 $response = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><subsonic-response/>'); 359 $response->addAttribute('xmlns', 'http://subsonic.org/restapi'); 360 // $response->addAttribute('type', 'ampache'); 361 $response->addAttribute('status', (string)$status); 362 $response->addAttribute('version', (string)$version); 363 364 return $response; 365 } 366 367 /** 368 * createError 369 * @param $code 370 * @param string $message 371 * @param string $function 372 * @return SimpleXMLElement 373 */ 374 public static function createError($code, $message, $function = '') 375 { 376 $response = self::createFailedResponse($function); 377 self::setError($response, $code, $message); 378 379 return $response; 380 } 381 382 /** 383 * setError 384 * Set error information. 385 * 386 * @param SimpleXMLElement $xml Parent node 387 * @param integer $code Error code 388 * @param string $message Error message 389 */ 390 public static function setError($xml, $code, $message = '') 391 { 392 $xerr = $xml->addChild('error'); 393 $xerr->addAttribute('code', (string)$code); 394 395 if (empty($message)) { 396 switch ($code) { 397 case self::SSERROR_GENERIC: 398 $message = "A generic error."; 399 break; 400 case self::SSERROR_MISSINGPARAM: 401 $message = "Required parameter is missing."; 402 break; 403 case self::SSERROR_APIVERSION_CLIENT: 404 $message = "Incompatible Subsonic REST protocol version. Client must upgrade."; 405 break; 406 case self::SSERROR_APIVERSION_SERVER: 407 $message = "Incompatible Subsonic REST protocol version. Server must upgrade."; 408 break; 409 case self::SSERROR_BADAUTH: 410 $message = "Wrong username or password."; 411 break; 412 case self::SSERROR_TOKENAUTHNOTSUPPORTED: 413 $message = "Token authentication not supported."; 414 break; 415 case self::SSERROR_UNAUTHORIZED: 416 $message = "User is not authorized for the given operation."; 417 break; 418 case self::SSERROR_TRIAL: 419 $message = "The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium. Visit subsonic.org for details."; 420 break; 421 case self::SSERROR_DATA_NOTFOUND: 422 $message = "The requested data was not found."; 423 break; 424 } 425 } 426 427 $xerr->addAttribute('message', (string)$message); 428 } 429 430 /** 431 * addLicense 432 * @param SimpleXMLElement $xml 433 */ 434 public static function addLicense($xml) 435 { 436 $xlic = $xml->addChild('license'); 437 $xlic->addAttribute('valid', 'true'); 438 $xlic->addAttribute('email', 'webmaster@ampache.org'); 439 $xlic->addAttribute('key', 'ABC123DEF'); 440 $xlic->addAttribute('date', '2009-09-03T14:46:43'); 441 } 442 443 /** 444 * addMusicFolders 445 * @param SimpleXMLElement $xml 446 * @param integer[] $catalogs 447 */ 448 public static function addMusicFolders($xml, $catalogs) 449 { 450 $xfolders = $xml->addChild('musicFolders'); 451 foreach ($catalogs as $folderid) { 452 $catalog = Catalog::create_from_id($folderid); 453 $xfolder = $xfolders->addChild('musicFolder'); 454 $xfolder->addAttribute('id', (string)$folderid); 455 $xfolder->addAttribute('name', (string)$catalog->name); 456 } 457 } 458 459 /** 460 * addIgnoredArticles 461 * @param SimpleXMLElement $xml 462 */ 463 private static function addIgnoredArticles($xml) 464 { 465 $ignoredArticles = AmpConfig::get('catalog_prefix_pattern'); 466 if (!empty($ignoredArticles)) { 467 $ignoredArticles = str_replace("|", " ", $ignoredArticles); 468 $xml->addAttribute('ignoredArticles', (string)$ignoredArticles); 469 } 470 } 471 472 /** 473 * addArtistsIndexes 474 * @param SimpleXMLElement $xml 475 * @param array $artists 476 * @param $lastModified 477 * @param array $catalogs 478 */ 479 public static function addArtistsIndexes($xml, $artists, $lastModified, $catalogs) 480 { 481 $xindexes = $xml->addChild('indexes'); 482 $xindexes->addAttribute('lastModified', number_format($lastModified * 1000, 0, '.', '')); 483 self::addIgnoredArticles($xindexes); 484 foreach ($catalogs as $folderid) { 485 $catalog = Catalog::create_from_id($folderid); 486 $xfolder = $xindexes->addChild('shortcut'); 487 $xfolder->addAttribute('id', (string)$folderid); 488 $xfolder->addAttribute('name', (string)$catalog->name); 489 } 490 self::addArtistArrays($xindexes, $artists); 491 } 492 493 /** 494 * addArtistsRoot 495 * @param SimpleXMLElement $xml 496 * @param array $artists 497 */ 498 public static function addArtistsRoot($xml, $artists) 499 { 500 $xartists = $xml->addChild('artists'); 501 self::addIgnoredArticles($xartists); 502 self::addArtistArrays($xartists, $artists); 503 } 504 505 /** 506 * addArtists 507 * @param SimpleXMLElement $xml 508 * @param Artist[] $artists 509 * @param boolean $extra 510 * @param boolean $albumsSet 511 */ 512 public static function addArtists($xml, $artists, $extra = false, $albumsSet = false) 513 { 514 $xlastcat = null; 515 $sharpartists = array(); 516 $xlastletter = ''; 517 foreach ($artists as $artist) { 518 if (strlen((string)$artist->name) > 0) { 519 $letter = strtoupper((string)$artist->name[0]); 520 if ($letter == "X" || $letter == "Y" || $letter == "Z") { 521 $letter = "X-Z"; 522 } else { 523 if (!preg_match("/^[A-W]$/", $letter)) { 524 $sharpartists[] = $artist; 525 continue; 526 } 527 } 528 529 if ($letter != $xlastletter) { 530 $xlastletter = $letter; 531 $xlastcat = $xml->addChild('index'); 532 $xlastcat->addAttribute('name', (string)$xlastletter); 533 } 534 } 535 536 if ($xlastcat != null) { 537 self::addArtist($xlastcat, $artist, $extra, false, $albumsSet); 538 } 539 } 540 541 // Always add # index at the end 542 if (count($sharpartists) > 0) { 543 $xsharpcat = $xml->addChild('index'); 544 $xsharpcat->addAttribute('name', '#'); 545 546 foreach ($sharpartists as $artist) { 547 self::addArtist($xsharpcat, $artist, $extra, false, $albumsSet); 548 } 549 } 550 } 551 552 /** 553 * addArtist 554 * @param SimpleXMLElement $xml 555 * @param Artist $artist 556 * @param boolean $extra 557 * @param boolean $albums 558 * @param boolean $albumsSet 559 */ 560 public static function addArtist($xml, $artist, $extra = false, $albums = false, $albumsSet = false) 561 { 562 $artist->format(); 563 $xartist = $xml->addChild('artist'); 564 $xartist->addAttribute('id', (string)self::getArtistId($artist->id)); 565 $xartist->addAttribute('name', (string)self::checkName($artist->f_name)); 566 $allalbums = array(); 567 if (($extra && !$albumsSet) || $albums) { 568 $allalbums = static::getAlbumRepository()->getByArtist($artist->id); 569 } 570 571 if ($extra) { 572 $xartist->addAttribute('coverArt', 'ar-' . (string)self::getArtistId($artist->id)); 573 if ($albumsSet) { 574 $xartist->addAttribute('albumCount', (string)$artist->albums); 575 } else { 576 $xartist->addAttribute('albumCount', (string)count($allalbums)); 577 } 578 } 579 if ($albums) { 580 foreach ($allalbums as $albumid) { 581 $album = new Album($albumid); 582 self::addAlbum($xartist, $album); 583 } 584 } 585 } 586 587 /** 588 * addArtistArrays 589 * @param SimpleXMLElement $xml 590 * @param array $artists 591 */ 592 public static function addArtistArrays($xml, $artists) 593 { 594 $xlastcat = null; 595 $sharpartists = array(); 596 $xlastletter = ''; 597 foreach ($artists as $artist) { 598 if (strlen((string)$artist['name']) > 0) { 599 $letter = strtoupper((string)$artist['name'][0]); 600 if ($letter == "X" || $letter == "Y" || $letter == "Z") { 601 $letter = "X-Z"; 602 } else { 603 if (!preg_match("/^[A-W]$/", $letter)) { 604 $sharpartists[] = $artist; 605 continue; 606 } 607 } 608 609 if ($letter != $xlastletter) { 610 $xlastletter = $letter; 611 $xlastcat = $xml->addChild('index'); 612 $xlastcat->addAttribute('name', (string)$xlastletter); 613 } 614 } 615 616 if ($xlastcat != null) { 617 self::addArtistArray($xlastcat, $artist); 618 } 619 } 620 621 // Always add # index at the end 622 if (count($sharpartists) > 0) { 623 $xsharpcat = $xml->addChild('index'); 624 $xsharpcat->addAttribute('name', '#'); 625 626 foreach ($sharpartists as $artist) { 627 self::addArtistArray($xsharpcat, $artist); 628 } 629 } 630 } 631 632 /** 633 * addArtistArray 634 * @param SimpleXMLElement $xml 635 * @param array $artist 636 */ 637 public static function addArtistArray($xml, $artist) 638 { 639 $sub_id = (string)self::getArtistId($artist['id']); 640 $xartist = $xml->addChild('artist'); 641 $xartist->addAttribute('id', $sub_id); 642 $xartist->addAttribute('parent', $artist['catalog_id']); 643 $xartist->addAttribute('name', (string)self::checkName($artist['f_name'])); 644 645 if (isset($artist['album_count'])) { 646 $xartist->addAttribute('coverArt', 'ar-' . $sub_id); 647 $xartist->addAttribute('albumCount', (string)$artist['album_count']); 648 } 649 } 650 651 /** 652 * addAlbumList 653 * @param SimpleXMLElement $xml 654 * @param $albums 655 * @param string $elementName 656 */ 657 public static function addAlbumList($xml, $albums, $elementName = "albumList") 658 { 659 $xlist = $xml->addChild(htmlspecialchars($elementName)); 660 foreach ($albums as $albumid) { 661 $album = new Album($albumid); 662 self::addAlbum($xlist, $album); 663 } 664 } 665 666 /** 667 * addAlbum 668 * @param SimpleXMLElement $xml 669 * @param Album $album 670 * @param boolean $songs 671 * @param string $elementName 672 */ 673 public static function addAlbum($xml, $album, $songs = false, $elementName = "album") 674 { 675 $album->format(); 676 $xalbum = $xml->addChild(htmlspecialchars($elementName)); 677 $xalbum->addAttribute('id', (string)self::getAlbumId($album->id)); 678 $xalbum->addAttribute('parent', (string) self::getArtistId($album->album_artist)); 679 $xalbum->addAttribute('album', (string)self::checkName($album->f_name)); 680 $xalbum->addAttribute('title', (string)self::checkName($album->f_title)); 681 $xalbum->addAttribute('name', (string)self::checkName($album->f_name)); 682 $xalbum->addAttribute('isDir', 'true'); 683 $xalbum->addAttribute('discNumber', (string)$album->disk); 684 685 $xalbum->addAttribute('coverArt', 'al-' . self::getAlbumId($album->id)); 686 $xalbum->addAttribute('songCount', (string) $album->song_count); 687 $xalbum->addAttribute('created', date("c", (int)$album->addition_time)); 688 $xalbum->addAttribute('duration', (string) $album->total_duration); 689 $xalbum->addAttribute('artistId', (string) self::getArtistId($album->album_artist)); 690 $xalbum->addAttribute('artist', (string) self::checkName($album->f_album_artist_name)); 691 // original year (fall back to regular year) 692 $original_year = AmpConfig::get('use_original_year'); 693 $year = ($original_year && $album->original_year) 694 ? $album->original_year 695 : $album->year; 696 if ($year > 0) { 697 $xalbum->addAttribute('year', (string)$year); 698 } 699 if (count($album->tags) > 0) { 700 $tag_values = array_values($album->tags); 701 $tag = array_shift($tag_values); 702 $xalbum->addAttribute('genre', (string)$tag['name']); 703 } 704 705 $rating = new Rating($album->id, "album"); 706 $user_rating = ($rating->get_user_rating() ?: 0); 707 if ($user_rating > 0) { 708 $xalbum->addAttribute('userRating', (string)ceil($user_rating)); 709 } 710 $avg_rating = $rating->get_average_rating(); 711 if ($avg_rating > 0) { 712 $xalbum->addAttribute('averageRating', (string)$avg_rating); 713 } 714 715 self::setIfStarred($xalbum, 'album', $album->id); 716 717 if ($songs) { 718 $disc_ids = $album->get_group_disks_ids(); 719 foreach ($disc_ids as $discid) { 720 $disc = new Album($discid); 721 $allsongs = static::getSongRepository()->getByAlbum($disc->id); 722 foreach ($allsongs as $songid) { 723 self::addSong($xalbum, $songid); 724 } 725 } 726 } 727 } 728 729 /** 730 * addSong 731 * @param SimpleXMLElement $xml 732 * @param integer $songId 733 * @param string $elementName 734 * @return SimpleXMLElement 735 */ 736 public static function addSong($xml, $songId, $elementName = 'song') 737 { 738 $songData = self::getSongData($songId); 739 $albumData = self::getAlbumData($songData['album']); 740 $artistData = self::getArtistData($songData['artist']); 741 $catalogData = self::getCatalogData($songData['catalog'], $songData['file']); 742 //$catalog_path = rtrim((string) $catalogData[0], "/"); 743 744 return self::createSong($xml, $songData, $albumData, $artistData, $catalogData, $elementName); 745 } 746 747 /** 748 * getSongData 749 * @param integer $songId 750 * @return array 751 */ 752 public static function getSongData($songId) 753 { 754 $sql = 'SELECT `song`.`id`, `song`.`file`, `song`.`catalog`, `song`.`album`, `album`.`album_artist` AS `albumartist`, `song`.`year`, `song`.`artist`, `song`.`title`, `song`.`bitrate`, `song`.`rate`, `song`.`mode`, `song`.`size`, `song`.`time`, `song`.`track`, `song`.`played`, `song`.`enabled`, `song`.`update_time`, `song`.`mbid`, `song`.`addition_time`, `song`.`license`, `song`.`composer`, `song`.`user_upload`, `song`.`total_count`, `album`.`mbid` AS `album_mbid`, `artist`.`mbid` AS `artist_mbid`, `album_artist`.`mbid` AS `albumartist_mbid` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` LEFT JOIN `artist` AS `album_artist` ON `album_artist`.`id` = `album`.`album_artist` WHERE `song`.`id` = ?'; 755 $db_results = Dba::read($sql, array($songId)); 756 757 $results = Dba::fetch_assoc($db_results); 758 if (isset($results['id'])) { 759 if (AmpConfig::get('show_played_times')) { 760 $results['object_cnt'] = (int) $results['total_count']; 761 } 762 } 763 $extension = pathinfo((string)$results['file'], PATHINFO_EXTENSION); 764 $results['type'] = strtolower((string)$extension); 765 $results['mime'] = Song::type_to_mime($results['type']); 766 767 return $results; 768 } 769 770 /** 771 * getAlbumData 772 * @param integer $albumId 773 * @return array 774 */ 775 public static function getAlbumData($albumId) 776 { 777 $sql = "SELECT * FROM `album` WHERE `id`='$albumId'"; 778 $db_results = Dba::read($sql); 779 780 if (!$db_results) { 781 return array(); 782 } 783 $row = Dba::fetch_assoc($db_results); 784 $row['f_name'] = trim(trim((string)$row['prefix']) . ' ' . trim((string)$row['name'])); 785 786 return $row; 787 } 788 789 /** 790 * getArtistData 791 * @param integer $artistId 792 * @return array 793 */ 794 public static function getArtistData($artistId) 795 { 796 $sql = "SELECT * FROM `artist` WHERE `id`='$artistId'"; 797 $db_results = Dba::read($sql); 798 799 if (!$db_results) { 800 return array(); 801 } 802 803 $row = Dba::fetch_assoc($db_results); 804 $row['f_name'] = trim(trim((string)$row['prefix']) . ' ' . trim((string)$row['name'])); 805 806 return $row; 807 } 808 809 /** 810 * getCatalogData 811 * @param integer $catalogId 812 * @param string $file_Path 813 * @return array 814 */ 815 public static function getCatalogData($catalogId, $file_Path) 816 { 817 $results = array(); 818 $sqllook = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?'; 819 $db_results = Dba::read($sqllook, [$catalogId]); 820 $resultcheck = Dba::fetch_assoc($db_results); 821 if (!empty($resultcheck)) { 822 $sql = 'SELECT `path` FROM `catalog_' . $resultcheck['catalog_type'] . '` WHERE `catalog_id` = ?'; 823 $db_results = Dba::read($sql, [$catalogId]); 824 $result = Dba::fetch_assoc($db_results); 825 $catalog_path = rtrim((string)$result['path'], "/"); 826 $results['path'] = str_replace($catalog_path . "/", "", $file_Path); 827 } 828 829 return $results; 830 } 831 832 /** 833 * createSong 834 * @param SimpleXMLElement $xml 835 * @param $songData 836 * @param $albumData 837 * @param $artistData 838 * @param $catalogData 839 * @param string $elementName 840 * @return SimpleXMLElement 841 */ 842 public static function createSong( 843 $xml, 844 $songData, 845 $albumData, 846 $artistData, 847 $catalogData, 848 $elementName = 'song' 849 ) { 850 // Don't create entries for disabled songs 851 if (!$songData['enabled']) { 852 return null; 853 } 854 855 $xsong = $xml->addChild(htmlspecialchars($elementName)); 856 $xsong->addAttribute('id', (string)self::getSongId($songData['id'])); 857 $xsong->addAttribute('parent', (string)self::getAlbumId($songData['album'])); 858 //$xsong->addAttribute('created', ); 859 $xsong->addAttribute('title', (string)self::checkName($songData['title'])); 860 $xsong->addAttribute('isDir', 'false'); 861 $xsong->addAttribute('isVideo', 'false'); 862 $xsong->addAttribute('type', 'music'); 863 // $album = new Album(songData->album); 864 $xsong->addAttribute('albumId', (string)self::getAlbumId($albumData['id'])); 865 $xsong->addAttribute('album', (string)self::checkName($albumData['f_name'])); 866 // $artist = new Artist($song->artist); 867 // $artist->format(); 868 $xsong->addAttribute('artistId', (string) self::getArtistId($songData['artist'])); 869 $xsong->addAttribute('artist', (string) self::checkName($artistData['f_name'])); 870 $art_object = (AmpConfig::get('show_song_art') && Art::has_db($songData['id'], 'song')) ? self::getSongId($songData['id']) : self::getAlbumId($albumData['id']); 871 $xsong->addAttribute('coverArt', (string) $art_object); 872 $xsong->addAttribute('duration', (string) $songData['time']); 873 $xsong->addAttribute('bitRate', (string) ((int) ($songData['bitrate'] / 1000))); 874 // <!-- Added in 1.14.0 --> 875 // $xsong->addAttribute('playCount', (string)$songData['object_cnt']); 876 $rating = new Rating($songData['id'], "song"); 877 $user_rating = ($rating->get_user_rating() ?: 0); 878 if ($user_rating > 0) { 879 $xsong->addAttribute('userRating', (string)ceil($user_rating)); 880 } 881 $avg_rating = $rating->get_average_rating(); 882 if ($avg_rating > 0) { 883 $xsong->addAttribute('averageRating', (string)$avg_rating); 884 } 885 self::setIfStarred($xsong, 'song', $songData['id']); 886 if ($songData['track'] > 0) { 887 $xsong->addAttribute('track', (string)$songData['track']); 888 } 889 if ($songData['year'] > 0) { 890 $xsong->addAttribute('year', (string)$songData['year']); 891 } 892 $tags = Tag::get_object_tags('song', (int) $songData['id']); 893 if (count($tags) > 0) { 894 $xsong->addAttribute('genre', (string)$tags[0]['name']); 895 } 896 $xsong->addAttribute('size', (string)$songData['size']); 897 if (Album::sanitize_disk($albumData['disk']) > 0) { 898 $xsong->addAttribute('discNumber', (string)Album::sanitize_disk($albumData['disk'])); 899 } 900 $xsong->addAttribute('suffix', (string)$songData['type']); 901 $xsong->addAttribute('contentType', (string)$songData['mime']); 902 // Return a file path relative to the catalog root path 903 $xsong->addAttribute('path', (string)$catalogData['path']); 904 905 // Set transcoding information if required 906 $transcode_cfg = AmpConfig::get('transcode'); 907 $valid_types = Song::get_stream_types_for_type($songData['type'], 'api'); 908 if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) { 909 // $transcode_settings = Song::get_transcode_settings_for_media(null, null, 'api', 'song'); 910 $transcode_type = AmpConfig::get('encode_player_api_target', 'mp3'); 911 $xsong->addAttribute('transcodedSuffix', (string)$transcode_type); 912 $xsong->addAttribute('transcodedContentType', Song::type_to_mime($transcode_type)); 913 } 914 915 return $xsong; 916 } 917 918 /** 919 * checkName 920 * This to fix xml=>json which can result to wrong type parsing 921 * @param string $name 922 * @return string|null 923 */ 924 private static function checkName($name) 925 { 926 // Ensure to have always a string type 927 if (self::$enable_json_checks && !empty($name)) { 928 if (is_numeric($name)) { 929 // Add space character to fail numeric test 930 $name = $name .= " "; 931 } 932 } 933 934 return html_entity_decode($name, ENT_NOQUOTES, 'UTF-8'); 935 } 936 937 /** 938 * getAmpacheObject 939 * Return the Ampache media object 940 * @param integer $object_id 941 * @return Song|Video|Podcast_Episode|null 942 */ 943 public static function getAmpacheObject($object_id) 944 { 945 if (Subsonic_Xml_Data::isSong($object_id)) { 946 return new Song(Subsonic_Xml_Data::getAmpacheId($object_id)); 947 } 948 if (Subsonic_Xml_Data::isVideo($object_id)) { 949 return new Video(Subsonic_Xml_Data::getAmpacheId($object_id)); 950 } 951 if (Subsonic_Xml_Data::isPodcastEp($object_id)) { 952 return new Podcast_Episode(Subsonic_Xml_Data::getAmpacheId($object_id)); 953 } 954 955 return null; 956 } // getAmpacheObject 957 958 /** 959 * addArtistDirectory for subsonic artist id 960 * @param SimpleXMLElement $xml 961 * @param string $artist_id 962 */ 963 public static function addArtistDirectory($xml, $artist_id) 964 { 965 $amp_id = self::getAmpacheId($artist_id); 966 $data = Artist::get_id_array($amp_id); 967 $xdir = $xml->addChild('directory'); 968 $xdir->addAttribute('id', (string)$artist_id); 969 $xdir->addAttribute('parent', (string)Catalog::get_catalog_map('artist', $artist_id)); 970 $xdir->addAttribute('name', (string)$data['f_name']); 971 $allalbums = static::getAlbumRepository()->getByArtist($amp_id); 972 foreach ($allalbums as $album_id) { 973 $album = new Album($album_id); 974 self::addAlbum($xdir, $album, false, "child"); 975 } 976 } 977 978 /** 979 * addAlbumDirectory for subsonic album id 980 * @param SimpleXMLElement $xml 981 * @param string $album_id 982 */ 983 public static function addAlbumDirectory($xml, $album_id) 984 { 985 $album = new Album(self::getAmpacheId($album_id)); 986 $album->format(); 987 $xdir = $xml->addChild('directory'); 988 $xdir->addAttribute('id', (string)$album_id); 989 if ($album->album_artist) { 990 $xdir->addAttribute('parent', (string)self::getArtistId($album->album_artist)); 991 } else { 992 $xdir->addAttribute('parent', (string)$album->catalog); 993 } 994 $xdir->addAttribute('name', (string)self::checkName($album->f_title)); 995 996 $disc_ids = $album->get_group_disks_ids(); 997 $media_ids = static::getAlbumRepository()->getSongsGrouped($disc_ids); 998 foreach ($media_ids as $song_id) { 999 self::addSong($xdir, $song_id, "child"); 1000 } 1001 } 1002 1003 /** 1004 * addGenres 1005 * @param SimpleXMLElement $xml 1006 * @param $tags 1007 */ 1008 public static function addGenres($xml, $tags) 1009 { 1010 $xgenres = $xml->addChild('genres'); 1011 1012 foreach ($tags as $tag) { 1013 $otag = new Tag($tag['id']); 1014 $xgenre = $xgenres->addChild('genre', htmlspecialchars($otag->name)); 1015 $counts = $otag->count(); 1016 $xgenre->addAttribute('songCount', (string) $counts['song'] ?: 0); 1017 $xgenre->addAttribute('albumCount', (string) $counts['album'] ?: 0); 1018 } 1019 } 1020 1021 /** 1022 * addVideos 1023 * @param SimpleXMLElement $xml 1024 * @param Video[] $videos 1025 */ 1026 public static function addVideos($xml, $videos) 1027 { 1028 $xvideos = $xml->addChild('videos'); 1029 foreach ($videos as $video) { 1030 $video->format(); 1031 self::addVideo($xvideos, $video); 1032 } 1033 } 1034 1035 /** 1036 * addVideo 1037 * @param SimpleXMLElement $xml 1038 * @param Video $video 1039 * @param string $elementName 1040 */ 1041 public static function addVideo($xml, $video, $elementName = 'video') 1042 { 1043 $xvideo = $xml->addChild(htmlspecialchars($elementName)); 1044 $xvideo->addAttribute('id', (string)self::getVideoId($video->id)); 1045 $xvideo->addAttribute('title', (string)$video->f_full_title); 1046 $xvideo->addAttribute('isDir', 'false'); 1047 $xvideo->addAttribute('coverArt', (string)self::getVideoId($video->id)); 1048 $xvideo->addAttribute('isVideo', 'true'); 1049 $xvideo->addAttribute('type', 'video'); 1050 $xvideo->addAttribute('duration', (string)$video->time); 1051 if ($video->year > 0) { 1052 $xvideo->addAttribute('year', (string)$video->year); 1053 } 1054 $tags = Tag::get_object_tags('video', (int)$video->id); 1055 if (count($tags) > 0) { 1056 $xvideo->addAttribute('genre', (string)$tags[0]['name']); 1057 } 1058 $xvideo->addAttribute('size', (string)$video->size); 1059 $xvideo->addAttribute('suffix', (string)$video->type); 1060 $xvideo->addAttribute('contentType', (string)$video->mime); 1061 // Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients 1062 $path = basename($video->file); 1063 $xvideo->addAttribute('path', (string)$path); 1064 1065 self::setIfStarred($xvideo, 'video', $video->id); 1066 // Set transcoding information if required 1067 $transcode_cfg = AmpConfig::get('transcode'); 1068 $valid_types = Song::get_stream_types_for_type($video->type, 'api'); 1069 if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) { 1070 $transcode_settings = $video->get_transcode_settings(null, 'api'); 1071 if (!empty($transcode_settings)) { 1072 $transcode_type = $transcode_settings['format']; 1073 $xvideo->addAttribute('transcodedSuffix', (string)$transcode_type); 1074 $xvideo->addAttribute('transcodedContentType', Video::type_to_mime($transcode_type)); 1075 } 1076 } 1077 } 1078 1079 /** 1080 * addPlaylists 1081 * @param SimpleXMLElement $xml 1082 * @param $playlists 1083 * @param array $smartplaylists 1084 */ 1085 public static function addPlaylists($xml, $playlists, $smartplaylists = array()) 1086 { 1087 $xplaylists = $xml->addChild('playlists'); 1088 foreach ($playlists as $plistid) { 1089 $playlist = new Playlist($plistid); 1090 self::addPlaylist($xplaylists, $playlist); 1091 } 1092 foreach ($smartplaylists as $splistid) { 1093 $smartplaylist = new Search((int)str_replace('smart_', '', (string)$splistid), 'song'); 1094 self::addSmartPlaylist($xplaylists, $smartplaylist); 1095 } 1096 } 1097 1098 /** 1099 * addPlaylist 1100 * @param SimpleXMLElement $xml 1101 * @param Playlist $playlist 1102 * @param boolean $songs 1103 */ 1104 public static function addPlaylist($xml, $playlist, $songs = false) 1105 { 1106 $playlist_id = (string)self::getPlaylistId($playlist->id); 1107 $songcount = $playlist->get_media_count('song'); 1108 $duration = ($songcount > 0) ? $playlist->get_total_duration() : 0; 1109 $xplaylist = $xml->addChild('playlist'); 1110 $xplaylist->addAttribute('id', $playlist_id); 1111 $xplaylist->addAttribute('name', (string)self::checkName($playlist->get_fullname())); 1112 $xplaylist->addAttribute('owner', (string)$playlist->username); 1113 $xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false"); 1114 $xplaylist->addAttribute('created', date("c", (int)$playlist->date)); 1115 $xplaylist->addAttribute('changed', date("c", (int)$playlist->last_update)); 1116 $xplaylist->addAttribute('songCount', (string)$songcount); 1117 $xplaylist->addAttribute('duration', (string)$duration); 1118 $xplaylist->addAttribute('coverArt', $playlist_id); 1119 1120 if ($songs) { 1121 $allsongs = $playlist->get_songs(); 1122 foreach ($allsongs as $songId) { 1123 self::addSong($xplaylist, $songId, "entry"); 1124 } 1125 } 1126 } 1127 1128 /** 1129 * addPlayQueue 1130 * current="133" position="45000" username="admin" changed="2015-02-18T15:22:22.825Z" changedBy="android" 1131 * @param SimpleXMLElement $xml 1132 * @param int $user_id 1133 * @param string $username 1134 */ 1135 public static function addPlayQueue($xml, $user_id, $username) 1136 { 1137 $PlayQueue = new User_Playlist($user_id); 1138 $items = $PlayQueue->get_items(); 1139 if (!empty($items)) { 1140 $current = $PlayQueue->get_current_object(); 1141 $changed = User::get_user_data($user_id, 'playqueue_date')['playqueue_date']; 1142 $changedBy = User::get_user_data($user_id, 'playqueue_client')['playqueue_date']; 1143 $xplayqueue = $xml->addChild('playQueue'); 1144 $xplayqueue->addAttribute('current', self::getSongId($current['object_id'])); 1145 $xplayqueue->addAttribute('position', (string)$current['current_time']); 1146 $xplayqueue->addAttribute('username', (string)$username); 1147 $xplayqueue->addAttribute('changed', date("c", (int)$changed)); 1148 $xplayqueue->addAttribute('changedBy', (string)$changedBy); 1149 1150 if ($items) { 1151 foreach ($items as $row) { 1152 self::addSong($xplayqueue, (int)$row['object_id'], "entry"); 1153 } 1154 } 1155 } 1156 } 1157 1158 /** 1159 * addSmartPlaylist 1160 * @param SimpleXMLElement $xml 1161 * @param Search $playlist 1162 * @param boolean $songs 1163 */ 1164 public static function addSmartPlaylist($xml, $playlist, $songs = false) 1165 { 1166 $playlist_id = (string) self::getSmartPlId($playlist->id); 1167 $xplaylist = $xml->addChild('playlist'); 1168 debug_event(self::class, 'addsmartplaylist ' . $playlist->id, 5); 1169 $xplaylist->addAttribute('id', $playlist_id); 1170 $xplaylist->addAttribute('name', (string) self::checkName($playlist->get_fullname())); 1171 $xplaylist->addAttribute('owner', (string)$playlist->username); 1172 $xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false"); 1173 $xplaylist->addAttribute('created', date("c", (int)$playlist->date)); 1174 $xplaylist->addAttribute('changed', date("c", time())); 1175 1176 if ($songs) { 1177 $allitems = $playlist->get_items(); 1178 $xplaylist->addAttribute('songCount', (string)count($allitems)); 1179 $duration = (count($allitems) > 0) ? Search::get_total_duration($allitems) : 0; 1180 $xplaylist->addAttribute('duration', (string)$duration); 1181 $xplaylist->addAttribute('coverArt', $playlist_id); 1182 foreach ($allitems as $item) { 1183 self::addSong($xplaylist, (int)$item['object_id'], "entry"); 1184 } 1185 } else { 1186 $xplaylist->addAttribute('songCount', (string)$playlist->last_count); 1187 $xplaylist->addAttribute('duration', (string)$playlist->last_duration); 1188 $xplaylist->addAttribute('coverArt', $playlist_id); 1189 } 1190 } 1191 1192 /** 1193 * addRandomSongs 1194 * @param SimpleXMLElement $xml 1195 * @param array $songs 1196 */ 1197 public static function addRandomSongs($xml, $songs) 1198 { 1199 $xsongs = $xml->addChild('randomSongs'); 1200 foreach ($songs as $songid) { 1201 self::addSong($xsongs, $songid); 1202 } 1203 } 1204 1205 /** 1206 * addSongsByGenre 1207 * @param SimpleXMLElement $xml 1208 * @param array $songs 1209 */ 1210 public static function addSongsByGenre($xml, $songs) 1211 { 1212 $xsongs = $xml->addChild('songsByGenre'); 1213 foreach ($songs as $songid) { 1214 self::addSong($xsongs, $songid); 1215 } 1216 } 1217 1218 /** 1219 * addTopSongs 1220 * @param SimpleXMLElement $xml 1221 * @param array $songs 1222 */ 1223 public static function addTopSongs($xml, $songs) 1224 { 1225 $xsongs = $xml->addChild('topSongs'); 1226 foreach ($songs as $songid) { 1227 self::addSong($xsongs, $songid); 1228 } 1229 } 1230 1231 /** 1232 * addNowPlaying 1233 * @param SimpleXMLElement $xml 1234 * @param array $data 1235 */ 1236 public static function addNowPlaying($xml, $data) 1237 { 1238 $xplaynow = $xml->addChild('nowPlaying'); 1239 foreach ($data as $d) { 1240 $track = self::addSong($xplaynow, $d['media']->getId(), "entry"); 1241 if ($track !== null) { 1242 $track->addAttribute('username', (string)$d['client']->username); 1243 $track->addAttribute('minutesAgo', 1244 (string)(abs((time() - ($d['expire'] - $d['media']->time)) / 60))); 1245 $track->addAttribute('playerId', (string)$d['agent']); 1246 } 1247 } 1248 } 1249 1250 /** 1251 * addSearchResult 1252 * @param SimpleXMLElement $xml 1253 * @param array $artists 1254 * @param array $albums 1255 * @param array $songs 1256 * @param string $elementName 1257 */ 1258 public static function addSearchResult($xml, $artists, $albums, $songs, $elementName = "searchResult2") 1259 { 1260 $xresult = $xml->addChild(htmlspecialchars($elementName)); 1261 foreach ($artists as $artistid) { 1262 $artist = new Artist($artistid); 1263 self::addArtist($xresult, $artist); 1264 } 1265 foreach ($albums as $albumid) { 1266 $album = new Album($albumid); 1267 self::addAlbum($xresult, $album); 1268 } 1269 foreach ($songs as $songid) { 1270 self::addSong($xresult, $songid); 1271 } 1272 } 1273 1274 /** 1275 * setIfStarred 1276 * @param SimpleXMLElement $xml 1277 * @param string $objectType 1278 * @param integer $object_id 1279 */ 1280 private static function setIfStarred($xml, $objectType, $object_id) 1281 { 1282 if (InterfaceImplementationChecker::is_library_item($objectType)) { 1283 if (AmpConfig::get('userflags')) { 1284 $starred = new Userflag($object_id, $objectType); 1285 if ($res = $starred->get_flag(null, true)) { 1286 $xml->addAttribute('starred', date("Y-m-d\TH:i:s\Z", (int)$res[1])); 1287 } 1288 } 1289 } 1290 } 1291 1292 /** 1293 * addStarred 1294 * @param SimpleXMLElement $xml 1295 * @param array $artists 1296 * @param array $albums 1297 * @param array $songs 1298 * @param string $elementName 1299 */ 1300 public static function addStarred($xml, $artists, $albums, $songs, $elementName = "starred") 1301 { 1302 $xstarred = $xml->addChild(htmlspecialchars($elementName)); 1303 1304 foreach ($artists as $artistid) { 1305 $artist = new Artist($artistid); 1306 self::addArtist($xstarred, $artist); 1307 } 1308 1309 foreach ($albums as $albumid) { 1310 $album = new Album($albumid); 1311 self::addAlbum($xstarred, $album); 1312 } 1313 1314 foreach ($songs as $songid) { 1315 self::addSong($xstarred, $songid); 1316 } 1317 } 1318 1319 /** 1320 * addUser 1321 * @param SimpleXMLElement $xml 1322 * @param User $user 1323 */ 1324 public static function addUser($xml, $user) 1325 { 1326 $xuser = $xml->addChild('user'); 1327 $xuser->addAttribute('username', (string)$user->username); 1328 $xuser->addAttribute('email', (string)$user->email); 1329 $xuser->addAttribute('scrobblingEnabled', 'true'); 1330 $isManager = ($user->access >= 75); 1331 $isAdmin = ($user->access >= 100); 1332 $xuser->addAttribute('adminRole', $isAdmin ? 'true' : 'false'); 1333 $xuser->addAttribute('settingsRole', 'true'); 1334 $xuser->addAttribute('downloadRole', Preference::get_by_user($user->id, 'download') ? 'true' : 'false'); 1335 $xuser->addAttribute('playlistRole', 'true'); 1336 $xuser->addAttribute('coverArtRole', $isManager ? 'true' : 'false'); 1337 $xuser->addAttribute('commentRole', (AmpConfig::get('social')) ? 'true' : 'false'); 1338 $xuser->addAttribute('podcastRole', (AmpConfig::get('podcast')) ? 'true' : 'false'); 1339 $xuser->addAttribute('streamRole', 'true'); 1340 $xuser->addAttribute('jukeboxRole', (AmpConfig::get('allow_localplay_playback') && AmpConfig::get('localplay_controller') && Access::check('localplay', 5)) ? 'true' : 'false'); 1341 $xuser->addAttribute('shareRole', Preference::get_by_user($user->id, 'share') ? 'true' : 'false'); 1342 } 1343 1344 /** 1345 * addUsers 1346 * @param SimpleXMLElement $xml 1347 * @param array $users 1348 */ 1349 public static function addUsers($xml, $users) 1350 { 1351 $xusers = $xml->addChild('users'); 1352 foreach ($users as $userid) { 1353 $user = new User($userid); 1354 self::addUser($xusers, $user); 1355 } 1356 } 1357 1358 /** 1359 * addRadio 1360 * @param SimpleXMLElement $xml 1361 * @param Live_Stream $radio 1362 */ 1363 public static function addRadio($xml, $radio) 1364 { 1365 $xradio = $xml->addChild('internetRadioStation '); 1366 $xradio->addAttribute('id', (string)$radio->id); 1367 $xradio->addAttribute('name', (string)self::checkName($radio->name)); 1368 $xradio->addAttribute('streamUrl', (string)$radio->url); 1369 $xradio->addAttribute('homePageUrl', (string)$radio->site_url); 1370 } 1371 1372 /** 1373 * addRadios 1374 * @param SimpleXMLElement $xml 1375 * @param $radios 1376 */ 1377 public static function addRadios($xml, $radios) 1378 { 1379 $xradios = $xml->addChild('internetRadioStations'); 1380 foreach ($radios as $radioid) { 1381 $radio = new Live_Stream($radioid); 1382 self::addRadio($xradios, $radio); 1383 } 1384 } 1385 1386 /** 1387 * addShare 1388 * @param SimpleXMLElement $xml 1389 * @param Share $share 1390 */ 1391 public static function addShare($xml, $share) 1392 { 1393 $xshare = $xml->addChild('share'); 1394 $xshare->addAttribute('id', (string)$share->id); 1395 $xshare->addAttribute('url', (string)$share->public_url); 1396 $xshare->addAttribute('description', (string)$share->description); 1397 $user = new User($share->user); 1398 $xshare->addAttribute('username', (string)$user->username); 1399 $xshare->addAttribute('created', date("c", (int)$share->creation_date)); 1400 if ($share->lastvisit_date > 0) { 1401 $xshare->addAttribute('lastVisited', date("c", (int)$share->lastvisit_date)); 1402 } 1403 if ($share->expire_days > 0) { 1404 $xshare->addAttribute('expires', date("c", (int)$share->creation_date + ($share->expire_days * 86400))); 1405 } 1406 $xshare->addAttribute('visitCount', (string)$share->counter); 1407 1408 if ($share->object_type == 'song') { 1409 self::addSong($xshare, $share->object_id, "entry"); 1410 } elseif ($share->object_type == 'playlist') { 1411 $playlist = new Playlist($share->object_id); 1412 $songs = $playlist->get_songs(); 1413 foreach ($songs as $songid) { 1414 self::addSong($xshare, $songid, "entry"); 1415 } 1416 } elseif ($share->object_type == 'album') { 1417 $songs = static::getSongRepository()->getByAlbum($share->object_id); 1418 foreach ($songs as $songid) { 1419 self::addSong($xshare, $songid, "entry"); 1420 } 1421 } 1422 } 1423 1424 /** 1425 * addShares 1426 * @param SimpleXMLElement $xml 1427 * @param array $shares 1428 */ 1429 public static function addShares($xml, $shares) 1430 { 1431 $xshares = $xml->addChild('shares'); 1432 foreach ($shares as $share_id) { 1433 $share = new Share($share_id); 1434 // Don't add share with max counter already reached 1435 if ($share->max_counter == 0 || $share->counter < $share->max_counter) { 1436 self::addShare($xshares, $share); 1437 } 1438 } 1439 } 1440 1441 /** 1442 * addJukeboxPlaylist 1443 * @param SimpleXMLElement $xml 1444 * @param LocalPlay $localplay 1445 */ 1446 public static function addJukeboxPlaylist($xml, LocalPlay $localplay) 1447 { 1448 $xjbox = self::createJukeboxStatus($xml, $localplay, 'jukeboxPlaylist'); 1449 $tracks = $localplay->get(); 1450 foreach ($tracks as $track) { 1451 if ($track['oid']) { 1452 self::addSong($xjbox, (int)$track['oid'], 'entry'); 1453 } 1454 } 1455 } 1456 1457 /** 1458 * createJukeboxStatus 1459 * @param SimpleXMLElement $xml 1460 * @param LocalPlay $localplay 1461 * @param string $elementName 1462 * @return SimpleXMLElement 1463 */ 1464 public static function createJukeboxStatus($xml, LocalPlay $localplay, $elementName = 'jukeboxStatus') 1465 { 1466 $xjbox = $xml->addChild(htmlspecialchars($elementName)); 1467 $status = $localplay->status(); 1468 $xjbox->addAttribute('currentIndex', 0); // Not supported 1469 $xjbox->addAttribute('playing', ($status['state'] == 'play') ? 'true' : 'false'); 1470 $xjbox->addAttribute('gain', (string)$status['volume']); 1471 $xjbox->addAttribute('position', 0); // Not supported 1472 1473 return $xjbox; 1474 } 1475 1476 /** 1477 * addLyrics 1478 * @param SimpleXMLElement $xml 1479 * @param $artist 1480 * @param $title 1481 * @param $song_id 1482 */ 1483 public static function addLyrics($xml, $artist, $title, $song_id) 1484 { 1485 $song = new Song($song_id); 1486 $song->fill_ext_info('lyrics'); 1487 $lyrics = $song->get_lyrics(); 1488 1489 if (!empty($lyrics) && $lyrics['text']) { 1490 $text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $lyrics['text']); 1491 $text = str_replace("\r", '', (string)$text); 1492 $xlyrics = $xml->addChild('lyrics', htmlspecialchars($text)); 1493 if ($artist) { 1494 $xlyrics->addAttribute('artist', (string)$artist); 1495 } 1496 if ($title) { 1497 $xlyrics->addAttribute('title', (string)$title); 1498 } 1499 } 1500 } 1501 1502 /** 1503 * addArtistInfo 1504 * @param SimpleXMLElement $xml 1505 * @param array $info 1506 * @param array $similars 1507 * @param string $child 1508 */ 1509 public static function addArtistInfo($xml, $info, $similars, $child) 1510 { 1511 $artist = new Artist($info['id']); 1512 1513 $xartist = $xml->addChild(htmlspecialchars($child)); 1514 $xartist->addChild('biography', htmlspecialchars(trim((string)$info['summary']))); 1515 $xartist->addChild('musicBrainzId', $artist->mbid); 1516 //$xartist->addChild('lastFmUrl', ""); 1517 $xartist->addChild('smallImageUrl', htmlentities($info['smallphoto'])); 1518 $xartist->addChild('mediumImageUrl', htmlentities($info['mediumphoto'])); 1519 $xartist->addChild('largeImageUrl', htmlentities($info['largephoto'])); 1520 1521 foreach ($similars as $similar) { 1522 $xsimilar = $xartist->addChild('similarArtist'); 1523 $xsimilar->addAttribute('id', ($similar['id'] !== null ? self::getArtistId($similar['id']) : "-1")); 1524 $xsimilar->addAttribute('name', (string)self::checkName($similar['name'])); 1525 } 1526 } 1527 1528 /** 1529 * addSimilarSongs 1530 * @param SimpleXMLElement $xml 1531 * @param array $similar_songs 1532 * @param string $child 1533 */ 1534 public static function addSimilarSongs($xml, $similar_songs, $child) 1535 { 1536 $xsimilar = $xml->addChild(htmlspecialchars($child)); 1537 foreach ($similar_songs as $similar_song) { 1538 if ($similar_song['id'] !== null) { 1539 self::addSong($xsimilar, $similar_song['id']); 1540 } 1541 } 1542 } 1543 1544 /** 1545 * addPodcasts 1546 * @param SimpleXMLElement $xml 1547 * @param Podcast[] $podcasts 1548 * @param boolean $includeEpisodes 1549 */ 1550 public static function addPodcasts($xml, $podcasts, $includeEpisodes = true) 1551 { 1552 $xpodcasts = $xml->addChild('podcasts'); 1553 foreach ($podcasts as $podcast) { 1554 $podcast->format(); 1555 $xchannel = $xpodcasts->addChild('channel'); 1556 $xchannel->addAttribute('id', (string)self::getPodcastId($podcast->id)); 1557 $xchannel->addAttribute('url', (string)$podcast->feed); 1558 $xchannel->addAttribute('title', (string)self::checkName($podcast->f_title)); 1559 $xchannel->addAttribute('description', (string)$podcast->f_description); 1560 if (Art::has_db($podcast->id, 'podcast')) { 1561 $xchannel->addAttribute('coverArt', 'pod-' . self::getPodcastId($podcast->id)); 1562 } 1563 $xchannel->addAttribute('status', 'completed'); 1564 if ($includeEpisodes) { 1565 $episodes = $podcast->get_episodes(); 1566 foreach ($episodes as $episode_id) { 1567 $episode = new Podcast_Episode($episode_id); 1568 self::addPodcastEpisode($xchannel, $episode); 1569 } 1570 } 1571 } 1572 } 1573 1574 /** 1575 * addPodcastEpisode 1576 * @param SimpleXMLElement $xml 1577 * @param Podcast_Episode $episode 1578 * @param string $elementName 1579 */ 1580 private static function addPodcastEpisode($xml, $episode, $elementName = 'episode') 1581 { 1582 $episode->format(); 1583 $xepisode = $xml->addChild(htmlspecialchars($elementName)); 1584 $xepisode->addAttribute('id', (string)self::getPodcastEpId($episode->id)); 1585 $xepisode->addAttribute('channelId', (string)self::getPodcastId($episode->podcast)); 1586 $xepisode->addAttribute('title', (string)self::checkName($episode->f_title)); 1587 $xepisode->addAttribute('album', (string)$episode->f_podcast); 1588 $xepisode->addAttribute('description', (string)self::checkName($episode->f_description)); 1589 $xepisode->addAttribute('duration', (string)$episode->time); 1590 $xepisode->addAttribute('genre', "Podcast"); 1591 $xepisode->addAttribute('isDir', "false"); 1592 $xepisode->addAttribute('publishDate', $episode->f_pubdate); 1593 $xepisode->addAttribute('status', (string)$episode->state); 1594 $xepisode->addAttribute('parent', (string)self::getPodcastId($episode->podcast)); 1595 if (Art::has_db($episode->podcast, 'podcast')) { 1596 $xepisode->addAttribute('coverArt', (string)self::getPodcastId($episode->podcast)); 1597 } 1598 1599 self::setIfStarred($xepisode, 'podcast_episode', $episode->id); 1600 1601 if ($episode->file) { 1602 $xepisode->addAttribute('streamId', (string)self::getPodcastEpId($episode->id)); 1603 $xepisode->addAttribute('size', (string)$episode->size); 1604 $xepisode->addAttribute('suffix', (string)$episode->type); 1605 $xepisode->addAttribute('contentType', (string)$episode->mime); 1606 // Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients 1607 $path = basename($episode->file); 1608 $xepisode->addAttribute('path', (string)$path); 1609 } 1610 } 1611 1612 /** 1613 * addNewestPodcastEpisodes 1614 * @param SimpleXMLElement $xml 1615 * @param Podcast_Episode[] $episodes 1616 */ 1617 public static function addNewestPodcastEpisodes($xml, $episodes) 1618 { 1619 $xpodcasts = $xml->addChild('newestPodcasts'); 1620 foreach ($episodes as $episode) { 1621 $episode->format(); 1622 self::addPodcastEpisode($xpodcasts, $episode); 1623 } 1624 } 1625 1626 /** 1627 * addBookmarks 1628 * @param SimpleXMLElement $xml 1629 * @param Bookmark[] $bookmarks 1630 */ 1631 public static function addBookmarks($xml, $bookmarks) 1632 { 1633 $xbookmarks = $xml->addChild('bookmarks'); 1634 foreach ($bookmarks as $bookmark) { 1635 self::addBookmark($xbookmarks, $bookmark); 1636 } 1637 } 1638 1639 /** 1640 * addBookmark 1641 * @param SimpleXMLElement $xml 1642 * @param Bookmark $bookmark 1643 */ 1644 private static function addBookmark($xml, $bookmark) 1645 { 1646 $xbookmark = $xml->addChild('bookmark'); 1647 $xbookmark->addAttribute('position', (string)$bookmark->position); 1648 $xbookmark->addAttribute('username', (string)$bookmark->getUserName()); 1649 $xbookmark->addAttribute('comment', (string)$bookmark->comment); 1650 $xbookmark->addAttribute('created', date("c", (int)$bookmark->creation_date)); 1651 $xbookmark->addAttribute('changed', date("c", (int)$bookmark->update_date)); 1652 if ($bookmark->object_type == "song") { 1653 $song = new Song($bookmark->object_id); 1654 self::addSong($xbookmark, $song->id, 'entry'); 1655 } elseif ($bookmark->object_type == "video") { 1656 self::addVideo($xbookmark, new Video($bookmark->object_id), 'entry'); 1657 } elseif ($bookmark->object_type == "podcast_episode") { 1658 self::addPodcastEpisode($xbookmark, new Podcast_Episode($bookmark->object_id), 'entry'); 1659 } 1660 } 1661 1662 /** 1663 * addMessages 1664 * @param SimpleXMLElement $xml 1665 * @param integer[] $messages 1666 */ 1667 public static function addMessages($xml, $messages) 1668 { 1669 $xmessages = $xml->addChild('chatMessages'); 1670 if (empty($messages)) { 1671 return; 1672 } 1673 foreach ($messages as $message) { 1674 $chat = new PrivateMsg($message); 1675 self::addMessage($xmessages, $chat); 1676 } 1677 } 1678 1679 /** 1680 * addMessage 1681 * @param SimpleXMLElement $xml 1682 * @param PrivateMsg $message 1683 */ 1684 private static function addMessage($xml, $message) 1685 { 1686 $user = new User($message->getSenderUserId()); 1687 $xbookmark = $xml->addChild('chatMessage'); 1688 if ($user->fullname_public) { 1689 $xbookmark->addAttribute('username', (string)$user->fullname); 1690 } else { 1691 $xbookmark->addAttribute('username', (string)$user->username); 1692 } 1693 $xbookmark->addAttribute('time', (string)($message->getCreationDate() * 1000)); 1694 $xbookmark->addAttribute('message', (string)$message->getMessage()); 1695 } 1696 1697 /** 1698 * @deprecated 1699 */ 1700 private static function getSongRepository(): SongRepositoryInterface 1701 { 1702 global $dic; 1703 1704 return $dic->get(SongRepositoryInterface::class); 1705 } 1706 1707 /** 1708 * @deprecated 1709 */ 1710 private static function getAlbumRepository(): AlbumRepositoryInterface 1711 { 1712 global $dic; 1713 1714 return $dic->get(AlbumRepositoryInterface::class); 1715 } 1716} 1717