ampache/ampache

View on GitHub
src/Module/Api/Json_Data.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

declare(strict_types=0);

/**
 * vim:set softtabstop=4 shiftwidth=4 expandtab:
 *
 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
 * Copyright Ampache.org, 2001-2023
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Ampache\Module\Api;

use Ampache\Config\AmpConfig;
use Ampache\Module\Playback\Stream;
use Ampache\Module\System\Core;
use Ampache\Module\System\Dba;
use Ampache\Module\Util\ObjectTypeToClassNameMapper;
use Ampache\Repository\AlbumRepositoryInterface;
use Ampache\Repository\BookmarkRepositoryInterface;
use Ampache\Repository\LabelRepositoryInterface;
use Ampache\Repository\LicenseRepositoryInterface;
use Ampache\Repository\Model\Album;
use Ampache\Repository\Model\Art;
use Ampache\Repository\Model\Artist;
use Ampache\Repository\Model\Catalog;
use Ampache\Repository\Model\Democratic;
use Ampache\Repository\Model\Live_Stream;
use Ampache\Repository\Model\Metadata;
use Ampache\Repository\Model\Playlist;
use Ampache\Repository\Model\Podcast_Episode;
use Ampache\Repository\Model\Preference;
use Ampache\Repository\Model\Rating;
use Ampache\Repository\Model\Search;
use Ampache\Repository\Model\Share;
use Ampache\Repository\Model\Shoutbox;
use Ampache\Repository\Model\Song;
use Ampache\Repository\Model\Tag;
use Ampache\Repository\Model\User;
use Ampache\Repository\Model\Useractivity;
use Ampache\Repository\Model\Userflag;
use Ampache\Repository\Model\Video;
use Ampache\Repository\PodcastRepositoryInterface;
use Ampache\Repository\SongRepositoryInterface;

/**
 * Json_Data Class
 *
 * This class takes care of all of the JSON document stuff in Ampache these
 * are all static calls
 *
 */
class Json_Data
{
    // This is added so that we don't pop any webservers
    private static ?int $limit = 5000;
    private static int $offset = 0;

    /**
     * set_offset
     *
     * This takes an int and changes the offset
     *
     * @param int $offset Change the starting position of your results. (e.g 5001 when selecting in groups of 5000)
     */
    public static function set_offset($offset): void
    {
        self::$offset = (int)$offset;
    }

    /**
     * set_limit
     *
     * This sets the limit for any ampache transactions
     *
     * @param int|string $limit Set a limit on your results
     */
    public static function set_limit($limit): bool
    {
        if (!$limit) {
            return false;
        }

        self::$limit = (strtolower((string)$limit) == "none") ? null : (int)$limit;

        return true;
    }

    /**
     * error
     *
     * This generates a JSON Error message
     * nothing fancy here...
     *
     * @param int|string $code Error code
     * @param string $string Error message
     * @param string $action Error method
     * @param string $type Error type
     */
    public static function error($code, $string, $action, $type): string
    {
        $message = array(
            "error" => array(
                "errorCode" => (string)$code,
                "errorAction" => $action,
                "errorType" => $type,
                "errorMessage" => $string
            )
        );

        return json_encode($message, JSON_PRETTY_PRINT);
    }

    /**
     * success
     *
     * This generates a standard JSON Success message
     * nothing fancy here...
     *
     * @param string $string success message
     * @param array $return_data
     */
    public static function success($string, $return_data = array()): string
    {
        $message = array(
            "success" => $string
        );
        foreach ($return_data as $title => $data) {
            $message[$title] = $data;
        }

        return json_encode($message, JSON_PRETTY_PRINT);
    }

    /**
     * empty
     *
     * This generates a JSON empty object
     * nothing fancy here...
     *
     * @param string $type object type
     */
    public static function empty($type): string
    {
        return json_encode(
            array(
                "total_count" => 0,
                $type => array()
            ),
            JSON_PRETTY_PRINT
        ) ?: '';
    }

    /**
     * genre_array
     *
     * This returns the formatted 'genre' array for a JSON document
     * @param array $tags
     */
    private static function genre_array($tags): array
    {
        $JSON = [];

        if (!empty($tags)) {
            $atags = array();
            foreach ($tags as $tag_id => $data) {
                if (array_key_exists($data['id'], $atags)) {
                    $atags[$data['id']]['count']++;
                } else {
                    $atags[$data['id']] = array(
                        'name' => $data['name'],
                        'count' => 1
                    );
                }
            }

            foreach ($atags as $tag_id => $data) {
                $JSON[] = array(
                    "id" => (string)$tag_id,
                    "name" => $data['name']
                );
            }
        }

        return $JSON;
    }

    /**
     * index
     *
     * This takes an array of object_ids and return JSON based on the type of object
     *
     * @param list<int> $objects Array of object_ids (Mixed string|int)
     * @param string $type 'catalog'|'artist'|'album'|'song'|'playlist'|'share'|'podcast'|'podcast_episode'|'video'|'live_stream'
     * @param User $user
     * @param bool $include (add child id's of the object (in sub array by type))
     * @return string  JSON Object "catalog"|"artist"|"album"|"song"|"playlist"|"share"|"podcast"|"podcast_episode"|"video"|"live_stream"
     */
    public static function index($objects, $type, $user, $include = false): string
    {
        $output = [];

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }

