CVE-2022-4665: Unrestricted Upload of File with Dangerous Type (CWE-434)
value: Unrestricted Upload of File with Dangerous Type in GitHub repository ampache/ampache prior to 5.5.6.
CWE description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
CVSS v3 base score: 8.8
Published: 2022-12-23T01:15Z
Repository: https://github.com/ampache/ampache
Language: PHP
Files changed: docs/CHANGELOG.md, public/templates/show_artist_info.inc.php, resources/sql/ampache.sql, src/Module/Art/Collector/FolderCollectorModule.php, src/Module/Playback/Localplay/Mpd/AmpacheMpd.php, src/Module/Playback/Localplay/Upnp/UPnPDevice.php, src/Module/Playback/Localplay/Vlc/VlcPlayer.php, src/Module/Playback/Stream_Playlist.php, src/Module/System/Update.php, src/Repository/Model/Art.php, src/Repository/Model/Catalog.php, src/Repository/Model/TVShow_Season.php, src/Repository/Model/Tag.php, src/Repository/Model/User.php
Commit date: 2022-12-14 17:47:15 +1000
Commit message: think we good now

Diff: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index ccf699417b..63688ed157 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -16,10 +16,12 @@ There will probably not be another big change to Ampache 5 as work has moved to
 * get_now_playing `has_access` check
 * Malformed HTML for regular users in preferences sidebar
 * Missing translation on preferences sidebar
 * Default catalog_filter group could be missing on a new install
 * Gather genre tags when not an array
+* Display webp images
+* Check for a valid image extensions when uploading art
 
 ## API 5.5.6
 
 Fix various runtime errors and incorrect parameters for responses.
 
diff --git a/public/templates/show_artist_info.inc.php b/public/templates/show_artist_info.inc.php
index 86d9762afa..895912aae5 100644
--- a/public/templates/show_artist_info.inc.php
+++ b/public/templates/show_artist_info.inc.php
@@ -46,12 +46,12 @@ use Ampache\Module\Util\Ui;
             echo implode(', ', $dcol);
         } ?>
     </div>
 </div>
 <div id="item_summary">
