"Fossies" - the Fresh Open Source Software Archive

Member "ampache-5.1.0/src/Module/Api/Subsonic_Xml_Data.php" (25 Oct 2021, 61972 Bytes) of package /linux/www/ampache-5.1.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Subsonic_Xml_Data.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.0.0_vs_5.1.0.

    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 
   23 declare(strict_types=0);
   24 
   25 namespace Ampache\Module\Api;
   26 
   27 use Ampache\Config\AmpConfig;
   28 use Ampache\Module\Authorization\Access;
   29 use Ampache\Module\Playback\Localplay\LocalPlay;
   30 use Ampache\Module\System\Dba;
   31 use Ampache\Module\Util\InterfaceImplementationChecker;
   32 use Ampache\Repository\AlbumRepositoryInterface;
   33 use Ampache\Repository\Model\Album;
   34 use Ampache\Repository\Model\Art;
   35 use Ampache\Repository\Model\Artist;
   36 use Ampache\Repository\Model\Bookmark;
   37 use Ampache\Repository\Model\Catalog;
   38 use Ampache\Repository\Model\Live_Stream;
   39 use Ampache\Repository\Model\Playlist;
   40 use Ampache\Repository\Model\Podcast;
   41 use Ampache\Repository\Model\Podcast_Episode;
   42 use Ampache\Repository\Model\Preference;
   43 use Ampache\Repository\Model\PrivateMsg;
   44 use Ampache\Repository\Model\Rating;
   45 use Ampache\Repository\Model\Search;
   46 use Ampache\Repository\Model\Share;
   47 use Ampache\Repository\Model\Song;
   48 use Ampache\Repository\Model\Tag;
   49 use Ampache\Repository\Model\User;
   50 use Ampache\Repository\Model\User_Playlist;
   51 use Ampache\Repository\Model\Userflag;
   52 use Ampache\Repository\Model\Video;
   53 use Ampache\Repository\SongRepositoryInterface;
   54 use SimpleXMLElement;
   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  */
   63 class 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         self::addArtistArrays($xindexes, $artists);
  485     }
  486 
  487     /**
  488      * addArtistsRoot
  489      * @param SimpleXMLElement $xml
  490      * @param array $artists
  491      */
  492     public static function addArtistsRoot($xml, $artists)
  493     {
  494         $xartists = $xml->addChild('artists');
  495         self::addIgnoredArticles($xartists);
  496         self::addArtistArrays($xartists, $artists);
  497     }
  498 
  499     /**
  500      * addArtists
  501      * @param SimpleXMLElement $xml
  502      * @param Artist[] $artists
  503      * @param boolean $extra
  504      * @param boolean $albumsSet
  505      */
  506     public static function addArtists($xml, $artists, $extra = false, $albumsSet = false)
  507     {
  508         $xlastcat     = null;
  509         $sharpartists = array();
  510         $xlastletter  = '';
  511         foreach ($artists as $artist) {
  512             if (strlen((string)$artist->name) > 0) {
  513                 $letter = strtoupper((string)$artist->name[0]);
  514                 if ($letter == "X" || $letter == "Y" || $letter == "Z") {
  515                     $letter = "X-Z";
  516                 } else {
  517                     if (!preg_match("/^[A-W]$/", $letter)) {
  518                         $sharpartists[] = $artist;
  519                         continue;
  520                     }
  521                 }
  522 
  523                 if ($letter != $xlastletter) {
  524                     $xlastletter = $letter;
  525                     $xlastcat    = $xml->addChild('index');
  526                     $xlastcat->addAttribute('name', (string)$xlastletter);
  527                 }
  528             }
  529 
  530             if ($xlastcat != null) {
  531                 self::addArtist($xlastcat, $artist, $extra, false, $albumsSet);
  532             }
  533         }
  534 
  535         // Always add # index at the end
  536         if (count($sharpartists) > 0) {
  537             $xsharpcat = $xml->addChild('index');
  538             $xsharpcat->addAttribute('name', '#');
  539 
  540             foreach ($sharpartists as $artist) {
  541                 self::addArtist($xsharpcat, $artist, $extra, false, $albumsSet);
  542             }
  543         }
  544     }
  545 
  546     /**
  547      * addArtist
  548      * @param SimpleXMLElement $xml
  549      * @param Artist $artist
  550      * @param boolean $extra
  551      * @param boolean $albums
  552      * @param boolean $albumsSet
  553      */
  554     public static function addArtist($xml, $artist, $extra = false, $albums = false, $albumsSet = false)
  555     {
  556         $artist->format();
  557         $xartist = $xml->addChild('artist');
  558         $xartist->addAttribute('id', (string)self::getArtistId($artist->id));
  559         $xartist->addAttribute('name', (string)self::checkName($artist->get_fullname()));
  560         $allalbums = array();
  561         if (($extra && !$albumsSet) || $albums) {
  562             $allalbums = static::getAlbumRepository()->getByArtist($artist->id);
  563         }
  564 
  565         if ($extra) {
  566             $xartist->addAttribute('coverArt', 'ar-' . (string)self::getArtistId($artist->id));
  567             if ($albumsSet) {
  568                 $xartist->addAttribute('albumCount', (string)$artist->albums);
  569             } else {
  570                 $xartist->addAttribute('albumCount', (string)count($allalbums));
  571             }
  572         }
  573         if ($albums) {
  574             foreach ($allalbums as $albumid) {
  575                 $album = new Album($albumid);
  576                 self::addAlbum($xartist, $album);
  577             }
  578         }
  579     }
  580 
  581     /**
  582      * addArtistArrays
  583      * @param SimpleXMLElement $xml
  584      * @param array $artists
  585      */
  586     public static function addArtistArrays($xml, $artists)
  587     {
  588         $xlastcat     = null;
  589         $sharpartists = array();
  590         $xlastletter  = '';
  591         foreach ($artists as $artist) {
  592             if (strlen((string)$artist['name']) > 0) {
  593                 $letter = strtoupper((string)$artist['name'][0]);
  594                 if ($letter == "X" || $letter == "Y" || $letter == "Z") {
  595                     $letter = "X-Z";
  596                 } else {
  597                     if (!preg_match("/^[A-W]$/", $letter)) {
  598                         $sharpartists[] = $artist;
  599                         continue;
  600                     }
  601                 }
  602 
  603                 if ($letter != $xlastletter) {
  604                     $xlastletter = $letter;
  605                     $xlastcat    = $xml->addChild('index');
  606                     $xlastcat->addAttribute('name', (string)$xlastletter);
  607                 }
  608             }
  609 
  610             if ($xlastcat != null) {
  611                 self::addArtistArray($xlastcat, $artist);
  612             }
  613         }
  614 
  615         // Always add # index at the end
  616         if (count($sharpartists) > 0) {
  617             $xsharpcat = $xml->addChild('index');
  618             $xsharpcat->addAttribute('name', '#');
  619 
  620             foreach ($sharpartists as $artist) {
  621                 self::addArtistArray($xsharpcat, $artist);
  622             }
  623         }
  624     }
  625 
  626     /**
  627      * addChildArray
  628      * @param SimpleXMLElement $xml
  629      * @param array $child
  630      */
  631     public static function addChildArray($xml, $child)
  632     {
  633         $sub_id = (string)self::getArtistId($child['id']);
  634         $xchild = $xml->addChild('child');
  635         $xchild->addAttribute('id', $sub_id);
  636         $xchild->addAttribute('parent', $child['catalog_id']);
  637         $xchild->addAttribute('isDir', 'true');
  638         $xchild->addAttribute('title', (string)self::checkName($child['f_name']));
  639         $xchild->addAttribute('artist', (string)self::checkName($child['f_name']));
  640         $xchild->addAttribute('coverArt', 'ar-' . $sub_id);
  641     }
  642 
  643     /**
  644      * addArtistArray
  645      * @param SimpleXMLElement $xml
  646      * @param array $artist
  647      */
  648     public static function addArtistArray($xml, $artist)
  649     {
  650         $sub_id  = (string)self::getArtistId($artist['id']);
  651         $xartist = $xml->addChild('artist');
  652         $xartist->addAttribute('id', $sub_id);
  653         $xartist->addAttribute('name', (string)self::checkName($artist['f_name']));
  654         $xartist->addAttribute('coverArt', 'ar-' . $sub_id);
  655         $xartist->addAttribute('albumCount', (string)$artist['album_count']);
  656     }
  657 
  658     /**
  659      * addAlbumList
  660      * @param SimpleXMLElement $xml
  661      * @param $albums
  662      * @param string $elementName
  663      */
  664     public static function addAlbumList($xml, $albums, $elementName = "albumList")
  665     {
  666         $xlist = $xml->addChild(htmlspecialchars($elementName));
  667         foreach ($albums as $albumid) {
  668             $album = new Album($albumid);
  669             self::addAlbum($xlist, $album);
  670         }
  671     }
  672 
  673     /**
  674      * addAlbum
  675      * @param SimpleXMLElement $xml
  676      * @param Album $album
  677      * @param boolean $songs
  678      * @param string $elementName
  679      */
  680     public static function addAlbum($xml, $album, $songs = false, $elementName = "album")
  681     {
  682         $album->format();
  683         $xalbum = $xml->addChild(htmlspecialchars($elementName));
  684         $f_name = (string)self::checkName($album->get_fullname());
  685         $xalbum->addAttribute('id', (string)self::getAlbumId($album->id));
  686         $xalbum->addAttribute('parent', (string) self::getArtistId($album->album_artist));
  687         $xalbum->addAttribute('album', $f_name);
  688         $xalbum->addAttribute('title', $f_name);
  689         $xalbum->addAttribute('name', $f_name);
  690         $xalbum->addAttribute('isDir', 'true');
  691         $xalbum->addAttribute('discNumber', (string)$album->disk);
  692 
  693         $xalbum->addAttribute('coverArt', 'al-' . self::getAlbumId($album->id));
  694         $xalbum->addAttribute('songCount', (string) $album->song_count);
  695         $xalbum->addAttribute('created', date("c", (int)$album->addition_time));
  696         $xalbum->addAttribute('duration', (string) $album->total_duration);
  697         $xalbum->addAttribute('artistId', (string) self::getArtistId($album->album_artist));
  698         $xalbum->addAttribute('artist', (string) self::checkName($album->f_album_artist_name));
  699         // original year (fall back to regular year)
  700         $original_year = AmpConfig::get('use_original_year');
  701         $year          = ($original_year && $album->original_year)
  702             ? $album->original_year
  703             : $album->year;
  704         if ($year > 0) {
  705             $xalbum->addAttribute('year', (string)$year);
  706         }
  707         if (count($album->tags) > 0) {
  708             $tag_values = array_values($album->tags);
  709             $tag        = array_shift($tag_values);
  710             $xalbum->addAttribute('genre', (string)$tag['name']);
  711         }
  712 
  713         $rating      = new Rating($album->id, "album");
  714         $user_rating = ($rating->get_user_rating() ?: 0);
  715         if ($user_rating > 0) {
  716             $xalbum->addAttribute('userRating', (string)ceil($user_rating));
  717         }
  718         $avg_rating = $rating->get_average_rating();
  719         if ($avg_rating > 0) {
  720             $xalbum->addAttribute('averageRating', (string)$avg_rating);
  721         }
  722 
  723         self::setIfStarred($xalbum, 'album', $album->id);
  724 
  725         if ($songs) {
  726             $disc_ids = $album->get_group_disks_ids();
  727             foreach ($disc_ids as $discid) {
  728                 $disc     = new Album($discid);
  729                 $allsongs = static::getSongRepository()->getByAlbum($disc->id);
  730                 foreach ($allsongs as $songid) {
  731                     self::addSong($xalbum, $songid);
  732                 }
  733             }
  734         }
  735     }
  736 
  737     /**
  738      * addSong
  739      * @param SimpleXMLElement $xml
  740      * @param integer $songId
  741      * @param string $elementName
  742      * @return SimpleXMLElement
  743      */
  744     public static function addSong($xml, $songId, $elementName = 'song')
  745     {
  746         $songData    = self::getSongData($songId);
  747         $albumData   = self::getAlbumData($songData['album']);
  748         $artistData  = self::getArtistData($songData['artist']);
  749         $catalogData = self::getCatalogData($songData['catalog'], $songData['file']);
  750         //$catalog_path = rtrim((string) $catalogData[0], "/");
  751 
  752         return self::createSong($xml, $songData, $albumData, $artistData, $catalogData, $elementName);
  753     }
  754 
  755     /**
  756      * getSongData
  757      * @param integer $songId
  758      * @return array
  759      */
  760     public static function getSongData($songId)
  761     {
  762         $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`, `song`.`total_skip`, `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` = ?';
  763         $db_results = Dba::read($sql, array($songId));
  764         $row        = Dba::fetch_assoc($db_results);
  765         if (empty($row)) {
  766             debug_event(self::class, 'getSongData failed: ' . $songId, 5);
  767 
  768             return array();
  769         }
  770         $extension   = pathinfo((string)$row['file'], PATHINFO_EXTENSION);
  771         $row['type'] = strtolower((string)$extension);
  772         $row['mime'] = Song::type_to_mime($row['type']);
  773 
  774         return $row;
  775     }
  776 
  777     /**
  778      * getAlbumData
  779      * @param integer $albumId
  780      * @return array
  781      */
  782     public static function getAlbumData($albumId)
  783     {
  784         $sql        = "SELECT * FROM `album` WHERE `id`=?";
  785         $db_results = Dba::read($sql, array($albumId));
  786         $row        = Dba::fetch_assoc($db_results);
  787         if (empty($row)) {
  788             debug_event(self::class, 'getAlbumData failed: ' . $albumId, 5);
  789 
  790             return array();
  791         }
  792         $row['f_name'] = trim(trim((string)$row['prefix']) . ' ' . trim((string)$row['name']));
  793 
  794         return $row;
  795     }
  796 
  797     /**
  798      * getArtistData
  799      * @param integer $artistId
  800      * @return array
  801      */
  802     public static function getArtistData($artistId)
  803     {
  804         $sql        = "SELECT * FROM `artist` WHERE `id` = ?";
  805         $db_results = Dba::read($sql, array($artistId));
  806         $row        = Dba::fetch_assoc($db_results);
  807         if (empty($row)) {
  808             debug_event(self::class, 'getArtistData failed: ' . $artistId, 5);
  809 
  810             return array();
  811         }
  812         $row['f_name'] = trim(trim((string)$row['prefix']) . ' ' . trim((string)$row['name']));
  813 
  814         return $row;
  815     }
  816 
  817     /**
  818      * getCatalogData
  819      * @param integer $catalogId
  820      * @param string $file_Path
  821      * @return array
  822      */
  823     public static function getCatalogData($catalogId, $file_Path)
  824     {
  825         $results     = array();
  826         $sqllook     = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
  827         $db_results  = Dba::read($sqllook, [$catalogId]);
  828         $resultcheck = Dba::fetch_assoc($db_results);
  829         if (!empty($resultcheck)) {
  830             $sql             = 'SELECT `path` FROM `catalog_' . $resultcheck['catalog_type'] . '` WHERE `catalog_id` = ?';
  831             $db_results      = Dba::read($sql, [$catalogId]);
  832             $result          = Dba::fetch_assoc($db_results);
  833             $catalog_path    = rtrim((string)$result['path'], "/");
  834             $results['path'] = str_replace($catalog_path . "/", "", $file_Path);
  835         }
  836 
  837         return $results;
  838     }
  839 
  840     /**
  841      * createSong
  842      * @param SimpleXMLElement $xml
  843      * @param $songData
  844      * @param $albumData
  845      * @param $artistData
  846      * @param $catalogData
  847      * @param string $elementName
  848      * @return SimpleXMLElement
  849      */
  850     public static function createSong(
  851         $xml,
  852         $songData,
  853         $albumData,
  854         $artistData,
  855         $catalogData,
  856         $elementName = 'song'
  857     ) {
  858         // Don't create entries for disabled songs
  859         if (!$songData['enabled']) {
  860             return null;
  861         }
  862 
  863         $xsong = $xml->addChild(htmlspecialchars($elementName));
  864         $xsong->addAttribute('id', (string)self::getSongId($songData['id']));
  865         $xsong->addAttribute('parent', (string)self::getAlbumId($songData['album']));
  866         //$xsong->addAttribute('created', );
  867         $xsong->addAttribute('title', (string)self::checkName($songData['title']));
  868         $xsong->addAttribute('isDir', 'false');
  869         $xsong->addAttribute('isVideo', 'false');
  870         $xsong->addAttribute('type', 'music');
  871         // $album = new Album(songData->album);
  872         $xsong->addAttribute('albumId', (string)self::getAlbumId($songData['album']));
  873         $xsong->addAttribute('album', (string)self::checkName($albumData['f_name'] ?? ''));
  874         // $artist = new Artist($song->artist);
  875         // $artist->format();
  876         $xsong->addAttribute('artistId', (string) self::getArtistId($songData['artist']));
  877         $xsong->addAttribute('artist', (string) self::checkName($artistData['f_name']));
  878         $art_object = (AmpConfig::get('show_song_art') && Art::has_db($songData['id'], 'song')) ? self::getSongId($songData['id']) : self::getAlbumId($songData['album']);
  879         $xsong->addAttribute('coverArt', (string) $art_object);
  880         $xsong->addAttribute('duration', (string) $songData['time']);
  881         $xsong->addAttribute('bitRate', (string) ((int) ($songData['bitrate'] / 1000)));
  882         // <!-- Added in 1.14.0 -->
  883         // $xsong->addAttribute('playCount', (string)$songData['total_count']);
  884         $rating      = new Rating($songData['id'], "song");
  885         $user_rating = ($rating->get_user_rating() ?: 0);
  886         if ($user_rating > 0) {
  887             $xsong->addAttribute('userRating', (string)ceil($user_rating));
  888         }
  889         $avg_rating = $rating->get_average_rating();
  890         if ($avg_rating > 0) {
  891             $xsong->addAttribute('averageRating', (string)$avg_rating);
  892         }
  893         self::setIfStarred($xsong, 'song', $songData['id']);
  894         if ($songData['track'] > 0) {
  895             $xsong->addAttribute('track', (string)$songData['track']);
  896         }
  897         if ($songData['year'] > 0) {
  898             $xsong->addAttribute('year', (string)$songData['year']);
  899         }
  900         $tags = Tag::get_object_tags('song', (int) $songData['id']);
  901         if (count($tags) > 0) {
  902             $xsong->addAttribute('genre', (string)$tags[0]['name']);
  903         }
  904         $xsong->addAttribute('size', (string)$songData['size']);
  905         if (array_key_exists('disk', $albumData) && Album::sanitize_disk($albumData['disk']) > 0) {
  906             $xsong->addAttribute('discNumber', (string)Album::sanitize_disk($albumData['disk']));
  907         }
  908         $xsong->addAttribute('suffix', (string)$songData['type']);
  909         $xsong->addAttribute('contentType', (string)$songData['mime']);
  910         // Return a file path relative to the catalog root path
  911         $xsong->addAttribute('path', (string)$catalogData['path']);
  912 
  913         // Set transcoding information if required
  914         $transcode_cfg = AmpConfig::get('transcode');
  915         $valid_types   = Song::get_stream_types_for_type($songData['type'], 'api');
  916         if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) {
  917             // $transcode_settings = Song::get_transcode_settings_for_media(null, null, 'api', 'song');
  918             $transcode_type = AmpConfig::get('encode_player_api_target', 'mp3');
  919             $xsong->addAttribute('transcodedSuffix', (string)$transcode_type);
  920             $xsong->addAttribute('transcodedContentType', Song::type_to_mime($transcode_type));
  921         }
  922 
  923         return $xsong;
  924     }
  925 
  926     /**
  927      * checkName
  928      * This to fix xml=>json which can result to wrong type parsing
  929      * @param string $name
  930      * @return string|null
  931      */
  932     private static function checkName($name)
  933     {
  934         // Ensure to have always a string type
  935         if (self::$enable_json_checks && !empty($name)) {
  936             if (is_numeric($name)) {
  937                 // Add space character to fail numeric test
  938                 $name .= " ";
  939             }
  940         }
  941 
  942         return html_entity_decode($name, ENT_NOQUOTES, 'UTF-8');
  943     }
  944 
  945     /**
  946      * getAmpacheObject
  947      * Return the Ampache media object
  948      * @param integer $object_id
  949      * @return Song|Video|Podcast_Episode|null
  950      */
  951     public static function getAmpacheObject($object_id)
  952     {
  953         if (Subsonic_Xml_Data::isSong($object_id)) {
  954             return new Song(Subsonic_Xml_Data::getAmpacheId($object_id));
  955         }
  956         if (Subsonic_Xml_Data::isVideo($object_id)) {
  957             return new Video(Subsonic_Xml_Data::getAmpacheId($object_id));
  958         }
  959         if (Subsonic_Xml_Data::isPodcastEp($object_id)) {
  960             return new Podcast_Episode(Subsonic_Xml_Data::getAmpacheId($object_id));
  961         }
  962 
  963         return null;
  964     } // getAmpacheObject
  965 
  966     /**
  967      * addArtistDirectory for subsonic artist id
  968      * @param SimpleXMLElement $xml
  969      * @param string $artist_id
  970      */
  971     public static function addArtistDirectory($xml, $artist_id)
  972     {
  973         $amp_id = self::getAmpacheId($artist_id);
  974         $data   = Artist::get_id_array($amp_id);
  975         $xdir   = $xml->addChild('directory');
  976         debug_event(self::class, $amp_id . ' artist ' . $artist_id . ' DaTA Runtime Error ' . print_r($data, true), 5);
  977         $xdir->addAttribute('id', (string)$artist_id);
  978         $xdir->addAttribute('parent', (string)$data['catalog_id']);
  979         $xdir->addAttribute('name', (string)$data['f_name']);
  980         $allalbums = static::getAlbumRepository()->getByArtist($amp_id);
  981         foreach ($allalbums as $album_id) {
  982             $album = new Album($album_id);
  983             self::addAlbum($xdir, $album, false, "child");
  984         }
  985     }
  986 
  987     /**
  988      * addAlbumDirectory for subsonic album id
  989      * @param SimpleXMLElement $xml
  990      * @param string $album_id
  991      */
  992     public static function addAlbumDirectory($xml, $album_id)
  993     {
  994         $album = new Album(self::getAmpacheId($album_id));
  995         $album->format();
  996         $xdir = $xml->addChild('directory');
  997         $xdir->addAttribute('id', (string)$album_id);
  998         if ($album->album_artist) {
  999             $xdir->addAttribute('parent', (string)self::getArtistId($album->album_artist));
 1000         } else {
 1001             $xdir->addAttribute('parent', (string)$album->catalog);
 1002         }
 1003         $xdir->addAttribute('name', (string)self::checkName($album->get_fullname()));
 1004 
 1005         $disc_ids  = $album->get_group_disks_ids();
 1006         $media_ids = static::getAlbumRepository()->getSongsGrouped($disc_ids);
 1007         foreach ($media_ids as $song_id) {
 1008             self::addSong($xdir, $song_id, "child");
 1009         }
 1010     }
 1011 
 1012     /**
 1013      * addCatalogDirectory for subsonic artist id
 1014      * @param SimpleXMLElement $xml
 1015      * @param string $catalog_id
 1016      */
 1017     public static function addCatalogDirectory($xml, $catalog_id)
 1018     {
 1019         $catalog = Catalog::create_from_id($catalog_id);
 1020         $xdir    = $xml->addChild('directory');
 1021         $xdir->addAttribute('id', (string)$catalog_id);
 1022         $xdir->addAttribute('name', $catalog->name);
 1023         $allartists = Catalog::get_artist_arrays(array($catalog_id));
 1024         foreach ($allartists as $artist) {
 1025             self::addChildArray($xdir, $artist);
 1026         }
 1027     }
 1028 
 1029     /**
 1030      * addGenres
 1031      * @param SimpleXMLElement $xml
 1032      * @param $tags
 1033      */
 1034     public static function addGenres($xml, $tags)
 1035     {
 1036         $xgenres = $xml->addChild('genres');
 1037 
 1038         foreach ($tags as $tag) {
 1039             $otag   = new Tag($tag['id']);
 1040             $xgenre = $xgenres->addChild('genre', htmlspecialchars($otag->name));
 1041             $counts = $otag->count();
 1042             $xgenre->addAttribute('songCount', (string) $counts['song'] ?? 0);
 1043             $xgenre->addAttribute('albumCount', (string) $counts['album'] ?? 0);
 1044         }
 1045     }
 1046 
 1047     /**
 1048      * addVideos
 1049      * @param SimpleXMLElement $xml
 1050      * @param Video[] $videos
 1051      */
 1052     public static function addVideos($xml, $videos)
 1053     {
 1054         $xvideos = $xml->addChild('videos');
 1055         foreach ($videos as $video) {
 1056             $video->format();
 1057             self::addVideo($xvideos, $video);
 1058         }
 1059     }
 1060 
 1061     /**
 1062      * addVideo
 1063      * @param SimpleXMLElement $xml
 1064      * @param Video $video
 1065      * @param string $elementName
 1066      */
 1067     public static function addVideo($xml, $video, $elementName = 'video')
 1068     {
 1069         $xvideo = $xml->addChild(htmlspecialchars($elementName));
 1070         $xvideo->addAttribute('id', (string)self::getVideoId($video->id));
 1071         $xvideo->addAttribute('title', (string)$video->f_full_title);
 1072         $xvideo->addAttribute('isDir', 'false');
 1073         $xvideo->addAttribute('coverArt', (string)self::getVideoId($video->id));
 1074         $xvideo->addAttribute('isVideo', 'true');
 1075         $xvideo->addAttribute('type', 'video');
 1076         $xvideo->addAttribute('duration', (string)$video->time);
 1077         if ($video->year > 0) {
 1078             $xvideo->addAttribute('year', (string)$video->year);
 1079         }
 1080         $tags = Tag::get_object_tags('video', (int)$video->id);
 1081         if (count($tags) > 0) {
 1082             $xvideo->addAttribute('genre', (string)$tags[0]['name']);
 1083         }
 1084         $xvideo->addAttribute('size', (string)$video->size);
 1085         $xvideo->addAttribute('suffix', (string)$video->type);
 1086         $xvideo->addAttribute('contentType', (string)$video->mime);
 1087         // Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients
 1088         $path = basename($video->file);
 1089         $xvideo->addAttribute('path', (string)$path);
 1090 
 1091         self::setIfStarred($xvideo, 'video', $video->id);
 1092         // Set transcoding information if required
 1093         $transcode_cfg = AmpConfig::get('transcode');
 1094         $valid_types   = Song::get_stream_types_for_type($video->type, 'api');
 1095         if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) {
 1096             $transcode_settings = $video->get_transcode_settings(null, 'api');
 1097             if (!empty($transcode_settings)) {
 1098                 $transcode_type = $transcode_settings['format'];
 1099                 $xvideo->addAttribute('transcodedSuffix', (string)$transcode_type);
 1100                 $xvideo->addAttribute('transcodedContentType', Video::type_to_mime($transcode_type));
 1101             }
 1102         }
 1103     }
 1104 
 1105     /**
 1106      * addPlaylists
 1107      * @param SimpleXMLElement $xml
 1108      * @param $playlists
 1109      * @param array $smartplaylists
 1110      */
 1111     public static function addPlaylists($xml, $playlists, $smartplaylists = array())
 1112     {
 1113         $xplaylists = $xml->addChild('playlists');
 1114         foreach ($playlists as $plistid) {
 1115             $playlist = new Playlist($plistid);
 1116             self::addPlaylist($xplaylists, $playlist);
 1117         }
 1118         foreach ($smartplaylists as $splistid) {
 1119             $smartplaylist = new Search((int)str_replace('smart_', '', (string)$splistid), 'song');
 1120             self::addSmartPlaylist($xplaylists, $smartplaylist);
 1121         }
 1122     }
 1123 
 1124     /**
 1125      * addPlaylist
 1126      * @param SimpleXMLElement $xml
 1127      * @param Playlist $playlist
 1128      * @param boolean $songs
 1129      */
 1130     public static function addPlaylist($xml, $playlist, $songs = false)
 1131     {
 1132         $playlist_id = (string)self::getPlaylistId($playlist->id);
 1133         $songcount   = $playlist->get_media_count('song');
 1134         $duration    = ($songcount > 0) ? $playlist->get_total_duration() : 0;
 1135         $xplaylist   = $xml->addChild('playlist');
 1136         $xplaylist->addAttribute('id', $playlist_id);
 1137         $xplaylist->addAttribute('name', (string)self::checkName($playlist->get_fullname()));
 1138         $xplaylist->addAttribute('owner', (string)$playlist->username);
 1139         $xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false");
 1140         $xplaylist->addAttribute('created', date("c", (int)$playlist->date));
 1141         $xplaylist->addAttribute('changed', date("c", (int)$playlist->last_update));
 1142         $xplaylist->addAttribute('songCount', (string)$songcount);
 1143         $xplaylist->addAttribute('duration', (string)$duration);
 1144         $xplaylist->addAttribute('coverArt', $playlist_id);
 1145 
 1146         if ($songs) {
 1147             $allsongs = $playlist->get_songs();
 1148             foreach ($allsongs as $songId) {
 1149                 self::addSong($xplaylist, $songId, "entry");
 1150             }
 1151         }
 1152     }
 1153 
 1154     /**
 1155      * addPlayQueue
 1156      * current="133" position="45000" username="admin" changed="2015-02-18T15:22:22.825Z" changedBy="android"
 1157      * @param SimpleXMLElement $xml
 1158      * @param int $user_id
 1159      * @param string $username
 1160      */
 1161     public static function addPlayQueue($xml, $user_id, $username)
 1162     {
 1163         $PlayQueue = new User_Playlist($user_id);
 1164         $items     = $PlayQueue->get_items();
 1165         if (!empty($items)) {
 1166             $current    = $PlayQueue->get_current_object();
 1167             $changed    = User::get_user_data($user_id, 'playqueue_date')['playqueue_date'] ?? '';
 1168             $changedBy  = User::get_user_data($user_id, 'playqueue_client')['playqueue_client'] ?? '';
 1169             $xplayqueue = $xml->addChild('playQueue');
 1170             $xplayqueue->addAttribute('current', self::getSongId($current['object_id']));
 1171             $xplayqueue->addAttribute('position', (string)$current['current_time']);
 1172             $xplayqueue->addAttribute('username', (string)$username);
 1173             $xplayqueue->addAttribute('changed', date("c", (int)$changed));
 1174             $xplayqueue->addAttribute('changedBy', (string)$changedBy);
 1175 
 1176             if ($items) {
 1177                 foreach ($items as $row) {
 1178                     self::addSong($xplayqueue, (int)$row['object_id'], "entry");
 1179                 }
 1180             }
 1181         }
 1182     }
 1183 
 1184     /**
 1185      * addSmartPlaylist
 1186      * @param SimpleXMLElement $xml
 1187      * @param Search $playlist
 1188      * @param boolean $songs
 1189      */
 1190     public static function addSmartPlaylist($xml, $playlist, $songs = false)
 1191     {
 1192         $playlist_id = (string) self::getSmartPlId($playlist->id);
 1193         $xplaylist   = $xml->addChild('playlist');
 1194         debug_event(self::class, 'addsmartplaylist ' . $playlist->id, 5);
 1195         $xplaylist->addAttribute('id', $playlist_id);
 1196         $xplaylist->addAttribute('name', (string) self::checkName($playlist->get_fullname()));
 1197         $xplaylist->addAttribute('owner', (string)$playlist->username);
 1198         $xplaylist->addAttribute('public', ($playlist->type != "private") ? "true" : "false");
 1199         $xplaylist->addAttribute('created', date("c", (int)$playlist->date));
 1200         $xplaylist->addAttribute('changed', date("c", time()));
 1201 
 1202         if ($songs) {
 1203             $allitems = $playlist->get_items();
 1204             $xplaylist->addAttribute('songCount', (string)count($allitems));
 1205             $duration = (count($allitems) > 0) ? Search::get_total_duration($allitems) : 0;
 1206             $xplaylist->addAttribute('duration', (string)$duration);
 1207             $xplaylist->addAttribute('coverArt', $playlist_id);
 1208             foreach ($allitems as $item) {
 1209                 self::addSong($xplaylist, (int)$item['object_id'], "entry");
 1210             }
 1211         } else {
 1212             $xplaylist->addAttribute('songCount', (string)$playlist->last_count);
 1213             $xplaylist->addAttribute('duration', (string)$playlist->last_duration);
 1214             $xplaylist->addAttribute('coverArt', $playlist_id);
 1215         }
 1216     }
 1217 
 1218     /**
 1219      * addRandomSongs
 1220      * @param SimpleXMLElement $xml
 1221      * @param array $songs
 1222      */
 1223     public static function addRandomSongs($xml, $songs)
 1224     {
 1225         $xsongs = $xml->addChild('randomSongs');
 1226         foreach ($songs as $songid) {
 1227             self::addSong($xsongs, $songid);
 1228         }
 1229     }
 1230 
 1231     /**
 1232      * addSongsByGenre
 1233      * @param SimpleXMLElement $xml
 1234      * @param array $songs
 1235      */
 1236     public static function addSongsByGenre($xml, $songs)
 1237     {
 1238         $xsongs = $xml->addChild('songsByGenre');
 1239         foreach ($songs as $songid) {
 1240             self::addSong($xsongs, $songid);
 1241         }
 1242     }
 1243 
 1244     /**
 1245      * addTopSongs
 1246      * @param SimpleXMLElement $xml
 1247      * @param array $songs
 1248      */
 1249     public static function addTopSongs($xml, $songs)
 1250     {
 1251         $xsongs = $xml->addChild('topSongs');
 1252         foreach ($songs as $songid) {
 1253             self::addSong($xsongs, $songid);
 1254         }
 1255     }
 1256 
 1257     /**
 1258      * addNowPlaying
 1259      * @param SimpleXMLElement $xml
 1260      * @param array $data
 1261      */
 1262     public static function addNowPlaying($xml, $data)
 1263     {
 1264         $xplaynow = $xml->addChild('nowPlaying');
 1265         foreach ($data as $d) {
 1266             $track = self::addSong($xplaynow, $d['media']->getId(), "entry");
 1267             if ($track !== null) {
 1268                 $track->addAttribute('username', (string)$d['client']->username);
 1269                 $track->addAttribute('minutesAgo', (string)(abs((time() - ($d['expire'] - $d['media']->time)) / 60)));
 1270                 $track->addAttribute('playerId', (string)$d['agent']);
 1271             }
 1272         }
 1273     }
 1274 
 1275     /**
 1276      * addSearchResult
 1277      * @param SimpleXMLElement $xml
 1278      * @param array $artists
 1279      * @param array $albums
 1280      * @param array $songs
 1281      * @param string $elementName
 1282      */
 1283     public static function addSearchResult($xml, $artists, $albums, $songs, $elementName = "searchResult2")
 1284     {
 1285         $xresult = $xml->addChild(htmlspecialchars($elementName));
 1286         foreach ($artists as $artistid) {
 1287             $artist = new Artist((int) $artistid);
 1288             self::addArtist($xresult, $artist);
 1289         }
 1290         foreach ($albums as $albumid) {
 1291             $album = new Album($albumid);
 1292             self::addAlbum($xresult, $album);
 1293         }
 1294         foreach ($songs as $songid) {
 1295             self::addSong($xresult, $songid);
 1296         }
 1297     }
 1298 
 1299     /**
 1300      * setIfStarred
 1301      * @param SimpleXMLElement $xml
 1302      * @param string $objectType
 1303      * @param integer $object_id
 1304      */
 1305     private static function setIfStarred($xml, $objectType, $object_id)
 1306     {
 1307         if (InterfaceImplementationChecker::is_library_item($objectType)) {
 1308             if (AmpConfig::get('ratings')) {
 1309                 $starred = new Userflag($object_id, $objectType);
 1310                 if ($res = $starred->get_flag(null, true)) {
 1311                     $xml->addAttribute('starred', date("Y-m-d\TH:i:s\Z", (int)$res[1]));
 1312                 }
 1313             }
 1314         }
 1315     }
 1316 
 1317     /**
 1318      * addStarred
 1319      * @param SimpleXMLElement $xml
 1320      * @param array $artists
 1321      * @param array $albums
 1322      * @param array $songs
 1323      * @param string $elementName
 1324      */
 1325     public static function addStarred($xml, $artists, $albums, $songs, $elementName = "starred")
 1326     {
 1327         $xstarred = $xml->addChild(htmlspecialchars($elementName));
 1328 
 1329         foreach ($artists as $artistid) {
 1330             $artist = new Artist((int) $artistid);
 1331             self::addArtist($xstarred, $artist);
 1332         }
 1333 
 1334         foreach ($albums as $albumid) {
 1335             $album = new Album($albumid);
 1336             self::addAlbum($xstarred, $album);
 1337         }
 1338 
 1339         foreach ($songs as $songid) {
 1340             self::addSong($xstarred, $songid);
 1341         }
 1342     }
 1343 
 1344     /**
 1345      * addUser
 1346      * @param SimpleXMLElement $xml
 1347      * @param User $user
 1348      */
 1349     public static function addUser($xml, $user)
 1350     {
 1351         $xuser = $xml->addChild('user');
 1352         $xuser->addAttribute('username', (string)$user->username);
 1353         $xuser->addAttribute('email', (string)$user->email);
 1354         $xuser->addAttribute('scrobblingEnabled', 'true');
 1355         $isManager = ($user->access >= 75);
 1356         $isAdmin   = ($user->access >= 100);
 1357         $xuser->addAttribute('adminRole', $isAdmin ? 'true' : 'false');
 1358         $xuser->addAttribute('settingsRole', 'true');
 1359         $xuser->addAttribute('downloadRole', Preference::get_by_user($user->id, 'download') ? 'true' : 'false');
 1360         $xuser->addAttribute('playlistRole', 'true');
 1361         $xuser->addAttribute('coverArtRole', $isManager ? 'true' : 'false');
 1362         $xuser->addAttribute('commentRole', (AmpConfig::get('social')) ? 'true' : 'false');
 1363         $xuser->addAttribute('podcastRole', (AmpConfig::get('podcast')) ? 'true' : 'false');
 1364         $xuser->addAttribute('streamRole', 'true');
 1365         $xuser->addAttribute('jukeboxRole', (AmpConfig::get('allow_localplay_playback') && AmpConfig::get('localplay_controller') && Access::check('localplay', 5)) ? 'true' : 'false');
 1366         $xuser->addAttribute('shareRole', Preference::get_by_user($user->id, 'share') ? 'true' : 'false');
 1367     }
 1368 
 1369     /**
 1370      * addUsers
 1371      * @param SimpleXMLElement $xml
 1372      * @param array $users
 1373      */
 1374     public static function addUsers($xml, $users)
 1375     {
 1376         $xusers = $xml->addChild('users');
 1377         foreach ($users as $userid) {
 1378             $user = new User($userid);
 1379             self::addUser($xusers, $user);
 1380         }
 1381     }
 1382 
 1383     /**
 1384      * addRadio
 1385      * @param SimpleXMLElement $xml
 1386      * @param Live_Stream $radio
 1387      */
 1388     public static function addRadio($xml, $radio)
 1389     {
 1390         $xradio = $xml->addChild('internetRadioStation ');
 1391         $xradio->addAttribute('id', (string)$radio->id);
 1392         $xradio->addAttribute('name', (string)self::checkName($radio->name));
 1393         $xradio->addAttribute('streamUrl', (string)$radio->url);
 1394         $xradio->addAttribute('homePageUrl', (string)$radio->site_url);
 1395     }
 1396 
 1397     /**
 1398      * addRadios
 1399      * @param SimpleXMLElement $xml
 1400      * @param $radios
 1401      */
 1402     public static function addRadios($xml, $radios)
 1403     {
 1404         $xradios = $xml->addChild('internetRadioStations');
 1405         foreach ($radios as $radioid) {
 1406             $radio = new Live_Stream((int)$radioid);
 1407             self::addRadio($xradios, $radio);
 1408         }
 1409     }
 1410 
 1411     /**
 1412      * addShare
 1413      * @param SimpleXMLElement $xml
 1414      * @param Share $share
 1415      */
 1416     public static function addShare($xml, $share)
 1417     {
 1418         $xshare = $xml->addChild('share');
 1419         $xshare->addAttribute('id', (string)$share->id);
 1420         $xshare->addAttribute('url', (string)$share->public_url);
 1421         $xshare->addAttribute('description', (string)$share->description);
 1422         $user = new User($share->user);
 1423         $xshare->addAttribute('username', (string)$user->username);
 1424         $xshare->addAttribute('created', date("c", (int)$share->creation_date));
 1425         if ($share->lastvisit_date > 0) {
 1426             $xshare->addAttribute('lastVisited', date("c", (int)$share->lastvisit_date));
 1427         }
 1428         if ($share->expire_days > 0) {
 1429             $xshare->addAttribute('expires', date("c", (int)$share->creation_date + ($share->expire_days * 86400)));
 1430         }
 1431         $xshare->addAttribute('visitCount', (string)$share->counter);
 1432 
 1433         if ($share->object_type == 'song') {
 1434             self::addSong($xshare, $share->object_id, "entry");
 1435         } elseif ($share->object_type == 'playlist') {
 1436             $playlist = new Playlist($share->object_id);
 1437             $songs    = $playlist->get_songs();
 1438             foreach ($songs as $songid) {
 1439                 self::addSong($xshare, $songid, "entry");
 1440             }
 1441         } elseif ($share->object_type == 'album') {
 1442             $songs = static::getSongRepository()->getByAlbum($share->object_id);
 1443             foreach ($songs as $songid) {
 1444                 self::addSong($xshare, $songid, "entry");
 1445             }
 1446         }
 1447     }
 1448 
 1449     /**
 1450      * addShares
 1451      * @param SimpleXMLElement $xml
 1452      * @param array $shares
 1453      */
 1454     public static function addShares($xml, $shares)
 1455     {
 1456         $xshares = $xml->addChild('shares');
 1457         foreach ($shares as $share_id) {
 1458             $share = new Share((int)$share_id);
 1459             // Don't add share with max counter already reached
 1460             if ($share->max_counter == 0 || $share->counter < $share->max_counter) {
 1461                 self::addShare($xshares, $share);
 1462             }
 1463         }
 1464     }
 1465 
 1466     /**
 1467      * addJukeboxPlaylist
 1468      * @param SimpleXMLElement $xml
 1469      * @param LocalPlay $localplay
 1470      */
 1471     public static function addJukeboxPlaylist($xml, LocalPlay $localplay)
 1472     {
 1473         $xjbox  = self::createJukeboxStatus($xml, $localplay, 'jukeboxPlaylist');
 1474         $tracks = $localplay->get();
 1475         foreach ($tracks as $track) {
 1476             if ($track['oid']) {
 1477                 self::addSong($xjbox, (int)$track['oid'], 'entry');
 1478             }
 1479         }
 1480     }
 1481 
 1482     /**
 1483      * createJukeboxStatus
 1484      * @param SimpleXMLElement $xml
 1485      * @param LocalPlay $localplay
 1486      * @param string $elementName
 1487      * @return SimpleXMLElement
 1488      */
 1489     public static function createJukeboxStatus($xml, LocalPlay $localplay, $elementName = 'jukeboxStatus')
 1490     {
 1491         $xjbox  = $xml->addChild(htmlspecialchars($elementName));
 1492         $status = $localplay->status();
 1493         $xjbox->addAttribute('currentIndex', 0); // Not supported
 1494         $xjbox->addAttribute('playing', ($status['state'] == 'play') ? 'true' : 'false');
 1495         $xjbox->addAttribute('gain', (string)$status['volume']);
 1496         $xjbox->addAttribute('position', 0); // Not supported
 1497 
 1498         return $xjbox;
 1499     }
 1500 
 1501     /**
 1502      * addLyrics
 1503      * @param SimpleXMLElement $xml
 1504      * @param $artist
 1505      * @param $title
 1506      * @param $song_id
 1507      */
 1508     public static function addLyrics($xml, $artist, $title, $song_id)
 1509     {
 1510         $song = new Song($song_id);
 1511         $song->fill_ext_info('lyrics');
 1512         $lyrics = $song->get_lyrics();
 1513 
 1514         if (!empty($lyrics) && $lyrics['text']) {
 1515             $text    = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $lyrics['text']);
 1516             $text    = str_replace("\r", '', (string)$text);
 1517             $xlyrics = $xml->addChild('lyrics', htmlspecialchars($text));
 1518             if ($artist) {
 1519                 $xlyrics->addAttribute('artist', (string)$artist);
 1520             }
 1521             if ($title) {
 1522                 $xlyrics->addAttribute('title', (string)$title);
 1523             }
 1524         }
 1525     }
 1526 
 1527     /**
 1528      * addArtistInfo
 1529      * @param SimpleXMLElement $xml
 1530      * @param array $info
 1531      * @param array $similars
 1532      * @param string $child
 1533      */
 1534     public static function addArtistInfo($xml, $info, $similars, $child)
 1535     {
 1536         $artist = new Artist((int) $info['id']);
 1537 
 1538         $xartist = $xml->addChild(htmlspecialchars($child));
 1539         $xartist->addChild('biography', htmlspecialchars(trim((string)$info['summary'])));
 1540         $xartist->addChild('musicBrainzId', $artist->mbid);
 1541         //$xartist->addChild('lastFmUrl', "");
 1542         $xartist->addChild('smallImageUrl', htmlentities($info['smallphoto']));
 1543         $xartist->addChild('mediumImageUrl', htmlentities($info['mediumphoto']));
 1544         $xartist->addChild('largeImageUrl', htmlentities($info['largephoto']));
 1545 
 1546         foreach ($similars as $similar) {
 1547             $xsimilar = $xartist->addChild('similarArtist');
 1548             $xsimilar->addAttribute('id', ($similar['id'] !== null ? self::getArtistId($similar['id']) : "-1"));
 1549             $xsimilar->addAttribute('name', (string)self::checkName($similar['name']));
 1550         }
 1551     }
 1552 
 1553     /**
 1554      * addSimilarSongs
 1555      * @param SimpleXMLElement $xml
 1556      * @param array $similar_songs
 1557      * @param string $child
 1558      */
 1559     public static function addSimilarSongs($xml, $similar_songs, $child)
 1560     {
 1561         $xsimilar = $xml->addChild(htmlspecialchars($child));
 1562         foreach ($similar_songs as $similar_song) {
 1563             if ($similar_song['id'] !== null) {
 1564                 self::addSong($xsimilar, $similar_song['id']);
 1565             }
 1566         }
 1567     }
 1568 
 1569     /**
 1570      * addPodcasts
 1571      * @param SimpleXMLElement $xml
 1572      * @param Podcast[] $podcasts
 1573      * @param boolean $includeEpisodes
 1574      */
 1575     public static function addPodcasts($xml, $podcasts, $includeEpisodes = true)
 1576     {
 1577         $xpodcasts = $xml->addChild('podcasts');
 1578         foreach ($podcasts as $podcast) {
 1579             $podcast->format();
 1580             $xchannel = $xpodcasts->addChild('channel');
 1581             $xchannel->addAttribute('id', (string)self::getPodcastId($podcast->id));
 1582             $xchannel->addAttribute('url', (string)$podcast->feed);
 1583             $xchannel->addAttribute('title', (string)self::checkName($podcast->get_fullname()));
 1584             $xchannel->addAttribute('description', (string)$podcast->f_description);
 1585             if (Art::has_db($podcast->id, 'podcast')) {
 1586                 $xchannel->addAttribute('coverArt', 'pod-' . self::getPodcastId($podcast->id));
 1587             }
 1588             $xchannel->addAttribute('status', 'completed');
 1589             if ($includeEpisodes) {
 1590                 $episodes = $podcast->get_episodes();
 1591                 foreach ($episodes as $episode_id) {
 1592                     $episode = new Podcast_Episode($episode_id);
 1593                     self::addPodcastEpisode($xchannel, $episode);
 1594                 }
 1595             }
 1596         }
 1597     }
 1598 
 1599     /**
 1600      * addPodcastEpisode
 1601      * @param SimpleXMLElement $xml
 1602      * @param Podcast_Episode $episode
 1603      * @param string $elementName
 1604      */
 1605     private static function addPodcastEpisode($xml, $episode, $elementName = 'episode')
 1606     {
 1607         $episode->format();
 1608         $xepisode = $xml->addChild(htmlspecialchars($elementName));
 1609         $xepisode->addAttribute('id', (string)self::getPodcastEpId($episode->id));
 1610         $xepisode->addAttribute('channelId', (string)self::getPodcastId($episode->podcast));
 1611         $xepisode->addAttribute('title', (string)self::checkName($episode->get_fullname()));
 1612         $xepisode->addAttribute('album', (string)$episode->f_podcast);
 1613         $xepisode->addAttribute('description', (string)self::checkName($episode->f_description));
 1614         $xepisode->addAttribute('duration', (string)$episode->time);
 1615         $xepisode->addAttribute('genre', "Podcast");
 1616         $xepisode->addAttribute('isDir', "false");
 1617         $xepisode->addAttribute('publishDate', $episode->f_pubdate);
 1618         $xepisode->addAttribute('status', (string)$episode->state);
 1619         $xepisode->addAttribute('parent', (string)self::getPodcastId($episode->podcast));
 1620         if (Art::has_db($episode->podcast, 'podcast')) {
 1621             $xepisode->addAttribute('coverArt', (string)self::getPodcastId($episode->podcast));
 1622         }
 1623 
 1624         self::setIfStarred($xepisode, 'podcast_episode', $episode->id);
 1625 
 1626         if ($episode->file) {
 1627             $xepisode->addAttribute('streamId', (string)self::getPodcastEpId($episode->id));
 1628             $xepisode->addAttribute('size', (string)$episode->size);
 1629             $xepisode->addAttribute('suffix', (string)$episode->type);
 1630             $xepisode->addAttribute('contentType', (string)$episode->mime);
 1631             // Create a clean fake path instead of song real file path to have better offline mode storage on Subsonic clients
 1632             $path = basename($episode->file);
 1633             $xepisode->addAttribute('path', (string)$path);
 1634         }
 1635     }
 1636 
 1637     /**
 1638      * addNewestPodcastEpisodes
 1639      * @param SimpleXMLElement $xml
 1640      * @param Podcast_Episode[] $episodes
 1641      */
 1642     public static function addNewestPodcastEpisodes($xml, $episodes)
 1643     {
 1644         $xpodcasts = $xml->addChild('newestPodcasts');
 1645         foreach ($episodes as $episode) {
 1646             $episode->format();
 1647             self::addPodcastEpisode($xpodcasts, $episode);
 1648         }
 1649     }
 1650 
 1651     /**
 1652      * addBookmarks
 1653      * @param SimpleXMLElement $xml
 1654      * @param Bookmark[] $bookmarks
 1655      */
 1656     public static function addBookmarks($xml, $bookmarks)
 1657     {
 1658         $xbookmarks = $xml->addChild('bookmarks');
 1659         foreach ($bookmarks as $bookmark) {
 1660             self::addBookmark($xbookmarks, $bookmark);
 1661         }
 1662     }
 1663 
 1664     /**
 1665      * addBookmark
 1666      * @param SimpleXMLElement $xml
 1667      * @param Bookmark $bookmark
 1668      */
 1669     private static function addBookmark($xml, $bookmark)
 1670     {
 1671         $xbookmark = $xml->addChild('bookmark');
 1672         $xbookmark->addAttribute('position', (string)$bookmark->position);
 1673         $xbookmark->addAttribute('username', (string)$bookmark->getUserName());
 1674         $xbookmark->addAttribute('comment', (string)$bookmark->comment);
 1675         $xbookmark->addAttribute('created', date("c", (int)$bookmark->creation_date));
 1676         $xbookmark->addAttribute('changed', date("c", (int)$bookmark->update_date));
 1677         if ($bookmark->object_type == "song") {
 1678             $song = new Song($bookmark->object_id);
 1679             self::addSong($xbookmark, $song->id, 'entry');
 1680         } elseif ($bookmark->object_type == "video") {
 1681             self::addVideo($xbookmark, new Video($bookmark->object_id), 'entry');
 1682         } elseif ($bookmark->object_type == "podcast_episode") {
 1683             self::addPodcastEpisode($xbookmark, new Podcast_Episode($bookmark->object_id), 'entry');
 1684         }
 1685     }
 1686 
 1687     /**
 1688      * addMessages
 1689      * @param SimpleXMLElement $xml
 1690      * @param integer[] $messages
 1691      */
 1692     public static function addMessages($xml, $messages)
 1693     {
 1694         $xmessages = $xml->addChild('chatMessages');
 1695         if (empty($messages)) {
 1696             return;
 1697         }
 1698         foreach ($messages as $message) {
 1699             $chat = new PrivateMsg($message);
 1700             self::addMessage($xmessages, $chat);
 1701         }
 1702     }
 1703 
 1704     /**
 1705      * addMessage
 1706      * @param SimpleXMLElement $xml
 1707      * @param PrivateMsg $message
 1708      */
 1709     private static function addMessage($xml, $message)
 1710     {
 1711         $user      = new User($message->getSenderUserId());
 1712         $xbookmark = $xml->addChild('chatMessage');
 1713         if ($user->fullname_public) {
 1714             $xbookmark->addAttribute('username', (string)$user->fullname);
 1715         } else {
 1716             $xbookmark->addAttribute('username', (string)$user->username);
 1717         }
 1718         $xbookmark->addAttribute('time', (string)($message->getCreationDate() * 1000));
 1719         $xbookmark->addAttribute('message', (string)$message->getMessage());
 1720     }
 1721 
 1722     /**
 1723      * @deprecated
 1724      */
 1725     private static function getSongRepository(): SongRepositoryInterface
 1726     {
 1727         global $dic;
 1728 
 1729         return $dic->get(SongRepositoryInterface::class);
 1730     }
 1731 
 1732     /**
 1733      * @deprecated
 1734      */
 1735     private static function getAlbumRepository(): AlbumRepositoryInterface
 1736     {
 1737         global $dic;
 1738 
 1739         return $dic->get(AlbumRepositoryInterface::class);
 1740     }
 1741 }