        if ($include) {
            switch ($type) {
                case 'album_artist':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        $sql        = "SELECT DISTINCT `album_map`.`album_id` FROM `album_map` WHERE `album_map`.`object_id` = ? AND `album_map`.`object_type` = 'album';";
                        $db_results = Dba::read($sql, array($object_id));
                        while ($row = Dba::fetch_assoc($db_results)) {
                            $output[$object_id][] = array(
                                "id" => $row['album_id'],
                                "type" => 'album'
                            );
                        }
                    }
                    break;
                case 'song_artist':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        $sql        = "SELECT DISTINCT `album_map`.`album_id` FROM `album_map` WHERE `album_map`.`object_id` = ? AND `album_map`.`object_type` = 'song';";
                        $db_results = Dba::read($sql, array($object_id));
                        while ($row = Dba::fetch_assoc($db_results)) {
                            $output[$object_id][] = array(
                                "id" => $row['album_id'],
                                "type" => 'album'
                            );
                        }
                    }
                    break;
                case 'artist':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        $sql        = "SELECT DISTINCT `album_map`.`album_id` FROM `album_map` WHERE `album_map`.`object_id` = ?;";
                        $db_results = Dba::read($sql, array($object_id));
                        while ($row = Dba::fetch_assoc($db_results)) {
                            $output[$object_id][] = array(
                                "id" => $row['id'],
                                "type" => 'album'
                            );
                        }
                    }
                    break;
                case 'album':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        $sql        = "SELECT DISTINCT `song`.`id` FROM `song` WHERE `song`.`album` = ?;";
                        $db_results = Dba::read($sql, array($object_id));
                        while ($row = Dba::fetch_assoc($db_results)) {
                            $output[$object_id][] = array(
                                "id" => $row['id'],
                                "type" => 'song'
                            );
                        }
                    }
                    break;
                case 'playlist':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        /**
                         * Strip smart_ from playlist id and compare to original
                         * smartlist = 'smart_1'
                         * playlist  = 1000000
                         */
                        if ((int)$object_id === 0) {
                            $playlist = new Search((int)str_replace('smart_', '', (string)$object_id), 'song', $user);
                            foreach ($playlist->get_items() as $song) {
                                $output[$object_id][] = array(
                                    "id" => $song['object_id'],
                                    "type" => 'song'
                                );
                            }
                        } else {
                            $sql        = "SELECT `playlist_data`.`id`, `playlist_data`.`object_id`, `playlist_data`.`object_type` FROM `playlist_data` WHERE `playlist_data`.`playlist` = ? ORDER BY `playlist_data`.`track`;";
                            $db_results = Dba::read($sql, array($object_id));
                            while ($row = Dba::fetch_assoc($db_results)) {
                                $output[$object_id][] = array(
                                    "id" => $row['object_id'],
                                    "type" => $row['object_type']
                                );
                            }
                        }
                    }
                    break;
                case 'podcast':
                    foreach ($objects as $object_id) {
                        $output[$object_id] = [];

                        $sql        = "SELECT DISTINCT `podcast_episode`.`id` FROM `podcast_episode` WHERE `podcast_episode`.`podcast` = ?;";
                        $db_results = Dba::read($sql, array($object_id));
                        while ($row = Dba::fetch_assoc($db_results)) {
                            $output[$object_id][] = array(
                                "id" => $row['id'],
                                "type" => 'podcast_episode'
                            );
                        }
                    }
                    break;
                case 'catalog':
                case 'live_stream':
                case 'podcast_episode':
                case 'share':
                case 'song':
                case 'video':
                    // These objects don't have children
                    $output = $objects;
                    break;
            }
        } else {
            $output = $objects;
        }
        $output = json_encode([$type => $output], JSON_PRETTY_PRINT);
        if ($output !== false) {
            return $output;
        }

        /* HINT: Requested object string/id/type ("album", "myusername", "some song title", 1298376) */
        return self::error('4710', sprintf(T_('Bad Request: %s'), $type), 'indexes', 'type');
    }

    /**
     * indexes
     *
     * This takes an array of object_ids and return JSON based on the type of object
     *
     * @param array $objects Array of object_ids (Mixed string|int)
     * @param string $type 'artist'|'album'|'song'|'playlist'|'share'|'podcast'|'podcast_episode'|'video'|'live_stream'
     * @param User $user
     * @param bool $include (add the extra songs details if a playlist or podcast_episodes if a podcast)
     * @return string  JSON Object "artist"|"album"|"song"|"playlist"|"share"|"podcast"|"podcast_episode"|"video"|"live_stream"
     */
    public static function indexes($objects, $type, $user, $include = false): string
    {
        // here is where we call the object type
        switch ($type) {
            case 'song':
                /** @var string $results */
                $results = self::songs($objects, $user);
                break;
            case 'album':
                $include_array = ($include) ? array('songs') : array();
                /** @var string $results */
                $results = self::albums($objects, $include_array, $user);
                break;
            case 'artist':
                $include_array = ($include) ? array('songs', 'albums') : array();
                /** @var string $results */
                $results = self::artists($objects, $include_array, $user);
                break;
            case 'playlist':
                $results = self::playlists($objects, $user, $include);
                break;
            case 'share':
                $results = self::shares($objects);
                break;
            case 'podcast':
                $results = self::podcasts($objects, $user, $include);
                break;
            case 'podcast_episode':
                /** @var string $results */
                $results = self::podcast_episodes($objects, $user);
                break;
            case 'video':
                /** @var string $results */
                $results = self::videos($objects, $user);
                break;
            case 'live_stream':
                $results = self::live_streams($objects);
                break;
            default:
                /* HINT: Requested object string/id/type ("album", "myusername", "some song title", 1298376) */
                $results = self::error('4710', sprintf(T_('Bad Request: %s'), $type), 'indexes', 'type');
        }

        return $results;
    }

    /**
     * lists
     *
     * This takes a name array of objects and return the data in JSON list object
     *
     * @param array $objects Array of object_ids array("id" => 1, "name" => 'Artist Name')
     */
    public static function lists($objects): string
    {
        $output = array(
            "total_count" => count($objects)
        );
        $pattern = '/^(' . implode('\\s|', explode('|', AmpConfig::get('catalog_prefix_pattern', 'The|An|A|Die|Das|Ein|Eine|Les|Le|La'))) . '\\s)(.*)/i';

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $object) {
            $trimmed  = Catalog::trim_prefix(trim((string)$object['name']), $pattern);
            $prefix   = $trimmed['prefix'];
            $basename = $trimmed['string'];
            $JSON[]   = array(
                "id" => (string)$object['id'],
                "name" => $object['name'],
                "prefix" => $prefix,
                "basename" => $basename,
            );
        } // end foreach
        $output["list"] = $JSON;

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * browses
     *
     * This takes a name array of objects and return the data in JSON browse object
     *
     * @param array $objects Array of object_ids array("id" => 1, "name" => 'Artist Name')
     * @param int|null $parent_id
     * @param string $parent_type
     * @param string $child_type
     * @param int|null $catalog_id
     */
    public static function browses($objects, $parent_id, $parent_type, $child_type, $catalog_id): string
    {
        $output = array(
            "total_count" => count($objects),
            "catalog_id" => (string)$catalog_id,
            "parent_id" => (string)$parent_id,
            "parent_type" => $parent_type,
            "child_type" => $child_type
        );
        $pattern = '/^(' . implode('\\s|', explode('|', AmpConfig::get('catalog_prefix_pattern', 'The|An|A|Die|Das|Ein|Eine|Les|Le|La'))) . '\\s)(.*)/i';

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $object) {
            $trimmed  = Catalog::trim_prefix(trim((string)$object['name']), $pattern);
            $prefix   = $trimmed['prefix'];
            $basename = $trimmed['string'];
            $JSON[]   = array(
                "id" => (string)$object['id'],
                "name" => $object['name'],
                "prefix" => $prefix,
                "basename" => $basename
            );
        } // end foreach
        $output["browse"] = $JSON;

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * live_streams
     *
     * This returns live_streams to the user, in a pretty JSON document with the information
     *
     * @param int[] $objects
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function live_streams($objects, $object = true): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $live_stream_id) {
            $live_stream = new Live_Stream($live_stream_id);
            if ($live_stream->isNew()) {
                continue;
            }
            $live_stream->format();
            $JSON[] = array(
                "id" => (string)$live_stream_id,
                "name" => $live_stream->get_fullname(),
                "url" => $live_stream->url,
                "codec" => $live_stream->codec,
                "catalog" => (string)$live_stream->catalog,
                "site_url" => $live_stream->site_url
            );
        } // end foreach
        if ($object) {
            $output["live_stream"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * licenses
     *
     * This returns licenses to the user, in a pretty JSON document with the information
     *
     * @param int[] $objects Licence id's assigned to songs and artists
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function licenses($objects, $object = true): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }

        $licenseRepository = self::getLicenseRepository();

        $JSON = [];
        foreach ($objects as $license_id) {
            $license = $licenseRepository->findById($license_id);

            if ($license !== null) {
                $JSON[]  = array(
                    'id' => (string)$license_id,
                    'name' => $license->getName(),
                    'description' => $license->getDescription(),
                    'external_link' => $license->getLinkFormatted()
                );
            }
        } // end foreach
        if ($object) {
            $output["license"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * labels
     *
     * This returns labels to the user, in a pretty JSON document with the information
     *
     * @param int[] $objects
     * @param bool $encode return the array and don't json_encode the data
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "label"
     */
    public static function labels($objects, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];

        $labelRepository = self::getLabelRepository();

        foreach ($objects as $label_id) {
            $label = $labelRepository->findById($label_id);
            if ($label === null) {
                continue;
            }
            $label->format();
            $JSON[] = array(
                "id" => (string)$label_id,
                "name" => $label->get_fullname(),
                "artists" => $label->artist_count,
                "summary" => $label->summary,
                "external_link" => $label->get_link(),
                "address" => $label->address,
                "category" => $label->category,
                "email" => $label->email,
                "website" => $label->website,
                "user" => (string)$label->user,
            );
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["label"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * genres
     *
     * This returns genres to the user, in a pretty JSON document with the information
     *
     * @param int[] $objects Genre id's to include
     * @param bool $encode return the array and don't json_encode the data
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "label"
     */
    public static function genres($objects, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $tag_id) {
            $tag    = new Tag($tag_id);
            $counts = $tag->count();
            $JSON[] = array(
                "id" => (string)$tag_id,
                "name" => $tag->name,
                "albums" => (int)($counts['album'] ?? 0),
                "artists" => (int)($counts['artist'] ?? 0),
                "songs" => (int)($counts['song'] ?? 0),
                "videos" => (int)($counts['video'] ?? 0),
                "playlists" => (int)($counts['playlist'] ?? 0),
                "live_streams" => (int)($counts['live_stream'] ?? 0)
            );
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["genre"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * artists
     *
     * This takes an array of artists and then returns a pretty JSON document with the information
     * we want
     *
     * @param int[] $objects Artist id's to include
     * @param array $include
     * @param User $user
     * @param bool $encode
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "artist"
     */
    public static function artists($objects, $include, $user, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && (self::$limit && $encode)) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        Rating::build_cache('artist', $objects);
        $JSON = [];
        foreach ($objects as $artist_id) {
            $artist = new Artist($artist_id);
            if ($artist->isNew()) {
                continue;
            }
            $artist->format();

            $rating      = new Rating($artist_id, 'artist');
            $user_rating = $rating->get_user_rating($user->getId());
            $flag        = new Userflag($artist_id, 'artist');

            // Build the Art URL, include session
            $art_url = AmpConfig::get('web_path') . '/image.php?object_id=' . $artist_id . '&object_type=artist';

            // Handle includes
            $albums = (in_array("albums", $include))
                ? self::albums(static::getAlbumRepository()->getAlbumByArtist($artist_id), array(), $user, false)
                : array();
            $songs = (in_array("songs", $include))
                ? self::songs(static::getSongRepository()->getByArtist($artist_id), $user, false)
                : array();

            $JSON[] = array(
                "id" => (string)$artist->id,
                "name" => $artist->get_fullname(),
                "prefix" => $artist->prefix,
                "basename" => $artist->name,
                "albums" => $albums,
                "albumcount" => $artist->album_count,
                "songs" => $songs,
                "songcount" => $artist->song_count,
                "genre" => self::genre_array($artist->tags),
                "art" => $art_url,
                "has_art" => $artist->has_art(),
                "flag" => (bool)$flag->get_flag($user->getId()),
                "rating" => $user_rating,
                "averagerating" => $rating->get_average_rating(),
                "mbid" => $artist->mbid,
                "summary" => $artist->summary,
                "time" => (int)$artist->time,
                "yearformed" => (int)$artist->yearformed,
                "placeformed" => $artist->placeformed
            );
        } // end foreach artists

        if ($encode) {
            if ($object) {
                $output["artist"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * albums
     *
     * This echos out a standard albums JSON document, it pays attention to the limit
     *
     * @param int[] $objects Album id's to include
     * @param array|false $include
     * @param User $user
     * @param bool $encode
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "album"
     */
    public static function albums($objects, $include, $user, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );
        // original year (fall back to regular year)
        $original_year = AmpConfig::get('use_original_year');

        if ((count($objects) > self::$limit || self::$offset > 0) && (self::$limit && $encode)) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        Rating::build_cache('album', $objects);
        $JSON = [];
        foreach ($objects as $album_id) {
            $album = new Album($album_id);
            if ($album->isNew()) {
                continue;
            }
            $album->format();

            $rating      = new Rating($album_id, 'album');
            $user_rating = $rating->get_user_rating($user->getId());
            $flag        = new Userflag($album_id, 'album');
            $year        = ($original_year && $album->original_year)
                ? $album->original_year
                : $album->year;

            // Build the Art URL, include session
            $art_url = AmpConfig::get('web_path') . '/image.php?object_id=' . $album->id . '&object_type=album';

            $objArray = [];

            $objArray['id']       = (string)$album->id;
            $objArray['name']     = $album->get_fullname();
            $objArray['prefix']   = $album->prefix;
            $objArray['basename'] = $album->name;
            if ($album->get_artist_fullname() != "") {
                $objArray['artist'] = array(
                    "id" => (string)$album->album_artist,
                    "name" => $album->f_artist_name,
                    "prefix" => $album->artist_prefix,
                    "basename" => $album->artist_name
                );
                $album_artists = array();
                foreach ($album->get_artists() as $artist_id) {
                    $album_artists[] = Artist::get_name_array_by_id($artist_id);
                }
                $objArray['artists'] = $album_artists;
                $song_artists        = array();
                foreach ($album->get_song_artists() as $artist_id) {
                    $song_artists[] = Artist::get_name_array_by_id($artist_id);
                }
                $objArray['songartists'] = $song_artists;
            }

            // Handle includes
            $songs = ($include && in_array("songs", $include))
                ? self::songs(static::getSongRepository()->getByAlbum($album->id), $user, false)
                : array();

            $objArray['time']          = (int)$album->total_duration;
            $objArray['year']          = (int)$year;
            $objArray['tracks']        = $songs;
            $objArray['songcount']     = (int)$album->song_count;
            $objArray['diskcount']     = (int)$album->disk_count;
            $objArray['type']          = $album->release_type;
            $objArray['genre']         = self::genre_array($album->tags);
            $objArray['art']           = $art_url;
            $objArray['has_art']       = $album->has_art();
            $objArray['flag']          = (bool)$flag->get_flag($user->getId());
            $objArray['rating']        = $user_rating;
            $objArray['averagerating'] = $rating->get_average_rating();
            $objArray['mbid']          = $album->mbid;

            $JSON[] = $objArray;
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["album"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * playlists
     *
     * This takes an array of playlist ids and then returns a nice pretty JSON document
     *
     * @param array $objects Playlist id's to include
     * @param User $user
     * @param bool $songs
     * @param bool $encode return the array and don't json_encode the data
     * @param bool $object (whether to return as a named object array or regular array)
     * @param bool $show_dupes
     * @return array|string JSON Object "playlist"
     */
    public static function playlists($objects, $user, $songs = false, $encode = true, $object = true, $show_dupes = true)
    {
        $output = array(
            "total_count" => count($objects)
        );
        $hide_dupe_searches = ($show_dupes === false) || (bool)Preference::get_by_user($user->getId(), 'api_hide_dupe_searches');
        $playlist_names     = array();

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_slice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $playlist_id) {
            /**
             * Strip smart_ from playlist id and compare to original
             * smartlist = 'smart_1'
             * playlist  = 1000000
             */
            if ((int)$playlist_id === 0) {
                $playlist = new Search((int) str_replace('smart_', '', (string)$playlist_id), 'song', $user);
                if (
                    $playlist->isNew() ||
                    ($hide_dupe_searches && $playlist->user == $user->getId() && in_array($playlist->name, $playlist_names))
                ) {
                    continue;
                }
                $object_type    = 'search';
                $art_url        = Art::url($playlist->id, $object_type, Core::get_request('auth'));
                $playitem_total = $playlist->last_count;
            } else {
                $playlist = new Playlist($playlist_id);
                if ($playlist->isNew()) {
                    continue;
                }
                $object_type    = 'playlist';
                $art_url        = Art::url($playlist_id, $object_type, Core::get_request('auth'));
                $playitem_total = $playlist->get_media_count('song');
                if (
                    $playlist->isNew() === false &&
                    $hide_dupe_searches &&
                    $playlist->user == $user->getId()
                ) {
                    $playlist_names[] = $playlist->name;
                }
            }
            $playlist_name = $playlist->get_fullname();
            $playlist_user = $playlist->username;
            $playlist_type = $playlist->type;

            if ($songs) {
                $items          = array();
                $playlisttracks = $playlist->get_items();
                foreach ($playlisttracks as $objects) {
                    $items[] = array(
                        "id" => (string)$objects['object_id'],
                        "playlisttrack" => $objects['track'],
                    );
                }
            } else {
                $items = (int)($playitem_total ?? 0);
            }
            $rating      = new Rating($playlist_id, $object_type);
            $user_rating = $rating->get_user_rating($user->getId());
            $flag        = new Userflag($playlist_id, $object_type);

            // Build this element
            $JSON[] = [
                "id" => (string)$playlist_id,
                "name" => $playlist_name,
                "owner" => $playlist_user,
                "items" => $items,
                "type" => $playlist_type,
                "art" => $art_url,
                "has_art" => $playlist->has_art(),
                "flag" => (bool)$flag->get_flag($user->getId()),
                "rating" => $user_rating,
                "averagerating" => $rating->get_average_rating()
            ];
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["playlist"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * shares
     *
     * This returns shares to the user, in a pretty json document with the information
     *
     * @param int[] $objects Share id's to include
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function shares($objects, $object = true): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $share_id) {
            $share                = new Share($share_id);
            $share_name           = $share->getObjectName();
            $share_user           = $share->getUserName();
            $share_allow_stream   = (bool)$share->allow_stream;
            $share_allow_download = (bool)$share->allow_download;
            $share_creation_date  = $share->creation_date;
            $share_lastvisit_date = $share->lastvisit_date;
            $share_object_type    = $share->object_type;
            $share_object_id      = (string)$share->object_id;
            $share_expire_days    = (int)$share->expire_days;
            $share_max_counter    = (int)$share->max_counter;
            $share_counter        = (int)$share->counter;
            $share_secret         = $share->secret;
            $share_public_url     = $share->public_url;
            $share_description    = $share->description;
            // Build this element
            $JSON[] = [
                "id" => (string)$share_id,
                "name" => $share_name,
                "owner" => $share_user,
                "allow_stream" => $share_allow_stream,
                "allow_download" => $share_allow_download,
                "creation_date" => $share_creation_date,
                "lastvisit_date" => $share_lastvisit_date,
                "object_type" => $share_object_type,
                "object_id" => $share_object_id,
                "expire_days" => $share_expire_days,
                "max_counter" => $share_max_counter,
                "counter" => $share_counter,
                "secret" => $share_secret,
                "public_url" => $share_public_url,
                "description" => $share_description
            ];
        } // end foreach
        if ($object) {
            $output["share"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * bookmarks
     *
     * This returns bookmarks to the user, in a pretty json document with the information
     *
     * @param int[] $objects Bookmark id's to include
     * @param bool $include if true include the object in the bookmark
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function bookmarks($objects, $include = false, $object = true): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }

        $bookmarkRepository = self::getBookmarkRepository();

        $count = 0;
        $JSON  = [];
        foreach ($objects as $bookmark_id) {
            $bookmark = $bookmarkRepository->findById($bookmark_id);
            if ($bookmark === null) {
                continue;
            }

            $bookmark_username      = $bookmark->getUserName();
            $bookmark_object_type   = $bookmark->object_type;
            $bookmark_object_id     = (string)$bookmark->object_id;
            $bookmark_position      = $bookmark->position;
            $bookmark_comment       = $bookmark->comment;
            $bookmark_creation_date = $bookmark->creation_date;
            $bookmark_update_date   = $bookmark->update_date;
            // Build this element
            $JSON[] = [
                "id" => (string)$bookmark_id,
                "owner" => $bookmark_username,
                "object_type" => $bookmark_object_type,
                "object_id" => $bookmark_object_id,
                "position" => $bookmark_position,
                "client" => $bookmark_comment,
                "creation_date" => $bookmark_creation_date,
                "update_date" => $bookmark_update_date
            ];
            if ($include) {
                $user = User::get_from_username($bookmark_username);
                switch ($bookmark_object_type) {
                    case 'song':
                        $JSON[$count]['song'] = self::songs(array((int)$bookmark_object_id), $user, false, false);
                        break;
                    case 'podcast_episode':
                        $JSON[$count]['podcast_episode'] = self::podcast_episodes(array((int)$bookmark_object_id), $user, false, false);
                        break;
                    case 'video':
                        $JSON[$count]['video'] = self::videos(array((int)$bookmark_object_id), $user, false, false);
                        break;
                }
            }
            $count++;
        } // end foreach
        if ($object) {
            $output["bookmark"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * catalogs
     *
     * This returns catalogs to the user, in a pretty json document with the information
     *
     * @param int[] $objects group of catalog id's
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function catalogs($objects, $object = true): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $catalog_id) {
            $catalog = Catalog::create_from_id($catalog_id);
            if ($catalog === null) {
                break;
            }
            $catalog->format();
            $catalog_name           = $catalog->name;
            $catalog_type           = $catalog->catalog_type;
            $catalog_gather_types   = $catalog->gather_types;
            $catalog_enabled        = (bool)$catalog->enabled;
            $catalog_last_add       = $catalog->last_add;
            $catalog_last_clean     = $catalog->last_clean;
            $catalog_last_update    = $catalog->last_update;
            $catalog_path           = $catalog->f_info;
            $catalog_rename_pattern = $catalog->rename_pattern;
            $catalog_sort_pattern   = $catalog->sort_pattern;
            // Build this element
            $JSON[] = [
                "id" => (string)$catalog_id,
                "name" => $catalog_name,
                "type" => $catalog_type,
                "gather_types" => $catalog_gather_types,
                "enabled" => $catalog_enabled,
                "last_add" => $catalog_last_add,
                "last_clean" => $catalog_last_clean,
                "last_update" => $catalog_last_update,
                "path" => $catalog_path,
                "rename_pattern" => $catalog_rename_pattern,
                "sort_pattern" => $catalog_sort_pattern
            ];
        } // end foreach
        if ($object) {
            $output["catalog"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * podcasts
     *
     * This returns podcasts to the user, in a pretty json document with the information
     *
     * @param int[] $objects Podcast id's to include
     * @param User $user
     * @param bool $episodes include the episodes of the podcast
     * @param bool $encode return the array and don't json_encode the data
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "podcast"
     */
    public static function podcasts($objects, $user, $episodes = false, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }

        $podcastRepository = self::getPodcastRepository();

        $JSON = [];
        foreach ($objects as $podcast_id) {
            $podcast = $podcastRepository->findById($podcast_id);
            if ($podcast === null) {
                continue;
            }

            $rating              = new Rating($podcast_id, 'podcast');
            $user_rating         = $rating->get_user_rating($user->getId());
            $flag                = new Userflag($podcast_id, 'podcast');
            $art_url             = Art::url($podcast_id, 'podcast', Core::get_request('auth'));
            $podcast_name        = $podcast->get_fullname();
            $podcast_description = $podcast->get_description();
            $podcast_language    = scrub_out($podcast->getLanguage());
            $podcast_copyright   = scrub_out($podcast->getCopyright());
            $podcast_feed_url    = $podcast->getFeedUrl();
            $podcast_generator   = scrub_out($podcast->getGenerator());
            $podcast_website     = scrub_out($podcast->getWebsite());
            $podcast_build_date  = $podcast->getLastBuildDate()->format(DATE_ATOM);
            $podcast_sync_date   = $podcast->getLastSyncDate()->format(DATE_ATOM);
            $podcast_public_url  = $podcast->get_link();
            $podcast_episodes    = array();
            if ($episodes) {
                $results          = $podcast->getEpisodeIds();
                $podcast_episodes = self::podcast_episodes($results, $user, false);
            }
            // Build this element
            $JSON[] = [
                "id" => (string)$podcast_id,
                "name" => $podcast_name,
                "description" => $podcast_description,
                "language" => $podcast_language,
                "copyright" => $podcast_copyright,
                "feed_url" => $podcast_feed_url,
                "generator" => $podcast_generator,
                "website" => $podcast_website,
                "build_date" => $podcast_build_date,
                "sync_date" => $podcast_sync_date,
                "public_url" => $podcast_public_url,
                "art" => $art_url,
                "has_art" => $podcast->has_art(),
                "flag" => (bool)$flag->get_flag($user->getId()),
                "rating" => $user_rating,
                "averagerating" => $rating->get_average_rating(),
                "podcast_episode" => $podcast_episodes
            ];
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["podcast"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * podcast_episodes
     *
     * This returns podcasts to the user, in a pretty json document with the information
     *
     * @param int[] $objects Podcast_Episode id's to include
     * @param User $user
     * @param bool $encode
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "podcast_episode"
     */
    public static function podcast_episodes($objects, $user, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && (self::$limit && $encode)) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $episode_id) {
            $episode = new Podcast_Episode($episode_id);
            if ($episode->isNew()) {
                continue;
            }
            $episode->format();
            $rating      = new Rating($episode_id, 'podcast_episode');
            $user_rating = $rating->get_user_rating($user->getId());
            $flag        = new Userflag($episode_id, 'podcast_episode');
            $art_url     = Art::url($episode->podcast, 'podcast', Core::get_request('auth'));
            $JSON[]      = [
                "id" => (string)$episode_id,
                "title" => $episode->get_fullname(),
                "name" => $episode->get_fullname(),
                "podcast" => array(
                    "id" => $episode->podcast,
                    "name" => $episode->getPodcastName()
                ),
                "description" => $episode->get_description(),
                "category" => $episode->getCategory(),
                "author" => $episode->getAuthor(),
                "author_full" => $episode->getAuthor(),
                "website" => $episode->getWebsite(),
                "pubdate" => $episode->getPubDate()->format(DATE_ATOM),
                "state" => $episode->getStateDescription(),
                "filelength" => $episode->f_time_h,
                "filesize" => $episode->getSizeFormatted(),
                "filename" => $episode->getFileName(),
                "mime" => $episode->mime,
                "time" => (int)$episode->time,
                "size" => (int)$episode->size,
                "bitrate" => $episode->bitrate,
                "stream_bitrate" => $episode->bitrate,
                "rate" => (int)$episode->rate,
                "mode" => $episode->mode,
                "channels" => $episode->channels,
                "public_url" => $episode->get_link(),
                "url" => $episode->play_url('', 'api', false, $user->getId(), $user->streamtoken),
                "catalog" => (string)$episode->catalog,
                "art" => $art_url,
                "has_art" => $episode->has_art(),
                "flag" => (bool)$flag->get_flag($user->getId()),
                "rating" => $user_rating,
                "averagerating" => $rating->get_average_rating(),
                "playcount" => (int)$episode->total_count,
                "played" => (string)$episode->played
            ];
        }
        if (!$encode) {
            return $JSON;
        }
        if ($object) {
            $output["podcast_episode"] = $JSON;
        } else {
            $output = $JSON[0] ?? array();
        }

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * songs
     *
     * This returns an array of songs populated from an array of song ids.
     * (Spiffy isn't it!)
     * @param int[] $objects
     * @param User $user
     * @param bool $encode
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "song"
     */
    public static function songs($objects, $user, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );
        Stream::set_session($_REQUEST['auth'] ?? '');
        $playlist_track = 0;

        if ((count($objects) > self::$limit || self::$offset > 0) && (self::$limit && $encode)) {
            $objects = array_slice($objects, self::$offset, self::$limit);
        }
        Song::build_cache($objects);
        $JSON = [];
        foreach ($objects as $song_id) {
            $song = new Song($song_id);
            // If the song id is invalid/null
            if ($song->isNew()) {
                continue;
            }
            $song->format();
            $rating       = new Rating($song_id, 'song');
            $user_rating  = $rating->get_user_rating($user->getId());
            $flag         = new Userflag($song_id, 'song');
            $art_url      = Art::url($song->album, 'album', $_REQUEST['auth'] ?? '');
            $songType     = $song->type;
            $songMime     = $song->mime;
            $songBitrate  = $song->bitrate;
            $play_url     = $song->play_url('', 'api', false, $user->id, $user->streamtoken);
            $song_album   = self::getAlbumRepository()->getNames($song->album);
            $song_artist  = Artist::get_name_array_by_id($song->artist);
            $song_artists = array();
            foreach ($song->get_artists() as $artist_id) {
                $song_artists[] = Artist::get_name_array_by_id($artist_id);
            }
            $license     = $song->getLicense();
            if ($license !== null) {
                $licenseLink = $license->getLinkFormatted();
            } else {
                $licenseLink = '';
            }

            $playlist_track++;

            $objArray = array(
                "id" => (string)$song->id,
                "title" => $song->get_fullname(),
                "name" => $song->get_fullname(),
                "artist" => array(
                    "id" => (string)$song->artist,
                    "name" => $song_artist['name'],
                    "prefix" => $song_artist['prefix'],
                    "basename" => $song_artist['basename']
                ),
                "artists" => $song_artists,
                "album" => array(
                    "id" => (string)$song->album,
                    "name" => $song_album['name'],
                    "prefix" => $song_album['prefix'],
                    "basename" => $song_album['basename']
                )
            );
            if ($song->get_album_artist_fullname() != "") {
                $album_artist = ($song->artist !== $song->albumartist)
                    ? Artist::get_name_array_by_id($song->albumartist)
                    : $song_artist;
                $objArray['albumartist'] = array(
                    "id" => (string)$song->albumartist,
                    "name" => $album_artist['name'],
                    "prefix" => $album_artist['prefix'],
                    "basename" => $album_artist['basename']
                );
            }

            $objArray['disk']                  = (int)$song->disk;
            $objArray['disksubtitle']          = $song->disksubtitle;
            $objArray['track']                 = (int)$song->track;
            $objArray['filename']              = $song->file;
            $objArray['genre']                 = self::genre_array($song->tags);
            $objArray['playlisttrack']         = $playlist_track;
            $objArray['time']                  = (int)$song->time;
            $objArray['year']                  = (int)$song->year;
            $objArray['format']                = $songType;
            $objArray['stream_format']         = $song->type;
            $objArray['bitrate']               = $songBitrate;
            $objArray['stream_bitrate']        = $song->bitrate;
            $objArray['rate']                  = (int)$song->rate;
            $objArray['mode']                  = $song->mode;
            $objArray['mime']                  = $songMime;
            $objArray['stream_mime']           = $song->mime;
            $objArray['url']                   = $play_url;
            $objArray['size']                  = (int)$song->size;
            $objArray['mbid']                  = $song->mbid;
            $objArray['art']                   = $art_url;
            $objArray['has_art']               = $song->has_art();
            $objArray['flag']                  = (bool)$flag->get_flag($user->getId());
            $objArray['rating']                = $user_rating;
            $objArray['averagerating']         = $rating->get_average_rating();
            $objArray['playcount']             = (int)$song->total_count;
            $objArray['catalog']               = $song->getCatalogId();
            $objArray['composer']              = $song->composer;
            $objArray['channels']              = $song->channels;
            $objArray['comment']               = $song->comment;
            $objArray['license']               = $licenseLink;
            $objArray['publisher']             = $song->label;
            $objArray['language']              = $song->language;
            $objArray['lyrics']                = $song->lyrics;
            $objArray['replaygain_album_gain'] = $song->replaygain_album_gain;
            $objArray['replaygain_album_peak'] = $song->replaygain_album_peak;
            $objArray['replaygain_track_gain'] = $song->replaygain_track_gain;
            $objArray['replaygain_track_peak'] = $song->replaygain_track_peak;
            $objArray['r128_album_gain']       = $song->r128_album_gain;
            $objArray['r128_track_gain']       = $song->r128_track_gain;

            /** @var Metadata $metadata */
            foreach ($song->getMetadata() as $metadata) {
                $field = $metadata->getField();

                if ($field !== null) {
                    $meta_name = str_replace(
                        array(' ', '(', ')', '/', '\\', '#'),
                        '_',
                        $field->getName()
                    );
                    $objArray[$meta_name] = $metadata->getData();
                }
            }
            $JSON[] = $objArray;
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["song"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * videos
     *
     * This builds the JSON document for displaying video objects
     *
     * @param int[] $objects Video id's to include
     * @param User $user
     * @param bool $encode
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string    JSON Object "video"
     */
    public static function videos($objects, $user, $encode = true, $object = true)
    {
        $output = array(
            "total_count" => count($objects)
        );
        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_slice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $video_id) {
            $video = new Video($video_id);
            if ($video->isNew()) {
                continue;
            }
            $video->format();
            $rating      = new Rating($video_id, 'video');
            $user_rating = $rating->get_user_rating($user->getId());
            $flag        = new Userflag($video_id, 'video');
            $art_url     = Art::url($video_id, 'video', Core::get_request('auth'));
            $JSON[]      = array(
                "id" => (string)$video->id,
                "title" => $video->title,
                "mime" => $video->mime,
                "resolution" => $video->f_resolution,
                "size" => (int)$video->size,
                "genre" => self::genre_array($video->tags),
                "time" => (int)$video->time,
                "url" => $video->play_url('', 'api', false, $user->getId(), $user->streamtoken),
                "art" => $art_url,
                "has_art" => $video->has_art(),
                "flag" => (bool)$flag->get_flag($user->getId()),
                "rating" => $user_rating,
                "averagerating" => $rating->get_average_rating(),
                "playcount" => (int)$video->total_count
            );
        } // end foreach

        if ($encode) {
            if ($object) {
                $output["video"] = $JSON;
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * democratic
     *
     * This handles creating an JSON document for democratic items, this can be a little complicated
     * due to the votes and all of that
     *
     * @param array $object_ids Object IDs
     * @param User $user
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function democratic($object_ids, $user, $object = true): string
    {
        if (!is_array($object_ids)) {
            $object_ids = array();
        }
        $democratic = Democratic::get_current_playlist($user);

        $JSON = [];
        foreach ($object_ids as $row_id => $data) {
            $className = ObjectTypeToClassNameMapper::map($data['object_type']);
            /** @var Song $song */
            $song = new $className($data['object_id']);
            if ($song->isNew()) {
                continue;
            }
            $song->format();

            $rating      = new Rating($song->id, 'song');
            $user_rating = $rating->get_user_rating($user->getId());
            $art_url     = Art::url($song->album, 'album', $_REQUEST['auth'] ?? '');
            $songType    = $song->type;
            $songMime    = $song->mime;
            $songBitrate = $song->bitrate;
            $play_url    = $song->play_url('', 'api', false, $user->id, $user->streamtoken);
            $song_album  = self::getAlbumRepository()->getNames($song->album);
            $song_artist = Artist::get_name_array_by_id($song->artist);

            $JSON[] = array(
                "id" => (string)$song->id,
                "title" => $song->get_fullname(),
                "artist" => array(
                    "id" => (string)$song->artist,
                    "name" => $song_artist['name'],
                    "prefix" => $song_artist['prefix'],
                    "basename" => $song_artist['basename']
                ),
                "album" => array(
                    "id" => (string)$song->album,
                    "name" => $song_album['name'],
                    "prefix" => $song_album['prefix'],
                    "basename" => $song_album['basename']
                ),
                "genre" => self::genre_array($song->tags),
                "track" => (int)$song->track,
                "time" => (int)$song->time,
                "format" => $songType,
                "bitrate" => $songBitrate,
                "mime" => $songMime,
                "url" => $play_url,
                "size" => (int)$song->size,
                "art" => $art_url,
                "has_art" => $song->has_art(),
                "rating" => $user_rating,
                "averagerating" => ($rating->get_average_rating() ?? null),
                "playcount" => (int)$song->total_count,
                "vote" => $democratic->get_vote($row_id)
            );
        } // end foreach
        $output = ($object) ? array("song" => $JSON) : $JSON[0] ?? array();

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * user
     *
     * This handles creating an JSON document for a user
     */
    public static function user(User $user, bool $fullinfo, ?bool $object = true): string
    {
        $user->format();
        $art_url = Art::url($user->id, 'user', $_REQUEST['auth'] ?? '');
        if ($fullinfo) {
            $JSON = array(
                "id" => (string)$user->id,
                "username" => $user->username,
                "auth" => $user->apikey,
                "email" => $user->email,
                "access" => (int)$user->access,
                "streamtoken" => $user->streamtoken,
                "fullname_public" => (bool)$user->fullname_public,
                "validation" => $user->validation,
                "disabled" => (bool)$user->disabled,
                "create_date" => (int)$user->create_date,
                "last_seen" => (int)$user->last_seen,
                "website" => $user->website,
                "state" => $user->state,
                "city" => $user->city,
                "art" => $art_url,
                "has_art" => $user->has_art()
            );
        } else {
            $JSON = array(
                "id" => (string)$user->id,
                "username" => $user->username,
                "create_date" => $user->create_date,
                "last_seen" => $user->last_seen,
                "website" => $user->website,
                "state" => $user->state,
                "city" => $user->city,
                "art" => $art_url,
                "has_art" => $user->has_art()
            );
        }
        if ($user->fullname_public) {
            $JSON['fullname'] = $user->fullname;
        }
        $output = ($object) ? array("user" => $JSON) : $JSON;

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * users
     *
     * This handles creating an JSON document for a user list
     *
     * @param int[] $objects User id list
     * @param bool $encode return the array and don't json_encode the data
     * @param bool $object (whether to return as a named object array or regular array)
     * @return array|string JSON Object "label"
     */
    public static function users($objects, $encode = true, $object = true)
    {
        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }

        $JSON = [];
        foreach ($objects as $user_id) {
            $user = new User($user_id);
            if ($user->isNew()) {
                continue;
            }
            $JSON[] = array(
                "id" => (string)$user_id,
                "username" => $user->username
            );
        } // end foreach

        if ($encode) {
            if ($object) {
                $output = array("user" => $JSON);
            } else {
                $output = $JSON[0] ?? array();
            }

            return json_encode($output, JSON_PRETTY_PRINT);
        }

        return $JSON;
    }

    /**
     * now_playing
     *
     * This handles creating an JSON document for a now_playing list
     *
     * @param array $results
     */
    public static function now_playing(array $results): string
    {
        $JSON = [];
        foreach ($results as $now_playing) {
            $user = $now_playing['client'];
            if ($user->isNew()) {
                continue;
            }
            $media = $now_playing['media'];

            $JSON[] = [
                'id' => (string) $media->getId(),
                'type' => (string) ObjectTypeToClassNameMapper::reverseMap(get_class($media)),
                'client' => $now_playing['agent'],
                'expire' => (int) $now_playing['expire'],
                'user' => array(
                    'id' => (string) $user->getId(),
                    'username' => $user->getUsername()
                )
            ];
        }
        $output = array("now_playing" => $JSON);

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * shouts
     *
     * This handles creating an JSON document for a shout list
     *
     * @param list<Shoutbox> $shouts Shout id list
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function shouts(array $shouts, bool $object = true): string
    {
        $JSON = [];

        foreach ($shouts as $shout) {
            $user = new User($shout->getUserId());

            $JSON[] = [
                'id' => (string) $shout->getId(),
                'date' => $shout->getDate()->getTimestamp(),
                'text' => $shout->getText(),
                'user' => array(
                    'id' => (string) $user->getId(),
                    'username' => $user->getUsername()
                )
            ];
        }
        $output = ($object) ? array("shout" => $JSON) : $JSON[0] ?? array();

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * timeline
     *
     * This handles creating an JSON document for an activity list
     *
     * @param int[] $activities Activity id list
     * @param bool $object (whether to return as a named object array or regular array)
     */
    public static function timeline($activities, $object = true): string
    {
        $JSON = array();
        foreach ($activities as $activity_id) {
            $activity = new Useractivity($activity_id);
            $user     = new User($activity->user);
            $objArray = array(
                "id" => (string)$activity_id,
                "date" => $activity->activity_date,
                "object_type" => $activity->object_type,
                "object_id" => (string)$activity->object_id,
                "action" => $activity->action,
                "user" => array(
                    "id" => (string)$activity->user,
                    "username" => $user->username
                )
            );
            $JSON[] = $objArray;
        }
        $output = ($object) ? array("activity" => $JSON) : $JSON[0] ?? array();

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * deleted
     *
     * This handles creating a JSON document for deleted items
     *
     * @param string $object_type ('song', 'podcast_episode', 'video')
     * @param array $objects deleted object list
     */
    public static function deleted($object_type, $objects): string
    {
        $output = array(
            "total_count" => count($objects)
        );

        if ((count($objects) > self::$limit || self::$offset > 0) && self::$limit) {
            $objects = array_splice($objects, self::$offset, self::$limit);
        }
        $JSON = [];
        foreach ($objects as $row) {
            switch ($object_type) {
                case 'song':
                    $objArray = array(
                        "id" => (string)$row['id'],
                        "addition_time" => $row['addition_time'],
                        "delete_time" => $row['delete_time'],
                        "title" => $row['title'],
                        "file" => $row['file'],
                        "catalog" => (string)$row['catalog'],
                        "total_count" => $row['total_count'],
                        "total_skip" => $row['total_skip'],
                        "update_time" => $row['update_time'],
                        "album" => (string)$row['album'],
                        "artist" => (string)$row['artist']
                    );
                    $JSON[] = $objArray;
                    break;
                case 'podcast_episode':
                    $objArray = array(
                        "id" => (string)$row['id'],
                        "addition_time" => $row['addition_time'],
                        "delete_time" => $row['delete_time'],
                        "title" => $row['title'],
                        "file" => $row['file'],
                        "catalog" => (string)$row['catalog'],
                        "total_count" => $row['total_count'],
                        "total_skip" => $row['total_skip'],
                        "podcast" => (string)$row['podcast']
                    );
                    $JSON[] = $objArray;
                    break;
                case 'video':
                    $objArray = array(
                        "id" => (string)$row['id'],
                        "addition_time" => $row['addition_time'],
                        "delete_time" => $row['delete_time'],
                        "title" => $row['title'],
                        "file" => $row['file'],
                        "catalog" => (string)$row['catalog'],
                        "total_count" => $row['total_count'],
                        "total_skip" => $row['total_skip']
                    );
                    $JSON[] = $objArray;
            }
        }
        $output["deleted_" . $object_type] = $JSON;

        return json_encode($output, JSON_PRETTY_PRINT);
    }

    /**
     * @deprecated Inject by constructor
     */
    private static function getSongRepository(): SongRepositoryInterface
    {
        global $dic;

        return $dic->get(SongRepositoryInterface::class);
    }

    /**
     * @deprecated Inject by constructor
     */
    private static function getAlbumRepository(): AlbumRepositoryInterface
    {
        global $dic;

        return $dic->get(AlbumRepositoryInterface::class);
    }

    /**
     * @deprecated Inject by constructor
     */
    private static function getPodcastRepository(): PodcastRepositoryInterface
    {
        global $dic;

        return $dic->get(PodcastRepositoryInterface::class);
    }

    /**
     * @deprecated Inject by constructor
     */
    private static function getLicenseRepository(): LicenseRepositoryInterface
    {
        global $dic;

        return $dic->get(LicenseRepositoryInterface::class);
    }

    /**
     * @deprecated Inject by constructor
     */
    private static function getBookmarkRepository(): BookmarkRepositoryInterface
    {
        global $dic;

        return $dic->get(BookmarkRepositoryInterface::class);
    }

    /**
     * @deprecated Inject dependency
     */
    private static function getLabelRepository(): LabelRepositoryInterface
    {
        global $dic;

        return $dic->get(LabelRepositoryInterface::class);
    }
}