-    <?php if (array_key_exists('summary', $biography) && !empty(trim($biography['summary']))) { ?>
-        <?php echo nl2br($biography['summary']); ?>
+    <?php if (array_key_exists('summary', $biography) && !empty($biography['summary'])) { ?>
+        <?php echo nl2br(trim($biography['summary'])); ?>
         <?php
     }?>
 </div>
 <script>
     $(document).ready(function(){
diff --git a/resources/sql/ampache.sql b/resources/sql/ampache.sql
index ffabbb6da4..7be87b65eb 100644
--- a/resources/sql/ampache.sql
+++ b/resources/sql/ampache.sql
@@ -16,11 +16,11 @@
 -- phpMyAdmin SQL Dump
 -- version 5.1.3
 -- https://www.phpmyadmin.net/
 --
 -- Host: 192.168.1.20
--- Generation Time: Nov 19, 2022 at 05:49 PM
+-- Generation Time: Dec 08, 2022 at 08:40 AM
 -- Server version: 10.5.15-MariaDB-0+deb11u1
 -- PHP Version: 8.1.11
 
 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
 SET AUTOCOMMIT = 0;
@@ -32,11 +32,11 @@ SET time_zone = "+00:00";
 /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 /*!40101 SET NAMES utf8mb4 */;
 
 --
--- Database: `ampache554`
+-- Database: `ampache556`
 --
 
 -- --------------------------------------------------------
 
 --
diff --git a/src/Module/Art/Collector/FolderCollectorModule.php b/src/Module/Art/Collector/FolderCollectorModule.php
index eb59bd50fc..da1183ec10 100644
--- a/src/Module/Art/Collector/FolderCollectorModule.php
+++ b/src/Module/Art/Collector/FolderCollectorModule.php
@@ -83,20 +83,10 @@ final class FolderCollectorModule implements CollectorModuleInterface
         /* See if we are looking for a specific filename */
         $preferred_filename = ($this->configContainer->get('album_art_preferred_filename')) ?: 'folder.jpg';
         $artist_filename    = $this->configContainer->get('artist_art_preferred_filename');
         $artist_art_folder  = $this->configContainer->get('artist_art_folder');
 
-        // Array of valid extensions
-        $image_extensions = [
-            'bmp',
-            'gif',
-            'jp2',
-            'jpeg',
-            'jpg',
-            'png'
-        ];
-
         $dirs = array();
         if ($art->type == 'album') {
             $media = new Album($art->uid);
             $songs = $this->songRepository->getByAlbum((int) $media->id);
             foreach ($songs as $song_id) {
@@ -152,11 +142,11 @@ final class FolderCollectorModule implements CollectorModuleInterface
             while (false !== ($file = readdir($handle))) {
                 $extension = pathinfo($file);
                 $extension = $extension['extension'] ?? '';
 
                 // Make sure it looks like an image file
-                if (!in_array($extension, $image_extensions)) {
+                if (!in_array($extension, Art::VALID_TYPES)) {
                     continue;
                 }
 
                 $full_filename = $dir . '/' . $file;
 
diff --git a/src/Module/Playback/Localplay/Mpd/AmpacheMpd.php b/src/Module/Playback/Localplay/Mpd/AmpacheMpd.php
index ea857a795e..c43486e5a5 100644
--- a/src/Module/Playback/Localplay/Mpd/AmpacheMpd.php
+++ b/src/Module/Playback/Localplay/Mpd/AmpacheMpd.php
@@ -372,11 +372,10 @@ class AmpacheMpd extends localplay_controller
     public function pause()
     {
         return $this->_mpd->Pause();
     } // pause
 
-
     /**
      * volume
      * This tells MPD to set the volume to the parameter
      * @param $volume
      * @return boolean|string
diff --git a/src/Module/Playback/Localplay/Upnp/UPnPDevice.php b/src/Module/Playback/Localplay/Upnp/UPnPDevice.php
index 1be51dc687..71f558ba0f 100644
--- a/src/Module/Playback/Localplay/Upnp/UPnPDevice.php
+++ b/src/Module/Playback/Localplay/Upnp/UPnPDevice.php
@@ -33,11 +33,10 @@ class UPnPDevice
         "host" => "",
         "controlURLs" => array(),
         "eventURLs" => array()
     );
 
-
     /**
      * UPnPDevice constructor.
      * @param $descriptionUrl
      */
     public function __construct($descriptionUrl)
diff --git a/src/Module/Playback/Localplay/Vlc/VlcPlayer.php b/src/Module/Playback/Localplay/Vlc/VlcPlayer.php
index 2b3a81580b..59947940eb 100644
--- a/src/Module/Playback/Localplay/Vlc/VlcPlayer.php
+++ b/src/Module/Playback/Localplay/Vlc/VlcPlayer.php
@@ -290,11 +290,10 @@ class VlcPlayer
         }
 
         return $results;
     } // fullstate
 
-
     /**
      * volume_up
      * This increases the volume of VLC, set to +20 can be changed to your preference
      */
     public function volume_up()
diff --git a/src/Module/Playback/Stream_Playlist.php b/src/Module/Playback/Stream_Playlist.php
index d856a28d4d..342badf49a 100644
--- a/src/Module/Playback/Stream_Playlist.php
+++ b/src/Module/Playback/Stream_Playlist.php
@@ -758,11 +758,10 @@ class Stream_Playlist
         header('Location: ' . $url);
 
         return false;
     } // create_download
 
-
     /**
      * create_ram
      *this functions creates a RAM file for use by Real Player
      */
     public function create_ram()
diff --git a/src/Module/System/Update.php b/src/Module/System/Update.php
index 79fc4fc414..7c14cef903 100644
--- a/src/Module/System/Update.php
+++ b/src/Module/System/Update.php
@@ -2366,12 +2366,10 @@ class Update
         $retval &= (Dba::write($sql) !== false);
 
         return $retval;
     }
 
-
-
     /**
      * update_370028
      *
      * Add width and height in table image
      *
diff --git a/src/Repository/Model/Art.php b/src/Repository/Model/Art.php
index e5ffffee74..8565792972 100644
--- a/src/Repository/Model/Art.php
+++ b/src/Repository/Model/Art.php
@@ -47,10 +47,11 @@ use RuntimeException;
  * to be more general and potentially apply to albums, artists, movies etc
  */
 class Art extends database_object
 {
     protected const DB_TABLENAME = 'art';
+    public const VALID_TYPES     = array('bmp', 'gif', 'jp2', 'jpeg', 'jpg', 'png', 'webp');
 
     /**
      * @var integer $id
      */
     public $id;
@@ -401,23 +402,31 @@ class Art extends database_object
                     $apics = $data['id3v2']['APIC'];
                 }
                 /* is the file flac or mp3? */
                 $apic_typeid   = ($fileformat == 'flac' || $fileformat == 'ogg') ? 'typeid' : 'picturetypeid';
                 $apic_mimetype = ($fileformat == 'flac' || $fileformat == 'ogg') ? 'image_mime' : 'mime';
-                $new_pic       = array('data' => $source, 'mime' => $mime,
-                    'picturetypeid' => $current_picturetypeid, 'description' => $description);
+                $new_pic       = array(
+                    'data' => $source,
+                    'mime' => $mime,
+                    'picturetypeid' => $current_picturetypeid,
+                    'description' => $description
+                );
 
                 if (is_null($apics)) {
                     $ndata['attached_picture'][]    = $new_pic;
                 } else {
                     switch (count($apics)) {
                         case 1:
                             $idx = $this->check_for_duplicate($apics, $ndata, $new_pic, $apic_typeid);
                             if (is_null($idx)) {
                                 $ndata['attached_picture'][] = $new_pic;
-                                $ndata['attached_picture'][] = array('data' => $apics[0]['data'], 'description' => $apics[0]['description'],
-                                    'mime' => $apics[0]['mime'], 'picturetypeid' => $apics[0]['picturetypeid']);
+                                $ndata['attached_picture'][] = array(
+                                    'data' => $apics[0]['data'],
+                                    'description' => $apics[0]['description'],
+                                    'mime' => $apics[0]['mime'],
+                                    'picturetypeid' => $apics[0]['picturetypeid']
+                                );
                             }
                             break;
                         case 2:
                             $idx = $this->check_for_duplicate($apics, $ndata, $new_pic, $apic_typeid);
                             /* If $idx is null, it means both images are of opposite types
@@ -426,12 +435,16 @@ class Art extends database_object
                              */
                             if (is_null($idx)) {
                                 $ndata['attached_picture'][0] = $new_pic;
                             } else {
                                 $apicsId                              = ($idx == 0) ? 1 : 0;
-                                $ndata['attached_picture'][$apicsId]  = array('data' => $apics[$apicsId]['data'], 'mime' => $apics[$apicsId][$apic_mimetype],
-                                'picturetypeid' => $apics[$apicsId][$apic_typeid], 'description' => $apics[$apicsId]['description']);
+                                $ndata['attached_picture'][$apicsId]  = array(
+                                    'data' => $apics[$apicsId]['data'],
+                                    'mime' => $apics[$apicsId][$apic_mimetype],
+                                    'picturetypeid' => $apics[$apicsId][$apic_typeid],
+                                    'description' => $apics[$apicsId]['description']
+                                );
                             }
 
                             break;
                     }
                 }
@@ -509,14 +522,14 @@ class Art extends database_object
     {
         $width  = (int)($dimensions['width']);
         $height = (int)($dimensions['height']);
 
         if ($width > 0 && $height > 0) {
-            $minw = (AmpConfig::get('album_art_min_width')) ? AmpConfig::get('album_art_min_width') : 0;
-            $maxw = (AmpConfig::get('album_art_max_width')) ? AmpConfig::get('album_art_max_width') : 0;
-            $minh = (AmpConfig::get('album_art_min_height')) ? AmpConfig::get('album_art_min_height') : 0;
-            $maxh = (AmpConfig::get('album_art_max_height')) ? AmpConfig::get('album_art_max_height') : 0;
+            $minw = AmpConfig::get('album_art_min_width', 0);
+            $maxw = AmpConfig::get('album_art_max_width', 0);
+            $minh = AmpConfig::get('album_art_min_height', 0);
+            $maxh = AmpConfig::get('album_art_max_height', 0);
 
             // minimum width is set and current width is too low
             if ($minw > 0 && $width < $minw) {
                 debug_event(self::class, "Image width not in range (min=$minw, max=$maxw, current=$width).", 1);
 
@@ -625,13 +638,12 @@ class Art extends database_object
         return true;
     }
 
     /**
      * read_from_images
-     * @param string $name
      */
-    private static function read_from_images($name = 'blankalbum.png')
+    private static function read_from_images()
     {
         $path = __DIR__ . '/../../../public/images/blankalbum.png';
         if (!Core::is_readable($path)) {
             debug_event(self::class, 'read_from_images ' . $path . ' cannot be read.', 1);
 
@@ -924,10 +936,14 @@ class Art extends database_object
             case 'bmp':
             case 'png':
                 imagepng($thumbnail);
                 $mime_type = image_type_to_mime_type(IMAGETYPE_PNG);
                 break;
+            case 'webp':
+                imagewebp($thumbnail);
+                $mime_type = image_type_to_mime_type(IMAGETYPE_WEBP);
+                break;
             default:
                 $mime_type = null;
         } // resized
 
         if ($mime_type === null) {
@@ -1496,11 +1512,11 @@ class Art extends database_object
 
         if ($prettyPhoto) {
             $class_name  = ObjectTypeToClassNameMapper::map($object_type);
             $libitem     = new $class_name($object_id);
             echo "<div class=\"item_art_actions\">";
-            if (Core::get_global('user')->has_access(50) || (Core::get_global('user')->has_access(25) && Core::get_global('user')->id == $libitem->get_user_owner())) {
+            if ((!empty(Core::get_global('user')) && Core::get_global('user')->has_access(50)) || (Core::get_global('user')->has_access(25) && Core::get_global('user')->id == $libitem->get_user_owner())) {
                 echo "<a href=\"javascript:NavigateTo('" . AmpConfig::get('web_path') . "/arts.php?action=show_art_dlg&object_type=" . $object_type . "&object_id=" . $object_id . "&burl=' + getCurrentPage());\">";
                 echo Ui::get_icon('edit', T_('Edit/Find Art'));
                 echo "</a>";
                 echo "<a href=\"javascript:NavigateTo('" . AmpConfig::get('web_path') . "/arts.php?action=clear_art&object_type=" . $object_type . "&object_id=" . $object_id . "&burl=' + getCurrentPage());\" onclick=\"return confirm('" . T_('Do you really want to reset art?') . "');\">";
                 echo Ui::get_icon('delete', T_('Reset Art'));
diff --git a/src/Repository/Model/Catalog.php b/src/Repository/Model/Catalog.php
index 668cfd76f1..36fcc88b7a 100644
--- a/src/Repository/Model/Catalog.php
+++ b/src/Repository/Model/Catalog.php
@@ -3819,10 +3819,11 @@ abstract class Catalog extends database_object
         Dba::write($sql);
 
         $sql = "DELETE FROM `catalog_map` WHERE `catalog_id` = 0";
         Dba::write($sql);
     }
+
     /**
      * Delete catalog filters that might have gone missing
      */
     public static function garbage_collect_filters()
     {
diff --git a/src/Repository/Model/TVShow_Season.php b/src/Repository/Model/TVShow_Season.php
index a8e0964508..f07c07a71e 100644
--- a/src/Repository/Model/TVShow_Season.php
+++ b/src/Repository/Model/TVShow_Season.php
@@ -113,11 +113,11 @@ class TVShow_Season extends database_object implements library_item, GarbageColl
     {
         // Try to find it in the cache and save ourselves the trouble
         if (parent::is_cached('tvshow_extra', $this->id)) {
             $row = parent::get_from_cache('tvshow_extra', $this->id);
         } else {
-            $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` AS `catalog_id` FROM `tvshow_episode` LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` WHERE `tvshow_episode`.`season` = ?GROUP BY `catalog_id`";
+            $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` AS `catalog_id` FROM `tvshow_episode` LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` WHERE `tvshow_episode`.`season` = ? GROUP BY `catalog_id`";
 
             $db_results = Dba::read($sql, array($this->id));
             $row        = Dba::fetch_assoc($db_results);
             parent::add_to_cache('tvshow_extra', $this->id, $row);
         }
diff --git a/src/Repository/Model/Tag.php b/src/Repository/Model/Tag.php
index 57a9e6a581..a521e16b31 100644
--- a/src/Repository/Model/Tag.php
+++ b/src/Repository/Model/Tag.php
@@ -551,11 +551,11 @@ class Tag extends database_object implements library_item, GarbageCollectibleInt
         }
 
         $object_id = (int)($object_id);
 
         $limit = (int)($limit);
-        $sql   = "SELECT `tag_map`.`id`, `tag_map`.`tag_id`, `tag`.`name`, `tag_map`.`user` FROM `tag` LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` WHERE `tag`.`is_hidden` = false AND `tag_map`.`object_type` = ? AND `tag_map`.`object_id` = ?LIMIT $limit";
+        $sql   = "SELECT `tag_map`.`id`, `tag_map`.`tag_id`, `tag`.`name`, `tag_map`.`user` FROM `tag` LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` WHERE `tag`.`is_hidden` = false AND `tag_map`.`object_type` = ? AND `tag_map`.`object_id` = ? LIMIT $limit";
 
         $db_results = Dba::read($sql, array($type, $object_id));
         $results    = array();
         while ($row = Dba::fetch_assoc($db_results)) {
             $results[$row['id']] = array('user' => $row['user'], 'id' => $row['tag_id'], 'name' => $row['name']);
diff --git a/src/Repository/Model/User.php b/src/Repository/Model/User.php
index 19c4cf0ca6..abecdaaa12 100644
--- a/src/Repository/Model/User.php
+++ b/src/Repository/Model/User.php
@@ -352,22 +352,20 @@ class User extends database_object
         parent::add_to_cache('user_catalog', $userid, $catalogs);
 
         return $catalogs;
     } // get_catalogs
 
-
     /**
      * get_catalogs
      * This returns the catalogs as an array of ids that this user is allowed to access
      * @return integer[]
      */
     public function get_catalogs()
     {
         return self::get_user_catalogs($this->id);
     } // get_catalogs
 
-
     /**
      * get_preferences
      * This is a little more complicate now that we've got many types of preferences
      * This function pulls all of them an arranges them into a spiffy little array
      * You can specify a type to limit it to a single type of preference
@@ -1438,12 +1436,15 @@ class User extends database_object
         $upload = array();
         if (!empty($_FILES['avatar']['tmp_name']) && $_FILES['avatar']['size'] <= AmpConfig::get('max_upload_size')) {
             $path_info      = pathinfo($_FILES['avatar']['name']);
             $upload['file'] = $_FILES['avatar']['tmp_name'];
             $upload['mime'] = 'image/' . $path_info['extension'];
-            $image_data     = Art::get_from_source($upload, 'user');
+            if (!in_array(strtolower($path_info['extension']), Art::VALID_TYPES)) {
+                return false;
+            }
 
+            $image_data = Art::get_from_source($upload, 'user');
             if ($image_data !== '') {
                 return $this->update_avatar($image_data, $upload['mime']);
             }
         }
 

Vulnerable code:     <?php if (array_key_exists('summary', $biography) && !empty(trim($biography['summary']))) { ?>
        <?php echo nl2br($biography['summary']); ?>
        // Array of valid extensions
        $image_extensions = [
            'bmp',
            'gif',
            'jp2',
            'jpeg',
            'jpg',
            'png'
        ];

                if (!in_array($extension, $image_extensions)) {






                $new_pic       = array('data' => $source, 'mime' => $mime,
                    'picturetypeid' => $current_picturetypeid, 'description' => $description);
                                $ndata['attached_picture'][] = array('data' => $apics[0]['data'], 'description' => $apics[0]['description'],
                                    'mime' => $apics[0]['mime'], 'picturetypeid' => $apics[0]['picturetypeid']);
                                $ndata['attached_picture'][$apicsId]  = array('data' => $apics[$apicsId]['data'], 'mime' => $apics[$apicsId][$apic_mimetype],
                                'picturetypeid' => $apics[$apicsId][$apic_typeid], 'description' => $apics[$apicsId]['description']);
            $minw = (AmpConfig::get('album_art_min_width')) ? AmpConfig::get('album_art_min_width') : 0;
            $maxw = (AmpConfig::get('album_art_max_width')) ? AmpConfig::get('album_art_max_width') : 0;
            $minh = (AmpConfig::get('album_art_min_height')) ? AmpConfig::get('album_art_min_height') : 0;
            $maxh = (AmpConfig::get('album_art_max_height')) ? AmpConfig::get('album_art_max_height') : 0;
     * @param string $name
    private static function read_from_images($name = 'blankalbum.png')
            if (Core::get_global('user')->has_access(50) || (Core::get_global('user')->has_access(25) && Core::get_global('user')->id == $libitem->get_user_owner())) {
            $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` AS `catalog_id` FROM `tvshow_episode` LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` WHERE `tvshow_episode`.`season` = ?GROUP BY `catalog_id`";
        $sql   = "SELECT `tag_map`.`id`, `tag_map`.`tag_id`, `tag`.`name`, `tag_map`.`user` FROM `tag` LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` WHERE `tag`.`is_hidden` = false AND `tag_map`.`object_type` = ? AND `tag_map`.`object_id` = ?LIMIT $limit";


            $image_data     = Art::get_from_source($upload, 'user');
Fixed code: * Display webp images
* Check for a valid image extensions when uploading art
    <?php if (array_key_exists('summary', $biography) && !empty($biography['summary'])) { ?>
        <?php echo nl2br(trim($biography['summary'])); ?>
-- Generation Time: Dec 08, 2022 at 08:40 AM
-- Database: `ampache556`
                if (!in_array($extension, Art::VALID_TYPES)) {
    public const VALID_TYPES     = array('bmp', 'gif', 'jp2', 'jpeg', 'jpg', 'png', 'webp');
                $new_pic       = array(
                    'data' => $source,
                    'mime' => $mime,
                    'picturetypeid' => $current_picturetypeid,
                    'description' => $description
                );
                                $ndata['attached_picture'][] = array(
                                    'data' => $apics[0]['data'],
                                    'description' => $apics[0]['description'],
                                    'mime' => $apics[0]['mime'],
                                    'picturetypeid' => $apics[0]['picturetypeid']
                                );
                                $ndata['attached_picture'][$apicsId]  = array(
                                    'data' => $apics[$apicsId]['data'],
                                    'mime' => $apics[$apicsId][$apic_mimetype],
                                    'picturetypeid' => $apics[$apicsId][$apic_typeid],
                                    'description' => $apics[$apicsId]['description']
                                );
            $minw = AmpConfig::get('album_art_min_width', 0);
            $maxw = AmpConfig::get('album_art_max_width', 0);
            $minh = AmpConfig::get('album_art_min_height', 0);
            $maxh = AmpConfig::get('album_art_max_height', 0);
    private static function read_from_images()
            case 'webp':
                imagewebp($thumbnail);
                $mime_type = image_type_to_mime_type(IMAGETYPE_WEBP);
                break;
            if ((!empty(Core::get_global('user')) && Core::get_global('user')->has_access(50)) || (Core::get_global('user')->has_access(25) && Core::get_global('user')->id == $libitem->get_user_owner())) {

            $sql = "SELECT COUNT(`tvshow_episode`.`id`) AS `episode_count`, `video`.`catalog` AS `catalog_id` FROM `tvshow_episode` LEFT JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` WHERE `tvshow_episode`.`season` = ? GROUP BY `catalog_id`";
        $sql   = "SELECT `tag_map`.`id`, `tag_map`.`tag_id`, `tag`.`name`, `tag_map`.`user` FROM `tag` LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` WHERE `tag`.`is_hidden` = false AND `tag_map`.`object_type` = ? AND `tag_map`.`object_id` = ? LIMIT $limit";
            if (!in_array(strtolower($path_info['extension']), Art::VALID_TYPES)) {
                return false;
            }
            $image_data = Art::get_from_source($upload, 'user');