src/Module/Api/Subsonic_Xml_Data.php
<?php
declare(strict_types=0);
/**
* vim:set softtabstop=4 shiftwidth=4 expandtab:
*
* LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
* Copyright Ampache.org, 2001-2023
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Ampache\Module\Api;
use Ampache\Config\AmpConfig;
use Ampache\Module\Authorization\Access;
use Ampache\Module\Playback\Localplay\LocalPlay;
use Ampache\Module\Playback\Stream;
use Ampache\Module\Util\InterfaceImplementationChecker;
use Ampache\Repository\AlbumRepositoryInterface;
use Ampache\Repository\Model\Album;
use Ampache\Repository\Model\Artist;
use Ampache\Repository\Model\Bookmark;
use Ampache\Repository\Model\Catalog;
use Ampache\Repository\Model\Live_Stream;
use Ampache\Repository\Model\Playlist;
use Ampache\Repository\Model\Podcast;
use Ampache\Repository\Model\Podcast_Episode;
use Ampache\Repository\Model\Preference;
use Ampache\Repository\Model\PrivateMsg;
use Ampache\Repository\Model\Rating;
use Ampache\Repository\Model\Search;
use Ampache\Repository\Model\Share;
use Ampache\Repository\Model\Song;
use Ampache\Repository\Model\Tag;
use Ampache\Repository\Model\User;
use Ampache\Repository\Model\User_Playlist;
use Ampache\Repository\Model\Userflag;
use Ampache\Repository\Model\Video;
use Ampache\Repository\PodcastRepositoryInterface;
use Ampache\Repository\SongRepositoryInterface;
use DateTime;
use DateTimeZone;
use SimpleXMLElement;
/**
* Subsonic_Xml_Data Class
*
* This class takes care of all of the xml document stuff for SubSonic Responses
*/
class Subsonic_Xml_Data
{
public const API_VERSION = "1.16.1";
public const SSERROR_GENERIC = 0;
public const SSERROR_MISSINGPARAM = 10;
public const SSERROR_APIVERSION_CLIENT = 20;
public const SSERROR_APIVERSION_SERVER = 30;
public const SSERROR_BADAUTH = 40;
public const SSERROR_TOKENAUTHNOTSUPPORTED = 41;
public const SSERROR_UNAUTHORIZED = 50;
public const SSERROR_TRIAL = 60;
public const SSERROR_DATA_NOTFOUND = 70;
// Ampache doesn't have a global unique id but each items are unique per category. We use id pattern to identify item category.
public const AMPACHEID_ARTIST = 100000000;
public const AMPACHEID_ALBUM = 200000000;
public const AMPACHEID_SONG = 300000000;
public const AMPACHEID_SMARTPL = 400000000;
public const AMPACHEID_VIDEO = 500000000;
public const AMPACHEID_PODCAST = 600000000;
public const AMPACHEID_PODCASTEP = 700000000;
public const AMPACHEID_PLAYLIST = 800000000;
public static $enable_json_checks = false;
/**
* addSubsonicResponse
* @param string $function
* @return SimpleXMLElement
*/
public static function addSubsonicResponse($function): SimpleXMLElement
{
return self::_createSuccessResponse($function);
}
/**
* addError
* Add a failed subsonic-response with error information.
*
* @param int $code Error code
* @param string $function
* @return SimpleXMLElement
*/
public static function addError($code, $function): SimpleXMLElement
{
$xml = self::_createFailedResponse($function);
/** @var SimpleXMLElement $xerr */
$xerr = self::addChildToResultXml($xml, 'error');
$xerr->addAttribute('code', (string)$code);
$message = "A generic error.";
switch ($code) {
case self::SSERROR_MISSINGPARAM:
$message = "Required parameter is missing.";
break;
case self::SSERROR_APIVERSION_CLIENT:
$message = "Incompatible Subsonic REST protocol version. Client must upgrade.";
break;
case self::SSERROR_APIVERSION_SERVER:
$message = "Incompatible Subsonic REST protocol version. Server must upgrade.";
break;
case self::SSERROR_BADAUTH:
$message = "Wrong username or password.";
break;
case self::SSERROR_TOKENAUTHNOTSUPPORTED:
$message = "Token authentication not supported.";
break;
case self::SSERROR_UNAUTHORIZED:
$message = "User is not authorized for the given operation.";
break;
case self::SSERROR_TRIAL:
$message = "The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium. Visit subsonic.org for details.";
break;
case self::SSERROR_DATA_NOTFOUND:
$message = "The requested data was not found.";
break;
}
$xerr->addAttribute('message', (string)$message);
return $xml;
}
/**
* addLicense
* @param SimpleXMLElement $xml
*/
public static function addLicense($xml): void
{
$xlic = self::addChildToResultXml($xml, 'license');
$xlic->addAttribute('valid', 'true');
$xlic->addAttribute('email', 'webmaster@ampache.org');
}
/**
* addMusicFolders
* @param SimpleXMLElement $xml
* @param int[] $catalogs
*/
public static function addMusicFolders($xml, $catalogs): void
{
$xfolders = self::addChildToResultXml($xml, 'musicFolders');
foreach ($catalogs as $folder_id) {
$catalog = Catalog::create_from_id($folder_id);
if ($catalog === null) {
break;
}
$xfolder = self::addChildToResultXml($xfolders, 'musicFolder');
$xfolder->addAttribute('id', (string)$folder_id);
$xfolder->addAttribute('name', (string)$catalog->name);
}
}
/**
* addIndexes
* @param SimpleXMLElement $xml
* @param array $artists
* @param int|null $lastModified
*/
public static function addIndexes($xml, $artists, $lastModified = 0): void
{
$xindexes = self::addChildToResultXml($xml, 'indexes');
$xindexes->addAttribute('lastModified', number_format($lastModified * 1000, 0, '.', ''));
self::addIgnoredArticles($xindexes);
self::addIndex($xindexes, $artists);
}
/**
* addIgnoredArticles
* @param SimpleXMLElement $xml
*/
private static function addIgnoredArticles($xml): void
{
$ignoredArticles = AmpConfig::get('catalog_prefix_pattern', 'The|An|A|Die|Das|Ein|Eine|Les|Le|La');
if (!empty($ignoredArticles)) {
$ignoredArticles = str_replace("|", " ", $ignoredArticles);
$xml->addAttribute('ignoredArticles', (string)$ignoredArticles);
}
}
/**
* addIndex
* @param SimpleXMLElement $xml
* @param array $artists
*/
private static function addIndex($xml, $artists): void
{
$xlastcat = null;
$sharpartists = array();
$xlastletter = '';
foreach ($artists as $artist) {
if (strlen((string)$artist['name']) > 0) {
$letter = strtoupper((string)$artist['name'][0]);
if ($letter == "X" || $letter == "Y" || $letter == "Z") {
$letter = "X-Z";
} else {
if (!preg_match("/^[A-W]$/", $letter)) {
$sharpartists[] = $artist;
continue;
}
}
if ($letter != $xlastletter) {
$xlastletter = $letter;
$xlastcat = self::addChildToResultXml($xml, 'index');
$xlastcat->addAttribute('name', (string)$xlastletter);
}
}
if ($xlastcat != null) {
self::addArtistArray($xlastcat, $artist);
}
}
// Always add # index at the end
if (count($sharpartists) > 0) {
$xsharpcat = self::addChildToResultXml($xml, 'index');
$xsharpcat->addAttribute('name', '#');
foreach ($sharpartists as $artist) {
self::addArtistArray($xsharpcat, $artist);
}
}
}
/**
* addArtists
* @param SimpleXMLElement $xml
* @param array $artists
*/
public static function addArtists($xml, $artists): void
{
$xartists = self::addChildToResultXml($xml, 'artists');
self::addIgnoredArticles($xartists);
self::addIndex($xartists, $artists);
}
/**
* addArtist
* @param SimpleXMLElement $xml
* @param Artist $artist
* @param bool $extra
* @param bool $albums
* @param bool $albumsSet
*/
public static function addArtist($xml, $artist, $extra = false, $albums = false, $albumsSet = false): void
{
if ($artist->isNew()) {
return;
}
$artist->format();
$sub_id = (string)self::_getArtistId($artist->id);
$xartist = self::addChildToResultXml($xml, 'artist');
$xartist->addAttribute('id', $sub_id);
$xartist->addAttribute('name', (string)self::_checkName($artist->get_fullname()));
$allalbums = array();
if (($extra && !$albumsSet) || $albums) {
$allalbums = static::getAlbumRepository()->getAlbumByArtist($artist->id);
}
if ($extra) {
if ($artist->has_art()) {
$xartist->addAttribute('coverArt', 'ar-' . $sub_id);
}
if ($albumsSet) {
$xartist->addAttribute('albumCount', (string)$artist->album_count);
} else {
$xartist->addAttribute('albumCount', (string)count($allalbums));
}
self::_setIfStarred($xartist, 'artist', $artist->id);
}
if ($albums) {
foreach ($allalbums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xartist, $album);
}
}
}
/**
* addChildArray
* @param SimpleXMLElement $xml
* @param array $child
*/
private static function addChildArray($xml, $child): void
{
$sub_id = (string)self::_getArtistId($child['id']);
$xchild = self::addChildToResultXml($xml, 'child');
$xchild->addAttribute('id', $sub_id);
if (array_key_exists('catalog_id', $child)) {
$xchild->addAttribute('parent', $child['catalog_id']);
}
$xchild->addAttribute('isDir', 'true');
$xchild->addAttribute('title', (string)self::_checkName($child['f_name']));
$xchild->addAttribute('artist', (string)self::_checkName($child['f_name']));
if (array_key_exists('has_art', $child) && !empty($child['has_art'])) {
$xchild->addAttribute('coverArt', 'ar-' . $sub_id);
}
}
/**
* addArtistArray
* @param SimpleXMLElement $xml
* @param array $artist
*/
private static function addArtistArray($xml, $artist): void
{
$sub_id = (string)self::_getArtistId($artist['id']);
$xartist = self::addChildToResultXml($xml, 'artist');
$xartist->addAttribute('id', $sub_id);
$xartist->addAttribute('name', (string)self::_checkName($artist['f_name']));
if (array_key_exists('has_art', $artist) && !empty($artist['has_art'])) {
$xartist->addAttribute('coverArt', 'ar-' . $sub_id);
}
$xartist->addAttribute('albumCount', (string)$artist['album_count']);
self::_setIfStarred($xartist, 'artist', $artist['id']);
}
/**
* addAlbumList
* @param SimpleXMLElement $xml
* @param array $albums
*/
public static function addAlbumList($xml, $albums): void
{
$xlist = self::addChildToResultXml($xml, htmlspecialchars('albumList'));
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xlist, $album);
}
}
/**
* addAlbumList2
* @param SimpleXMLElement $xml
* @param array $albums
*/
public static function addAlbumList2($xml, $albums): void
{
$xlist = self::addChildToResultXml($xml, htmlspecialchars('albumList2'));
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xlist, $album);
}
}
/**
* addAlbum
* @param SimpleXMLElement $xml
* @param Album $album
* @param bool $songs
* @param string $elementName
*/
public static function addAlbum($xml, $album, $songs = false, $elementName = "album"): void
{
if ($album->isNew()) {
return;
}
$album->format();
$sub_id = (string)self::_getAlbumId($album->id);
$xalbum = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$xalbum->addAttribute('id', $sub_id);
if ($album->album_artist) {
$xalbum->addAttribute('parent', (string)self::_getArtistId($album->album_artist));
}
$f_name = (string)self::_checkName($album->get_fullname());
$xalbum->addAttribute('album', $f_name);
$xalbum->addAttribute('title', $f_name);
$xalbum->addAttribute('name', $f_name);
$xalbum->addAttribute('isDir', 'true');
//$xalbum->addAttribute('discNumber', (string)$album->disk);
if ($album->has_art()) {
$xalbum->addAttribute('coverArt', 'al-' . $sub_id);
}
$xalbum->addAttribute('songCount', (string) $album->song_count);
$xalbum->addAttribute('created', date("c", (int)$album->addition_time));
$xalbum->addAttribute('duration', (string) $album->total_duration);
$xalbum->addAttribute('playCount', (string)$album->total_count);
if ($album->album_artist) {
$xalbum->addAttribute('artistId', (string)self::_getArtistId($album->album_artist));
}
$xalbum->addAttribute('artist', (string) self::_checkName($album->get_artist_fullname()));
// original year (fall back to regular year)
$original_year = AmpConfig::get('use_original_year');
$year = ($original_year && $album->original_year)
? $album->original_year
: $album->year;
if ($year > 0) {
$xalbum->addAttribute('year', (string)$year);
}
if (count($album->tags) > 0) {
$tag_values = array_values($album->tags);
$tag = array_shift($tag_values);
$xalbum->addAttribute('genre', (string)$tag['name']);
}
$rating = new Rating($album->id, "album");
$user_rating = ($rating->get_user_rating() ?? 0);
if ($user_rating > 0) {
$xalbum->addAttribute('userRating', (string)ceil($user_rating));
}
$avg_rating = $rating->get_average_rating();
if ($avg_rating > 0) {
$xalbum->addAttribute('averageRating', (string)$avg_rating);
}
self::_setIfStarred($xalbum, 'album', $album->id);
if ($songs) {
$media_ids = static::getAlbumRepository()->getSongs($album->id);
foreach ($media_ids as $song_id) {
self::addSong($xalbum, $song_id);
}
}
}
/**
* addSong
* @param SimpleXMLElement $xml
* @param int $song_id
* @param string $elementName
* @return SimpleXMLElement
*/
public static function addSong($xml, $song_id, $elementName = 'song'): SimpleXMLElement
{
$song = new Song($song_id);
if ($song->isNew()) {
return $xml;
}
// Don't create entries for disabled songs
if ($song->enabled) {
$sub_id = (string)self::_getSongId($song->id);
$subParent = (string)self::_getAlbumId($song->album);
$xsong = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$xsong->addAttribute('id', $sub_id);
$xsong->addAttribute('parent', $subParent);
//$xsong->addAttribute('created', );
$xsong->addAttribute('title', (string)self::_checkName($song->title));
$xsong->addAttribute('isDir', 'false');
$xsong->addAttribute('isVideo', 'false');
$xsong->addAttribute('type', 'music');
$xsong->addAttribute('albumId', $subParent);
$xsong->addAttribute('album', (string)self::_checkName($song->get_album_fullname()));
// $artist = new Artist($song->artist);
// $artist->format();
$xsong->addAttribute('artistId', ($song->artist) ? (string)self::_getArtistId($song->artist) : '');
$xsong->addAttribute('artist', (string)self::_checkName($song->get_artist_fullname()));
if ($song->has_art()) {
$art_id = (AmpConfig::get('show_song_art', false)) ? $sub_id : $subParent;
$xsong->addAttribute('coverArt', $art_id);
}
$xsong->addAttribute('duration', (string)$song->time);
$xsong->addAttribute('bitRate', (string)((int)($song->bitrate / 1024)));
$rating = new Rating($song->id, "song");
$user_rating = ($rating->get_user_rating() ?? 0);
if ($user_rating > 0) {
$xsong->addAttribute('userRating', (string)ceil($user_rating));
}
$avg_rating = $rating->get_average_rating();
if ($avg_rating > 0) {
$xsong->addAttribute('averageRating', (string)$avg_rating);
}
self::_setIfStarred($xsong, 'song', $song->id);
if ($song->track > 0) {
$xsong->addAttribute('track', (string)$song->track);
}
if ($song->year > 0) {
$xsong->addAttribute('year', (string)$song->year);
}
$tags = Tag::get_object_tags('song', (int)$song->id);
if (is_array($tags) && count($tags) > 0) {
$xsong->addAttribute('genre', (string)$tags[0]['name']);
}
$xsong->addAttribute('size', (string)$song->size);
$disk = $song->disk;
if ($disk > 0) {
$xsong->addAttribute('discNumber', (string)$disk);
}
$xsong->addAttribute('suffix', (string)$song->type);
$xsong->addAttribute('contentType', (string)$song->mime);
// Always return the original filename, not the transcoded one
$xsong->addAttribute('path', (string)$song->file);
if (AmpConfig::get('transcode') != 'never') {
$cache_path = (string)AmpConfig::get('cache_path', '');
$cache_target = (string)AmpConfig::get('cache_target', '');
$file_target = Catalog::get_cache_path($song->getId(), $song->getCatalogId(), $cache_path, $cache_target);
$transcode_type = ($file_target !== null && is_file($file_target))
? $cache_target
: Stream::get_transcode_format($song->type, null, 'api');
if (!empty($transcode_type) && $song->type !== $transcode_type) {
// Set transcoding information
$xsong->addAttribute('transcodedSuffix', $transcode_type);
$xsong->addAttribute('transcodedContentType', Song::type_to_mime($transcode_type));
}
}
return $xsong;
}
return $xml;
}
/**
* addDirectory will create the directory element based on the type
* @param SimpleXMLElement $xml
* @param string $sub_id
* @param string $dirType
*/
public static function addDirectory($xml, $sub_id, $dirType): void
{
switch ($dirType) {
case 'artist':
self::addDirectory_Artist($xml, $sub_id);
break;
case 'album':
self::addDirectory_Album($xml, $sub_id);
break;
case 'catalog':
self::addDirectory_Catalog($xml, $sub_id);
break;
}
}
/**
* addDirectory_Artist for subsonic artist id
* @param SimpleXMLElement $xml
* @param string $sub_id
*/
private static function addDirectory_Artist($xml, $sub_id): void
{
$artist_id = self::_getAmpacheId($sub_id);
$data = Artist::get_id_array($artist_id);
$xdir = self::addChildToResultXml($xml, 'directory');
$xdir->addAttribute('id', (string)$sub_id);
if (array_key_exists('catalog_id', $data)) {
$xdir->addAttribute('parent', (string)$data['catalog_id']);
}
$xdir->addAttribute('name', (string)$data['f_name']);
self::_setIfStarred($xdir, 'artist', $artist_id);
$allalbums = static::getAlbumRepository()->getAlbumByArtist($artist_id);
foreach ($allalbums as $album_id) {
$album = new Album($album_id);
// TODO addChild || use addChildArray
self::addAlbum($xdir, $album, false, "child");
}
}
/**
* addDirectory_Album for subsonic album id
* @param SimpleXMLElement $xml
* @param string $album_id
*/
private static function addDirectory_Album($xml, $album_id): void
{
$album = new Album(self::_getAmpacheId($album_id));
$album->format();
/** @var SimpleXMLElement $xdir */
$xdir = self::addChildToResultXml($xml, 'directory');
$xdir->addAttribute('id', (string)$album_id);
if ($album->album_artist) {
$xdir->addAttribute('parent', (string)self::_getArtistId($album->album_artist));
} else {
$xdir->addAttribute('parent', (string)$album->catalog);
}
$xdir->addAttribute('name', (string)self::_checkName($album->get_fullname()));
self::_setIfStarred($xdir, 'album', $album->id);
$media_ids = static::getAlbumRepository()->getSongs($album->id);
foreach ($media_ids as $song_id) {
// TODO addChild || use addChildArray
self::addSong($xdir, $song_id, "child");
}
}
/**
* addDirectory_Catalog for subsonic artist id
* @param SimpleXMLElement $xml
* @param string $catalog_id
*/
private static function addDirectory_Catalog($xml, $catalog_id): void
{
$catalog = Catalog::create_from_id((int)$catalog_id);
if ($catalog === null) {
return;
}
$xdir = self::addChildToResultXml($xml, 'directory');
$xdir->addAttribute('id', (string)$catalog_id);
$xdir->addAttribute('name', (string)$catalog->name);
$allartists = Catalog::get_artist_arrays(array($catalog_id));
foreach ($allartists as $artist) {
self::addChildArray($xdir, $artist);
}
}
/**
* addGenres
* @param SimpleXMLElement $xml
* @param array $tags
*/
public static function addGenres($xml, $tags): void
{
$xgenres = self::addChildToResultXml($xml, 'genres');
foreach ($tags as $tag) {
$otag = new Tag($tag['id']);
$counts = $otag->count();
$xgenre = self::addChildToResultXml($xgenres, 'genre', htmlspecialchars((string)$otag->name));
$xgenre->addAttribute('songCount', (string)($counts['song'] ?? 0));
$xgenre->addAttribute('albumCount', (string)($counts['album'] ?? 0));
}
}
/**
* addVideos
* @param SimpleXMLElement $xml
* @param Video[] $videos
*/
public static function addVideos($xml, $videos): void
{
$xvideos = self::addChildToResultXml($xml, 'videos');
foreach ($videos as $video) {
$video->format();
self::addVideo($xvideos, $video);
}
}
/**
* addVideo
* @param SimpleXMLElement $xml
* @param Video $video
* @param string $elementName
*/
private static function addVideo($xml, $video, $elementName = 'video'): void
{
$sub_id = (string)self::_getVideoId($video->id);
$xvideo = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$xvideo->addAttribute('id', $sub_id);
$xvideo->addAttribute('title', $video->getFileName());
$xvideo->addAttribute('isDir', 'false');
if ($video->has_art()) {
$xvideo->addAttribute('coverArt', $sub_id);
}
$xvideo->addAttribute('isVideo', 'true');
$xvideo->addAttribute('type', 'video');
$xvideo->addAttribute('duration', (string)$video->time);
if (isset($video->year) && $video->year > 0) {
$xvideo->addAttribute('year', (string)$video->year);
}
$tags = Tag::get_object_tags('video', (int)$video->id);
if (is_array($tags) && count($tags) > 0) {
$xvideo->addAttribute('genre', (string)$tags[0]['name']);
}
$xvideo->addAttribute('size', (string)$video->size);
$xvideo->addAttribute('suffix', (string)$video->type);
$xvideo->addAttribute('contentType', (string)$video->mime);
// Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients
$path = basename($video->file);
$xvideo->addAttribute('path', (string)$path);
self::_setIfStarred($xvideo, 'video', $video->id);
// Set transcoding information if required
$transcode_cfg = AmpConfig::get('transcode');
$valid_types = Stream::get_stream_types_for_type($video->type, 'api');
if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) {
$transcode_settings = $video->get_transcode_settings(null, 'api');
if (!empty($transcode_settings)) {
$transcode_type = $transcode_settings['format'];
$xvideo->addAttribute('transcodedSuffix', (string)$transcode_type);
$xvideo->addAttribute('transcodedContentType', Video::type_to_mime($transcode_type));
}
}
}
/**
* addVideoInfo
* @param SimpleXMLElement $xml
* @param int $video_id
*/
public static function addVideoInfo($xml, $video_id): void
{
$xvideoinfo = self::addChildToResultXml($xml, 'videoinfo');
$xvideoinfo->addAttribute('id', (string)$video_id);
}
/**
* addPlaylists
* @param SimpleXMLElement $xml
* @param int $user_id
* @param array $playlists
* @param array $smartplaylists
* @param bool $hide_dupe_searches
*/
public static function addPlaylists($xml, $user_id, $playlists, $smartplaylists = array(), $hide_dupe_searches = false): void
{
$playlist_names = array();
$xplaylists = self::addChildToResultXml($xml, 'playlists');
foreach ($playlists as $plist_id) {
$playlist = new Playlist($plist_id);
if ($playlist->isNew()) {
continue;
}
if ($hide_dupe_searches && $playlist->user == $user_id) {
$playlist_names[] = $playlist->name;
}
self::addPlaylist($xplaylists, $playlist);
}
foreach ($smartplaylists as $plist_id) {
$playlist = new Search((int)str_replace('smart_', '', (string)$plist_id), 'song');
if (
$playlist->isNew() ||
($hide_dupe_searches && $playlist->user == $user_id && in_array($playlist->name, $playlist_names))
) {
continue;
}
self::addPlaylist($xplaylists, $playlist);
}
}
/**
* addPlaylist
* @param SimpleXMLElement $xml
* @param Playlist|Search $playlist
* @param bool $songs
*/
public static function addPlaylist($xml, $playlist, $songs = false): void
{
if ($playlist instanceof Playlist) {
self::addPlaylist_Playlist($xml, $playlist, $songs);
}
if ($playlist instanceof Search) {
self::addPlaylist_Search($xml, $playlist, $songs);
}
}
/**
* addPlaylist_Playlist
* @param SimpleXMLElement $xml
* @param Playlist $playlist
* @param bool $songs
*/
private static function addPlaylist_Playlist($xml, $playlist, $songs = false): void
{
$sub_id = (string)self::_getPlaylistId($playlist->id);
$songcount = $playlist->get_media_count('song');
$duration = ($songcount > 0) ? $playlist->get_total_duration() : 0;
$xplaylist = self::addChildToResultXml($xml, 'playlist');
$xplaylist->addAttribute('id', $sub_id);
$xplaylist->addAttribute('name', (string)self::_checkName($playlist->get_fullname()));
$xplaylist->addAttribute('owner', (string)$playlist->username);
$xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false");
$xplaylist->addAttribute('songCount', (string)$songcount);
$xplaylist->addAttribute('duration', (string)$duration);
$xplaylist->addAttribute('created', date("c", (int)$playlist->date));
$xplaylist->addAttribute('changed', date("c", (int)$playlist->last_update));
if ($playlist->has_art()) {
$xplaylist->addAttribute('coverArt', $sub_id);
}
if ($songs) {
$allsongs = $playlist->get_songs();
foreach ($allsongs as $song_id) {
// TODO addEntry
self::addSong($xplaylist, $song_id, "entry");
}
}
}
/**
* addPlaylist_Search
* @param SimpleXMLElement $xml
* @param Search $search
* @param bool $songs
*/
private static function addPlaylist_Search($xml, $search, $songs = false): void
{
$sub_id = (string) self::_getSmartPlaylistId($search->id);
$xplaylist = self::addChildToResultXml($xml, 'playlist');
$xplaylist->addAttribute('id', $sub_id);
$xplaylist->addAttribute('name', (string) self::_checkName($search->get_fullname()));
$xplaylist->addAttribute('owner', (string)$search->username);
$xplaylist->addAttribute('public', ($search->type != "private") ? "true" : "false");
$xplaylist->addAttribute('created', date("c", (int)$search->date));
$xplaylist->addAttribute('changed', date("c", time()));
if ($songs) {
$allitems = $search->get_items();
$xplaylist->addAttribute('songCount', (string)count($allitems));
$duration = (count($allitems) > 0) ? Search::get_total_duration($allitems) : 0;
$xplaylist->addAttribute('duration', (string)$duration);
$xplaylist->addAttribute('coverArt', $sub_id);
foreach ($allitems as $item) {
// TODO addEntry
self::addSong($xplaylist, (int)$item['object_id'], "entry");
}
} else {
$xplaylist->addAttribute('songCount', (string)$search->last_count);
$xplaylist->addAttribute('duration', (string)$search->last_duration);
$xplaylist->addAttribute('coverArt', $sub_id);
}
}
/**
* addPlayQueue
* current="133" position="45000" username="admin" changed="2015-02-18T15:22:22.825Z" changedBy="android"
* @param SimpleXMLElement $xml
* @param User_Playlist $playQueue
* @param string $username
*/
public static function addPlayQueue($xml, $playQueue, $username): void
{
$items = $playQueue->get_items();
if (!empty($items)) {
$current = $playQueue->get_current_object();
$play_time = date("Y-m-d H:i:s", $playQueue->get_time());
$date = new DateTime($play_time);
$date->setTimezone(new DateTimeZone('UTC'));
$changedBy = $playQueue->client ?? '';
$xplayqueue = self::addChildToResultXml($xml, 'playQueue');
$xplayqueue->addAttribute('current', (string)self::_getSongId($current['object_id']));
$xplayqueue->addAttribute('position', (string)($current['current_time'] * 1000));
$xplayqueue->addAttribute('username', (string)$username);
$xplayqueue->addAttribute('changed', $date->format("c"));
$xplayqueue->addAttribute('changedBy', (string)$changedBy);
foreach ($items as $row) {
// TODO addEntry
self::addSong($xplayqueue, (int)$row['object_id'], "entry");
}
}
}
/**
* addRandomSongs
* @param SimpleXMLElement $xml
* @param array $songs
*/
public static function addRandomSongs($xml, $songs): void
{
$xsongs = self::addChildToResultXml($xml, 'randomSongs');
foreach ($songs as $song_id) {
self::addSong($xsongs, $song_id);
}
}
/**
* addSongsByGenre
* @param SimpleXMLElement $xml
* @param array $songs
*/
public static function addSongsByGenre($xml, $songs): void
{
$xsongs = self::addChildToResultXml($xml, 'songsByGenre');
foreach ($songs as $song_id) {
self::addSong($xsongs, $song_id);
}
}
/**
* addTopSongs
* @param SimpleXMLElement $xml
* @param array $songs
*/
public static function addTopSongs($xml, $songs): void
{
$xsongs = self::addChildToResultXml($xml, 'topSongs');
foreach ($songs as $song_id) {
self::addSong($xsongs, $song_id);
}
}
/**
* addNowPlaying
* @param SimpleXMLElement $xml
* @param array $data
*/
public static function addNowPlaying($xml, $data): void
{
$xplaynow = self::addChildToResultXml($xml, 'nowPlaying');
foreach ($data as $row) {
// TODO addEntry
$track = self::addSong($xplaynow, $row['media']->getId(), "entry");
if ($track !== null) {
$track->addAttribute('username', (string)$row['client']->username);
$track->addAttribute('minutesAgo', (string)(abs((time() - ($row['expire'] - $row['media']->time)) / 60)));
$track->addAttribute('playerId', (string)$row['agent']);
}
}
}
/**
* addSearchResult2
* @param SimpleXMLElement $xml
* @param array $artists
* @param array $albums
* @param array $songs
*/
public static function addSearchResult2($xml, $artists, $albums, $songs): void
{
$xresult = self::addChildToResultXml($xml, htmlspecialchars('searchResult2'));
foreach ($artists as $artist_id) {
$artist = new Artist((int) $artist_id);
self::addArtist($xresult, $artist);
}
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xresult, $album);
}
foreach ($songs as $song_id) {
self::addSong($xresult, $song_id);
}
}
/**
* addSearchResult3
* @param SimpleXMLElement $xml
* @param array $artists
* @param array $albums
* @param array $songs
*/
public static function addSearchResult3($xml, $artists, $albums, $songs): void
{
$xresult = self::addChildToResultXml($xml, htmlspecialchars('searchResult3'));
foreach ($artists as $artist_id) {
$artist = new Artist((int) $artist_id);
self::addArtist($xresult, $artist);
}
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xresult, $album);
}
foreach ($songs as $song_id) {
self::addSong($xresult, $song_id);
}
}
/**
* addStarred
* @param SimpleXMLElement $xml
* @param array $artists
* @param array $albums
* @param array $songs
*/
public static function addStarred($xml, $artists, $albums, $songs): void
{
$xstarred = self::addChildToResultXml($xml, htmlspecialchars('starred'));
foreach ($artists as $artist_id) {
$artist = new Artist((int) $artist_id);
self::addArtist($xstarred, $artist);
}
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xstarred, $album);
}
foreach ($songs as $song_id) {
self::addSong($xstarred, $song_id);
}
}
/**
* addStarred2
* @param SimpleXMLElement $xml
* @param array $artists
* @param array $albums
* @param array $songs
*/
public static function addStarred2($xml, $artists, $albums, $songs): void
{
$xstarred = self::addChildToResultXml($xml, htmlspecialchars('starred2'));
foreach ($artists as $artist_id) {
$artist = new Artist((int) $artist_id);
self::addArtist($xstarred, $artist);
}
foreach ($albums as $album_id) {
$album = new Album($album_id);
self::addAlbum($xstarred, $album);
}
foreach ($songs as $song_id) {
self::addSong($xstarred, $song_id);
}
}
/**
* addUser
* @param SimpleXMLElement $xml
* @param User $user
*/
public static function addUser($xml, $user): void
{
$xuser = self::addChildToResultXml($xml, 'user');
$xuser->addAttribute('username', (string)$user->username);
$xuser->addAttribute('email', (string)$user->email);
$xuser->addAttribute('scrobblingEnabled', 'true');
$isManager = ($user->access >= 75);
$isAdmin = ($user->access === 100);
$xuser->addAttribute('adminRole', $isAdmin ? 'true' : 'false');
$xuser->addAttribute('settingsRole', 'true');
$xuser->addAttribute('downloadRole', Preference::get_by_user($user->id, 'download') ? 'true' : 'false');
$xuser->addAttribute('playlistRole', 'true');
$xuser->addAttribute('coverArtRole', $isManager ? 'true' : 'false');
$xuser->addAttribute('commentRole', (AmpConfig::get('social')) ? 'true' : 'false');
$xuser->addAttribute('podcastRole', (AmpConfig::get('podcast')) ? 'true' : 'false');
$xuser->addAttribute('streamRole', 'true');
$xuser->addAttribute('jukeboxRole', (AmpConfig::get('allow_localplay_playback') && AmpConfig::get('localplay_controller') && Access::check('localplay', 5)) ? 'true' : 'false');
$xuser->addAttribute('shareRole', Preference::get_by_user($user->id, 'share') ? 'true' : 'false');
$xuser->addAttribute('videoConversionRole', 'false');
}
/**
* addUsers
* @param SimpleXMLElement $xml
* @param array $users
*/
public static function addUsers($xml, $users): void
{
$xusers = self::addChildToResultXml($xml, 'users');
foreach ($users as $user_id) {
$user = new User($user_id);
if ($user->isNew() === false) {
self::addUser($xusers, $user);
}
}
}
/**
* addInternetRadioStations
* @param SimpleXMLElement $xml
* @param array $radios
*/
public static function addInternetRadioStations($xml, $radios): void
{
$xradios = self::addChildToResultXml($xml, 'internetRadioStations');
foreach ($radios as $radio_id) {
$radio = new Live_Stream((int)$radio_id);
self::addInternetRadioStation($xradios, $radio);
}
}
/**
* addInternetRadioStation
* @param SimpleXMLElement $xml
* @param Live_Stream $radio
*/
private static function addInternetRadioStation($xml, $radio): void
{
$xradio = self::addChildToResultXml($xml, 'internetRadioStation');
$xradio->addAttribute('id', (string)$radio->id);
$xradio->addAttribute('name', (string)self::_checkName($radio->name));
$xradio->addAttribute('streamUrl', (string)$radio->url);
$xradio->addAttribute('homepageUrl', (string)$radio->site_url);
}
/**
* addShares
* @param SimpleXMLElement $xml
* @param list<int> $shares
*/
public static function addShares($xml, $shares): void
{
$xshares = self::addChildToResultXml($xml, 'shares');
foreach ($shares as $share_id) {
$share = new Share($share_id);
// Don't add share with max counter already reached
if ($share->max_counter === 0 || $share->counter < $share->max_counter) {
self::addShare($xshares, $share);
}
}
}
/**
* addShare
* @param SimpleXMLElement $xml
* @param Share $share
*/
private static function addShare($xml, $share): void
{
$xshare = self::addChildToResultXml($xml, 'share');
$xshare->addAttribute('id', (string)$share->id);
$xshare->addAttribute('url', (string)$share->public_url);
$xshare->addAttribute('description', (string)$share->description);
$user = new User($share->user);
$xshare->addAttribute('username', (string)$user->username);
$xshare->addAttribute('created', date("c", (int)$share->creation_date));
if ($share->lastvisit_date > 0) {
$xshare->addAttribute('lastVisited', date("c", (int)$share->lastvisit_date));
}
if ($share->expire_days > 0) {
$xshare->addAttribute('expires', date("c", (int)$share->creation_date + ($share->expire_days * 86400)));
}
$xshare->addAttribute('visitCount', (string)$share->counter);
if ($share->object_type == 'song') {
// TODO addEntry
self::addSong($xshare, $share->object_id, "entry");
} elseif ($share->object_type == 'playlist') {
$playlist = new Playlist($share->object_id);
$songs = $playlist->get_songs();
foreach ($songs as $song_id) {
// TODO addEntry
self::addSong($xshare, $song_id, "entry");
}
} elseif ($share->object_type == 'album') {
$songs = static::getSongRepository()->getByAlbum($share->object_id);
foreach ($songs as $song_id) {
// TODO addEntry
self::addSong($xshare, $song_id, "entry");
}
}
}
/**
* addJukeboxPlaylist
* @param SimpleXMLElement $xml
* @param LocalPlay $localplay
*/
public static function addJukeboxPlaylist($xml, LocalPlay $localplay): void
{
$xjbox = self::addJukeboxStatus($xml, $localplay, 'jukeboxPlaylist');
$tracks = $localplay->get();
foreach ($tracks as $track) {
if (array_key_exists('oid', $track)) {
// TODO addEntry
self::addSong($xjbox, (int)$track['oid'], 'entry');
}
// TODO This can be random play, democratic, podcasts, etc. not just songs
}
}
/**
* addJukeboxStatus
* @param SimpleXMLElement $xml
* @param LocalPlay $localplay
* @param string $elementName
*/
public static function addJukeboxStatus($xml, LocalPlay $localplay, $elementName = 'jukeboxStatus'): SimpleXMLElement
{
$xjbox = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$status = $localplay->status();
$index = (((int)$status['track']) === 0)
? 0
: $status['track'] - 1;
$xjbox->addAttribute('currentIndex', (string)$index);
$xjbox->addAttribute('playing', ($status['state'] == 'play') ? 'true' : 'false');
$xjbox->addAttribute('gain', (string)$status['volume']);
$xjbox->addAttribute('position', '0'); // TODO Not supported
return $xjbox;
}
/**
* addLyrics
* @param SimpleXMLElement $xml
* @param string $artist
* @param string $title
* @param int $song_id
*/
public static function addLyrics($xml, $artist, $title, $song_id): void
{
$song = new Song($song_id);
$song->fill_ext_info('lyrics');
$lyrics = $song->get_lyrics();
if (!empty($lyrics) && $lyrics['text']) {
$text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $lyrics['text']);
$text = preg_replace('/\\n\\n/i', "\n", $text);
$text = str_replace("\r", '', (string)$text);
$xlyrics = self::addChildToResultXml($xml, 'lyrics', htmlspecialchars($text));
if ($artist) {
$xlyrics->addAttribute('artist', (string)$artist);
}
if ($title) {
$xlyrics->addAttribute('title', (string)$title);
}
}
}
/**
* addAlbumInfo
* @param SimpleXMLElement $xml
* @param array $info
*/
public static function addAlbumInfo($xml, $info): void
{
$album = new Album((int) $info['id']);
$xartist = self::addChildToResultXml($xml, htmlspecialchars('albumInfo'));
$xartist->addChild('notes', htmlspecialchars(trim((string)$info['summary'])));
$xartist->addChild('musicBrainzId', $album->mbid);
//$xartist->addChild('lastFmUrl', "");
$xartist->addChild('smallImageUrl', htmlentities($info['smallphoto']));
$xartist->addChild('mediumImageUrl', htmlentities($info['mediumphoto']));
$xartist->addChild('largeImageUrl', htmlentities($info['largephoto']));
}
/**
* addArtistInfo
* @param SimpleXMLElement $xml
* @param array $info
* @param array $similars
*/
public static function addArtistInfo($xml, $info, $similars, $elementName = 'artistInfo'): void
{
$artist = new Artist((int) $info['id']);
$xartist = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$biography = trim((string)$info['summary']);
if (!empty($biography)) {
$xartist->addChild('biography', htmlspecialchars($biography));
}
$xartist->addChild('musicBrainzId', (string)$artist->mbid);
//$xartist->addChild('lastFmUrl', "");
$xartist->addChild('smallImageUrl', htmlentities($info['smallphoto']));
$xartist->addChild('mediumImageUrl', htmlentities($info['mediumphoto']));
$xartist->addChild('largeImageUrl', htmlentities($info['largephoto']));
foreach ($similars as $similar) {
$xsimilar = self::addChildToResultXml($xartist, 'similarArtist');
$xsimilar->addAttribute('id', ($similar['id'] !== null ? (string)self::_getArtistId($similar['id']) : "-1"));
$xsimilar->addAttribute('name', (string)self::_checkName($similar['name']));
}
}
/**
* addArtistInfo2
* @param SimpleXMLElement $xml
* @param array $info
* @param array $similars
*/
public static function addArtistInfo2($xml, $info, $similars): void
{
self::addArtistInfo($xml, $info, $similars, 'artistInfo2');
}
/**
* addSimilarSongs
* @param SimpleXMLElement $xml
* @param array $similar_songs
* @param string $child
*/
public static function addSimilarSongs($xml, $similar_songs, $child): void
{
$xsimilar = self::addChildToResultXml($xml, htmlspecialchars($child));
foreach ($similar_songs as $similar_song) {
if ($similar_song['id'] !== null) {
self::addSong($xsimilar, $similar_song['id']);
}
}
}
/**
* addSimilarSongs2
* @param SimpleXMLElement $xml
* @param array $similar_songs
* @param string $child
*/
public static function addSimilarSongs2($xml, $similar_songs, $child): void
{
$xsimilar = self::addChildToResultXml($xml, htmlspecialchars($child));
foreach ($similar_songs as $similar_song) {
if ($similar_song['id'] !== null) {
self::addSong($xsimilar, $similar_song['id']);
}
}
}
/**
* addPodcasts
* @param SimpleXMLElement $xml
* @param Podcast[] $podcasts
* @param bool $includeEpisodes
*/
public static function addPodcasts($xml, $podcasts, $includeEpisodes = true): void
{
$podcastRepository = self::getPodcastRepository();
$xpodcasts = self::addChildToResultXml($xml, 'podcasts');
foreach ($podcasts as $podcast) {
$podcast->format();
$sub_id = (string)self::_getPodcastId($podcast->getId());
$xchannel = self::addChildToResultXml($xpodcasts, 'channel');
$xchannel->addAttribute('id', $sub_id);
$xchannel->addAttribute('url', $podcast->getFeedUrl());
$xchannel->addAttribute('title', self::_checkName($podcast->get_fullname()));
$xchannel->addAttribute('description', $podcast->get_description());
if ($podcast->has_art()) {
$xchannel->addAttribute('coverArt', 'pod-' . $sub_id);
}
$xchannel->addAttribute('status', 'completed');
if ($includeEpisodes) {
$episodes = $podcast->getEpisodeIds();
foreach ($episodes as $episode_id) {
$episode = new Podcast_Episode($episode_id);
self::addPodcastEpisode($xchannel, $episode);
}
}
}
}
/**
* addNewestPodcasts
* @param SimpleXMLElement $xml
* @param Podcast_Episode[] $episodes
*/
public static function addNewestPodcasts($xml, $episodes): void
{
$xpodcasts = self::addChildToResultXml($xml, 'newestPodcasts');
foreach ($episodes as $episode) {
$episode->format();
self::addPodcastEpisode($xpodcasts, $episode);
}
}
/**
* addBookmarks
* @param SimpleXMLElement $xml
* @param list<Bookmark> $bookmarks
*/
public static function addBookmarks($xml, $bookmarks): void
{
$xbookmarks = self::addChildToResultXml($xml, 'bookmarks');
foreach ($bookmarks as $bookmark) {
self::addBookmark($xbookmarks, $bookmark);
}
}
/**
* addBookmark
* @param SimpleXMLElement $xml
* @param Bookmark $bookmark
*/
private static function addBookmark($xml, $bookmark): void
{
$xbookmark = self::addChildToResultXml($xml, 'bookmark');
$xbookmark->addAttribute('position', (string)$bookmark->position);
$xbookmark->addAttribute('username', $bookmark->getUserName());
$xbookmark->addAttribute('comment', (string)$bookmark->comment);
$xbookmark->addAttribute('created', date("c", (int)$bookmark->creation_date));
$xbookmark->addAttribute('changed', date("c", (int)$bookmark->update_date));
if ($bookmark->object_type == "song") {
// TODO addEntry
self::addSong($xbookmark, $bookmark->object_id, 'entry');
} elseif ($bookmark->object_type == "video") {
// TODO addEntry
self::addVideo($xbookmark, new Video($bookmark->object_id), 'entry');
} elseif ($bookmark->object_type == "podcast_episode") {
// TODO addEntry
self::addPodcastEpisode($xbookmark, new Podcast_Episode($bookmark->object_id), 'entry');
}
}
/**
* addPodcastEpisode
* @param SimpleXMLElement $xml
* @param Podcast_Episode $episode
* @param string $elementName
*/
private static function addPodcastEpisode($xml, $episode, $elementName = 'episode'): void
{
$episode->format();
$sub_id = (string)self::_getPodcastEpisodeId($episode->id);
$subParent = (string)self::_getPodcastId($episode->podcast);
$xepisode = self::addChildToResultXml($xml, htmlspecialchars($elementName));
$xepisode->addAttribute('id', $sub_id);
$xepisode->addAttribute('channelId', $subParent);
$xepisode->addAttribute('title', self::_checkName($episode->get_fullname()));
$xepisode->addAttribute('album', $episode->getPodcastName());
$xepisode->addAttribute('description', self::_checkName($episode->get_description()));
$xepisode->addAttribute('duration', (string)$episode->time);
$xepisode->addAttribute('genre', "Podcast");
$xepisode->addAttribute('isDir', "false");
$xepisode->addAttribute('publishDate', $episode->getPubDate()->format(DATE_ATOM));
$xepisode->addAttribute('status', (string)$episode->state);
$xepisode->addAttribute('parent', $subParent);
if ($episode->has_art()) {
$xepisode->addAttribute('coverArt', $subParent);
}
self::_setIfStarred($xepisode, 'podcast_episode', $episode->id);
if ($episode->file) {
$xepisode->addAttribute('streamId', $sub_id);
$xepisode->addAttribute('size', (string)$episode->size);
$xepisode->addAttribute('suffix', (string)$episode->type);
$xepisode->addAttribute('contentType', (string)$episode->mime);
// Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients
$path = basename($episode->file);
$xepisode->addAttribute('path', (string)$path);
}
}
/**
* addChatMessages
* @param SimpleXMLElement $xml
* @param int[] $messages
*/
public static function addChatMessages($xml, $messages): void
{
$xmessages = self::addChildToResultXml($xml, 'chatMessages');
if (empty($messages)) {
return;
}
foreach ($messages as $message) {
$chat = new PrivateMsg($message);
self::addMessage($xmessages, $chat);
}
}
/**
* addScanStatus
* @param SimpleXMLElement $xml
* @param User $user
*/
public static function addScanStatus($xml, $user): void
{
$counts = Catalog::get_server_counts($user->id ?? 0);
$count = $counts['artist'] + $counts['album'] + $counts['song'] + $counts['podcast_episode'];
$xscan = self::addChildToResultXml($xml, htmlspecialchars('scanStatus'));
$xscan->addAttribute('scanning', "false");
$xscan->addAttribute('count', (string)$count);
}
/**
* addMessage
* @param SimpleXMLElement $xml
* @param PrivateMsg $message
*/
private static function addMessage($xml, $message): void
{
$user = new User($message->getSenderUserId());
$xbookmark = self::addChildToResultXml($xml, 'chatMessage');
if ($user->fullname_public) {
$xbookmark->addAttribute('username', (string)$user->fullname);
} else {
$xbookmark->addAttribute('username', (string)$user->username);
}
$xbookmark->addAttribute('time', (string)($message->getCreationDate() * 1000));
$xbookmark->addAttribute('message', (string)$message->getMessage());
}
/**
* _createResponse
* @param string $version
* @param string $status
* @return SimpleXMLElement
*/
private static function _createResponse($version, $status = 'ok'): SimpleXMLElement
{
$response = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><subsonic-response/>');
$response->addAttribute('xmlns', 'http://subsonic.org/restapi');
$response->addAttribute('status', (string)$status);
$response->addAttribute('version', (string)$version);
$response->addAttribute('type', 'ampache');
$response->addAttribute('serverVersion', Api::$version);
return $response;
}
/**
* _createSuccessResponse
* @param string $function
*/
private static function _createSuccessResponse($function = ''): SimpleXMLElement
{
$version = self::API_VERSION;
$response = self::_createResponse($version);
debug_event(self::class, 'API success in function ' . $function . '-' . $version, 5);
return $response;
}
/**
* _createFailedResponse
* @param string $function
* @return SimpleXMLElement
*/
private static function _createFailedResponse($function = ''): SimpleXMLElement
{
$version = self::API_VERSION;
$response = self::_createResponse($version, 'failed');
debug_event(self::class, 'API fail in function ' . $function . '-' . $version, 3);
return $response;
}
/**
* @param int|string $artist_id
* @return int
*/
private static function _getArtistId($artist_id): int
{
return ((int)$artist_id) + self::AMPACHEID_ARTIST;
}
/**
* @param int|string $album_id
* @return int
*/
private static function _getAlbumId($album_id): int
{
return ((int)$album_id) + self::AMPACHEID_ALBUM;
}
/**
* @param int|string $song_id
* @return int
*/
private static function _getSongId($song_id): int
{
return ((int)$song_id) + self::AMPACHEID_SONG;
}
/**
* @param int $video_id
*/
private static function _getVideoId($video_id): int
{
return $video_id + Subsonic_Xml_Data::AMPACHEID_VIDEO;
}
/**
* @param int $podcast_id
*/
private static function _getPodcastId($podcast_id): int
{
return $podcast_id + self::AMPACHEID_PODCAST;
}
/**
* @param int $episode_id
*/
private static function _getPodcastEpisodeId($episode_id): int
{
return $episode_id + self::AMPACHEID_PODCASTEP;
}
/**
* @param int $plist_id
*/
private static function _getPlaylistId($plist_id): int
{
return $plist_id + self::AMPACHEID_PLAYLIST;
}
/**
* @param int $plist_id
*/
private static function _getSmartPlaylistId($plist_id): int
{
return $plist_id + self::AMPACHEID_SMARTPL;
}
/**
* _cleanId
* @param string $object_id
*/
private static function _cleanId($object_id): int
{
// Remove all al-, ar-, ... prefixes
$tpos = strpos((string)$object_id, "-");
if ($tpos !== false) {
$object_id = substr((string) $object_id, $tpos + 1);
}
return (int)$object_id;
}
/**
* _checkName
* This to fix xml=>json which can result to wrong type parsing
* @param null|string $name
*/
private static function _checkName($name): string
{
// Ensure to have always a string type
if (self::$enable_json_checks && !empty($name)) {
if (is_numeric($name)) {
// Add space character to fail numeric test
$name .= " ";
}
}
return html_entity_decode((string)$name, ENT_NOQUOTES, 'UTF-8');
}
/**
* _getAmpacheObject
* Return the Ampache media object
* @param string $object_id
* @return Song|Video|Podcast_Episode|null
*/
public static function _getAmpacheObject($object_id)
{
if (Subsonic_Xml_Data::_isSong($object_id)) {
return new Song(Subsonic_Xml_Data::_getAmpacheId($object_id));
}
if (Subsonic_Xml_Data::_isVideo($object_id)) {
return new Video(Subsonic_Xml_Data::_getAmpacheId($object_id));
}
if (Subsonic_Xml_Data::_isPodcastEpisode($object_id)) {
return new Podcast_Episode(Subsonic_Xml_Data::_getAmpacheId($object_id));
}
return null;
}
/**
* _getAmpacheId
* @param string $object_id
*/
public static function _getAmpacheId($object_id): int
{
return (self::_cleanId($object_id) % self::AMPACHEID_ARTIST);
}
/**
* _getAmpacheIdArrays
* @param array $object_ids
* @return array
*/
public static function _getAmpacheIdArrays($object_ids): array
{
$ampidarrays = array();
$track = 1;
foreach ($object_ids as $object_id) {
$ampidarrays[] = array(
'object_id' => self::_getAmpacheId((string)$object_id),
'object_type' => self::_getAmpacheType((string)$object_id),
'track' => $track
);
$track++;
}
return $ampidarrays;
}
/**
* _getAmpacheType
* @param string $object_id
*/
public static function _getAmpacheType($object_id): string
{
if (self::_isArtist($object_id)) {
return "artist";
} elseif (self::_isAlbum($object_id)) {
return "album";
} elseif (self::_isSong($object_id)) {
return "song";
} elseif (self::_isSmartPlaylist($object_id)) {
return "search";
} elseif (self::_isVideo($object_id)) {
return "video";
} elseif (self::_isPodcast($object_id)) {
return "podcast";
} elseif (self::_isPodcastEpisode($object_id)) {
return "podcast_episode";
} elseif (self::_isPlaylist($object_id)) {
return "playlist";
}
return "";
}
/**
* @param string $artist_id
*/
public static function _isArtist($artist_id): bool
{
$artist_id = self::_cleanId($artist_id);
return ($artist_id >= self::AMPACHEID_ARTIST && $artist_id < self::AMPACHEID_ALBUM);
}
/**
* @param string $album_id
*/
public static function _isAlbum($album_id): bool
{
$album_id = self::_cleanId($album_id);
return ($album_id >= self::AMPACHEID_ALBUM && $album_id < self::AMPACHEID_SONG);
}
/**
* @param string $song_id
*/
public static function _isSong($song_id): bool
{
$song_id = self::_cleanId($song_id);
return ($song_id >= self::AMPACHEID_SONG && $song_id < self::AMPACHEID_SMARTPL);
}
/**
* @param string $video_id
*/
public static function _isVideo($video_id): bool
{
$video_id = self::_cleanId($video_id);
return ($video_id >= self::AMPACHEID_VIDEO && $video_id < self::AMPACHEID_PODCAST);
}
/**
* @param string $podcast_id
*/
public static function _isPodcast($podcast_id): bool
{
$podcast_id = self::_cleanId($podcast_id);
return ($podcast_id >= self::AMPACHEID_PODCAST && $podcast_id < self::AMPACHEID_PODCASTEP);
}
/**
* @param string $episode_id
*/
public static function _isPodcastEpisode($episode_id): bool
{
$episode_id = self::_cleanId($episode_id);
return ($episode_id >= self::AMPACHEID_PODCASTEP && $episode_id < self::AMPACHEID_PLAYLIST);
}
/**
* @param string $plist_id
*/
public static function _isPlaylist($plist_id): bool
{
$plist_id = self::_cleanId($plist_id);
return ($plist_id >= self::AMPACHEID_PLAYLIST);
}
/**
* @param string $plist_id
*/
public static function _isSmartPlaylist($plist_id): bool
{
$plist_id = self::_cleanId($plist_id);
return ($plist_id >= self::AMPACHEID_SMARTPL && $plist_id < self::AMPACHEID_VIDEO);
}
/**
* _setIfStarred
* @param SimpleXMLElement $xml
* @param string $objectType
* @param int $object_id
*/
private static function _setIfStarred($xml, $objectType, $object_id): void
{
if (InterfaceImplementationChecker::is_library_item($objectType)) {
if (AmpConfig::get('ratings')) {
$starred = new Userflag($object_id, $objectType);
$result = $starred->get_flag(null, true);
if (is_array($result) && isset($result[1])) {
$xml->addAttribute('starred', date("Y-m-d\TH:i:s\Z", (int)$result[1]));
}
}
}
}
/**
* Adds a child to an existing result xml structure
*/
private static function addChildToResultXml(SimpleXMLElement $xml, string $qualifiedName, ?string $value = null): SimpleXMLElement
{
/** @var SimpleXMLElement $child */
$child = $xml->addChild($qualifiedName, $value);
return $child;
}
/**
* @deprecated
*/
private static function getSongRepository(): SongRepositoryInterface
{
global $dic;
return $dic->get(SongRepositoryInterface::class);
}
/**
* @deprecated
*/
private static function getAlbumRepository(): AlbumRepositoryInterface
{
global $dic;
return $dic->get(AlbumRepositoryInterface::class);
}
/**
* @deprecated Inject by constructor
*/
private static function getPodcastRepository(): PodcastRepositoryInterface
{
global $dic;
return $dic->get(PodcastRepositoryInterface::class);
}
}