"Fossies" - the Fresh Open Source Software Archive

Member "ampache-5.0.0/src/Module/Api/Xml_Data.php" (31 Aug 2021, 64613 Bytes) of package /linux/www/ampache-5.0.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 "Xml_Data.php" see the Fossies "Dox" file reference documentation.

    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\Repository\Model\Album;
   28 use Ampache\Repository\Model\Bookmark;
   29 use Ampache\Repository\Model\Label;
   30 use Ampache\Repository\Model\library_item;
   31 use Ampache\Repository\Model\License;
   32 use Ampache\Repository\Model\Live_Stream;
   33 use Ampache\Repository\Model\Shoutbox;
   34 use Ampache\Repository\Model\Video;
   35 use Ampache\Module\Playback\Stream;
   36 use Ampache\Module\Util\ObjectTypeToClassNameMapper;
   37 use Ampache\Module\Util\Ui;
   38 use Ampache\Config\AmpConfig;
   39 use Ampache\Repository\Model\Art;
   40 use Ampache\Repository\Model\Artist;
   41 use Ampache\Repository\Model\Catalog;
   42 use Ampache\Module\System\Core;
   43 use Ampache\Repository\Model\Democratic;
   44 use Ampache\Repository\AlbumRepositoryInterface;
   45 use Ampache\Repository\SongRepositoryInterface;
   46 use DOMDocument;
   47 use Ampache\Repository\Model\Playlist;
   48 use Ampache\Repository\Model\Podcast;
   49 use Ampache\Repository\Model\Podcast_Episode;
   50 use Ampache\Repository\Model\Rating;
   51 use Ampache\Repository\Model\Search;
   52 use Ampache\Repository\Model\Share;
   53 use SimpleXMLElement;
   54 use Ampache\Repository\Model\Song;
   55 use Ampache\Repository\Model\Tag;
   56 use Ampache\Repository\Model\User;
   57 use Ampache\Repository\Model\Useractivity;
   58 use Ampache\Repository\Model\Userflag;
   59 
   60 /**
   61  * XML_Data Class
   62  *
   63  * This class takes care of all of the xml document stuff in Ampache these
   64  * are all static calls
   65  */
   66 class Xml_Data
   67 {
   68     // This is added so that we don't pop any webservers
   69     private static $limit  = 5000;
   70     private static $offset = 0;
   71     private static $type   = '';
   72 
   73     /**
   74      * set_offset
   75      *
   76      * This takes an int and changes the offset
   77      *
   78      * @param integer $offset Change the starting position of your results. (e.g 5001 when selecting in groups of 5000)
   79      */
   80     public static function set_offset($offset)
   81     {
   82         self::$offset = (int)$offset;
   83     } // set_offset
   84 
   85     /**
   86      * set_limit
   87      *
   88      * This sets the limit for any ampache transactions
   89      *
   90      * @param  integer $limit Set a limit on your results
   91      * @return boolean
   92      */
   93     public static function set_limit($limit)
   94     {
   95         if (!$limit) {
   96             return false;
   97         }
   98 
   99         self::$limit = (strtolower((string) $limit) == "none") ? null : (int) $limit;
  100 
  101         return true;
  102     } // set_limit
  103 
  104     /**
  105      * set_type
  106      *
  107      * This sets the type of XML_Data we are working on
  108      *
  109      * @param  string  $type XML_Data type
  110      * @return boolean
  111      */
  112     public static function set_type($type)
  113     {
  114         if (!in_array($type, array('rss', 'xspf', 'itunes'))) {
  115             return false;
  116         }
  117 
  118         self::$type = $type;
  119 
  120         return true;
  121     } // set_type
  122 
  123     /**
  124      * error
  125      *
  126      * This generates a standard XML Error message
  127      * nothing fancy here...
  128      *
  129      * @param  string $code Error code
  130      * @param  string $string Error message
  131      * @param  string $action
  132      * @param  string $type
  133      * @return string return error message xml
  134      */
  135     public static function error($code, $string, $action, $type)
  136     {
  137         $xml_string = "\t<error errorCode=\"$code\">\n\t\t<errorAction><![CDATA[$action]]></errorAction>\n\t\t<errorType><![CDATA[$type]]></errorType>\n\t\t<errorMessage><![CDATA[$string]]></errorMessage>\n\t</error>";
  138 
  139         return self::output_xml($xml_string);
  140     } // error
  141 
  142     /**
  143      * success
  144      *
  145      * This generates a standard XML Success message
  146      * nothing fancy here...
  147      *
  148      * @param  string $string success message
  149      * @param  array  $return_data
  150      * @return string return success message xml
  151      */
  152     public static function success($string, $return_data = array())
  153     {
  154         $xml_string = "\t<success code=\"1\"><![CDATA[$string]]></success>";
  155         foreach ($return_data as $title => $data) {
  156             $xml_string .= "\n\t<$title><![CDATA[$data]]></$title>";
  157         }
  158 
  159         return self::output_xml($xml_string);
  160     } // success
  161 
  162     /**
  163      * empty
  164      *
  165      * This generates an empty root element
  166      */
  167     public static function empty()
  168     {
  169         return "<?xml version=\"1.0\" encoding=\"" . AmpConfig::get('site_charset') . "\" ?>\n<root>\n</root>\n";
  170     } // empty
  171 
  172     /**
  173      * header
  174      *
  175      * This returns the header
  176      *
  177      * @param  string $title
  178      * @return string return xml
  179      * @see _header()
  180      */
  181     public static function header($title = null)
  182     {
  183         return self::_header($title);
  184     } // header
  185 
  186     /**
  187      * footer
  188      *
  189      * This returns the footer
  190      *
  191      * @return string return xml
  192      * @see _footer()
  193      */
  194     public static function footer()
  195     {
  196         return self::_footer();
  197     } // footer
  198 
  199     /**
  200      * genre_string
  201      *
  202      * This returns the formatted 'genre' string for an xml document
  203      * @param  array  $tags
  204      * @return string
  205      */
  206     private static function genre_string($tags)
  207     {
  208         $string = '';
  209 
  210         if (!empty($tags)) {
  211             $atags = array();
  212             foreach ($tags as $tag_id => $data) {
  213                 if (array_key_exists($data['id'], $atags)) {
  214                     $atags[$data['id']]['count']++;
  215                 } else {
  216                     $atags[$data['id']] = array(
  217                         'name' => $data['name'],
  218                         'count' => 1
  219                     );
  220                 }
  221             }
  222 
  223             foreach ($atags as $tag => $data) {
  224                 $string .= "\t<genre id=\"" . $tag . "\"><![CDATA[" . $data['name'] . "]]></genre>\n";
  225             }
  226         }
  227 
  228         return $string;
  229     } // genre_string
  230 
  231     /**
  232      * output_xml_from_array
  233      * This takes a one dimensional array and creates a XML document from it. For
  234      * use primarily by the ajax mojo.
  235      * @param  array   $array
  236      * @param  boolean $callback
  237      * @param  string  $type
  238      * @return string
  239      */
  240     public static function output_xml_from_array($array, $callback = false, $type = '')
  241     {
  242         $string = '';
  243 
  244         // If we weren't passed an array then return
  245         if (!is_array($array)) {
  246             return $string;
  247         }
  248 
  249         // The type is used for the different XML docs we pass
  250         switch ($type) {
  251             case 'itunes':
  252                 foreach ($array as $key => $value) {
  253                     if (is_array($value)) {
  254                         $value = xoutput_from_array($value, true, $type);
  255                         $string .= "\t\t<$key>\n$value\t\t</$key>\n";
  256                     } else {
  257                         if ($key == "key") {
  258                             $string .= "\t\t<$key>$value</$key>\n";
  259                         } elseif (is_int($value)) {
  260                             $string .= "\t\t\t<key>$key</key><integer>$value</integer>\n";
  261                         } elseif ($key == "Date Added") {
  262                             $string .= "\t\t\t<key>$key</key><date>$value</date>\n";
  263                         } elseif (is_string($value)) {
  264                             /* We need to escape the value */
  265                             $string .= "\t\t\t<key>$key</key><string><![CDATA[$value]]></string>\n";
  266                         }
  267                     }
  268                 } // end foreach
  269 
  270                 return $string;
  271             case 'xspf':
  272                 foreach ($array as $key => $value) {
  273                     if (is_array($value)) {
  274                         $value = xoutput_from_array($value, true, $type);
  275                         $string .= "\t\t<$key>\n$value\t\t</$key>\n";
  276                     } else {
  277                         if ($key == "key") {
  278                             $string .= "\t\t<$key>$value</$key>\n";
  279                         } elseif (is_numeric($value)) {
  280                             $string .= "\t\t\t<$key>$value</$key>\n";
  281                         } elseif (is_string($value)) {
  282                             /* We need to escape the value */
  283                             $string .= "\t\t\t<$key><![CDATA[$value]]></$key>\n";
  284                         }
  285                     }
  286                 } // end foreach
  287 
  288                 return $string;
  289             default:
  290                 foreach ($array as $key => $value) {
  291                     // No numeric keys
  292                     if (is_numeric($key)) {
  293                         $key = 'item';
  294                     }
  295 
  296                     if (is_array($value)) {
  297                         // Call ourself
  298                         $value = xoutput_from_array($value, true);
  299                         $string .= "\t<content div=\"$key\">$value</content>\n";
  300                     } else {
  301                         /* We need to escape the value */
  302                         $string .= "\t<content div=\"$key\"><![CDATA[$value]]></content>\n";
  303                     }
  304                     // end foreach elements
  305                 }
  306                 if (!$callback) {
  307                     $string = '<?xml version="1.0" encoding="utf-8" ?>' . "\n<root>\n" . $string . "</root>\n";
  308                 }
  309 
  310                 return Ui::clean_utf8($string);
  311         }
  312     } // output_from_array
  313 
  314     /**
  315      * keyed_array
  316      *
  317      * This will build an xml document from a key'd array,
  318      *
  319      * @param  array          $array keyed array of objects (key => value, key => value)
  320      * @param  boolean        $callback (don't output xml when true)
  321      * @param  string|boolean $object
  322      * @return string         return xml
  323      */
  324     public static function keyed_array($array, $callback = false, $object = false)
  325     {
  326         $string = '';
  327         // Foreach it
  328         foreach ($array as $key => $value) {
  329             $attribute = '';
  330             // See if the key has attributes
  331             if (is_array($value) && isset($value['attributes'])) {
  332                 $attribute = ' ' . $value['attributes'];
  333                 $key       = $value['value'];
  334             }
  335 
  336             // If it's an array, run again
  337             if (is_array($value)) {
  338                 $value = self::keyed_array($value, true);
  339                 $string .= ($object) ? "<$object>\n$value\n</$object>\n" : "<$key$attribute>\n$value\n</$key>\n";
  340             } else {
  341                 $string .= ($object) ? "\t<$object index=\"" . $key . "\"><![CDATA[$value]]></$object>\n" : "\t<$key$attribute><![CDATA[$value]]></$key>\n";
  342             }
  343         } // end foreach
  344 
  345         if (!$callback) {
  346             $string = self::output_xml($string);
  347         }
  348 
  349         return $string;
  350     } // keyed_array
  351 
  352     /**
  353      * object_array
  354      *
  355      * This will build an xml document from an array of arrays, an id is required for the array data
  356      * <root>
  357      *   <$object_type> //optional
  358      *     <$item id="123">
  359      *       <data></data>
  360      *
  361      * @param  array  $array
  362      * @param  string $item
  363      * @param  string $object_type
  364      * @return string return xml
  365      */
  366     public static function object_array($array, $item, $object_type = '')
  367     {
  368         $string = ($object_type == '') ? '' : "<$object_type>\n";
  369         // Foreach it
  370         foreach ($array as $object) {
  371             $string .= "\t<$item id=\"" . $object['id'] . "\">\n";
  372             foreach ($object as $name => $value) {
  373                 $filter = (is_numeric($value)) ? $value : "<![CDATA[$value]]>";
  374                 $string .= ($name !== 'id') ? "\t\t<$name>$filter</$name>\n" : '';
  375             }
  376             $string .= "\t</$item>\n";
  377         } // end foreach
  378         $string .= ($object_type == '') ? '' : "</$object_type>";
  379 
  380         return self::output_xml($string);
  381     } // object_array
  382 
  383     /**
  384      * indexes
  385      *
  386      * This takes an array of object_ids and return XML based on the type of object
  387      * we want
  388      *
  389      * @param  array   $objects Array of object_ids (Mixed string|int)
  390      * @param  string  $object_type 'artist'|'album'|'song'|'playlist'|'share'|'podcast'|'podcast_episode'|'video'|'live_stream'
  391      * @param  integer $user_id
  392      * @param  boolean $full_xml whether to return a full XML document or just the node.
  393      * @param  boolean $include include episodes from podcasts or tracks in a playlist
  394      * @return string  return xml
  395      */
  396     public static function indexes($objects, $object_type, $user_id = null, $full_xml = true, $include = false)
  397     {
  398         if ((count($objects) > self::$limit || self::$offset > 0) && (self::$limit && $full_xml)) {
  399             $objects = array_splice($objects, self::$offset, self::$limit);
  400         }
  401         $string = ($full_xml) ? "<total_count>" . Catalog::get_count($object_type) . "</total_count>\n" : '';
  402 
  403         // here is where we call the object type
  404         foreach ($objects as $object_id) {
  405             switch ($object_type) {
  406                 case 'artist':
  407                     if ($include) {
  408                         $string .= self::artists(array($object_id), array('songs', 'albums'), $user_id, false);
  409                     } else {
  410                         $artist = new Artist($object_id);
  411                         $artist->format();
  412                         $albums = static::getAlbumRepository()->getByArtist($object_id);
  413                         $string .= "<$object_type id=\"" . $object_id . "\">\n\t<name><![CDATA[" . $artist->f_name . "]]></name>\n";
  414                         foreach ($albums as $album_id) {
  415                             if ($album_id > 0) {
  416                                 $album = new Album($album_id);
  417                                 $string .= "\t<album id=\"" . $album_id . '"><![CDATA[' . $album->f_name . "]]></album>\n";
  418                             }
  419                         }
  420                         $string .= "</$object_type>\n";
  421                     }
  422                     break;
  423                 case 'album':
  424                     if ($include) {
  425                         $string .= self::albums(array($object_id), array('songs'), $user_id, false);
  426                     } else {
  427                         $album = new Album($object_id);
  428                         $album->format();
  429                         $string .= "<$object_type id=\"" . $object_id . "\">\n\t<name><![CDATA[" . $album->f_name . "]]></name>\n\t\t<artist id=\"" . $album->album_artist . "\"><![CDATA[" . $album->f_album_artist_name . "]]></artist>\n</$object_type>\n";
  430                     }
  431                     break;
  432                 case 'song':
  433                     $song = new Song($object_id);
  434                     $song->format();
  435                     $string .= "<$object_type id=\"" . $object_id . "\">\n\t<title><![CDATA[" . $song->f_title . "]]></title>\n\t<name><![CDATA[" . $song->f_title . "]]></name>\n\t<artist id=\"" . $song->artist . "\"><![CDATA[" . $song->get_artist_name() . "]]></artist>\n\t<album id=\"" . $song->album . "\"><![CDATA[" . $song->get_album_name() . "]]></album>\n\t<albumartist id=\"" . $song->albumartist . "\"><![CDATA[" . $song->get_album_artist_name() . "]]></albumartist>\n\t<disk><![CDATA[" . $song->disk . "]]></disk>\n\t<track>" . $song->track . "</track>\n</$object_type>\n";
  436                     break;
  437                 case 'playlist':
  438                     if ((int) $object_id === 0) {
  439                         $playlist       = new Search((int) str_replace('smart_', '', (string) $object_id));
  440                         $last_count     = ((int) $playlist->last_count > 0) ? $playlist->last_count : 5000;
  441                         $playitem_total = ($playlist->limit == 0) ? $last_count : $playlist->limit;
  442                     } else {
  443                         $playlist       = new Playlist($object_id);
  444                         $playitem_total = $playlist->get_media_count('song');
  445                     }
  446                     $playlist_name = $playlist->get_fullname();
  447                     $playlist_user = $playlist->username;
  448 
  449                     $songs = ($include) ? $playlist->get_items() : array();
  450                     $string .= "<$object_type id=\"" . $object_id . "\">\n\t<name><![CDATA[" . $playlist_name . "]]></name>\n\t<items><![CDATA[" . $playitem_total . "]]></items>\n\t<owner><![CDATA[" . $playlist_user . "]]></owner>\n\t<type><![CDATA[" . $playlist->type . "]]></type>\n";
  451                     $playlist_track = 0;
  452                     foreach ($songs as $song_id) {
  453                         if ($song_id['object_type'] == 'song') {
  454                             $playlist_track++;
  455                             $string .= "\t\t<playlisttrack id=\"" . $song_id['object_id'] . "\">" . $playlist_track . "</playlisttrack>\n";
  456                         }
  457                     }
  458                     $string .= "</$object_type>\n";
  459                     break;
  460                 case 'share':
  461                     $string .= self::shares($objects);
  462                     break;
  463                 case 'podcast':
  464                     $podcast = new Podcast($object_id);
  465                     $podcast->format();
  466                     $string .= "<podcast id=\"$object_id\">\n\t<name><![CDATA[" . $podcast->f_title . "]]></name>\n\t<description><![CDATA[" . $podcast->description . "]]></description>\n\t<language><![CDATA[" . $podcast->f_language . "]]></language>\n\t<copyright><![CDATA[" . $podcast->f_copyright . "]]></copyright>\n\t<feed_url><![CDATA[" . $podcast->feed . "]]></feed_url>\n\t<generator><![CDATA[" . $podcast->f_generator . "]]></generator>\n\t<website><![CDATA[" . $podcast->f_website . "]]></website>\n\t<build_date><![CDATA[" . $podcast->f_lastbuilddate . "]]></build_date>\n\t<sync_date><![CDATA[" . $podcast->f_lastsync . "]]></sync_date>\n\t<public_url><![CDATA[" . $podcast->link . "]]></public_url>\n";
  467                     if ($include) {
  468                         $episodes = $podcast->get_episodes();
  469                         foreach ($episodes as $episode_id) {
  470                             $string .= self::podcast_episodes(array($episode_id), $user_id, false);
  471                         }
  472                     }
  473                     $string .= "\t</podcast>\n";
  474                     break;
  475                 case 'podcast_episode':
  476                     $string .= self::podcast_episodes($objects, $user_id);
  477                     break;
  478                 case 'video':
  479                     $string .= self::videos($objects, $user_id);
  480                     break;
  481                 case 'live_stream':
  482                     $live_stream = new Live_Stream($object_id);
  483                     $live_stream->format();
  484                     $string .= "<$object_type id=\"" . $object_id . "\">\n\t<name><![CDATA[" . $live_stream->f_name . "]]></name>\n\t<url><![CDATA[" . $live_stream->url . "]]></url>\n\t<codec><![CDATA[" . $live_stream->codec . "]]></codec>\n</$object_type>\n";
  485             }
  486         } // end foreach objects
  487 
  488         return self::output_xml($string, $full_xml);
  489     } // indexes
  490 
  491     /**
  492      * licenses
  493      *
  494      * This returns licenses to the user, in a pretty xml document with the information
  495      *
  496      * @param  integer[] $licenses Licence id's assigned to songs and artists
  497      * @return string    return xml
  498      */
  499     public static function licenses($licenses)
  500     {
  501         if ((count($licenses) > self::$limit || self::$offset > 0) && self::$limit) {
  502             $licenses = array_splice($licenses, self::$offset, self::$limit);
  503         }
  504         $string = "<total_count>" . Catalog::get_count('license') . "</total_count>\n";
  505 
  506         foreach ($licenses as $license_id) {
  507             $license = new license($license_id);
  508             $string .= "<license id=\"$license_id\">\n\t<name><![CDATA[$license->name]]></name>\n\t<description><![CDATA[$license->description]]></description>\n\t<external_link><![CDATA[$license->external_link]]></external_link>\n</license>\n";
  509         } // end foreach
  510 
  511         return self::output_xml($string);
  512     } // licenses
  513 
  514     /**
  515      * labels
  516      *
  517      * This returns labels to the user, in a pretty xml document with the information
  518      *
  519      * @param  integer[] $labels
  520      * @return string    return xml
  521      */
  522     public static function labels($labels)
  523     {
  524         if ((count($labels) > self::$limit || self::$offset > 0) && self::$limit) {
  525             $labels = array_splice($labels, self::$offset, self::$limit);
  526         }
  527         $string = "<total_count>" . Catalog::get_count('license') . "</total_count>\n";
  528 
  529         foreach ($labels as $label_id) {
  530             $label = new Label($label_id);
  531             $label->format();
  532 
  533             $string .= "<license id=\"$label_id\">\n\t<name><![CDATA[$label->f_name]]></name>\n\t<artists><![CDATA[$label->artists]]></artists>\n\t<summary><![CDATA[$label->summary]]></summary>\n\t<external_link><![CDATA[$label->link]]></external_link>\n\t<address><![CDATA[$label->address]]></address>\n\t<category><![CDATA[$label->category]]></category>\n\t<email><![CDATA[$label->email]]></email>\n\t<website><![CDATA[$label->website]]></website>\n\t<user><![CDATA[$label->user]]></user>\n</license>\n";
  534         } // end foreach
  535 
  536         return self::output_xml($string);
  537     } // labels
  538 
  539     /**
  540      * genres
  541      *
  542      * This returns genres to the user, in a pretty xml document with the information
  543      *
  544      * @param  integer[] $tags Genre id's to include
  545      * @return string    return xml
  546      */
  547     public static function genres($tags)
  548     {
  549         if ((count($tags) > self::$limit || self::$offset > 0) && self::$limit) {
  550             $tags = array_splice($tags, self::$offset, self::$limit);
  551         }
  552         $string = "<total_count>" . Catalog::get_count('tag') . "</total_count>\n";
  553 
  554         foreach ($tags as $tag_id) {
  555             $tag    = new Tag($tag_id);
  556             $counts = $tag->count();
  557             $string .= "<genre id=\"$tag_id\">\n\t<name><![CDATA[$tag->name]]></name>\n\t<albums>" . (int) ($counts['album']) . "</albums>\n\t<artists>" . (int) ($counts['artist']) . "</artists>\n\t<songs>" . (int) ($counts['song']) . "</songs>\n\t<videos>" . (int) ($counts['video']) . "</videos>\n\t<playlists>" . (int) ($counts['playlist']) . "</playlists>\n\t<live_streams>" . (int) ($counts['live_stream']) . "</live_streams>\n</genre>\n";
  558         } // end foreach
  559 
  560         return self::output_xml($string);
  561     } // genres
  562 
  563     /**
  564      * artists
  565      *
  566      * This takes an array of artists and then returns a pretty xml document with the information
  567      * we want
  568      *
  569      * @param  integer[] $artists Artist id's to include
  570      * @param  array     $include Array of other items to include.
  571      * @param  integer   $user_id
  572      * @param  boolean   $full_xml whether to return a full XML document or just the node.
  573      * @return string    return xml
  574      */
  575     public static function artists($artists, $include = [], $user_id = null, $full_xml = true)
  576     {
  577         if ((count($artists) > self::$limit || self::$offset > 0) && (self::$limit && $full_xml)) {
  578             $artists = array_splice($artists, self::$offset, self::$limit);
  579         }
  580         $string = ($full_xml) ? "<total_count>" . Catalog::get_count('artist') . "</total_count>\n" : '';
  581 
  582         Rating::build_cache('artist', $artists);
  583 
  584         foreach ($artists as $artist_id) {
  585             $artist = new Artist($artist_id);
  586             $artist->format();
  587 
  588             $rating     = new Rating($artist_id, 'artist');
  589             $flag       = new Userflag($artist_id, 'artist');
  590             $tag_string = self::genre_string($artist->tags);
  591 
  592             // Build the Art URL, include session
  593             $art_url = AmpConfig::get('web_path') . '/image.php?object_id=' . $artist_id . '&object_type=artist&auth=' . scrub_out(Core::get_request('auth'));
  594 
  595             // Handle includes
  596             $albums = (in_array("albums", $include))
  597                 ? self::albums(static::getAlbumRepository()->getByArtist($artist_id), array(), $user_id, false)
  598                 : '';
  599             $songs = (in_array("songs", $include))
  600                 ? self::songs(static::getSongRepository()->getByArtist($artist_id), $user_id, false)
  601                 : '';
  602 
  603             $string .= "<artist id=\"" . $artist->id . "\">\n\t<name><![CDATA[" . $artist->f_name . "]]></name>\n" . $tag_string . "\t<albums>" . $albums . "</albums>\n\t<albumcount>" . ($artist->albums ?: 0) . "</albumcount>\n\t<songs>" . $songs . "</songs>\n\t<songcount>" . ($artist->songs ?: 0) . "</songcount>\n\t<art><![CDATA[$art_url]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n\t<mbid><![CDATA[" . $artist->mbid . "]]></mbid>\n\t<summary><![CDATA[" . $artist->summary . "]]></summary>\n\t<time><![CDATA[" . $artist->time . "]]></time>\n\t<yearformed>" . (int) $artist->yearformed . "</yearformed>\n\t<placeformed><![CDATA[" . $artist->placeformed . "]]></placeformed>\n</artist>\n";
  604         } // end foreach artists
  605 
  606         return self::output_xml($string, $full_xml);
  607     } // artists
  608 
  609     /**
  610      * albums
  611      *
  612      * This echos out a standard albums XML document, it pays attention to the limit
  613      *
  614      * @param  integer[] $albums Album id's to include
  615      * @param  array     $include Array of other items to include.
  616      * @param  integer   $user_id
  617      * @param  boolean   $full_xml whether to return a full XML document or just the node.
  618      * @return string    return xml
  619      */
  620     public static function albums($albums, $include = [], $user_id = null, $full_xml = true)
  621     {
  622         if ($include == null || $include == '') {
  623             $include = array();
  624         }
  625 
  626         if ((count($albums) > self::$limit || self::$offset > 0) && (self::$limit && $full_xml)) {
  627             $albums = array_splice($albums, self::$offset, self::$limit);
  628         }
  629         $string = ($full_xml) ? "<total_count>" . Catalog::get_count('album') . "</total_count>\n" : '';
  630         // original year (fall back to regular year)
  631         $original_year = AmpConfig::get('use_original_year');
  632 
  633         Rating::build_cache('album', $albums);
  634 
  635         foreach ($albums as $album_id) {
  636             $album = new Album($album_id);
  637             $album->format();
  638 
  639             $disk   = $album->disk;
  640             $rating = new Rating($album_id, 'album');
  641             $flag   = new Userflag($album_id, 'album');
  642             $year   = ($original_year && $album->original_year)
  643                 ? $album->original_year
  644                 : $album->year;
  645 
  646             // Build the Art URL, include session
  647             $art_url = AmpConfig::get('web_path') . '/image.php?object_id=' . $album->id . '&object_type=album&auth=' . scrub_out(Core::get_request('auth'));
  648 
  649             $string .= "<album id=\"" . $album->id . "\">\n\t<name><![CDATA[" . $album->f_name . "]]></name>\n";
  650 
  651             // Do a little check for artist stuff
  652             if ($album->f_album_artist_name != "") {
  653                 $string .= "\t<artist id=\"$album->album_artist\"><![CDATA[$album->f_album_artist_name]]></artist>\n";
  654             } elseif ($album->artist_count != 1) {
  655                 $string .= "\t<artist id=\"0\"><![CDATA[Various]]></artist>\n";
  656             } else {
  657                 $string .= "\t<artist id=\"$album->album_artist\"><![CDATA[$album->f_artist_name]]></artist>\n";
  658             }
  659 
  660             // Handle includes
  661             $songs = (in_array("songs", $include))
  662                 ? self::songs(static::getSongRepository()->getByAlbum($album->id), $user_id, false)
  663                 : '';
  664 
  665             // count multiple disks
  666             if ($album->allow_group_disks) {
  667                 $disk = (count($album->album_suite) <= 1) ? $album->disk : count($album->album_suite);
  668             }
  669 
  670             $string .= "\t<time>" . $album->total_duration . "</time>\n\t<year>" . $year . "</year>\n\t<tracks>" . $songs . "</tracks>\n\t<songcount>" . $album->song_count . "</songcount>\n\t<diskcount>" . $disk . "</diskcount>\n\t<type>" . $album->release_type . "</type>\n" . self::genre_string($album->tags) . "\t<art><![CDATA[$art_url]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . ($rating->get_average_rating() ?: null) . "</averagerating>\n\t<mbid><![CDATA[" . $album->mbid . "]]></mbid>\n</album>\n";
  671         } // end foreach
  672 
  673         return self::output_xml($string, $full_xml);
  674     } // albums
  675 
  676     /**
  677      * playlists
  678      *
  679      * This takes an array of playlist ids and then returns a nice pretty XML document
  680      *
  681      * @param  array   $playlists Playlist id's to include
  682      * @param  integer $user_id
  683      * @return string  return xml
  684      */
  685     public static function playlists($playlists, $user_id = null)
  686     {
  687         if ((count($playlists) > self::$limit || self::$offset > 0) && self::$limit) {
  688             $playlists = array_slice($playlists, self::$offset, self::$limit);
  689         }
  690         $string = "<total_count>" . Catalog::get_count('playlist') . "</total_count>\n";
  691 
  692         // Foreach the playlist ids
  693         foreach ($playlists as $playlist_id) {
  694             /**
  695              * Strip smart_ from playlist id and compare to original
  696              * smartlist = 'smart_1'
  697              * playlist  = 1000000
  698              */
  699             if ((int) $playlist_id === 0) {
  700                 $playlist = new Search((int) str_replace('smart_', '', (string) $playlist_id));
  701 
  702                 $last_count     = ((int) $playlist->last_count > 0) ? $playlist->last_count : 5000;
  703                 $playitem_total = ($playlist->limit == 0) ? $last_count : $playlist->limit;
  704                 $object_type    = 'search';
  705             } else {
  706                 $playlist    = new Playlist($playlist_id);
  707                 $playlist_id = $playlist->id;
  708 
  709                 $playitem_total = $playlist->get_media_count('song');
  710                 $object_type    = 'playlist';
  711             }
  712             $playlist_name = $playlist->get_fullname();
  713             $playlist_user = $playlist->username;
  714             $playlist_type = $playlist->type;
  715 
  716             $rating  = new Rating($playlist_id, $object_type);
  717             $flag    = new Userflag($playlist_id, $object_type);
  718             $art_url = Art::url($playlist_id, $object_type, Core::get_request('auth'));
  719 
  720             // Build this element
  721             $string .= "<playlist id=\"$playlist_id\">\n\t<name><![CDATA[$playlist_name]]></name>\n\t<owner><![CDATA[$playlist_user]]></owner>\n\t<items>$playitem_total</items>\n\t<type>$playlist_type</type>\n\t<art><![CDATA[" . $art_url . "]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n</playlist>\n";
  722         } // end foreach
  723 
  724         return self::output_xml($string);
  725     } // playlists
  726 
  727     /**
  728      * shares
  729      *
  730      * This returns shares to the user, in a pretty xml document with the information
  731      *
  732      * @param  integer[] $shares Share id's to include
  733      * @return string    return xml
  734      */
  735     public static function shares($shares)
  736     {
  737         if ((count($shares) > self::$limit || self::$offset > 0) && self::$limit) {
  738             $shares = array_splice($shares, self::$offset, self::$limit);
  739         }
  740         $string = "<total_count>" . Catalog::get_count('share') . "</total_count>\n";
  741 
  742         foreach ($shares as $share_id) {
  743             $share = new Share($share_id);
  744             $string .= "<share id=\"$share_id\">\n\t<name><![CDATA[" . $share->getObjectName() . "]]></name>\n\t<user><![CDATA[" . $share->getUserName() . "]]></user>\n\t<allow_stream>" . $share->allow_stream . "</allow_stream>\n\t<allow_download>" . $share->allow_download . "</allow_download>\n\t<creation_date>" . $share->creation_date . "</creation_date>\n\t<lastvisit_date>" . $share->lastvisit_date . "</lastvisit_date>\n\t<object_type><![CDATA[" . $share->object_type . "]]></object_type>\n\t<object_id>" . $share->object_id . "</object_id>\n\t<expire_days>" . $share->expire_days . "</expire_days>\n\t<max_counter>" . $share->max_counter . "</max_counter>\n\t<counter>" . $share->counter . "</counter>\n\t<secret><![CDATA[" . $share->secret . "]]></secret>\n\t<public_url><![CDATA[" . $share->public_url . "]]></public_url>\n\t<description><![CDATA[" . $share->description . "]]></description>\n</share>\n";
  745         } // end foreach
  746 
  747         return self::output_xml($string);
  748     } // shares
  749 
  750     /**
  751      * bookmarks
  752      *
  753      * This returns bookmarks to the user, in a pretty xml document with the information
  754      *
  755      * @param  integer[] $bookmarks Bookmark id's to include
  756      * @return string    return xml
  757      */
  758     public static function bookmarks($bookmarks)
  759     {
  760         $string = "";
  761         foreach ($bookmarks as $bookmark_id) {
  762             $bookmark = new Bookmark($bookmark_id);
  763             $string .= "<bookmark id=\"$bookmark_id\">\n\t<user><![CDATA[" . $bookmark->getUserName() . "]]></user>\n\t<object_type><![CDATA[" . $bookmark->object_type . "]]></object_type>\n\t<object_id>" . $bookmark->object_id . "</object_id>\n\t<position>" . $bookmark->position . "</position>\n\t<client><![CDATA[" . $bookmark->comment . "]]></client>\n\t<creation_date>" . $bookmark->creation_date . "</creation_date>\n\t<update_date><![CDATA[" . $bookmark->update_date . "]]></update_date>\n</bookmark>\n";
  764         } // end foreach
  765 
  766         return self::output_xml($string);
  767     } // bookmarks
  768 
  769     /**
  770      * catalogs
  771      *
  772      * This returns catalogs to the user, in a pretty xml document with the information
  773      *
  774      * @param  integer[] $catalogs group of catalog id's
  775      * @return string    return xml
  776      */
  777     public static function catalogs($catalogs)
  778     {
  779         if ((count($catalogs) > self::$limit || self::$offset > 0) && self::$limit) {
  780             $catalogs = array_splice($catalogs, self::$offset, self::$limit);
  781         }
  782         $string = "<total_count>" . Catalog::get_count('catalog') . "</total_count>\n";
  783 
  784         foreach ($catalogs as $catalog_id) {
  785             $catalog = Catalog::create_from_id($catalog_id);
  786             $catalog->format();
  787             $string .= "<catalog id=\"$catalog_id\">\n\t<name><![CDATA[" . $catalog->name . "]]></name>\n\t<type><![CDATA[" . $catalog->catalog_type . "]]></type>\n\t<gather_types><![CDATA[" . $catalog->gather_types . "]]></gather_types>\n\t<enabled>" . $catalog->enabled . "</enabled>\n\t<last_add>" . $catalog->last_add . "</last_add>\n\t<last_clean>" . $catalog->last_clean . "</last_clean>\n\t<last_update>" . $catalog->last_update . "</last_update>\n\t<path><![CDATA[" . $catalog->f_info . "]]></path>\n\t<rename_pattern><![CDATA[" . $catalog->rename_pattern . "]]></rename_pattern>\n\t<sort_pattern><![CDATA[" . $catalog->sort_pattern . "]]></sort_pattern>\n</catalog>\n";
  788         } // end foreach
  789 
  790         return self::output_xml($string);
  791     } // catalogs
  792 
  793     /**
  794      * podcasts
  795      *
  796      * This returns podcasts to the user, in a pretty xml document with the information
  797      *
  798      * @param  integer[] $podcasts Podcast id's to include
  799      * @param  integer   $user_id
  800      * @param  boolean   $episodes include the episodes of the podcast // optional
  801      * @return string    return xml
  802      */
  803     public static function podcasts($podcasts, $user_id = null, $episodes = false)
  804     {
  805         if ((count($podcasts) > self::$limit || self::$offset > 0) && self::$limit) {
  806             $podcasts = array_splice($podcasts, self::$offset, self::$limit);
  807         }
  808         $string = "<total_count>" . Catalog::get_count('podcast') . "</total_count>\n";
  809 
  810         foreach ($podcasts as $podcast_id) {
  811             $podcast = new Podcast($podcast_id);
  812             $podcast->format();
  813             $rating  = new Rating($podcast_id, 'podcast');
  814             $flag    = new Userflag($podcast_id, 'podcast');
  815             $art_url = Art::url($podcast_id, 'podcast', Core::get_request('auth'));
  816             $string .= "<podcast id=\"$podcast_id\">\n\t<name><![CDATA[" . $podcast->f_title . "]]></name>\n\t<description><![CDATA[" . $podcast->description . "]]></description>\n\t<language><![CDATA[" . $podcast->f_language . "]]></language>\n\t<copyright><![CDATA[" . $podcast->f_copyright . "]]></copyright>\n\t<feed_url><![CDATA[" . $podcast->feed . "]]></feed_url>\n\t<generator><![CDATA[" . $podcast->f_generator . "]]></generator>\n\t<website><![CDATA[" . $podcast->f_website . "]]></website>\n\t<build_date><![CDATA[" . $podcast->f_lastbuilddate . "]]></build_date>\n\t<sync_date><![CDATA[" . $podcast->f_lastsync . "]]></sync_date>\n\t<public_url><![CDATA[" . $podcast->link . "]]></public_url>\n\t<art><![CDATA[" . $art_url . "]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n";
  817             if ($episodes) {
  818                 $items = $podcast->get_episodes();
  819                 if (count($items) > 0) {
  820                     $string .= self::podcast_episodes($items, $user_id, false);
  821                 }
  822             }
  823             $string .= "\t</podcast>\n";
  824         } // end foreach
  825 
  826         return self::output_xml($string);
  827     } // podcasts
  828 
  829     /**
  830      * podcast_episodes
  831      *
  832      * This returns podcasts to the user, in a pretty xml document with the information
  833      *
  834      * @param  integer[] $podcast_episodes Podcast_Episode id's to include
  835      * @param  integer   $user_id
  836      * @param  boolean   $full_xml whether to return a full XML document or just the node.
  837      * @return string    return xml
  838      */
  839     public static function podcast_episodes($podcast_episodes, $user_id = null, $full_xml = true)
  840     {
  841         if ((count($podcast_episodes) > self::$limit || self::$offset > 0) && (self::$limit && $full_xml)) {
  842             $podcast_episodes = array_splice($podcast_episodes, self::$offset, self::$limit);
  843         }
  844         $string = ($full_xml) ? "<total_count>" . Catalog::get_count('podcast_episode') . "</total_count>\n" : '';
  845 
  846         foreach ($podcast_episodes as $episode_id) {
  847             $episode = new Podcast_Episode($episode_id);
  848             $episode->format();
  849             $rating  = new Rating($episode_id, 'podcast_episode');
  850             $flag    = new Userflag($episode_id, 'podcast_episode');
  851             $art_url = Art::url($episode->podcast, 'podcast', Core::get_request('auth'));
  852             $string .= "\t<podcast_episode id=\"$episode_id\">\n\t\t<title><![CDATA[" . $episode->f_title . "]]></title>\n\t\t<name><![CDATA[" . $episode->f_title . "]]></name>\n\t\t<description><![CDATA[" . $episode->f_description . "]]></description>\n\t\t<category><![CDATA[" . $episode->f_category . "]]></category>\n\t\t<author><![CDATA[" . $episode->f_author . "]]></author>\n\t\t<author_full><![CDATA[" . $episode->f_artist_full . "]]></author_full>\n\t\t<website><![CDATA[" . $episode->f_website . "]]></website>\n\t\t<pubdate><![CDATA[" . $episode->f_pubdate . "]]></pubdate>\n\t\t<state><![CDATA[" . $episode->f_state . "]]></state>\n\t\t<filelength><![CDATA[" . $episode->f_time_h . "]]></filelength>\n\t\t<filesize><![CDATA[" . $episode->f_size . "]]></filesize>\n\t\t<filename><![CDATA[" . $episode->f_file . "]]></filename>\n\t\t<mime><![CDATA[" . $episode->mime . "]]></mime>\n\t\t<public_url><![CDATA[" . $episode->link . "]]></public_url>\n\t\t<url><![CDATA[" . $episode->play_url('', 'api', false, $user_id) . "]]></url>\n\t\t<catalog><![CDATA[" . $episode->catalog . "]]></catalog>\n\t\t<art><![CDATA[" . $art_url . "]]></art>\n\t\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n\t\t<playcount>" . $episode->object_cnt . "</playcount>\n\t\t<played>" . $episode->played . "</played>\n\t</podcast_episode>\n";
  853         } // end foreach
  854 
  855         return self::output_xml($string, $full_xml);
  856     } // podcast_episodes
  857 
  858     /**
  859      * songs
  860      *
  861      * This returns an xml document from an array of song ids.
  862      * (Spiffy isn't it!)
  863      * @param integer[] $songs
  864      * @param integer   $user_id
  865      * @param boolean   $full_xml
  866      * @return string   return xml
  867      */
  868     public static function songs($songs, $user_id = null, $full_xml = true)
  869     {
  870         if ((count($songs) > self::$limit || self::$offset > 0) && (self::$limit && $full_xml)) {
  871             $songs = array_slice($songs, self::$offset, self::$limit);
  872         }
  873         $string = ($full_xml) ? "<total_count>" . Catalog::get_count('song') . "</total_count>\n" : '';
  874 
  875         Song::build_cache($songs);
  876         Stream::set_session(Core::get_request('auth'));
  877 
  878         $playlist_track = 0;
  879 
  880         // Foreach the ids!
  881         foreach ($songs as $song_id) {
  882             $song = new Song($song_id);
  883 
  884             // If the song id is invalid/null
  885             if (!$song->id) {
  886                 continue;
  887             }
  888 
  889             $song->format();
  890             $tag_string    = self::genre_string(Tag::get_top_tags('song', $song_id));
  891             $rating        = new Rating($song_id, 'song');
  892             $flag          = new Userflag($song_id, 'song');
  893             $show_song_art = AmpConfig::get('show_song_art', false);
  894             $has_art       = Art::has_db($song->id, 'song');
  895             $art_object    = ($show_song_art && $has_art) ? $song->id : $song->album;
  896             $art_type      = ($show_song_art && $has_art) ? 'song' : 'album';
  897             $art_url       = Art::url($art_object, $art_type, Core::get_request('auth'));
  898             $playlist_track++;
  899 
  900             $string .= "<song id=\"" . $song->id . "\">\n\t<title><![CDATA[" . $song->f_title . "]]></title>\n\t<name><![CDATA[" . $song->f_title . "]]></name>\n\t<artist id=\"" . $song->artist . "\"><![CDATA[" . $song->get_artist_name() . "]]></artist>\n\t<album id=\"" . $song->album . "\"><![CDATA[" . $song->get_album_name() . "]]></album>\n\t<albumartist id=\"" . $song->albumartist . "\"><![CDATA[" . $song->get_album_artist_name() . "]]></albumartist>\n\t<disk><![CDATA[" . $song->disk . "]]></disk>\n\t<track>" . $song->track . "</track>\n" . $tag_string . "\t<filename><![CDATA[" . $song->file . "]]></filename>\n\t<playlisttrack>" . $playlist_track . "</playlisttrack>\n\t<time>" . $song->time . "</time>\n\t<year>" . $song->year . "</year>\n\t<bitrate>" . $song->bitrate . "</bitrate>\n\t<rate>" . $song->rate . "</rate>\n\t<mode><![CDATA[" . $song->mode . "]]></mode>\n\t<mime><![CDATA[" . $song->mime . "]]></mime>\n\t<url><![CDATA[" . $song->play_url('', 'api', false, $user_id) . "]]></url>\n\t<size>" . $song->size . "</size>\n\t<mbid><![CDATA[" . $song->mbid . "]]></mbid>\n\t<album_mbid><![CDATA[" . $song->album_mbid . "]]></album_mbid>\n\t<artist_mbid><![CDATA[" . $song->artist_mbid . "]]></artist_mbid>\n\t<albumartist_mbid><![CDATA[" . $song->albumartist_mbid . "]]></albumartist_mbid>\n\t<art><![CDATA[" . $art_url . "]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n\t<playcount>" . $song->object_cnt . "</playcount>\n\t<catalog>" . $song->catalog . "</catalog>\n\t<composer><![CDATA[" . $song->composer . "]]></composer>\n\t<channels>" . $song->channels . "</channels>\n\t<comment><![CDATA[" . $song->comment . "]]></comment>\n\t<license><![CDATA[" . $song->f_license . "]]></license>\n\t<publisher><![CDATA[" . $song->label . "]]></publisher>\n\t<language>" . $song->language . "</language>\n\t<replaygain_album_gain>" . $song->replaygain_album_gain . "</replaygain_album_gain>\n\t<replaygain_album_peak>" . $song->replaygain_album_peak . "</replaygain_album_peak>\n\t<replaygain_track_gain>" . $song->replaygain_track_gain . "</replaygain_track_gain>\n\t<replaygain_track_peak>" . $song->replaygain_track_peak . "</replaygain_track_peak>\n\t<r128_album_gain>" . $song->r128_album_gain . "</r128_album_gain>\n\t<r128_track_gain>" . $song->r128_track_gain . "</r128_track_gain>\n";
  901             if (Song::isCustomMetadataEnabled()) {
  902                 foreach ($song->getMetadata() as $metadata) {
  903                     $meta_name = str_replace(array(' ', '(', ')', '/', '\\', '#'), '_',
  904                         $metadata->getField()->getName());
  905                     $string .= "\t<" . $meta_name . "><![CDATA[" . $metadata->getData() . "]]></" . $meta_name . ">\n";
  906                 }
  907             }
  908 
  909             $string .= "</song>\n";
  910         } // end foreach
  911 
  912         return self::output_xml($string, $full_xml);
  913     } // songs
  914 
  915     /**
  916      * videos
  917      *
  918      * This builds the xml document for displaying video objects
  919      *
  920      * @param  integer[] $videos Video id's to include
  921      * @param  integer   $user_id
  922      * @return string    return xml
  923      */
  924     public static function videos($videos, $user_id = null)
  925     {
  926         if ((count($videos) > self::$limit || self::$offset > 0) && self::$limit) {
  927             $videos = array_slice($videos, self::$offset, self::$limit);
  928         }
  929         $string = "<total_count>" . Catalog::get_count('video') . "</total_count>\n";
  930 
  931         foreach ($videos as $video_id) {
  932             $video = new Video($video_id);
  933             $video->format();
  934             $rating  = new Rating($video_id, 'video');
  935             $flag    = new Userflag($video_id, 'video');
  936             $art_url = Art::url($video_id, 'video', Core::get_request('auth'));
  937 
  938             $string .= "<video id=\"" . $video->id . "\">\n\t<title><![CDATA[" . $video->title . "]]></title>\n\t<name><![CDATA[" . $video->title . "]]></name>\n\t<mime><![CDATA[" . $video->mime . "]]></mime>\n\t<resolution><![CDATA[" . $video->f_resolution . "]]></resolution>\n\t<size>" . $video->size . "</size>\n" . self::genre_string($video->tags) . "\t<time><![CDATA[" . $video->time . "]]></time>\n\t<url><![CDATA[" . $video->play_url('', 'api', false, $user_id) . "]]></url>\n\t<art><![CDATA[" . $art_url . "]]></art>\n\t<flag>" . (!$flag->get_flag($user_id, false) ? 0 : 1) . "</flag>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . (string) ($rating->get_average_rating() ?: null) . "</averagerating>\n\t<playcount>" . $video->object_cnt . "</playcount>\n</video>\n";
  939         } // end foreach
  940 
  941         return self::output_xml($string);
  942     } // videos
  943 
  944     /**
  945      * democratic
  946      *
  947      * This handles creating an xml document for democratic items, this can be a little complicated
  948      * due to the votes and all of that
  949      *
  950      * @param  array    $object_ids Object IDs
  951      * @param  integer  $user_id
  952      * @return string   return xml
  953      */
  954     public static function democratic($object_ids = array(), $user_id = null)
  955     {
  956         $democratic = Democratic::get_current_playlist();
  957         $string     = '';
  958 
  959         foreach ($object_ids as $row_id => $data) {
  960             $class_name = ObjectTypeToClassNameMapper::map($data['object_type']);
  961             $song       = new $class_name($data['object_id']);
  962             $song->format();
  963 
  964             // FIXME: This is duplicate code and so wrong, functions need to be improved
  965             $tag           = new Tag($song->tags['0']);
  966             $song->genre   = $tag->id;
  967             $song->f_genre = $tag->name;
  968 
  969             $tag_string = self::genre_string($song->tags);
  970             $rating     = new Rating($song->id, 'song');
  971             $art_url    = Art::url($song->album, 'album', Core::get_request('auth'));
  972 
  973             $string .= "<song id=\"" . $song->id . "\">\n\t<title><![CDATA[" . $song->f_title . "]]></title>\n\t<name><![CDATA[" . $song->f_title . "]]></name>\n\t<artist id=\"" . $song->artist . "\"><![CDATA[" . $song->f_artist_full . "]]></artist>\n\t<album id=\"" . $song->album . "\"><![CDATA[" . $song->f_album_full . "]]></album>\n\t<genre id=\"" . $song->genre . "\"><![CDATA[" . $song->f_genre . "]]></genre>\n" . $tag_string . "\t<track>" . $song->track . "</track>\n\t<time><![CDATA[" . $song->time . "]]></time>\n\t<mime><![CDATA[" . $song->mime . "]]></mime>\n\t<url><![CDATA[" . $song->play_url('', 'api', false, $user_id) . "]]></url>\n\t<size>" . $song->size . "</size>\n\t<art><![CDATA[" . $art_url . "]]></art>\n\t<preciserating>" . ($rating->get_user_rating($user_id) ?: null) . "</preciserating>\n\t<rating>" . ($rating->get_user_rating($user_id) ?: null) . "</rating>\n\t<averagerating>" . ($rating->get_average_rating() ?: null) . "</averagerating>\n<playcount>" . $song->object_cnt . "</playcount>\n\t<vote>" . $democratic->get_vote($row_id) . "</vote>\n</song>\n";
  974         } // end foreach
  975 
  976         return self::output_xml($string);
  977     } // democratic
  978 
  979     /**
  980      * user
  981      *
  982      * This handles creating an xml document for a user
  983      *
  984      * @param  User    $user User Object
  985      * @param  boolean $fullinfo
  986      * @return string  return xml
  987      */
  988     public static function user(User $user, $fullinfo)
  989     {
  990         $user->format();
  991         $string = "<user id=\"" . (string)$user->id . "\">\n\t<username><![CDATA[" . $user->username . "]]></username>\n";
  992         if ($fullinfo) {
  993             $string .= "\t<auth><![CDATA[" . $user->apikey . "]]></auth>\n\t<email><![CDATA[" . $user->email . "]]></email>\n\t<access>" . (int) $user->access . "</access>\n\t<fullname_public>" . (int) $user->fullname_public . "</fullname_public>\n\t<validation><![CDATA[" . $user->validation . "]]></validation>\n\t<disabled>" . (int) $user->disabled . "</disabled>\n";
  994         }
  995         $string .= "\t<create_date>" . (int) $user->create_date . "</create_date>\n\t<last_seen>" . (int) $user->last_seen . "</last_seen>\n\t<link><![CDATA[" . $user->link . "]]></link>\n\t<website><![CDATA[" . $user->website . "]]></website>\n\t<state><![CDATA[" . $user->state . "]]></state>\n\t<city><![CDATA[" . $user->city . "]]></city>\n";
  996         if ($user->fullname_public || $fullinfo) {
  997             $string .= "\t<fullname><![CDATA[" . $user->fullname . "]]></fullname>\n";
  998         }
  999         $string .= "</user>\n";
 1000 
 1001         return self::output_xml($string);
 1002     } // user
 1003 
 1004     /**
 1005      * users
 1006      *
 1007      * This handles creating an xml document for an user list
 1008      *
 1009      * @param  integer[] $users User identifier list
 1010      * @return string    return xml
 1011      */
 1012     public static function users($users)
 1013     {
 1014         $string = "";
 1015         foreach ($users as $user_id) {
 1016             $user = new User($user_id);
 1017             $string .= "<user id=\"" . (string)$user->id . "\">\n\t<username><![CDATA[" . $user->username . "]]></username>\n</user>\n";
 1018         }
 1019 
 1020         return self::output_xml($string);
 1021     } // users
 1022 
 1023     /**
 1024      * shouts
 1025      *
 1026      * This handles creating an xml document for a shout list
 1027      *
 1028      * @param  integer[] $shouts Shout identifier list
 1029      * @return string    return xml
 1030      */
 1031     public static function shouts($shouts)
 1032     {
 1033         $string = "";
 1034         foreach ($shouts as $shout_id) {
 1035             $shout = new Shoutbox($shout_id);
 1036             $user  = new User($shout->user);
 1037             $string .= "\t<shout id=\"" . $shout_id . "\">\n\t\t<date>" . $shout->date . "</date>\n\t\t<text><![CDATA[" . $shout->text . "]]></text>\n";
 1038             if ($user->id) {
 1039                 $string .= "\t\t<user id=\"" . (string)$user->id . "\">\n\t\t\t<username><![CDATA[" . $user->username . "]]></username>\n\t\t</user>\n";
 1040             }
 1041             $string .= "\t</shout>\n";
 1042         }
 1043 
 1044         return self::output_xml($string);
 1045     } // shouts
 1046 
 1047     /**
 1048      * @param  string  $string
 1049      * @param  boolean $full_xml
 1050      * @return string
 1051      */
 1052     public static function output_xml($string, $full_xml = true)
 1053     {
 1054         $xml = "";
 1055         if ($full_xml) {
 1056             $xml .= self::_header();
 1057         }
 1058         $xml .= Ui::clean_utf8($string);
 1059         if ($full_xml) {
 1060             $xml .= self::_footer();
 1061         }
 1062         // return formatted xml when asking for full_xml
 1063         if ($full_xml) {
 1064             $dom = new DOMDocument;
 1065             // format the string
 1066             $dom->preserveWhiteSpace = false;
 1067             $dom->loadXML($xml);
 1068             $dom->formatOutput = true;
 1069 
 1070             return $dom->saveXML();
 1071         }
 1072 
 1073         return $xml;
 1074     }
 1075 
 1076     /**
 1077      * timeline
 1078      *
 1079      * This handles creating an xml document for an activity list
 1080      *
 1081      * @param  integer[] $activities Activity identifier list
 1082      * @return string    return xml
 1083      */
 1084     public static function timeline($activities)
 1085     {
 1086         $string = "";
 1087         foreach ($activities as $activity_id) {
 1088             $activity = new Useractivity($activity_id);
 1089             $user     = new User($activity->user);
 1090             $string .= "\t<activity id=\"" . $activity_id . "\">\n\t\t<date>" . $activity->activity_date . "</date>\n\t\t<object_type><![CDATA[" . $activity->object_type . "]]></object_type>\n\t\t<object_id>" . $activity->object_id . "</object_id>\n\t\t<action><![CDATA[" . $activity->action . "]]></action>\n";
 1091             if ($user->id) {
 1092                 $string .= "\t\t<user id=\"" . (string)$user->id . "\">\n\t\t\t<username><![CDATA[" . $user->username . "]]></username>\n\t\t</user>\n";
 1093             }
 1094             $string .= "\t</activity>\n";
 1095         }
 1096 
 1097         return self::_header() . $string . self::_footer();
 1098     } // timeline
 1099 
 1100     /**
 1101      * rss_feed
 1102      *
 1103      * returns xml for rss types that aren't podcasts (Feed generation of plays/albums/etc)
 1104      *
 1105      * @param  array  $data Keyed array of information to RSS'ify
 1106      * @param  string $title RSS feed title
 1107      * @param  string $date publish date
 1108      * @return string RSS feed xml
 1109      */
 1110     public static function rss_feed($data, $title, $date = null)
 1111     {
 1112         $string = "\t<title>$title</title>\n\t<link>" . AmpConfig::get('web_path') . "</link>\n\t";
 1113         if (is_int($date)) {
 1114             $string .= "<pubDate>" . date("r", (int)$date) . "</pubDate>\n";
 1115         }
 1116 
 1117         // Pass it to the keyed array xml function
 1118         foreach ($data as $item) {
 1119             // We need to enclose it in an item tag
 1120             $string .= self::keyed_array(array('item' => $item), true);
 1121         }
 1122 
 1123         return self::_header() . $string . self::_footer();
 1124     } // rss_feed
 1125 
 1126     /**
 1127      * deleted
 1128      *
 1129      * This takes an array of deleted objects and return XML based on the type of object
 1130      * we want
 1131      *
 1132      * @param  string  $object_type ('song', 'podcast_episode', 'video')
 1133      * @param  array   $objects deleted object list
 1134      * @return string  return xml
 1135      */
 1136     public static function deleted($object_type, $objects)
 1137     {
 1138         if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
 1139             $objects = array_splice($objects, self::$offset, self::$limit);
 1140         }
 1141 
 1142         $string = '';
 1143         // here is where we call the object type
 1144         foreach ($objects as $row) {
 1145             switch ($object_type) {
 1146                 case 'song':
 1147                     // id, addition_time, delete_time, title, file, `catalog`, total_count, total_skip, update_time, album, artist
 1148                     $string .= "<deleted_song id=\"" . $row['id'] . "\">\n\t<addition_time>" . $row['addition_time'] . "</addition_time>\n\t<delete_time>" . $row['delete_time'] . "</delete_time>\n\t<title><![CDATA[" . $row['title'] . "]]></title>\n\t<file><![CDATA[" . $row['file'] . "]]></file>\n\t<catalog>" . $row['catalog'] . "</albumartist>\n\t<total_count>" . $row['total_count'] . "</track>\n\t<total_skip>" . $row['total_skip'] . "</track>\n\t<update_time>" . $row['update_time'] . "</update_time>\n\t<album>" . $row['album'] . "</album>\n\t<artist>" . $row['artist'] . "</artist>\n</deleted_song>\n";
 1149                     break;
 1150                 case 'podcast_episode':
 1151                     // id, addition_time, delete_time, title, file, `catalog`, total_count, total_skip, podcast
 1152                     $string .= "\t<deleted_podcast_episode id=\"" . $row['id'] . "\">\n\t<addition_time>" . $row['addition_time'] . "</addition_time>\n\t<delete_time>" . $row['delete_time'] . "</delete_time>\n\t<title><![CDATA[" . $row['title'] . "]]></title>\n\t<file><![CDATA[" . $row['file'] . "]]></file>\n\t<catalog>" . $row['catalog'] . "</albumartist>\n\t<total_count>" . $row['total_count'] . "</track>\n\t<total_skip>" . $row['total_skip'] . "</track>\n\t<played>" . $row['podcast'] . "</played>\n\t</deleted_podcast_episode>\n";
 1153                     break;
 1154                 case 'video':
 1155                     // id, addition_time, delete_time, title, file, catalog, total_count, total_skip
 1156                     $string .= "<deleted_video id=\"" . $row['id'] . "\">\n\t<addition_time>" . $row['addition_time'] . "</addition_time>\n\t<delete_time>" . $row['delete_time'] . "</delete_time>\n\t<title><![CDATA[" . $row['title'] . "]]></title>\n\t<file><![CDATA[" . $row['file'] . "]]></file>\n\t<catalog>" . $row['catalog'] . "</albumartist>\n\t<total_count>" . $row['total_count'] . "</track>\n\t<total_skip>" . $row['total_skip'] . "</track>\n</deleted_video>\n";
 1157             }
 1158         } // end foreach objects
 1159 
 1160         return self::output_xml($string);
 1161     } // deleted
 1162 
 1163     /**
 1164      * _header
 1165      *
 1166      * this returns a standard header, there are a few types
 1167      * so we allow them to pass a type if they want to
 1168      *
 1169      * @param  string $title
 1170      * @return string Header xml tag.
 1171      */
 1172     private static function _header($title = null)
 1173     {
 1174         switch (self::$type) {
 1175             case 'xspf':
 1176                 $header = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<playlist version = \"1\" xmlns=\"http://xspf.org/ns/0/\">\n<title>" . ($title ?: T_("Ampache XSPF Playlist")) . "</title>\n<creator>" . scrub_out(AmpConfig::get('site_title')) . "</creator>\n<annotation>" . scrub_out(AmpConfig::get('site_title')) . "</annotation>\n<info>" . AmpConfig::get('web_path') . "</info>\n<trackList>\n";
 1177                 break;
 1178             case 'itunes':
 1179                 $header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- XML Generated by Ampache v." . AmpConfig::get('version') . " -->\n";
 1180                 break;
 1181             case 'rss':
 1182                 $header = "<?xml version=\"1.0\" encoding=\"" . AmpConfig::get('site_charset') . "\" ?>\n <!-- RSS Generated by Ampache v." . AmpConfig::get('version') . " on " . date("r", time()) . "-->\n<rss version=\"2.0\">\n<channel>\n";
 1183                 break;
 1184             default:
 1185                 $header = "<?xml version=\"1.0\" encoding=\"" . AmpConfig::get('site_charset') . "\" ?>\n<root>\n";
 1186                 break;
 1187         } // end switch
 1188 
 1189         return $header;
 1190     } // _header
 1191 
 1192     /**
 1193      * _footer
 1194      *
 1195      * this returns the footer for this document, these are pretty boring
 1196      *
 1197      * @return string Footer xml tag.
 1198      */
 1199     private static function _footer()
 1200     {
 1201         switch (self::$type) {
 1202             case 'itunes':
 1203                 $footer = "\t\t</dict>\t\n</dict>\n</plist>\n";
 1204                 break;
 1205             case 'xspf':
 1206                 $footer = "</trackList>\n</playlist>\n";
 1207                 break;
 1208             case 'rss':
 1209                 $footer = "\n</channel>\n</rss>\n";
 1210                 break;
 1211             default:
 1212                 $footer = "\n</root>\n";
 1213                 break;
 1214         } // end switch on type
 1215 
 1216 
 1217         return $footer;
 1218     }
 1219 
 1220     // _footer
 1221 
 1222     /**
 1223      * podcast
 1224      * @param  library_item $libitem
 1225      * @param  integer      $user_id
 1226      * @return string|false
 1227      */
 1228     public static function podcast(library_item $libitem, $user_id = null)
 1229     {
 1230         $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><rss />');
 1231         $xml->addAttribute("xmlns:xmlns:atom", "http://www.w3.org/2005/Atom");
 1232         $xml->addAttribute("xmlns:xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd");
 1233         $xml->addAttribute("version", "2.0");
 1234         $xchannel = $xml->addChild("channel");
 1235         $xchannel->addChild("title", htmlspecialchars($libitem->get_fullname() . " Podcast"));
 1236         //$xlink = $xchannel->addChild("atom:link", htmlentities($libitem->link));
 1237         $libitem_type = ObjectTypeToClassNameMapper::reverseMap(get_class($libitem));
 1238         if (Art::has_db($libitem->id, $libitem_type)) {
 1239             $ximg = $xchannel->addChild("xmlns:itunes:image");
 1240             $ximg->addAttribute("href", Art::url($libitem->id, $libitem_type));
 1241         }
 1242         $summary = $libitem->get_description();
 1243         if (!empty($summary)) {
 1244             $summary = htmlentities($summary);
 1245             $xchannel->addChild("description", $summary);
 1246             $xchannel->addChild("xmlns:itunes:summary", $summary);
 1247         }
 1248         $xchannel->addChild("generator", "ampache");
 1249         $xchannel->addChild("xmlns:itunes:category", "Music");
 1250         $owner = $libitem->get_user_owner();
 1251         if ($owner) {
 1252             $user_owner = new User($owner);
 1253             $user_owner->format();
 1254             $xowner = $xchannel->addChild("xmlns:itunes:owner");
 1255             $xowner->addChild("xmlns:itunes:name", $user_owner->f_name);
 1256         }
 1257 
 1258         $medias = $libitem->get_medias();
 1259         foreach ($medias as $media_info) {
 1260             $class_name = ObjectTypeToClassNameMapper::map($media_info['object_type']);
 1261             $media      = new $class_name($media_info['object_id']);
 1262             $media->format();
 1263             $xitem = $xchannel->addChild("item");
 1264             $xitem->addChild("title", htmlentities($media->get_fullname()));
 1265             if ($media->f_artist) {
 1266                 $xitem->addChild("xmlns:itunes:author", $media->f_artist);
 1267             }
 1268             //$xmlink = $xitem->addChild("link", htmlentities($media->link));
 1269             $xitem->addChild("guid", htmlentities($media->link));
 1270             if ($media->addition_time) {
 1271                 $xitem->addChild("pubDate", date("r", (int)$media->addition_time));
 1272             }
 1273             $description = $media->get_description();
 1274             if (!empty($description)) {
 1275                 $xitem->addChild("description", htmlentities($description));
 1276             }
 1277             $xitem->addChild("xmlns:itunes:duration", $media->f_time);
 1278             if ($media->mime) {
 1279                 $surl  = $media->play_url('', 'api', false, $user_id);
 1280                 $xencl = $xitem->addChild("enclosure");
 1281                 $xencl->addAttribute("type", (string)$media->mime);
 1282                 $xencl->addAttribute("length", (string)$media->size);
 1283                 $xencl->addAttribute("url", $surl);
 1284             }
 1285         }
 1286 
 1287         $xmlstr = $xml->asXml();
 1288         // Format xml output
 1289         $dom = new DOMDocument();
 1290         if ($dom->loadXML($xmlstr, LIBXML_PARSEHUGE) !== false) {
 1291             $dom->formatOutput = true;
 1292 
 1293             return $dom->saveXML();
 1294         } else {
 1295             return $xmlstr;
 1296         }
 1297     }
 1298 
 1299     /**
 1300      * @deprecated
 1301      */
 1302     private static function getSongRepository(): SongRepositoryInterface
 1303     {
 1304         global $dic;
 1305 
 1306         return $dic->get(SongRepositoryInterface::class);
 1307     }
 1308 
 1309     /**
 1310      * @deprecated
 1311      */
 1312     private static function getAlbumRepository(): AlbumRepositoryInterface
 1313     {
 1314         global $dic;
 1315 
 1316         return $dic->get(AlbumRepositoryInterface::class);
 1317     }
 1318 }