]> git.mjollnir.org Git - s9y.git/commitdiff
fix sorting media paths
authorgarvinhicking <garvinhicking>
Sat, 5 Aug 2006 21:43:19 +0000 (21:43 +0000)
committergarvinhicking <garvinhicking>
Sat, 5 Aug 2006 21:43:19 +0000 (21:43 +0000)
docs/NEWS
include/functions_images.inc.php

index be261797f1ce763c1e5b0a07d842d094b40535ca..b87a6cdc1980c27ca50e93145a9aa168cc393a98 100644 (file)
--- a/docs/NEWS
+++ b/docs/NEWS
@@ -3,6 +3,8 @@
 Version 1.1-alpha7()
 ------------------------------------------------------------------------
 
+    * Properly sort media directories (garvinhicking)
+
     * Better use of "return by references" in some vital areas.
       Improves performance. Might introduce glitches. Keep an eye on this!
       (garvinhicking)
index c45e9f0f20c9a7fceae0fdeacc83de513c393ac0..ca45056f3e0830a44e0ffffdd9b25b70799d0b86 100644 (file)
@@ -2,6 +2,11 @@
 # Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
 # All rights reserved.  See LICENSE file for licensing details
 
+if (defined('S9Y_FRAMEWORK_IMAGES')) {
+    return;
+}
+@define('S9Y_FRAMEWORK_IMAGES', true);
+
 /**
  * Check if an uploaded file is "evil"
  *
@@ -30,57 +35,184 @@ function serendipity_isActiveFile($file) {
  * @param   string  Order by DESC or ASC
  * @param   string  Only fetch files from a specific directory
  * @param   string  Only fetch specific filenames
+ * @param   string  Only fetch media with specific keyword
  * @return  array   Resultset of images
  */
-function serendipity_fetchImagesFromDatabase($start=0, $limit=0, &$total, $order = false, $ordermode = false, $directory = '', $filename = '') {
+function serendipity_fetchImagesFromDatabase($start=0, $limit=0, &$total, $order = false, $ordermode = false, $directory = '', $filename = '', $keywords = '', $filter = array()) {
     global $serendipity;
 
+    $cond = array(
+        'joinparts' => array(),
+        'parts'     => array(),
+    );
+
     $orderfields = serendipity_getImageFields();
     if (empty($order) || !isset($orderfields[$order])) {
-        $order = 'date';
+        $order = 'i.date';
+    }
+
+    if (!is_array($filter)) {
+        $filter = array();
     }
 
     if (empty($ordermode) || ($ordermode != 'DESC' && $ordermode != 'ASC')) {
         $ordermode = 'DESC';
     }
 
+    if ($order == 'name') {
+        $order = 'realname ' . $ordermode . ', name';
+    }
+
     if ($limit != 0) {
         $limitsql = serendipity_db_limit_sql(serendipity_db_limit($start, $limit));
     }
 
     if (!empty($directory)) {
-        $directorysql = ' WHERE path LIKE \'' . serendipity_db_escape_string($directory) . '%\' ';
+        $cond['parts']['directory'] = " AND i.path LIKE '" . serendipity_db_escape_string($directory) . "%'\n";
     }
 
     if (!empty($filename)) {
-        if (empty($directorysql)) {
-            $directorysql = " WHERE  name like '%" . serendipity_db_escape_string($filename) . "%'";
+        $cond['parts']['filename'] = " AND (i.name     like '%" . serendipity_db_escape_string($filename) . "%' OR
+                  i.realname like '%" . serendipity_db_escape_string($filename) . "%')\n";
+    }
+
+    if (!is_array($keywords)) {
+        if (!empty($keywords)) {
+            $keywords = explode(';', $keywords);
         } else {
-            $directorysql .= " AND   name like '%" . serendipity_db_escape_string($filename) . "%'";
+            $keywords = array();
         }
     }
 
-    $perm = $permsql = '';
-    if (isset($serendipity['authorid']) && !serendipity_checkPermission('adminImagesViewOthers')) {
-        $perm = " (i.authorid = 0 OR i.authorid = " . (int)$serendipity['authorid'] . ")";
-        if (empty($directorysql)) {
-            $directorysql = " WHERE  $perm";
+    foreach($keywords AS $i => $keyword) {
+        $keywords[$i] = serendipity_db_escape_string($keyword);
+    }
+
+    if (count($keywords) > 0) {
+        $cond['parts']['keywords'] = " AND (mk.property IN ('" . implode("', '", $keywords) . "'))\n";
+        $cond['joinparts']['keywords'] = true;
+    }
+
+    foreach($filter AS $f => $fval) {
+        if (!isset($orderfields[$f]) || empty($fval)) {
+            continue;
+        }
+
+        if (is_array($fval)) {
+            if (empty($fval['from']) || empty($fval['to'])) {
+                continue;
+            }
+
+            if ($orderfields[$f]['type'] == 'date') {
+                $fval['from'] = serendipity_convertToTimestamp(trim($fval['from']));
+                $fval['to']   = serendipity_convertToTimestamp(trim($fval['to']));
+            }
+
+            if (substr($f, 0, 3) === 'bp.') {
+                $realf = substr($f, 3);
+                $cond['parts']['filter'] .= " AND (bp2.property = '$realf' AND bp2.value >= " . (int)$fval['from'] . " AND bp2.value <= " . (int)$fval['to'] . ")\n";
+            } else {
+                $cond['parts']['filter'] .= " AND ($f >= " . (int)$fval['from'] . " AND $f <= " . (int)$fval['to'] . ")\n";
+            }
+        } elseif ($f == 'i.authorid') {
+            $cond['parts']['filter'] .= " AND (
+                                    (hp.property = 'authorid' AND hp.value = " . (int)$fval . ")
+                                    OR
+                                    (i.authorid = " . (int)$fval . ")
+                                )\n";
+            $cond['joinparts']['hiddenproperties'] = true;
+        } elseif ($orderfields[$f]['type'] == 'int') {
+            if (substr($f, 0, 3) === 'bp.') {
+                $realf = substr($f, 3);
+                $cond['parts']['filter'] .= " AND (bp2.property = '$realf' AND bp2.value = '" . serendipity_db_escape_string(trim($fval)) . "')\n";
+            } else {
+                $cond['parts']['filter'] .= " AND ($f = '" . serendipity_db_escape_string(trim($fval)) . "')\n";
+            }
         } else {
-            $directorysql .= " AND   $perm";
+            if (substr($f, 0, 3) === 'bp.') {
+                $realf = substr($f, 3);
+                $cond['parts']['filter'] .= " AND (bp2.property = '$realf' AND bp2.value LIKE '%" . serendipity_db_escape_string(trim($fval)) . "%')\n";
+            } else {
+                $cond['parts']['filter'] .= " AND ($f LIKE '%" . serendipity_db_escape_string(trim($fval)) . "%')\n";
+            }
         }
-        $permsql = " WHERE $perm";
+        $cond['joinparts']['filterproperties'] = true;
+    }
+
+    if (isset($serendipity['authorid']) && !serendipity_checkPermission('adminImagesViewOthers')) {
+        $cond['parts']['authorid'] .= " AND (i.authorid = 0 OR i.authorid = " . (int)$serendipity['authorid'] . ")\n";
     }
 
-    $query = "SELECT i.*, a.realname AS authorname FROM {$serendipity['dbPrefix']}images AS i LEFT OUTER JOIN {$serendipity['dbPrefix']}authors AS a ON i.authorid = a.authorid $directorysql ORDER BY i.$order $ordermode $limitsql";
+    $cond['and']  = 'WHERE 1=1 ' . implode("\n", $cond['parts']);
+    $cond['args'] = func_get_args();
+    serendipity_plugin_api::hook_event('fetch_images_sql', $cond);
+    serendipity_ACL_SQL($cond, false, 'directory');
+
+    if ($cond['joinparts']['keywords']) {
+        $cond['joins'] .= "\n LEFT OUTER JOIN {$serendipity['dbPrefix']}mediaproperties AS mk
+                                        ON (mk.mediaid = i.id AND mk.property_group = 'base_keyword')\n";
+    }
+
+    if (substr($order, 0, 3) === 'bp.') {
+        $cond['orderproperty'] = substr($order, 3);
+        $cond['orderkey']   = 'bp.value';
+        $order              = 'bp.value';
+        $cond['joinparts']['properties'] = true;
+    } else {
+        $cond['orderkey'] = "''";
+    }
+
+    if ($cond['joinparts']['properties']) {
+        $cond['joins'] .= "\n LEFT OUTER JOIN {$serendipity['dbPrefix']}mediaproperties AS bp
+                                        ON (bp.mediaid = i.id AND bp.property_group = 'base_property' AND bp.property = '{$cond['orderproperty']}')\n";
+    }
+
+    if ($cond['joinparts']['filterproperties']) {
+        $cond['joins'] .= "\n LEFT OUTER JOIN {$serendipity['dbPrefix']}mediaproperties AS bp2
+                                        ON (bp2.mediaid = i.id AND bp2.property_group = 'base_property')\n";
+    }
+
+    if ($cond['joinparts']['hiddenproperties']) {
+        $cond['joins'] .= "\n LEFT OUTER JOIN {$serendipity['dbPrefix']}mediaproperties AS hp
+                                        ON (hp.mediaid = i.id AND hp.property_group = 'base_hidden')\n";
+    }
+
+    if ($serendipity['dbType'] == 'postgres') {
+        $cond['group']    = '';
+        $cond['distinct'] = 'DISTINCT';
+    } else {
+        $cond['group']    = 'GROUP BY i.id';
+        $cond['distinct'] = '';
+    }
+
+    $basequery = "FROM {$serendipity['dbPrefix']}images AS i
+       LEFT OUTER JOIN {$serendipity['dbPrefix']}authors AS a
+                    ON i.authorid = a.authorid
+                       {$cond['joins']}
+
+                       {$cond['and']}";
+
+    $query = "SELECT {$cond['distinct']} i.id, {$cond[orderkey]} AS orderkey, i.name, i.extension, i.mime, i.size, i.dimensions_width, i.dimensions_height, i.date, i.thumbnail_name, i.authorid, i.path, i.hotlink, i.realname,
+                     a.realname AS authorname
+                     $basequery
+                     {$cond['group']}
+            ORDER BY $order $ordermode $limitsql";
+
     $rs = serendipity_db_query($query, false, 'assoc');
-    if (!is_array($rs)) {
+
+    if (!is_array($rs) && $rs !== true && $rs !== 1) {
+        echo '<div>' . $rs . '</div>';
+        return array();
+    } elseif (!is_array($rs)) {
         return array();
     }
 
-    $total_query = "SELECT count(i.id) FROM {$serendipity['dbPrefix']}images AS i LEFT OUTER JOIN {$serendipity['dbPrefix']}authors AS a on i.authorid = a.authorid $permsql";
-    $total_rs = serendipity_db_query($total_query, true, 'num');
+    $total_query = "SELECT count(i.id)
+                           $basequery
+                           GROUP BY i.id";
+    $total_rs = serendipity_db_query($total_query, false, 'num');
     if (is_array($total_rs)) {
-        $total = $total_rs[0];
+        $total = count($total_rs);
     }
 
     return $rs;
@@ -93,9 +225,40 @@ function serendipity_fetchImagesFromDatabase($start=0, $limit=0, &$total, $order
  * @param   int     The ID of an media item
  * @return  array   The media info data
  */
-function serendipity_fetchImageFromDatabase($id) {
+function serendipity_fetchImageFromDatabase($id, $mode = 'read') {
     global $serendipity;
-    $rs = serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}images WHERE id = ". (int)$id, true, 'assoc');
+
+    if (is_array($id)) {
+        $cond = array(
+            'and' => "WHERE i.id IN (" . implode(',', $id) . ")"
+        );
+        $single   = false;
+        $assocKey = 'id';
+        $assocVal = false;
+    } else {
+        $cond = array(
+            'and' => "WHERE i.id = " . (int)$id
+        );
+        $single   = true;
+        $assocKey = false;
+        $assocVal = false;
+    }
+
+    if ($serendipity['dbType'] == 'postgres') {
+        $cond['group']    = '';
+        $cond['distinct'] = 'DISTINCT';
+    } else {
+        $cond['group']    = 'GROUP BY i.id';
+        $cond['distinct'] = '';
+    }
+
+    serendipity_ACL_SQL($cond, false, 'directory', $mode);
+
+    $rs = serendipity_db_query("SELECT {$cond['distinct']} i.id, i.name, i.extension, i.mime, i.size, i.dimensions_width, i.dimensions_height, i.date, i.thumbnail_name, i.authorid, i.path, i.hotlink, i.realname
+                                  FROM {$serendipity['dbPrefix']}images AS i
+                                       {$cond['joins']}
+                                       {$cond['and']}
+                                       {$cond['group']}", $single, 'assoc', false, $assocKey, $assocVal);
     return $rs;
 }
 
@@ -138,6 +301,12 @@ function serendipity_deleteImage($id) {
     $dThumb = array();
 
     $file   = serendipity_fetchImageFromDatabase($id);
+
+    if (!is_array($file) || !isset($file['path'])) {
+        printf(FILE_NOT_FOUND . '<br />', $id);
+        return false;
+    }
+
     $dFile  = $file['path'] . $file['name'] . '.' . $file['extension'];
 
     $dThumb = array(array(
@@ -178,7 +347,7 @@ function serendipity_deleteImage($id) {
     }
 
     serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}images WHERE id = ". (int)$id);
-
+    serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}mediaproperties WHERE mediaid = ". (int)$id);
 }
 
 /**
@@ -199,17 +368,29 @@ function serendipity_fetchImages($group = false, $start = 0, $end = 20, $images
     $basedir = $serendipity['serendipityPath'] . $serendipity['uploadPath'];
     $images = array();
     if ($dir = @opendir($basedir . $odir)) {
-        while(false !== ($f = readdir($dir))) {
-            if ($f != '.' && $f != '..' && $f != '.svn' && $f != 'CVS' && strpos($f, $serendipity['thumbSuffix']) === false) {
-                $cdir = ($odir != '' ? $odir . '/' : '');
-                if (is_dir($basedir . $odir . '/' . $f)) {
-                    $temp = serendipity_fetchImages($group, $start, $end, $images, $cdir . $f);
-                    foreach($temp AS $tkey => $tval) {
-                        array_push($images, $tval);
-                    }
-                } else {
-                    array_push($images, $cdir . $f);
+               $aTempArray = array();
+               while (($file = @readdir($dir)) !== false) {
+                   if ($file == '.svn' || $file == 'CVS' || $file == '.' || $file == '..') {
+                       continue;
+                   }
+                       array_push($aTempArray, $file);
+               }
+               @closedir($dir);
+               sort($aTempArray);
+               foreach($aTempArray as $f) {
+            if (strpos($f, $serendipity['thumbSuffix']) !== false) {
+                // This is a s9y thumbnail, skip it.
+                continue;
+            }
+
+            $cdir = ($odir != '' ? $odir . '/' : '');
+            if (is_dir($basedir . $odir . '/' . $f)) {
+                $temp = serendipity_fetchImages($group, $start, $end, $images, $cdir . $f);
+                foreach($temp AS $tkey => $tval) {
+                    array_push($images, $tval);
                 }
+            } else {
+                array_push($images, $cdir . $f);
             }
         }
     }
@@ -262,7 +443,8 @@ function serendipity_insertHotlinkedImageInDatabase($filename, $url, $authorid =
                     dimensions_width,
                     dimensions_height,
                     path,
-                    hotlink
+                    hotlink,
+                    realname
                    ) VALUES (
                     '%s',
                     %s,
@@ -273,7 +455,8 @@ function serendipity_insertHotlinkedImageInDatabase($filename, $url, $authorid =
                     %s,
                     %s,
                     '%s',
-                    1
+                    1,
+                    '%s'
                    )",
       serendipity_db_escape_string($filebase),
       (int)$time,
@@ -283,7 +466,8 @@ function serendipity_insertHotlinkedImageInDatabase($filename, $url, $authorid =
       (int)$filesize,
       (int)$width,
       (int)$height,
-      serendipity_db_escape_string($url)
+      serendipity_db_escape_string($url),
+      serendipity_db_escape_string($filename)
     );
 
     $sql = serendipity_db_query($query);
@@ -310,13 +494,17 @@ function serendipity_insertHotlinkedImageInDatabase($filename, $url, $authorid =
  * @param   int         The timestamp of when the media item was inserted
  * @return  int         The new media ID
  */
-function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0, $time = NULL) {
+function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0, $time = NULL, $realname = NULL) {
     global $serendipity;
 
-    if ( is_null($time) ) {
+    if (is_null($time)) {
         $time = time();
     }
 
+    if (is_null($realname)) {
+        $realname = $filename;
+    }
+
     $filepath = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $filename;
     $filesize = @filesize($filepath);
 
@@ -342,7 +530,8 @@ function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0,
                     thumbnail_name,
                     date,
                     authorid,
-                    path
+                    path,
+                    realname
                    ) VALUES (
                     '%s',
                     '%s',
@@ -353,6 +542,7 @@ function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0,
                     '%s',
                     %s,
                     %s,
+                    '%s',
                     '%s'
                    )",
       serendipity_db_escape_string($filebase),
@@ -364,7 +554,8 @@ function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0,
       serendipity_db_escape_string($thumbnail),
       (int)$time,
       (int)$authorid,
-      serendipity_db_escape_string($directory)
+      serendipity_db_escape_string($directory),
+      serendipity_db_escape_string($realname)
     );
 
     $sql = serendipity_db_query($query);
@@ -392,9 +583,11 @@ function serendipity_insertImageInDatabase($filename, $directory, $authorid = 0,
  * @param   string      The directory to the image file
  * @param   string      The target size of the thumbnail (2-dimensional array width,height)
  * @param   string      Name of the thumbnail
+ * @param   bool        Store thumbnail in temporary place?
+ * @param   bool        Force enlarging of small images?
  * @return  array       The result size of the thumbnail
  */
-function serendipity_makeThumbnail($file, $directory = '', $size = false, $thumbname = false) {
+function serendipity_makeThumbnail($file, $directory = '', $size = false, $thumbname = false, $is_temporary = false, $force_resize = false) {
     global $serendipity;
 
     if ($size === false) {
@@ -411,27 +604,45 @@ function serendipity_makeThumbnail($file, $directory = '', $size = false, $thumb
 
 
     $infile  = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $file;
-    $outfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $f . '.' . $thumbname . '.' . $suf;
+#    echo 'From: ' . $infile . '<br />';
+    if ($is_temporary) {
+        $temppath = dirname($thumbname);
+        if (!is_dir($temppath)) {
+            @mkdir($temppath);
+        }
+        $outfile = $thumbname;
+    } else {
+        $outfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $f . '.' . $thumbname . '.' . $suf;
+    }
+#    echo 'To: ' . $outfile . '<br />';
 
     $fdim    = @serendipity_getimagesize($infile, '', $suf);
     if (isset($fdim['noimage'])) {
         $r = array(0, 0);
     } else {
         if ($serendipity['magick'] !== true) {
-            $r = serendipity_resize_image_gd($infile, $outfile, $size);
+            if (is_array($size)) {
+                $r = serendipity_resize_image_gd($infile, $outfile, $size['width'], $size['height']);
+            } else {
+                $r = serendipity_resize_image_gd($infile, $outfile, $size);
+            }
         } else {
-            $r = array($size, $size);
-            $newSize = $size . 'x' . $size;
+            if (is_array($size)) {
+                $r = $size;
+            } else {
+                $r = array('width' => $size, 'height' => $size);
+            }
+            $newSize = $r['width'] . 'x' . $r['height'];
             if ($fdim['mime'] == 'application/pdf') {
                 $cmd     = escapeshellcmd($serendipity['convert']) . ' -antialias -flatten -scale '. serendipity_escapeshellarg($newSize) .' '. serendipity_escapeshellarg($infile) .' '. serendipity_escapeshellarg($outfile . '.png');
             } else {
-                if ( serendipity_ini_bool(ini_get('safe_mode')) === false ) {
+                if (!$force_resize && serendipity_ini_bool(ini_get('safe_mode')) === false) {
                     $newSize .= '>'; // Tell imagemagick to not enlarge small images, only works if safe_mode is off (safe_mode turns > in to \>)
                 }
                 $cmd     = escapeshellcmd($serendipity['convert']) . ' -antialias -resize '. serendipity_escapeshellarg($newSize) .' '. serendipity_escapeshellarg($infile) .' '. serendipity_escapeshellarg($outfile);
             }
             exec($cmd, $output, $result);
-            if ( $result != 0 ) {
+            if ($result != 0) {
                 echo '<div class="serendipityAdminMsgError">'. sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) .'</div>';
                 $r = false; // return failure
             } else {
@@ -459,6 +670,9 @@ function serendipity_scaleImg($id, $width, $height) {
     global $serendipity;
 
     $file = serendipity_fetchImageFromDatabase($id);
+    if (!is_array($file)) {
+        return false;
+    }
 
     $admin = '';
     if (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid']) {
@@ -496,6 +710,9 @@ function serendipity_rotateImg($id, $degrees) {
     global $serendipity;
 
     $file = serendipity_fetchImageFromDatabase($id);
+    if (!is_array($file)) {
+        return false;
+    }
 
     $admin = '';
     if (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid']) {
@@ -847,10 +1064,18 @@ function serendipity_syncThumbs() {
         $ft_mime = serendipity_guessMime($f[1]);
         $fdim    = serendipity_getimagesize($ffull, $ft_mime);
 
-        $rs = serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}images
-                                            WHERE name = '" . serendipity_db_escape_string($fbase) . "'
-                                              " . ($fdir != '' ? "AND path = '" . serendipity_db_escape_string($fdir) . "'" : '') . "
-                                              AND mime = '" . serendipity_db_escape_string($fdim['mime']) . "'", true, 'assoc');
+        $cond = array(
+            'and' => "WHERE name = '" . serendipity_db_escape_string($fbase) . "'
+                            " . ($fdir != '' ? "AND path = '" . serendipity_db_escape_string($fdir) . "'" : '') . "
+                            AND mime = '" . serendipity_db_escape_string($fdim['mime']) . "'"
+        );
+        serendipity_ACL_SQL($cond, false, 'directory');
+
+        $rs = serendipity_db_query("SELECT *
+                                      FROM {$serendipity['dbPrefix']}images AS i
+                                           {$cond['joins']}
+
+                                           {$cond['and']}", true, 'assoc');
         if (is_array($rs)) {
             $update    = array();
             $checkfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $rs['path'] . $rs['name'] . '.' . $rs['thumbnail_name'] . '.' . $rs['extension'];
@@ -999,6 +1224,12 @@ function serendipity_resize_image_gd($infilename, $outfilename, $newwidth, $newh
         $newheight = $newsizes[1];
     }
 
+    if (is_null($newwidth)) {
+        $newsizes  = serendipity_calculate_aspect_size($width, $height, null, $newheight);
+        $newwidth  = $newsizes[0];
+        $newheight = $newsizes[1];
+    }
+
     $out = imagecreatetruecolor($newwidth, $newheight);
 
     /* Attempt to copy transparency information, this really only works for PNG */
@@ -1008,7 +1239,9 @@ function serendipity_resize_image_gd($infilename, $outfilename, $newwidth, $newh
     }
 
     imagecopyresampled($out, $in, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);
+    @umask(0000);
     $func['save']($out, $outfilename, $func['qual']);
+    @chmod($outfilename, 0664);
     $out = null;
     $in  = null;
 
@@ -1024,27 +1257,45 @@ function serendipity_resize_image_gd($infilename, $outfilename, $newwidth, $newh
  * @param   int     Target width
  * @return  int     Target height
  */
-function serendipity_calculate_aspect_size($width, $height, $newwidth) {
+function serendipity_calculate_aspect_size($width, $height, $orig_newwidth, $orig_newheight = null) {
 
     // calculate aspect ratio
-    $div_width  = $width  / $newwidth;
-    $div_height = $height / $newwidth;
+    if (!is_null($orig_newheight)) {
+        $div_width  = $width  / $orig_newheight;
+        $div_height = $height / $orig_newheight;
+    } else {
+        $div_width  = $width  / $orig_newwidth;
+        $div_height = $height / $orig_newwidth;
+    }
 
     if ($div_width <= 1 && $div_height <= 1) {
         // do not scale small images where both sides are smaller than the thumbnail dimensions
         $newheight = $height;
         $newwidth  = $width;
-    } elseif ($div_width >= $div_height) {
+    } elseif (is_null($orig_newheight) &&  $div_width >= $div_height) {
         // max width - calculate height, keep width as scaling base
         $newheight = round($height / $div_width);
         // make sure the height is at least 1 pixel for extreme images
         $newheight = ($newheight >= 1 ? $newheight : 1);
-    } else {
+        $newwidth = $orig_newwidth;
+    } elseif (is_null($orig_newwidth) && $div_width >= $div_height) {
+        // max width - calculate height, keep width as scaling base
+        $newwidth = round($width / $div_height);
+        // make sure the height is at least 1 pixel for extreme images
+        $newwidth = ($newwidth >= 1 ? $newwidth : 1);
+        $newheight = $orig_newheight;
+    } elseif (is_null($orig_newheight)) {
         // max height - calculate width, keep height as scaling base
-        $newheight = $newwidth;
+        $newheight = $orig_newwidth;
         $newwidth  = round($width / $div_height);
         // make sure the width is at least 1 pixel for extreme images
         $newwidth  = ($newwidth >= 1 ? $newwidth : 1);
+    } else {
+        // max height - calculate width, keep height as scaling base
+        $newwidth = $orig_newheight;
+        $newheight  = round($height / $div_width);
+        // make sure the width is at least 1 pixel for extreme images
+        $newheight = ($newheight >= 1 ? $newheight : 1);
     }
 
     return array($newwidth, $newheight);
@@ -1060,13 +1311,15 @@ function serendipity_calculate_aspect_size($width, $height, $newwidth) {
  * @param   string  The URL to use for pagination
  * @param   boolean Show the "upload media item" feature?
  * @param   boolean Restrict viewing images to a specific directory
- * @return null
+ * @param   boolean  If TRUE, will echo Smarty output.
+ * @return  string   Smarty block name
  */
-function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = false, $url = NULL, $show_upload = false, $limit_path = NULL) {
+function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = false, $url = NULL, $show_upload = false, $limit_path = NULL, $smarty_display = true) {
     global $serendipity;
-    $sort_row_interval = array(8, 16, 50, 100);
+    static $debug = false;
+
     $sortParams        = array('perpage', 'order', 'ordermode');
-    $importParams      = array('adminModule', 'htmltarget', 'filename_only', 'textarea', 'subpage');
+    $importParams      = array('adminModule', 'htmltarget', 'filename_only', 'textarea', 'subpage',  'keywords');
     $extraParems       = '';
     $filterParams      = array('only_path', 'only_filename');
 
@@ -1093,9 +1346,135 @@ function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = fa
     $serendipity['GET']['only_path']     = serendipity_uploadSecure($limit_path . $serendipity['GET']['only_path'], true);
     $serendipity['GET']['only_filename'] = str_replace(array('*', '?'), array('%', '_'), $serendipity['GET']['only_filename']);
 
-    $perPage = (!empty($serendipity['GET']['sortorder']['perpage']) ? $serendipity['GET']['sortorder']['perpage'] : $sort_row_interval[0]);
+    $perPage = (!empty($serendipity['GET']['sortorder']['perpage']) ? $serendipity['GET']['sortorder']['perpage'] : 8);
+    while ($perPage % $lineBreak !== 0) {
+        $perPage++;
+    }
     $start   = ($page-1) * $perPage;
 
+       ## SYNCH START ##
+       $aExclude = array("CVS" => true, ".svn" => true);
+       serendipity_plugin_api::hook_event('backend_media_path_exclude_directories', $aExclude);
+       $paths        = array();
+       $aFilesOnDisk = array();
+
+       $aResultSet   = serendipity_traversePath(
+           $serendipity['serendipityPath'] . $serendipity['uploadPath']. $limit_path,
+           '',
+           false,
+           NULL,
+           1,
+           NULL,
+           FALSE,
+           $aExclude
+       );
+
+       foreach ($aResultSet AS $sKey => $sFile) {
+               if ($sFile['directory']) {
+                   if ($debug) echo "{$sFile['relpath']} is a directory.<br />";
+                       array_push($paths, $sFile);
+               } else {
+                   if ($debug) echo "{$sFile['relpath']} is a file.<br />";
+                   // Store the file in our array, remove any ending slashes
+                       $aFilesOnDisk[$sFile['relpath']] = 1;
+               }
+               unset($aResultSet[$sKey]);
+       }
+
+    usort($paths, 'serendipity_sortPath');
+
+       if ($debug) echo "<p>Got files: <pre>" . print_r($aFilesOnDisk, true) . "</pre></p>";
+       $serendipity['current_image_hash'] = md5(serialize($aFilesOnDisk));
+
+       $nTimeStart = microtime_float();
+       // MTG 21/01/06: request all images from the database, delete any which don't exist
+       // on the filesystem, and mark off files from the file list which are already
+       // in the database
+
+       $nCount = 0;
+       if ($serendipity['onTheFlySynch'] && serendipity_checkPermission('adminImagesSync') && $serendipity['current_image_hash'] != $serendipity['last_image_hash']) {
+               $aResultSet = serendipity_db_query("SELECT path, name, extension, thumbnail_name, id
+                                                     FROM {$serendipity['dbPrefix']}images", false, 'assoc');
+       if ($debug) echo "<p>Got images: <pre>" . print_r($aResultSet, true) . "</pre></p>";
+               if (is_array($aResultSet)) {
+                       foreach ($aResultSet AS $sKey => $sFile) {
+                               serendipity_plugin_api::hook_event('backend_thumbnail_filename_select', $sFile);
+                               $sThumbNailFile = '';
+                               if (isset($sFile['thumbnail_filename'])) {
+                                       $sThumbNailFile = $sFile['thumbnail_filename'];
+                               } else {
+                                       $sThumbNailFile = $sFile['path'] . $sFile['name'] . '.' . $sFile['thumbnail_name'] . '.' . $sFile['extension'];
+                               }
+
+                               $sFileName = $sFile['path'] . $sFile['name'] . '.' . $sFile['extension'];
+                               if ($debug) echo "<p>File name is $sFileName,<br />thumbnail is $sThumbNailFile</p>";
+                               unset($aResultSet[$sKey]);
+
+                               if (isset($aFilesOnDisk[$sFileName])){
+                                       unset($aFilesOnDisk[$sFileName]);
+                               } else {
+                    if ($debug) "Deleting Image {$sFile['id']}<br />\n";
+                    serendipity_deleteImage($sFile['id']);
+                                       ++$nCount;
+                               }
+
+                               unset($aFilesOnDisk[$sThumbNailFile]);
+                       }
+               }
+
+               if ($nCount > 0){
+                       if ($debug) echo "<p>Cleaned up ".$nCount." database entries</p>";
+               }
+
+               serendipity_set_config_var('last_image_hash', $serendipity['current_image_hash'], 0);
+               $aUnmatchedOnDisk = array_keys($aFilesOnDisk);
+       if ($debug) echo "<p>Got unmatched files: <pre>" . print_r($aUnmatchedOnDisk, true) . "</pre></p>";
+               $nCount = 0;
+               foreach ($aUnmatchedOnDisk AS $sFile) {
+                   if (preg_match('@\.' . $serendipity['thumbSuffix'] . '\.@', $sFile)) {
+                       if ($debug) echo "<p>Skipping thumbnailed file $sFile</p>";
+                       continue;
+                   } else {
+                       if ($debug) echo "<p>Checking $sFile</p>";
+                   }
+
+                       // MTG: 21/01/06: put files which have just 'turned up' into the database
+                       $aImageData = serendipity_getImageData($sFile);
+                       if (serendipity_isImage($aImageData)) {
+                               $nPos = strrpos($sFile, "/");
+                               if (is_bool($nPos) && !$nPos) {
+                                       $sFileName  = $sFile;
+                                       $sDirectory = "";
+                               } else {
+                                       ++$nPos;
+                                       $sFileName  = substr($sFile, $nPos);
+                                       $sDirectory = substr($sFile, 0, $nPos);
+                               }
+                               if ($debug) echo "<p>Inserting image $sFileName from $sDirectory <pre>" . print_r($aImageData, true) . "</pre> into database</p>";
+                               # TODO: Check if the thumbnail generation goes fine with Marty's code
+                serendipity_makeThumbnail($sFileName, $sDirectory);
+                               serendipity_insertImageInDatabase($sFileName, $sDirectory);
+                               ++$nCount;
+                       }
+               }
+
+               if ($nCount > 0) {
+                       if ($debug) echo "<p>Inserted ".$nCount." images into the database</p>";
+               }
+       } else {
+               if ($debug) echo "<p>Media Gallery database is up to date</p>";
+       }
+
+       /*
+       $nTimeEnd = microtime_float ( );
+       $nDifference = $nTimeEnd - $nTimeStart;
+       echo "<p> total time taken was " . $nDifference . "</p>";
+       */
+       ## SYNCH FINISHED ##
+
+    ## Aply ACL afterwards:
+    serendipity_directoryACL($paths, 'read');
+
     $serendipity['imageList'] = serendipity_fetchImagesFromDatabase(
                                   $start,
                                   $perPage,
@@ -1103,202 +1482,69 @@ function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = fa
                                   (isset($serendipity['GET']['sortorder']['order']) ? $serendipity['GET']['sortorder']['order'] : false),
                                   (isset($serendipity['GET']['sortorder']['ordermode']) ? $serendipity['GET']['sortorder']['ordermode'] : false),
                                   (isset($serendipity['GET']['only_path']) ? $serendipity['GET']['only_path'] : ''),
-                                  (isset($serendipity['GET']['only_filename']) ? $serendipity['GET']['only_filename'] : '')
+                                  (isset($serendipity['GET']['only_filename']) ? $serendipity['GET']['only_filename'] : ''),
+                                  (isset($serendipity['GET']['keywords']) ? $serendipity['GET']['keywords'] : ''),
+                                  (isset($serendipity['GET']['filter']) ? $serendipity['GET']['filter'] : '')
     );
 
     $pages         = ceil($totalImages / $perPage);
     $linkPrevious  = '?' . $extraParems . 'serendipity[page]=' . ($page-1);
     $linkNext      = '?' . $extraParems . 'serendipity[page]=' . ($page+1);
-    $sort_order    = serendipity_getImageFields();
-    $paths         = serendipity_traversePath($serendipity['serendipityPath'] . $serendipity['uploadPath']. $limit_path);
-
     if (is_null($lineBreak)) {
         $lineBreak = floor(750 / ($serendipity['thumbSize'] + 20));
     }
-?>
-<form style="display: inline; margin: 0px; padding: 0px;" method="get" action="?">
-<?php
-    echo serendipity_setFormToken();
-    foreach($serendipity['GET'] AS $g_key => $g_val) {
-        if ( !is_array($g_val) && $g_key != 'page' ) {
-            echo '<input type="hidden" name="serendipity[' . $g_key . ']" value="' . htmlspecialchars($g_val) . '" />';
-        }
-    }
-?>
-    <table class="serendipity_admin_filters" width="100%">
-        <tr>
-            <td class="serendipity_admin_filters_headline" colspan="6"><strong><?php echo FILTERS ?></strong> - <?php echo FIND_MEDIA ?></td>
-        </tr>
-        <tr>
-            <td><?php echo FILTER_DIRECTORY ?></td>
-            <td><select name="serendipity[only_path]">
-                    <option value=""> <?php if (!$limit_path) { echo ALL_DIRECTORIES; } else { echo basename($limit_path);}?></option>
-                    <?php foreach ( $paths as $folder ) { ?>
-                    <option <?php echo ($serendipity['GET']['only_path'] == $limit_path.$folder['relpath']) ? 'selected="selected"' : '' ?> value="<?php echo $folder['relpath'] ?>"><?php echo str_repeat('&nbsp;', $folder['depth']*2) . ' '. $folder['name'] ?></option>
-                    <?php } ?>
-                </select>
-            </td>
-            <td><?php echo SORT_ORDER_NAME ?></td>
-            <td colspan="3"><input type="text" name="serendipity[only_filename]" value="<?php echo htmlspecialchars($serendipity['GET']['only_filename']); ?>" /></td>
-        </tr>
-        <tr>
-            <td class="serendipity_admin_filters_headline" colspan="6"><strong><?php echo SORT_ORDER ?></strong></td>
-        </tr>
-        <tr>
-            <td><?php echo SORT_BY ?></td>
-            <td><select name="serendipity[sortorder][order]">
-<?php
-        foreach($sort_order AS $so_key => $so_val) {
-            echo '<option value="' . $so_key . '" ' . (isset($serendipity['GET']['sortorder']['order']) && $serendipity['GET']['sortorder']['order'] == $so_key ? 'selected="selected"' : '') . '>' . $so_val . '</option>';
-        }
-?>              </select>
-</td>
-            <td><?php echo SORT_ORDER ?></td>
-            <td><select name="serendipity[sortorder][ordermode]">
-                    <option value="DESC" <?php echo (isset($serendipity['GET']['sortorder']['ordermode']) && $serendipity['GET']['sortorder']['ordermode'] == 'DESC' ? 'selected="selected"' : '') ?>><?php echo SORT_ORDER_DESC ?></option>
-                    <option value="ASC"  <?php echo (isset($serendipity['GET']['sortorder']['ordermode']) && $serendipity['GET']['sortorder']['ordermode'] == 'ASC'  ? 'selected="selected"' : '') ?>><?php echo SORT_ORDER_ASC  ?></option>
-                </select>
-            </td>
-            <td><?php echo FILES_PER_PAGE ?></td>
-            <td><select name="serendipity[sortorder][perpage]">
-<?php
-        foreach($sort_row_interval AS $so_val) {
-            echo '<option value="' . $so_val . '" ' . ($perPage == $so_val ? 'selected="selected"' : '') . '>' . $so_val . '</option>';
-        }
-?>              </select>
-            </td>
-        </tr>
-        <tr>
-            <td align="right" colspan="6">
-<?php
-        if ($show_upload) {
-?>
-                <input type="button" value="<?php echo htmlspecialchars(ADD_MEDIA); ?>" onclick="location.href='<?php echo $url; ?>&amp;serendipity[adminAction]=addSelect'; return false" class="serendipityPrettyButton" />
-<?php
-        }
-?>
-                <input type="submit" name="go" value=" - <?php echo GO ?> - " class="serendipityPrettyButton" />
-            </td>
-        </tr>
-</table>
-</form>
-<?php if ( sizeof($serendipity['imageList']) == 0 ) { ?>
-    <div align="center">- <?php echo NO_IMAGES_FOUND ?> -</div>
-<?php } else { ?>
-<table border="0" width="100%">
-    <tr>
-        <td colspan="<?php echo floor($lineBreak); ?>">
-            <table width="100%">
-                <tr>
-                    <td>
-                    <?php if ( $page != 1 && $page <= $pages ) { ?>
-                        <a href="<?php echo $linkPrevious ?>" class="serendipityIconLink"><img src="<?php echo serendipity_getTemplateFile('admin/img/previous.png') ?>" /><?php echo PREVIOUS ?></a>
-                    <?php } ?></td>
-                    <td align="right">
-                    <?php if ($page != $pages ) { ?>
-                        <a href="<?php echo $linkNext ?>" class="serendipityIconLinkRight"><?php echo NEXT ?><img src="<?php echo serendipity_getTemplateFile('admin/img/next.png') ?>" /></a>
-                    <?php } ?></td>
-                </tr>
-            </table>
-        </td>
-    </tr>
-    <tr>
-<?php
-        $x = 0;
+
+    $dprops = $keywords = array();
+    if ($serendipity['parseMediaOverview']) {
+        $ids = array();
         foreach ($serendipity['imageList'] as $k => $file) {
-            ++$x; $preview = '';
-            $img = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . (!empty($file['thumbnail_name']) ? '.' . $file['thumbnail_name'] : '') . '.' . $file['extension'];
-            $i = @getimagesize($img);
-            $file['imgsrc'] = $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . (!empty($file['thumbnail_name']) ? '.' . $file['thumbnail_name'] : '') . '.' . $file['extension'];
-            $is_image = serendipity_isImage($file);
+            $ids[] = $file['id'];
+        }
+        $allprops =& serendipity_fetchMediaProperties($ids);
+    }
 
+    if (count($serendipity['imageList']) > 0) {
+        foreach ($serendipity['imageList'] as $k => $file) {
             if (!($serendipity['authorid'] == $file['authorid'] || $file['authorid'] == '0' || serendipity_checkPermission('adminImagesViewOthers'))) {
                 // This is a fail-safe continue. Basically a non-matching file should already be filtered in SQL.
                 continue;
             }
 
-            /* If it is an image, and the thumbnail exists */
-            if ($is_image && file_exists($img)) {
-                $preview .= '<img src="' . $serendipity['serendipityHTTPPath'] . $file['imgsrc'] . '" border="0" title="' . $file['path'] . $file['name'] . '" alt="'. $file['name'] . '" />';
-                if ($url) {
-                    $preview = '<a href="'. $url .'&amp;serendipity[image]='. $file['id'] .'">'. $preview .'</a>';
-                }
-            } elseif ($is_image && $file['hotlink']) {
-                $sizes = serendipity_calculate_aspect_size($file['dimensions_width'], $file['dimensions_height'], $serendipity['thumbSize']);
-                $preview .= '<img src="' . $file['path'] . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" border="0" title="' . $file['path'] . '" alt="'. $file['name'] . '" />';
-                if ($url) {
-                    $preview = '<a href="'. $url .'&amp;serendipity[image]='. $file['id'] .'">'. $preview .'</a>';
-                }
-            /* If it's not an image, or the thumbnail does not exist */
-            } else {
-                $preview .= '<img src="'. serendipity_getTemplateFile('admin/img/mime_unknown.png') .'" title="' . $file['path'] . $file['name'] . ' (' . $file['mime'] . ')" alt="'. $file['mime'] .'" /><br /><span style="font-weight: bold; font-size: 8pt">- ' . (($file['hotlink']) ? MEDIA_HOTLINKED : $file['mime']) .' -</span>';
-                if ($url) {
-                    $preview .= '<br /><a href="' . $url . '&amp;serendipity[image]=' . $file['id'] . '">' . $file['name'] . '.' . $file['extension'] . '</a>';
-                }
-                $preview .= '</div>';
-            }
+            serendipity_prepareMedia($serendipity['imageList'][$k], $url);
 
-?>
-                <td nowrap="nowrap" align="center" valign="<?php echo $manage ? 'top' : 'middle' ?>" width="<?php echo round(1/$lineBreak*100) ?>%" class="serendipity_admin_list_item serendipity_admin_list_item_<?php echo (($i % 2) ? 'even' : 'uneven') ?>">
-<?php
-if ( !$manage ) {
-    echo $preview;
-} else { ?>
-                    <table width="100%" border="0" cellspacing="0" cellpadding="3">
-                        <tr>
-                            <td valign="top" width="16" rowspan="3">
-<?php
-                if ($serendipity['authorid'] == $file['authorid'] || $file['authorid'] == '0' || serendipity_checkPermission('adminImagesMaintainOthers')) {
-                    $popupWidth = ($is_image ? ($file['dimensions_width'] + 20) : 600);
-                    $popupHeight = ($is_image ? ($file['dimensions_height'] + 20) : 500);
-?>
-                            <img class="serendipityImageButton" title="<?php echo MEDIA_FULLSIZE; ?>" alt="<?php echo MEDIA_FULLSIZE; ?>" src="<?php echo serendipity_getTemplateFile('admin/img/big_zoom.png') ?>"   border="0" onclick="F1 = window.open('<?php echo ($file['hotlink'] ? $file['path'] : $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . '.'. $file['extension']); ?>','Zoom','height=<?php echo $popupHeight; ?>,width=<?php echo $popupWidth; ?>,top='+ (screen.height-<?php echo $popupHeight ?>)/2 +',left='+ (screen.width-<?php echo $popupWidth ?>)/2 +',toolbar=no,menubar=no,location=no,resize=1,resizable=1<?php echo ($is_image ? '' : ',scrollbars=yes'); ?>');" /><br />
-                            <img class="serendipityImageButton" title="<?php echo MEDIA_RENAME; ?>" alt="<?php echo MEDIA_RENAME; ?>" src="<?php echo serendipity_getTemplateFile('admin/img/big_rename.png') ?>" border="0" onclick="rename('<?php echo $file['id']; ?>', '<?php echo addslashes($file['name']); ?>')" /><br />
-                            <?php if ($is_image && !$file['hotlink']) { ?><img class="serendipityImageButton" title="<?php echo IMAGE_RESIZE; ?>"   alt="<?php echo IMAGE_RESIZE; ?>"   src="<?php echo serendipity_getTemplateFile('admin/img/big_resize.png') ?>" border="0" onclick="location.href='?serendipity[adminModule]=images&amp;serendipity[adminAction]=scaleSelect&amp;serendipity[fid]=<?php echo $file['id']; ?>';" /><br /><?php } ?>
-                            <?php if ($is_image && !$file['hotlink']) { ?><a href="?serendipity[adminModule]=images&amp;serendipity[adminAction]=rotateCCW&amp;serendipity[fid]=<?php echo $file['id']; ?>"><img class="serendipityImageButton" title="<?php echo IMAGE_ROTATE_LEFT; ?>" alt="<?php echo IMAGE_ROTATE_LEFT; ?>" src="<?php echo serendipity_getTemplateFile('admin/img/big_rotate_ccw.png') ?>" border="0" /><br /><?php } ?>
-                            <?php if ($is_image && !$file['hotlink']) { ?><a href="?serendipity[adminModule]=images&amp;serendipity[adminAction]=rotateCW&amp;serendipity[fid]=<?php echo $file['id']; ?>"><img class="serendipityImageButton" title="<?php echo IMAGE_ROTATE_RIGHT; ?>" alt="<?php echo IMAGE_ROTATE_RIGHT; ?>" src="<?php echo serendipity_getTemplateFile('admin/img/big_rotate_cw.png') ?>" border="0" /><br /><?php } ?>
-                            <a href="?serendipity[adminModule]=images&amp;serendipity[adminAction]=delete&amp;serendipity[fid]=<?php echo $file['id']; ?>"><img class="serendipityImageButton" title="<?php echo MEDIA_DELETE; ?>"   alt="<?php echo MEDIA_DELETE; ?>"   src="<?php echo serendipity_getTemplateFile('admin/img/big_delete.png') ?>" border="0" /><br />
-<?php
-                }
-?>
-                            </td>
-                            <td height="10" align="left" style="font-weight: bold; font-size: 8pt"><?php echo $file['name'] . '.' . $file['extension']; ?></td>
-                            <td height="10" align="right" style="font-size: 8pt"><?php echo ($file['authorid'] == '0' ? ALL_AUTHORS : $file['authorname']); ?></td>
-                        </tr>
-                        <tr>
-                            <td align="center" colspan="2"><?php echo $preview ?></td>
-                        </tr>
-                        <tr>
-                            <td colspan="2" height="10" align="center" style="font-size: 8pt">
-<?php
-                if ($is_image && !$file['hotlink']) {
-                    echo ORIGINAL_SHORT . ': ' . $file['dimensions_width'] . 'x' . $file['dimensions_height'] .', ';
-                    echo THUMBNAIL_SHORT . ': ' . $i[0] . 'x' . $i[1];
-                } elseif ($file['hotlink']) {
-                    echo wordwrap($file['path'], 45, '<br />', 1);
+            if ($serendipity['parseMediaOverview']) {
+                $serendipity['imageList'][$k]['props'] =& $allprops[$file['id']];
+                if (!is_array($serendipity['imageList'][$k]['props']['base_metadata'])) {
+                    $serendipity['imageList'][$k]['metadata'] =& serendipity_getMetaData($serendipity['imageList'][$k]['realfile'], $serendipity['imageList'][$k]['header']);
                 } else {
-                    echo SORT_ORDER_SIZE . ': ' . number_format(round($file['size']/1024, 2), NUMBER_FORMAT_DECIMALS, NUMBER_FORMAT_DECPOINT, NUMBER_FORMAT_THOUSANDS) . 'kb';
+                    $serendipity['imageList'][$k]['metadata'] = $serendipity['imageList'][$k]['props']['base_metadata'];
+                    serendipity_plugin_api::hook_event('media_getproperties_cached', $serendipity['imageList'][$k]['metadata'], $serendipity['imageList'][$k]['realfile']);
                 }
-?>
-                            </td>
-                        </tr>
-                    </table>
-<?php } ?>
-        </td>
-<?php
-        // Newline?
-        if ($x % $lineBreak == 0) {
-?>
-    </tr>
-    <tr>
-<?php
-        }
-    }
-?>
-    </tr>
-</table>
-<?php
-}
+                serendipity_parseMediaProperties($dprops, $keywords, $serendipity['imageList'][$k], $serendipity['imageList'][$k]['props'], 3, false);
+            }
+        }
+    }
+
+    $smarty_vars = array(
+        'limit_path'    => $limit_path,
+        'perPage'       => $perPage,
+        'show_upload'   => $show_upload,
+        'page'          => $page,
+        'pages'         => $pages,
+        'linkNext'      => $linkNext,
+        'linkPrevious'  => $linkPrevious,
+        'extraParems'   => $extraParems
+    );
+    return serendipity_showMedia(
+        $serendipity['imageList'],
+        $paths,
+        $url,
+        $manage,
+        $lineBreak,
+        true,
+        $smarty_vars,
+        $smarty_display
+    );
 } // End serendipity_displayImageList()
 
 /**
@@ -1410,6 +1656,7 @@ function serendipity_killPath($basedir, $directory = '', $forceDelete = false) {
 /**
  * Recursively walk a directory tree
  *
+ *
  * @access public
  * @param   string      The core directory
  * @param   string      The subdirectory
@@ -1417,38 +1664,70 @@ function serendipity_killPath($basedir, $directory = '', $forceDelete = false) {
  * @param   string      A regexp patter to include files
  * @param   int         Level of nesting (recursive use)
  * @param   int         The maximum level of nesting (recursive use)
+ * @param   mixed       Toggle whether to apply serendipity_directoryACL (false / 'read' / 'write')
+ * @param   array       An array of directories to skip [passed by plugins, for example]
  * @return  array       Array of files/directories
  */
-function serendipity_traversePath($basedir, $dir='', $onlyDirs=true, $pattern = NULL, $depth = 1, $max_depth = null) {
+function serendipity_traversePath($basedir, $dir='', $onlyDirs = true, $pattern = NULL, $depth = 1, $max_depth = NULL, $apply_ACL = false, $aExcludeDirs = NULL) {
 
+    if ($aExcludeDirs === null) {
+        $aExcludeDirs = array("CVS" => true, ".svn" => true);
+    }
 
-    $dh = @opendir($basedir . '/' . $dir);
-    if ( !$dh ) {
+    $odir = serendipity_dirSlash('end', $basedir) . serendipity_dirSlash('end', $dir);
+    $dh = @opendir($odir);
+    if (!$dh) {
         return array();
     }
 
     $files = array();
     while (($file = @readdir($dh)) !== false) {
-        if ( $file != '.' && $file != '..' ) {
-            if ( $onlyDirs === false || ($onlyDirs === true && is_dir($basedir . '/' . $dir . '/' . $file)) ) {
-                if ( is_null($pattern) || preg_match($pattern, $file) ) {
+        if ($file != '.' && $file != '..') {
+            $bPatternMatch = (is_null($pattern) || preg_match($pattern, $file));
+                       $sFullPath     = $odir . $file;
+                       $bIsDir        = is_dir($sFullPath);
+            if ($onlyDirs === false || $bIsDir) {
+                               if ($bPatternMatch &&
+                                   (!$bIsDir || $aExcludeDirs == null || !isset($aExcludeDirs[$file]))) {
                     $files[] = array(
-                        'name'    => $file,
-                        'depth'   => $depth,
-                        'relpath' => ltrim(str_replace('\\', '/', $dir) . basename($file) . '/', '/')
+                        'name'      => $file,
+                        'depth'     => $depth,
+                        'relpath'   => ltrim(str_replace('\\', '/', serendipity_dirSlash('end', $dir)) . basename($file) . ($bIsDir ? '/' : ''), '/'),
+                        'directory' => $bIsDir
                     );
                 }
             }
-            if ( is_dir($basedir . '/' . $dir . '/' . $file) && ($max_depth === null || $depth < $max_depth)) {
-                $files = array_merge($files, serendipity_traversePath($basedir, $dir . '/' . basename($file) . '/', $onlyDirs, $pattern, ($depth+1), $max_depth));
+
+            if ($bIsDir &&
+                               ($max_depth === null || $depth < $max_depth) &&
+                               ($aExcludeDirs == null || !isset($aExcludeDirs[$file]))) {
+                $next_dir = serendipity_dirSlash('end', $dir) . basename($file);
+                               $files = array_merge($files, serendipity_traversePath($basedir, $next_dir, $onlyDirs, $pattern, ($depth+1), $max_depth, $apply_ACL, $aExcludeDirs));
             }
         }
     }
 
     @closedir($dh);
+
+    if ($depth == 1 && $apply_ACL !== FALSE) {
+        serendipity_directoryACL($files, $apply_ACL);
+    }
+
     return $files;
 }
 
+/**
+ * Custom usort() function that properly sorts a path
+ *
+ * @access public
+ * @param   array      First array
+ * @param   array      Second array
+ * @return
+ */
+function serendipity_sortPath($a, $b) {
+    return strcasecmp($a['relpath'], $b['relpath']);
+}
+
 /**
  * Delete a directory with all its files
  *
@@ -1551,15 +1830,53 @@ function serendipity_getimagesize($file, $ft_mime = '', $suf = '') {
  * @return array    Array with available, sortable fields
  */
 function serendipity_getImageFields() {
-    return array(
-        'date'              => SORT_ORDER_DATE,
-        'name'              => SORT_ORDER_NAME,
-        'authorid'          => AUTHOR,
-        'extension'         => SORT_ORDER_EXTENSION,
-        'size'              => SORT_ORDER_SIZE,
-        'dimensions_width'  => SORT_ORDER_WIDTH,
-        'dimensions_height' => SORT_ORDER_HEIGHT
+    global $serendipity;
+
+    $x = array(
+        'i.date'              => array('desc' => SORT_ORDER_DATE,
+                                     'type' => 'date'
+                               ),
+
+        'i.name'              => array('desc' => SORT_ORDER_NAME
+                               ),
+
+        'i.authorid'          => array('desc' => AUTHOR,
+                                     'type' => 'authors'
+                               ),
+
+        'i.extension'         => array('desc' => SORT_ORDER_EXTENSION
+                               ),
+
+        'i.size'              => array('desc' => SORT_ORDER_SIZE,
+                                     'type' => 'intrange'
+                               ),
+
+        'i.dimensions_width'  => array('desc' => SORT_ORDER_WIDTH,
+                                     'type' => 'intrange'
+                               ),
+
+        'i.dimensions_height' => array('desc' => SORT_ORDER_HEIGHT,
+                                     'type' => 'intrange'
+                               )
     );
+
+    $addProp = explode(';', $serendipity['mediaProperties']);
+    foreach($addProp AS $prop) {
+        $parts = explode(':', $prop);
+        $name  = $parts[0];
+        $x['bp.' . $name] = array('desc' => (defined('MEDIA_PROPERTY_' . $name) ? constant('MEDIA_PROPERTY_' . $name) : htmlspecialchars($name)));
+        if (preg_match('@date@i', $name)) {
+            $x['bp.' . $name]['type'] = 'date';
+        }
+        if (preg_match('@length@i', $name)) {
+            $x['bp.' . $name]['type'] = 'intrange';
+        }
+        if (preg_match('@dpi@i', $name)) {
+            $x['bp.' . $name]['type'] = 'int';
+        }
+    }
+
+    return $x;
 }
 
 /**
@@ -1572,3 +1889,1397 @@ function serendipity_getImageFields() {
 function serendipity_escapeshellarg($string) {
     return escapeshellarg(str_replace('%', '', $string));
 }
+
+/**
+ * Rename a media directory
+ *
+ * @access public
+ * @param   string  Old directory name
+ * @param   string  New directory name
+ */
+function serendipity_renameDir($old, $new) {
+}
+
+/**
+ * Makes sure a directory begins with or ends with a "/"
+ *
+ * @access public
+ * @param   string  Type of where to append/prepend slash ('end', 'start', 'both')
+ * @param   string  Directory name
+ * @return  string  Output argument
+ */
+function serendipity_dirSlash($type, $dir) {
+
+    if ($dir == '') {
+        return $dir;
+    }
+
+    if ($type == 'start' || $type == 'both') {
+        if (substr($dir, 0, 1) != '/') {
+            $dir = '/' . $dir;
+        }
+    }
+
+    if ($type == 'end' || $type == 'both') {
+        if (substr($dir, -1) != '/') {
+            $dir .= '/';
+        }
+    }
+
+    return $dir;
+}
+
+/**
+ * Cycle a serendipity_traversePath resultset and apply read/write ACLs.
+ *
+ * @access public
+ * @param   array   serendipity_traversePath result array
+ * @param   string  ACL type ('read', 'write')
+ */
+function serendipity_directoryACL(&$paths, $type = 'read') {
+    global $serendipity;
+    static $debug = false;
+
+    if ($debug) {
+        echo "Applying ACL for mode '$type'.<br />\n";
+    }
+
+    if (!is_array($paths)) {
+        return true;
+    }
+
+    $startCount = count($paths);
+    if (serendipity_userLoggedIn() && (!isset($serendipity['enableACL']) || $serendipity['enableACL'] == true)) {
+        // Check if we are a cool superuser. Bail out if we are.
+        if (serendipity_checkPermission('adminImagesMaintainOthers') && serendipity_checkPermission('adminImagesDirectories')) {
+            if (!$debug) {
+                return true;
+            }
+        }
+
+        // Get list of all ACLs for directories.
+        $q = "SELECT a.artifact_index AS directory,
+                     a.groupid
+                FROM {$serendipity['dbPrefix']}access AS a
+               WHERE a.artifact_type = 'directory'
+                 AND a.artifact_mode = '" . serendipity_db_escape_string($type) . "'";
+        $allowed = serendipity_db_query($q);
+        if (!is_array($allowed)) {
+            return true;
+        }
+
+        // Get a list of all the groups for this user. Pipe it into a usable array.
+        $my_groups =& serendipity_getGroups($serendipity['authorid']);
+        $acl_allowed_groups = array();
+        foreach($my_groups AS $my_group) {
+            $acl_allowed_groups[$my_group['id']] = true;
+        }
+
+        // Iterate every ACL and check if we are allowed to use it.
+        $acl_allowed = array();
+        foreach($allowed AS $row) {
+            $acl_allowed[$row['directory']][$row['groupid']] = true;
+        }
+
+        // Iterate the input path array and check it against ACL.
+        foreach($paths AS $idx => $info) {
+            if (!isset($acl_allowed[$info['relpath']])) {
+                // ACL for directory not set. Assume we are allowed to access.
+                continue;
+            }
+
+            $granted = false;
+            foreach($acl_allowed[$info['relpath']] AS $groupid => $set) {
+                if ($groupid === 0 || isset($acl_allowed_groups[$groupid])) {
+                    // We are allowed to access this element
+                    $granted = true;
+                    break;
+                }
+            }
+
+            if ($granted === false) {
+                // We are not allowed to access this element
+                if ($debug) {
+                    echo "ACL for " . $info['relpath'] . " DENIED.<br />\n";
+                }
+                unset($paths[$idx]);
+            } else {
+                if ($debug) {
+                    echo "ACL for " . $info['relpath'] . " granted.<br />\n";
+                }
+            }
+        }
+
+        if (count($paths) < $startCount) {
+            if ($debug) {
+                echo "ACL denied all.<br />\n";
+            }
+            return false;
+        }
+    }
+
+    return true;
+}
+
+ /**
+ * Build the name of a thumbnail image file.
+ *
+ * @author MTG
+ * @param  string   Relative Path
+ * @param  string   File name
+ * @param  string   File extension
+ * @param  string   Thumbnail suffix
+ * @return array    Thumbnail path
+ *
+ */
+function serendipity_getThumbNailPath($sRelativePath, $sName, $sExtension, $sThumbName) {
+    $aTempArray = array('path'      => $sRelativePath,
+                        'name'      => $sName,
+                        'extension' => $sExtension);
+    serendipity_plugin_api::hook_event('backend_thumbnail_filename_select', $aTempArray);
+
+    if (isset($aTempArray['thumbnail_filename'])) {
+        $sThumbNailPath = $aTempArray['thumbnail_filename'];
+    } else {
+        $sThumbNailPath = $sRelativePath . $sName . (!empty($sThumbName) ? '.' . $sThumbName : '') . '.' . $sExtension;
+    }
+
+    return $sThumbNailPath;
+}
+
+ /**
+ * Given a relative path to an image, construct an array containing all
+ * relevant information about that image in the file structure.
+ *
+ * @author MTG
+ * @param  string   Relative Path
+ * @return array    Data about image
+ *
+ */
+function &serendipity_getImageData($sRelativePath) {
+    global $serendipity;
+
+    // First, peel off the file name from the path
+    $nPos = strrpos($sRelativePath, '/');
+    if (is_bool($nPos) && !$nPos) {
+        $sFileName  = $sRelativePath;
+        $sDirectory = '';
+    } else {
+        $nLastSlashPos = 1 + $nPos;
+        $sFileName     = substr($sRelativePath, $nLastSlashPos);
+        $sDirectory    = substr($sRelativePath, 0, $nLastSlashPos);
+    }
+
+    list($sName, $sExtension) = serendipity_parseFileName($sFileName);
+
+    $sImagePath = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $sRelativePath;
+
+    $aSizeData = @serendipity_getimagesize($sImagePath , '', $sExtension);
+    $nWidth    = $aSizeData[0];
+    $nHeight   = $aSizeData[1];
+    $sMime     = $aSizeData['mime'];
+    $nFileSize = @filesize($sImagePath);
+
+    $array = array(
+        'name'              => $sName,
+        'extension'         => $sExtension,
+        'mime'              => $sMime,
+        'size'              => $nFileSize,
+        'dimensions_width'  => $nWidth,
+        'dimensions_height' => $nHeight,
+        'path'              => $sDirectory,
+        'authorid'          => 0,
+        'hotlink'           => 0,
+        'id'                => $sRelativePath,
+        'realname'          => $sFilename
+    );
+
+    return $array;
+}
+
+/**
+ * Shows the HTML form to add/edit properties of uploaded media items
+ *
+ * @param  array    Associative array holding an array('image_id', 'target', 'created_thumbnail') that points to the uploaded media
+ * @param  int      How many keyword checkboxes to display next to each other?
+ * @param  boolean  Can existing data be modified?
+ * @return boolean
+ *
+ */
+function serendipity_showPropertyForm(&$new_media, $keywordsPerBlock = 3, $is_edit = true) {
+    global $serendipity;
+
+    if (!is_array($new_media) || count($new_media) < 1) {
+        return true;
+    }
+
+    $mirror = array();
+    serendipity_checkPropertyAccess($new_media, $mirror, 'read');
+
+    $editform_hidden = '';
+    if (isset($GLOBALS['image_selector_addvars']) && is_array($GLOBALS['image_selector_addvars'])) {
+        // These variables may come from serendipity_admin_image_selector.php to show embedded upload form
+        foreach($GLOBALS['image_selector_addvars'] AS $imgsel_key => $imgsel_val) {
+            $editform_hidden .= '          <input type="hidden" name="serendipity[' . htmlspecialchars($imgsel_key) . ']" value="' . htmlspecialchars($imgsel_val) . '" />' . "\n";
+        }
+    }
+
+    $dprops   = explode(';', $serendipity['mediaProperties']);
+    $keywords = explode(';', $serendipity['mediaKeywords']);
+
+    $now  = serendipity_serverOffsetHour();
+    $show = array();
+    foreach($new_media AS $idx => $media) {
+        $props =& serendipity_fetchMediaProperties($media['image_id']);
+
+        $show[$idx] =& $media['internal'];
+        $show[$idx]['image_id'] = $media['image_id'];
+
+        serendipity_prepareMedia($show[$idx]);
+        if (!is_array($props['base_metadata'])) {
+            $show[$idx]['metadata'] =& serendipity_getMetaData($show[$idx]['realfile'], $show[$idx]['header']);
+        } else {
+            $show[$idx]['metadata'] = $props['base_metadata'];
+            serendipity_plugin_api::hook_event('media_getproperties_cached', $show[$idx]['metadata'], $show[$idx]['realfile']);
+        }
+
+        serendipity_parseMediaProperties($dprops, $keywords, $show[$idx], $props, $keywordsPerBlock, $is_edit);
+    }
+
+    $smarty_vars = array(
+        'is_edit'           => $is_edit,
+        'editform_hidden'   => $editform_hidden,
+        'keywordsPerBlock'  => $keywordsPerBlock,
+        'keywords'          => $keywords,
+        'dprops'            => $dprops
+    );
+
+    return serendipity_showMedia(
+        $show,
+        $mirror,
+        $url,
+        false,
+        1,
+        false,
+        $smarty_vars);
+}
+
+/**
+ * Parse/Convert properties
+ *
+ * @param  array    Holds the property key array
+ * @param  array    Holds the keyword key array
+ * @param  int      Holds the media metadata
+ * @param  int      Holds the media properties
+ * @param  int      How many keyword checkboxes to display next to each other?
+ * @param  boolean  Can existing data be modified?
+ * @return boolean
+ *
+ */
+function serendipity_parseMediaProperties(&$dprops, &$keywords, &$media, &$props, $keywordsPerBlock, $is_edit) {
+    global $serendipity;
+
+    if (!is_array($dprops)) {
+        $dprops   = explode(';', $serendipity['mediaProperties']);
+    }
+    if (!is_array($keywords)) {
+        $keywords = explode(';', $serendipity['mediaKeywords']);
+    }
+
+    $media['references'] = serendipity_db_query("SELECT link, name
+                            FROM {$serendipity['dbPrefix']}references
+                           WHERE entry_id = " . $media['id'] . "
+                             AND type = 'media'
+                        ORDER BY name DESC
+                           LIMIT 15", false, 'assoc');
+    if (!is_array($media['references'])) {
+        $media['references'] = false;
+    }
+
+    foreach($dprops AS $prop) {
+        $type = 'input';
+        $parts = explode(':', trim($prop));
+
+        if (in_array('MULTI', $parts)) {
+            $type = 'textarea';
+        }
+
+        if (preg_match('@(AUDIO|VIDEO|DOCUMENT|IMAGE|ARCHIVE|BINARY)@i', $prop)) {
+            $show_item = false;
+            if ($media['mediatype'] == 'video' && in_array('VIDEO', $parts)) {
+                $show_item = true;
+            }
+
+            if ($media['mediatype'] == 'audio'  && in_array('AUDIO', $parts)) {
+                $show_item = true;
+            }
+
+            if ($media['mediatype'] == 'image'  && in_array('IMAGE', $parts)) {
+                $show_item = true;
+            }
+
+            if ($media['mediatype'] == 'document' && in_array('DOCUMENT', $parts)) {
+                $show_item = true;
+            }
+
+            if ($media['mediatype'] == 'archive' && in_array('ARCHIVE', $parts)) {
+                $show_item = true;
+            }
+
+            if ($media['mediatype'] == 'binary' && in_array('BINARY', $parts)) {
+                $show_item = true;
+            }
+
+            if (!$show_item) {
+                continue;
+            }
+        }
+
+        if (!$is_edit) {
+            $type = 'readonly';
+        }
+        $val =& serendipity_mediaTypeCast($parts[0], $props['base_property'][$parts[0]], true);
+
+        $propkey = htmlspecialchars($parts[0]) . $idx;
+
+        $media['base_property'][$propkey] = array(
+            'label' => htmlspecialchars(defined('MEDIA_PROPERTY_' . strtoupper($parts[0])) ? constant('MEDIA_PROPERTY_' . strtoupper($parts[0])) : $parts[0]),
+            'type'  => $type,
+            'val'   => $val,
+            'title' => htmlspecialchars($parts[0])
+        );
+
+        if (empty($val)) {
+            switch($parts[0]) {
+                case 'DATE':
+                    $media['base_property'][$propkey]['val'] = serendipity_strftime(DATE_FORMAT_SHORT, serendipity_pickKey($media['metadata'], 'DateCreated', $now));
+                    break;
+
+                case 'RUN_LENGTH':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'RunLength', '00:00:00.00');
+                    break;
+
+                case 'DPI':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'XResolution', 72);
+                    break;
+
+                case 'COPYRIGHT':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'Creator', $serendipity['serendipityUser']);
+                    break;
+
+                case 'TITLE':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'Title', $media['internal']['realname']);
+                    break;
+
+                case 'COMMENT1':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'Keywords', '');
+                    break;
+
+                case 'COMMENT2':
+                    $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'PhotoLocation', '');
+                    break;
+
+                default:
+                    serendipity_plugin_api::hook_event('media_showproperties', $media, $propkey);
+                    break;
+            }
+        }
+    }
+
+    if ($keywordsPerBlock > 0) {
+        $rows = ceil(count($keywords) / $keywordsPerBlock);
+        for($i = 0; $i < $rows; $i++) {
+            for ($j = 0; $j < $keywordsPerBlock; $j++) {
+                $kidx = ($i*$keywordsPerBlock) + $j;
+                if (isset($keywords[$kidx])) {
+                    $media['base_keywords'][$i][$j] = array(
+                        'name'      => htmlspecialchars($keywords[$kidx]),
+                        'selected'  => isset($props['base_keyword'][$keywords[$kidx]]) ? true : false
+                    );
+                } else {
+                    $media['base_keywords'][$i][$j] = array();
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Tries to auto-convert specific fields into DB-storable values
+ *
+ * @param  string   The keyname
+ * @param  string   The value
+ * @param  string   Invert?
+ * @return array    array('image_id') holding the last created thumbnail for immediate processing
+ *
+ */
+function serendipity_mediaTypeCast($key, $val, $invert = false) {
+    if (stristr($key, 'date') !== FALSE) {
+        if ($invert && is_numeric($val)) {
+            return serendipity_strftime(DATE_FORMAT_SHORT, $val, false);
+        } elseif ($invert === false) {
+            $tmp = strtotime($val);
+            if ($tmp !== FALSE && $tmp > 1) {
+                return $tmp;
+            }
+        }
+    } elseif ($invert && stristr($key, 'length') !== FALSE) {
+        $tmp = '';
+
+        $hours    = intval(intval($val) / 3600);
+        $minutes  = intval(($val / 60) % 60);
+        $seconds  = intval($val % 60);
+        $mseconds = intval((($val - $seconds) * 100) % 100);
+
+        $tmp .= str_pad($hours, 2, '0', STR_PAD_LEFT) . ':';
+        $tmp .= str_pad($minutes, 2, '0', STR_PAD_LEFT). ':';
+        $tmp .= str_pad($seconds, 2, '0', STR_PAD_LEFT) . '.';
+        $tmp .= str_pad($mseconds, 2, '0', STR_PAD_LEFT);
+
+        return $tmp;
+    } elseif ($invert === false && preg_match('@^([0-9]+):([0-9]+):([0-9]+).([0-9]+)$@i', $val, $m)) {
+        $tmp = ($m[1] * 3600)
+             + ($m[2] * 60)
+             + ($m[3])
+             + ($m[4] / 100);
+        return $tmp;
+    }
+
+    return $val;
+}
+
+/**
+ * Inserts media properties
+ *
+ * @param   string  Property_group
+ * @return array    array('image_id') holding the last created thumbnail for immediate processing
+ *
+ */
+function serendipity_insertMediaProperty($property_group, $property_subgroup = '', $image_id, &$media, $use_cast = true) {
+    global $serendipity;
+
+    serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}mediaproperties
+                                WHERE mediaid = " . (int)$image_id . "
+                                  " . ($property_subgroup != 'ALL' ? "AND property_subgroup = '" . serendipity_db_escape_string($property_subgroup) . "'" : '') . "
+                                  AND property_group = '" . serendipity_db_escape_string($property_group) . "'");
+
+    if (is_array($media)) {
+        foreach($media AS $key => $val) {
+            if ($key == 'image_id') continue;
+
+            if (is_array($val)) {
+                $use_property_subgroup = $key;
+                $use_val = $val;
+            } else {
+                $use_property_subgroup = $property_subgroup;
+                $use_val = array($key => $val);
+            }
+
+            foreach($use_val AS $insert_key => $insert_val) {
+                if ($use_cast) {
+                    $insert_val = serendipity_mediaTypeCast($insert_key, $insert_val);
+                }
+                $q = sprintf("INSERT INTO {$serendipity['dbPrefix']}mediaproperties
+                                          (mediaid, property_group, property_subgroup, property, value)
+                                   VALUES (%d, '%s', '%s', '%s', '%s')",
+                             $image_id,
+                             serendipity_db_escape_string($property_group),
+                             serendipity_db_escape_string($useproperty_subgroup),
+                             serendipity_db_escape_string($insert_key),
+                             serendipity_db_escape_string($insert_val));
+                serendipity_db_query($q);
+            }
+        }
+    }
+}
+
+/**
+ * Inserts the submitted properties of uploaded media items
+ *
+ * @return array    array('image_id') holding the last created thumbnail for immediate processing
+ *
+ */
+function serendipity_parsePropertyForm() {
+    global $serendipity;
+
+    if (!is_array($serendipity['POST']['mediaProperties'])) {
+        return false;
+    }
+
+    serendipity_checkPropertyAccess($serendipity['POST']['mediaProperties'], $serendipity['POST']['mediaKeywords'], 'write');
+
+    foreach($serendipity['POST']['mediaProperties'] AS $id => $media) {
+        serendipity_insertMediaProperty('base_property', '', $media['image_id'], $media);
+
+        $s9y_img = $media['internal'];
+        $s9y_img['image_id'] = $media['image_id'];
+        serendipity_prepareMedia($s9y_img);
+        $s9y_img['metadata'] =& serendipity_getMetaData($s9y_img['realfile'], $s9y_img['header']);
+        serendipity_insertMediaProperty('base_metadata', 'ALL', $media['image_id'], $s9y_img['metadata']);
+        $s9y_img['hidden'] = array(
+            'author'   => $serendipity['serendipityUser'],
+            'authorid' => $serendipity['authorid']
+        );
+        serendipity_insertMediaProperty('base_hidden', '', $media['image_id'], $s9y_img['hidden']);
+
+        if ($serendipity['POST']['oldDir'][$id] != $serendipity['POST']['newDir'][$id]) {
+            serendipity_moveMediaDirectory(
+                serendipity_uploadSecure($serendipity['POST']['oldDir'][$id]),
+                serendipity_uploadSecure($serendipity['POST']['newDir'][$id]),
+                'filedir',
+                $media['image_id']);
+        }
+    }
+
+    foreach($serendipity['POST']['mediaKeywords'] AS $id => $keywords) {
+        serendipity_insertMediaProperty('base_keyword', '', $serendipity['POST']['mediaProperties'][$id]['image_id'], $keywords);
+    }
+
+    $array = array(
+        'image_id'          => $serendipity['POST']['mediaProperties'][0]['image_id'],
+    );
+
+    return $array;
+}
+
+/**
+ * Fetches existing Media Properties for images
+ *
+ * @param  int      The media item id
+ * @return array    Array of image metadata
+ *
+ */
+function &serendipity_fetchMediaProperties($id) {
+    global $serendipity;
+
+    $sql = "SELECT mediaid, property, property_group, property_subgroup, value
+              FROM {$serendipity['dbPrefix']}mediaproperties
+             WHERE mediaid IN (" . (is_array($id) ? implode(',', $id) : (int)$id) . ")";
+    $rows  = serendipity_db_query($sql, false, 'assoc');
+    $props = array();
+    if (is_array($rows)) {
+        foreach($rows AS $row) {
+            if (empty($row['property_subgroup'])) {
+                if (is_array($id)) {
+                    $props[$row['mediaid']][$row['property_group']][$row['property']] = $row['value'];
+                } else {
+                    $props[$row['property_group']][$row['property']] = $row['value'];
+                }
+            } else {
+                if (is_array($id)) {
+                    $props[$row['mediaid']][$row['property_group']][$row['property_subgroup']][$row['property']] = $row['value'];
+                } else {
+                    $props[$row['property_group']][$row['property_subgroup']][$row['property']] = $row['value'];
+                }
+            }
+        }
+    }
+    return $props;
+}
+
+/**
+ * Checks if properties to a specific image are allowed to be fetched
+ *
+ * @param  array    Array of image metadata
+ * @param  array    Array of additional image metadata
+ * @param  string   ACL toggle type ('read', 'write')
+ * @return array    Stripped Array of image metadata
+ *
+ */
+function serendipity_checkPropertyAccess(&$new_media, &$additional, $mode = 'read') {
+    global $serendipity;
+
+    // Strip out images we don't have access to
+    $ids = array();
+    foreach($new_media AS $id => $item) {
+        $ids[] = $item['image_id'];
+    }
+
+    $valid_images = serendipity_fetchImageFromDatabase($ids, $mode);
+    foreach ($new_media AS $id => $media) {
+        if (!isset($valid_images[$media['image_id']])) {
+            unset($new_media[$id]);
+            unset($additional[$id]);
+        } else {
+            $new_media[$id]['internal'] = $valid_images[$media['image_id']];
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Prepare a media item for showing
+ *
+ * @param  array    Array of image metadata
+ * @param  string   URL for maintenance tasks
+ * @return bool
+ *
+ */
+function serendipity_prepareMedia(&$file, $url = '') {
+    global $serendipity;
+    static $full_perm = null;
+
+    if ($full_perm === null) {
+        $full_perm = serendipity_checkPermission('adminImagesMaintainOthers');
+    }
+
+       $sThumbSource           = serendipity_getThumbNailPath($file['path'], $file['name'], $file['extension'], $file['thumbnail_name']);
+       $file['full_thumb']     = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $sThumbSource;
+       $file['full_thumbHTTP'] = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $sThumbSource;
+
+    if ($file['hotlink']) {
+        $file['full_file']  = $file['path'];
+       $file['show_thumb'] = $file['path'];
+       if (!isset($file['imgsrc'])) {
+               $file['imgsrc'] = $file['show_thumb'];
+       }
+    } else {
+        $file['full_file']  = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . '.'. $file['extension'];
+       $file['show_thumb'] = $file['full_thumbHTTP'];
+       if (!isset($file['imgsrc'])) {
+               $file['imgsrc'] = $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . (!empty($file['thumbnail_name']) ? '.' . $file['thumbnail_name'] : '') . '.' . $file['extension'];
+       }
+    }
+
+    if (empty($file['realname'])) {
+        $file['realname'] = $file['name'] . '.' . $file['extension'];
+    }
+    $file['diskname'] = $file['name'] . '.' . $file['extension'];
+
+    $file['links'] = array('imagelinkurl' => $file['full_file']);
+
+       $file['dim']       = @getimagesize($file['full_thumb'], $file['header']);
+    $file['is_image']  = serendipity_isImage($file);
+
+    if ($file['is_image']) {
+        $file['mediatype'] = 'image';
+    } elseif (0 === strpos(strtolower($file['displaymime']), 'video/') || 0 === strpos(strtolower($file['displaymime']), 'application/x-shockwave')) {
+        $file['mediatype'] = 'video';
+    } elseif (0 === strpos(strtolower($file['displaymime']), 'audio/') || 0 === strpos(strtolower($file['displaymime']), 'application/vnd.rn-') || 0 === strpos(strtolower($file['displaymime']), 'application/ogg')) {
+        $file['mediatype'] = 'audio';
+    } elseif (0 === strpos(strtolower($file['displaymime']), 'text/')) {
+        $file['mediatype'] = 'document';
+    } elseif (preg_match('@application/(pdf|rtf|msword|msexcel|excel|x-excel|mspowerpoint|postscript|vnd\.ms*|powerpoint)@i', $file['displaymime'])) {
+        $file['mediatype'] = 'document';
+    } elseif (preg_match('@application/(java-archive|zip|gzip|arj|x-bzip|x-bzip2|x-compressed|x-gzip|x-stuffit)@i', $file['displaymime'])) {
+        $file['mediatype'] = 'archive';
+    } else {
+        $file['mediatype'] = 'binary';
+    }
+
+    $file['realfile']  = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . '.'. $file['extension'];
+
+    if ($full_perm || $serendipity['authorid'] == $file['authorid'] || $file['authorid'] == '0') {
+        $file['is_editable'] = true;
+    } else {
+        $file['is_editable'] = false;
+    }
+
+    /* If it is an image, and the thumbnail exists */
+    if ($file['is_image'] && file_exists($file['full_thumb'])) {
+        $file['thumbWidth']  = $file['dim'][0];
+        $file['thumbHeight'] = $file['dim'][1];
+        $file['preview'] .= '<img src="' . $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $sThumbSource . '" border="0" title="' . $file['path'] . $file['name'] . '" alt="'. $file['realname'] . '" />';
+        if ($url) {
+            $file['preview_url'] = $url .'&amp;serendipity[image]='. $file['id'];
+            $file['preview'] = '<a href="'. $file['preview_url'] .'">'. $file['preview'] .'</a>';
+        }
+    } elseif ($file['is_image'] && $file['hotlink']) {
+        $sizes = serendipity_calculate_aspect_size($file['dimensions_width'], $file['dimensions_height'], $serendipity['thumbSize']);
+        $file['thumbWidth']  = $sizes[0];
+        $file['thumbHeight'] = $sizes[1];
+        $file['preview'] .= '<img src="' . $file['path'] . '" width="' . $sizes[0] . '" height="' . $sizes[1] . '" border="0" title="' . $file['path'] . '" alt="'. $file['realname'] . '" />';
+        if ($url) {
+            $file['preview_url'] = $url .'&amp;serendipity[image]='. $file['id'];
+            $file['preview'] = '<a href="'. $file['preview_url'] .'">'. $file['preview'] .'</a>';
+        }
+    /* If it's not an image, or the thumbnail does not exist */
+    } else {
+        $mimeicon = serendipity_getTemplateFile('admin/img/mime_' . preg_replace('@[^a-z0-9\-\_]@i', '-', $file['mime']) . '.png');
+        if (!$mimeicon) {
+            $mimeicon = serendipity_getTemplateFile('admin/img/mime_unknown.png');
+        }
+        $file['preview'] .= '<img src="'. $mimeicon .'" title="' . $file['path'] . $file['name'] . ' (' . $file['mime'] . ')" alt="'. $file['mime'] .'" /><br /><span style="font-weight: bold; font-size: 8pt">- ' . (($file['hotlink']) ? MEDIA_HOTLINKED : $file['mime']) .' -</span>';
+        if ($url) {
+            $file['preview_url'] = $url .'&amp;serendipity[image]='. $file['id'];
+            $file['preview'] .= '<br /><a href="' . $file['preview_url'] . '">' . $file['name'] . '.' . $file['extension'] . '</a>';
+        }
+    }
+
+    $file['popupWidth']   = ($file['is_image'] ? ($file['dimensions_width']  + 20) : 600);
+    $file['popupHeight']  = ($file['is_image'] ? ($file['dimensions_height'] + 20) : 500);
+    if ($file['hotlink']) {
+        $file['nice_hotlink'] = wordwrap($file['path'], 45, '<br />', 1);
+    }
+    $file['nice_size']    = number_format(round($file['size']/1024, 2), NUMBER_FORMAT_DECIMALS, NUMBER_FORMAT_DECPOINT, NUMBER_FORMAT_THOUSANDS);
+
+    return true;
+}
+
+/**
+ * Prints a media item
+ *
+ * @param  array    Array of image metadata
+ * @param  string   URL for maintenance tasks
+ * @param  boolean  Whether to show maintenance task items
+ * @param  int      how many media items to display per row
+ * @param  boolean  Enclose within a table cell?
+ * @param  array    Additional Smarty variables
+ * @param  boolean  If TRUE, will echo Smarty output.
+ * @return string   Smarty block name
+ *
+ */
+function serendipity_showMedia(&$file, &$paths, $url = '', $manage = false, $lineBreak = 3, $enclose = true, $smarty_vars = array(), $smarty_display = true) {
+    global $serendipity;
+
+    $form_hidden = '';
+    foreach($serendipity['GET'] AS $g_key => $g_val) {
+        if (!is_array($g_val) && $g_key != 'page') {
+            $form_hidden .= '<input type="hidden" name="serendipity[' . $g_key . ']" value="' . htmlspecialchars($g_val) . '" />';
+        }
+    }
+
+    serendipity_smarty_init();
+    $media = array(
+        'manage'            => $manage,
+        'lineBreak'         => $lineBreak,
+        'lineBreakP'        => round(1/$lineBreak*100),
+        'url'               => $url,
+        'enclose'           => $enclose,
+        'zoomIMG'           => serendipity_getTemplateFile('admin/img/big_zoom.png'),
+        'renameIMG'         => serendipity_getTemplateFile('admin/img/big_rename.png'),
+        'resizeIMG'         => serendipity_getTemplateFile('admin/img/big_resize.png'),
+        'rotatecwIMG'       => serendipity_getTemplateFile('admin/img/big_rotate_cw.png'),
+        'rotateccwIMG'      => serendipity_getTemplateFile('admin/img/big_rotate_ccw.png'),
+        'configureIMG'      => serendipity_getTemplateFile('admin/img/configure.png'),
+        'deleteIMG'         => serendipity_getTemplateFile('admin/img/big_delete.png'),
+        'prevIMG'           => serendipity_getTemplateFile('admin/img/previous.png'),
+        'nextIMG'           => serendipity_getTemplateFile('admin/img/next.png'),
+        'token'             => serendipity_setFormToken(),
+        'form_hidden'       => $form_hidden,
+        'blimit_path'       => basename($limit_path),
+        'only_path'         => $serendipity['GET']['only_path'],
+        'only_filename'     => $serendipity['GET']['only_filename'],
+        'sortorder'         => $serendipity['GET']['sortorder'],
+        'keywords_selected' => $serendipity['GET']['keywords'],
+        'filter'            => $serendipity['GET']['filter'],
+        'sort_order'        => serendipity_getImageFields(),
+        'authors'           => serendipity_fetchUsers(),
+        'sort_row_interval' => array(8, 16, 50, 100),
+        'nr_files'          => count($file),
+        'keywords'          => explode(';', $serendipity['mediaKeywords']),
+    );
+
+    $media = array_merge($media, $smarty_vars);
+    $media['files'] =& $file;
+    if (count($paths) > 0) {
+        $media['paths'] =& $paths;
+    } else {
+        $media['paths'] =& serendipity_getMediaPaths();
+    }
+
+    $serendipity['smarty']->assign_by_ref('media', $media);
+
+    if ($enclose) {
+        serendipity_smarty_fetch('MEDIA_ITEMS', 'admin/media_items.tpl');
+        $block = 'admin/media_pane.tpl';
+        if ($smarty_display) {
+            $serendipity['smarty']->display(serendipity_getTemplateFile('admin/media_pane.tpl', 'serendipityPath'));
+        }
+    } else {
+        serendipity_smarty_fetch('MEDIA_ITEMS', 'admin/media_items.tpl');
+        $block = 'admin/media_properties.tpl';
+        if ($smarty_display) {
+            $serendipity['smarty']->display(serendipity_getTemplateFile('admin/media_properties.tpl', 'serendipityPath'));
+        }
+    }
+
+    return $block;
+}
+
+/**
+ * Convert a IPTC/EXIF/XMP item
+ *
+ * @param  string   The content
+ * @param  string   The type of the content
+ * @return string   The converted content
+ *
+ */
+function serendipity_metaFieldConvert(&$item, $type) {
+    switch($type) {
+        case 'math':
+            $parts = explode('/', $item);
+            return ($parts[0] / $parts[1]);
+            break;
+
+        case 'or':
+            if ($item == '1') {
+                return 'Landscape';
+            } else {
+                return 'Portrait';
+            }
+
+        case 'date':
+            return strtotime($item);
+            break;
+
+        case 'date2':
+            $parts = explode(':', $item);
+            return mktime($parts[3], $parts[4], $parts[5], $parts[1], $parts[2], $parts[0]);
+            break;
+
+        case 'rdf':
+            if (preg_match('@<rdf:li[^>]*>(.*)</rdf:li>@i', $item, $ret)) {
+                return $ret[1];
+            }
+            break;
+
+        case 'text':
+        default:
+            return trim($item);
+            break;
+    }
+
+    return '';
+}
+
+/**
+ * Get the RAW media header data (XMP)
+ *
+ * @param  string   Filename
+ * @return array    The raw media header data
+ *
+ * Inspired, but rewritten,  by "PHP JPEG Metadata Toolkit" from http://electronics.ozhiker.com.
+ * Code is GPL so sadly we couldn't bundle that GREAT library.
+ */
+function serendipity_getMediaRaw($filename) {
+    $abort = false;
+
+    $f = @fopen($filename, 'rb');
+    $ret = array();
+    if (!$f) {
+        return $ret;
+    }
+
+    $filedata = fread($f, 2);
+
+    if ($filedata != "\xFF\xD8") {
+        fclose($f);
+        return $ret;
+    }
+
+    $filedata = fread($f, 2);
+
+    if ($filedata{0} != "\xFF") {
+        fclose($f);
+        return $ret;
+    }
+
+    while (!$abort && !feof($f) && $filedata{1} != "\xD9") {
+        if ((ord($filedata{1}) < 0xD0) || (ord($filedata{1}) > 0xD7)) {
+            $ordret   = fread($f, 2);
+            $ordstart = ftell($f);
+            $int      = unpack('nsize', $ordret);
+
+            if (ord($filedata{1}) == 225) {
+                $content  = fread($f, $int['size'] - 2);
+
+                if (substr($content, 0, 24) == 'http://ns.adobe.com/xap/') {
+                    $ret[] = array(
+                        'ord'      => ord($filedata{1}),
+                        'ordstart' => $ordstart,
+                        'int'      => $int,
+                        'content'  => $content
+                    );
+                }
+            } else {
+                fseek($f, $int['size'] - 2, SEEK_CUR);
+            }
+        }
+
+        if ($filedata{1} == "\xDA") {
+            $abort = true;
+        } else {
+            $filedata = fread($f, 2);
+            if ($filedata{0} != "\xFF") {
+                fclose($f);
+                return $ret;
+            }
+        }
+    }
+
+    fclose($f);
+
+    return $ret;
+}
+
+/**
+ * Get the IPTC/EXIF/XMP media metadata
+ *
+ * @param  string   Filename
+ * @return array    The raw media header data
+ *
+ */
+function &serendipity_getMetaData($file, &$info) {
+    global $serendipity;
+
+    # Fields taken from: http://demo.imagefolio.com/demo/ImageFolio31_files/skins/cool_blue/images/iptc.html
+    static $IPTC_Fields = array(
+    '2#005' => 'ObjectName',
+    '2#025' => 'Keywords',
+    '2#026' => 'LocationCode',
+    '2#027' => 'LocationName',
+    '2#030' => 'ReleaseDate',
+    '2#035' => 'ReleaseTime',
+    '2#037' => 'ExpirationDate',
+    '2#038' => 'ExpirationTime',
+    '2#055' => 'DateCreated',
+    '2#060' => 'TimeCreated',
+    '2#062' => 'DigitalDateCreated',
+    '2#063' => 'DigitalTimeCreated',
+    '2#065' => 'Software',
+    '2#070' => 'SoftwareVersion',
+    '2#080' => 'Photographer',
+    '2#085' => 'Photographer Name',
+    '2#090' => 'PhotoLocation',
+    '2#092' => 'PhotoLocation2',
+    '2#095' => 'PhotoState',
+    '2#100' => 'PhotoCountryCode',
+    '2#101' => 'PhotoCountry',
+    '2#105' => 'Title',
+    '2#110' => 'Credits',
+    '2#115' => 'Source',
+    '2#116' => 'Creator',
+    '2#118' => 'Contact',
+    '2#120' => 'Description',
+    '2#131' => 'Orientation',
+    '2#150' => 'AudioType',
+    '2#151' => 'AudioSamplingRate',
+    '2#152' => 'AudioSamplingResolution',
+    '2#153' => 'AudioDuration'
+    );
+
+    static $ExifFields = array(
+        'IFD0' => array(
+            'Make'          => array('type' => 'text',  'name' => 'CameraMaker'),
+            'Model'         => array('type' => 'text',  'name' => 'CameraModel'),
+            'Orientation'   => array('type' => 'or',    'name' => 'Orientation'),
+            'XResolution'   => array('type' => 'math',  'name' => 'XResolution'),
+            'YResolution'   => array('type' => 'math',  'name' => 'YResolution'),
+            'Software'      => array('type' => 'text',  'name' => 'Software'),
+            'DateTime'      => array('type' => 'date2', 'name' => 'DateCreated'),
+            'Artist'        => array('type' => 'text',  'name' => 'Creator'),
+        ),
+
+        'EXIF' => array(
+            'ExposureTime'          => array('type' => 'math',  'name' => 'ExposureTime'),
+            'ApertureValue'         => array('type' => 'math',  'name' => 'ApertureValue'),
+            'MaxApertureValue'      => array('type' => 'math',  'name' => 'MaxApertureValue'),
+            'ISOSpeedRatings'       => array('type' => 'text',  'name' => 'ISOSpeedRatings'),
+            'DateTimeOriginal'      => array('type' => 'date2', 'name' => 'DateCreated'),
+            'MeteringMode'          => array('type' => 'text',  'name' => 'MeteringMode'),
+            'FNumber'               => array('type' => 'math',  'name' => 'FNumber'),
+            'ExposureProgram'       => array('type' => 'text',  'name' => 'ExposureProgram'),
+            'FocalLength'           => array('type' => 'math',  'name' => 'FocalLength'),
+            'WhiteBalance'          => array('type' => 'text',  'name' => 'WhiteBalance'),
+            'DigitalZoomRatio'      => array('type' => 'math',  'name' => 'DigitalZoomRatio'),
+            'FocalLengthIn35mmFilm' => array('type' => 'text',  'name' => 'FocalLengthIn35mmFilm'),
+            'Flash'                 => array('type' => 'text',  'name' => 'Flash'),
+            'Fired'                 => array('type' => 'text',  'name' => 'FlashFired'),
+            'RedEyeMode'            => array('type' => 'text',  'name' => 'RedEyeMode'),
+        )
+    );
+
+    static $xmpPatterns = array(
+    'tiff:Orientation'              => array('type' => 'or',   'name' => 'Orientation'),
+    'tiff:XResolution'              => array('type' => 'math', 'name' => 'XResolution'),
+    'tiff:YResolution'              => array('type' => 'math', 'name' => 'YResolution'),
+    'tiff:Make'                     => array('type' => 'text', 'name' => 'CameraMaker'),
+    'tiff:Model'                    => array('type' => 'text', 'name' => 'CameraModel'),
+    'xap:ModifyDate'                => array('type' => 'date', 'name' => 'DateModified'),
+    'xap:CreatorTool'               => array('type' => 'text', 'name' => 'Software'),
+    'xap:CreateDate'                => array('type' => 'date', 'name' => 'DateCreated'),
+    'xap:MetadataDate'              => array('type' => 'date', 'name' => 'DateMetadata'),
+
+    'exif:ExposureTime'             => array('type' => 'math',  'name' => 'ExposureTime'),
+    'exif:ApertureValue'            => array('type' => 'math',  'name' => 'ApertureValue'),
+    'exif:MaxApertureValue'         => array('type' => 'math',  'name' => 'MaxApertureValue'),
+    'exif:ISOSpeedRatings'          => array('type' => 'text',  'name' => 'ISOSpeedRatings'),
+    'exif:DateTimeOriginal'         => array('type' => 'date',  'name' => 'DateCreated'),
+    'exif:MeteringMode'             => array('type' => 'text',  'name' => 'MeteringMode'),
+    'exif:FNumber'                  => array('type' => 'math',  'name' => 'FNumber'),
+    'exif:ExposureProgram'          => array('type' => 'text',  'name' => 'ExposureProgram'),
+    'exif:FocalLength'              => array('type' => 'math',  'name' => 'FocalLength'),
+    'exif:WhiteBalance'             => array('type' => 'text',  'name' => 'WhiteBalance'),
+    'exif:DigitalZoomRatio'         => array('type' => 'math',  'name' => 'DigitalZoomRatio'),
+    'exif:FocalLengthIn35mmFilm'    => array('type' => 'text',  'name' => 'FocalLengthIn35mmFilm'),
+    'exif:Fired'                    => array('type' => 'text',  'name' => 'FlashFired'),
+    'exif:RedEyeMode'               => array('type' => 'text',  'name' => 'RedEyeMode'),
+
+    'dc:title'                      => array('type' => 'rdf',   'name' => 'Title'),
+    'dc:creator'                    => array('type' => 'rdf',   'name' => 'Creator'),
+    );
+
+    $ret = array();
+
+    if (!$serendipity['mediaExif']) {
+        return $ret;
+    }
+
+    if (!file_exists($file)) {
+        return $ret;
+    }
+
+    if (function_exists('iptcparse') && is_array($info) && isset($info['APP13'])) {
+        $iptc = iptcparse($info['APP13']);
+        foreach($IPTC_Fields AS $field => $desc) {
+            if ($iptc[$field]) {
+                if (is_array($iptc[$field])) {
+                    $ret['IPTC'][$desc] = trim(implode(';', $iptc[$field]));
+                } else {
+                    $ret['IPTC'][$desc] = trim($iptc[$field]);
+                }
+            }
+        }
+    }
+
+    if (function_exists('exif_read_data') && is_array($info)) {
+        $exif = @exif_read_data($file, 'FILE,COMPUTED,ANY_TAG,IFD0,COMMENT,EXIF', true, false);
+        if (is_array($exif)) {
+            foreach($ExifFields AS $Exifgroup => $ExifField) {
+                foreach($ExifField AS $ExifName => $ExifItem) {
+                    if (!isset($exif[$Exifgroup][$ExifName])) {
+                        continue;
+                    }
+                    $ret['EXIF'][$ExifItem['name']] = serendipity_metaFieldConvert($exif[$Exifgroup][$ExifName], $ExifItem['type']);
+                    if ($ret['EXIF'][$item['name']] == $ret['IPTC'][$item['name']]) {
+                        unset($ret['IPTC'][$item['name']]);
+                    }
+                }
+            }
+        }
+    }
+
+    $xmp = serendipity_getMediaRaw($file);
+    foreach($xmp AS $xmp_data) {
+        if (empty($xmp_data['content'])) {
+            continue;
+        }
+        foreach($xmpPatterns AS $lookup => $item) {
+            if (preg_match('@<' . $lookup . '>(.*)</' . $lookup . '>@', $xmp_data['content'], $match)) {
+                $ret['XMP'][$item['name']] = serendipity_metaFieldConvert($match[1], $item['type']);
+                if ($ret['EXIF'][$item['name']] == $ret['XMP'][$item['name']]) {
+                    unset($ret['EXIF'][$item['name']]);
+                }
+            }
+        }
+    }
+
+    serendipity_plugin_api::hook_event('media_getproperties', $ret, $file);
+
+    return $ret;
+}
+
+/**
+ * Parses an existing filename and increases the filecount.
+ *
+ * @param  string   The (duplicate) filename
+ * @param  string   The full path to the (duplicate) filename
+ * @param  string   The directory of the (duplicate) filename
+ * @param  boolean  Show new filename?
+ * @return string   The new filename
+ *
+ */
+function serendipity_imageAppend(&$tfile, &$target, $dir, $echo = true) {
+    static $safe_bail = 20;
+
+    $realname = $tfile;
+    list($filebase, $extension) = serendipity_parseFileName($tfile);
+
+    $cnum = 1;
+    if (preg_match('@^(.*)([0-9]+)$@', $filebase, $match)) {
+        $cnum     = $match[2];
+        $filebase = $match[1];
+    }
+
+    $i = 0;
+    while ($i <= $safe_bail && file_exists($dir . $filebase . $cnum . '.' . $extension)) {
+        $cnum++;
+    }
+
+    // Check if the file STILL exists and append a MD5 if that's the case. That should be unique enough.
+    if (file_exists($dir . $filebase . $cnum . '.' . $extension)) {
+        $cnum = md5(time() . $filebase);
+    }
+
+    // Those variables are passed by reference!
+    $tfile  = $filebase . $cnum . '.' . $extension;
+    $target = $dir . $tfile;
+
+    if ($echo) {
+        printf(FILENAME_REASSIGNED . '<br />', htmlspecialchars($tfile));
+    }
+    return $realname;
+}
+
+/**
+ * Checks if an uploaded media item hits any configured limits.
+ *
+ * @param  string   The filename
+ * @return boolean  TRUE when file is okay, FALSE when it is beyond limits
+ *
+ */
+function serendipity_checkMediaSize($file) {
+    global $serendipity;
+
+    if (!empty($serendipity['maxFileSize'])) {
+        if (filesize($file) > $serendipity['maxFileSize']) {
+            printf(MEDIA_UPLOAD_SIZEERROR . '<br />', (int)$serendipity['maxFileSize']);
+            return false;
+        }
+    }
+
+    if (!empty($serendipity['maxImgWidth']) || !empty($serendipity['maxImgHeight'])) {
+        $dim = serendipity_getimagesize($file);
+        if (!is_array($dim) || !isset($dim[0])) {
+            return true;
+        }
+
+        if (!empty($serendipity['maxImgWidth'])) {
+            if ($dim[0] > $serendipity['maxImgWidth']) {
+                printf(MEDIA_UPLOAD_DIMERROR . '<br />', (int)$serendipity['maxImgWidth'], (int)$serendipity['maxImgHeight']);
+                return false;
+            }
+        }
+
+        if (!empty($serendipity['maxImgHeight'])) {
+            if ($dim[1] > $serendipity['maxImgHeight']) {
+                printf(MEDIA_UPLOAD_DIMERROR . '<br />', (int)$serendipity['maxImgWidth'], (int)$serendipity['maxImgHeight']);
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Moves a media directory
+ *
+ * @param  string   The old directory
+ * @param  string   The new directory
+ * @param  string   The type of what to remove (dir|file|filedir)
+ * @param  string   An item id of a file
+ * @return boolean
+ *
+ */
+function serendipity_moveMediaDirectory($oldDir, $newDir, $type = 'dir', $item_id = null, $file = null) {
+    global $serendipity;
+
+    $real_oldDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir;
+    $real_newDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir;
+
+    if ($type == 'dir') {
+        if (!is_dir($real_oldDir)) {
+            printf(ERROR_FILE_NOT_EXISTS . '<br />', $oldDir);
+            return false;
+        }
+
+        if (is_dir($real_newDir)) {
+            printf(ERROR_FILE_EXISTS . '<br />', $newDir);
+            return false;
+        }
+
+        if (!rename($real_oldDir, $real_newDir)) {
+            printf(MEDIA_DIRECTORY_MOVE_ERROR . '<br />', $newDir);
+            return false;
+        }
+
+        printf(MEDIA_DIRECTORY_MOVED . '<br />', $newDir);
+
+        $dirs = serendipity_db_query("SELECT id, path
+                                        FROM {$serendipity['dbPrefix']}images
+                                       WHERE path LIKE '" . serendipity_db_escape_string($oldDir) . "%'", false, 'assoc');
+        if (is_array($dirs)) {
+            foreach($dirs AS $dir) {
+                $old = $dir['path'];
+                $new = preg_replace('@^(' . preg_quote($oldDir) . ')@i', $newDir, $old);
+                serendipity_db_query("UPDATE {$serendipity['dbPrefix']}images
+                                         SET path = '" . serendipity_db_escape_string($new) . "'
+                                       WHERE id = {$dir['id']}");
+            }
+        }
+
+        $dirs = serendipity_db_query("SELECT groupid, artifact_id, artifact_type, artifact_mode, artifact_index
+                                        FROM {$serendipity['dbPrefix']}access
+                                       WHERE artifact_type = 'directory'
+                                         AND artifact_index LIKE '" . serendipity_db_escape_string($oldDir) . "%'", false, 'assoc');
+        if (is_array($dirs)) {
+            foreach($dirs AS $dir) {
+                $old = $dir['artifact_index'];
+                $new = preg_replace('@^(' . preg_quote($oldDir) . ')@i', $newDir, $old);
+                serendipity_db_query("UPDATE {$serendipity['dbPrefix']}access
+                                         SET artifact_index = '" . serendipity_db_escape_string($new) . "'
+                                       WHERE groupid        = '" . serendipity_db_escape_string($dir['groupid']) . "'
+                                         AND artifact_id    = '" . serendipity_db_escape_string($dir['artifact_id']) . "'
+                                         AND artifact_type  = '" . serendipity_db_escape_string($dir['artifact_type']) . "'
+                                         AND artifact_mode  = '" . serendipity_db_escape_string($dir['artifact_mode']) . "'
+                                         AND artifact_index = '" . serendipity_db_escape_string($dir['artifact_index']) . "'");
+            }
+        }
+    }
+
+    if ($type == 'file') {
+        if (serendipity_isActiveFile(basename($newDir))) {
+            printf(ERROR_FILE_FORBIDDEN, htmlspecialchars($newDir));
+            return false;
+        }
+
+        if ($file['hotlink']) {
+            serendipity_updateImageInDatabase(array('name' => $newDir), $item_id);
+        } else {
+            $file_new = $file['path'] . $newDir . '.';
+            $file_old = $file['path'] . $file['name'] . '.';
+
+            $newfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file_new . $file['extension'];
+            $oldfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file_old . $file['extension'];
+            if ($newDir != '' && file_exists($oldfile) && !file_exists($newfile)) {
+                $renameValues = array(array(
+                    'from'   => $oldfile,
+                    'to'     => $newfile,
+                    'thumb'  => $serendipity['thumbSuffix'],
+                    'fthumb' => $file['thumbnail_name']
+                ));
+
+                serendipity_plugin_api::hook_event('backend_media_rename', $renameValues);
+
+                // Rename file
+                rename($renameValues[0]['from'], $renameValues[0]['to']);
+
+                foreach($renameValues as $renameData) {
+                    // Rename thumbnail
+                    rename($serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . (!empty($renameData['fthumb']) ? '.' . $renameData['fthumb'] : '') . '.' .  $file['extension'],
+                           $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $newDir . '.' . $renameData['thumb'] . '.' . $file['extension']);
+                }
+
+                serendipity_updateImageInDatabase(array('thumbnail_name' => $renameValues[0]['thumb'], 'name' => $newDir), $item_id);
+                $oldDir = $file_old;
+                $newDir = $file_new;
+                $real_oldDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir;
+                $real_newDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir;
+                // Forward user to overview (we don't want the user's back button to rename things again)
+            } else {
+                if (!file_exists($oldfile)) {
+                    echo ERROR_FILE_NOT_EXISTS;
+                } elseif (file_exists($newfile)) {
+                    echo ERROR_FILE_EXISTS;
+                } else {
+                    echo ERROR_SOMETHING;
+                }
+
+                return false;
+            }
+        }
+    }
+
+    if ($type == 'filedir') {
+        serendipity_db_query("UPDATE {$serendipity['dbPrefix']}images
+                                 SET path = '" . serendipity_db_escape_string($newDir) . "'
+                               WHERE id   = " . (int)$item_id);
+        $pick = serendipity_db_query("SELECT * FROM  {$serendipity['dbPrefix']}images
+                               WHERE id   = " . (int)$item_id, true, 'assoc');
+
+        // Move thumbs
+        $oldfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir . $pick['name'] . '.' . $pick['extension'];
+        $newfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir . $pick['name'] . '.' . $pick['extension'];
+
+        $renameValues = array(array(
+            'from'   => $oldfile,
+            'to'     => $newfile,
+            'thumb'  => $serendipity['thumbSuffix'],
+            'fthumb' => $pick['thumbnail_name']
+        ));
+
+        serendipity_plugin_api::hook_event('backend_media_rename', $renameValues);
+
+        // Rename file
+        rename($renameValues[0]['from'], $renameValues[0]['to']);
+
+        foreach($renameValues as $renameData) {
+            // Rename thumbnail
+            rename($serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir . $pick['name'] . (!empty($renameData['fthumb']) ? '.' . $renameData['fthumb'] : '') . '.' .  $pick['extension'],
+                   $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir . $pick['name'] . '.' . $renameData['thumb'] . '.' . $pick['extension']);
+        }
+
+        $oldDir .= $pick['name'];
+        $newDir .= $pick['name'];
+    }
+
+    // Only MySQL supported, since I don't know how to use REGEXPs differently.
+    if ($serendipity['dbType'] != 'mysql' && $serendipity['dbType'] != 'mysqli') {
+        echo MEDIA_DIRECTORY_MOVE_ENTRY . '<br />';
+        return true;
+    }
+
+    $q = "SELECT id, body, extended
+            FROM {$serendipity['dbPrefix']}entries
+           WHERE body     REGEXP '(src|href)=(\'|\")" . serendipity_db_escape_string($serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $oldDir) . "'
+              OR extended REGEXP '(src|href)=(\'|\")" . serendipity_db_escape_string($serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $oldDir) . "'
+    ";
+
+    $dirs = serendipity_db_query($q);
+    if (is_array($dirs)) {
+        foreach($dirs AS $dir) {
+            $dir['body']     = preg_replace('@(src|href)=(\'|")' . preg_quote($serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $oldDir) . '@', '\1=\2' . $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $newDir, $dir['body']);
+            $dir['extended'] = preg_replace('@(src|href)=(\'|")' . preg_quote($serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $oldDir) . '@', '\1=\2' . $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $newDir, $dir['extended']);
+
+            $uq = "UPDATE {$serendipity['dbPrefix']}entries
+                                     SET body     = '" . serendipity_db_escape_string($dir['body']) . "' ,
+                                         extended = '" . serendipity_db_escape_string($dir['extended']) . "'
+                                   WHERE id       = " . serendipity_db_escape_string($dir['id']);
+            serendipity_db_query($uq);
+        }
+
+        printf(MEDIA_DIRECTORY_MOVE_ENTRIES . '<br />', count($dirs));
+    }
+
+    return true;
+}
+
+/**
+ * Gets all available media directories
+ *
+ * @return array
+ *
+ */
+function &serendipity_getMediaPaths() {
+    global $serendipity;
+
+       $aExclude = array("CVS" => true, ".svn" => true);
+       serendipity_plugin_api::hook_event('backend_media_path_exclude_directories', $aExclude);
+       $paths        = array();
+
+       $aResultSet   = serendipity_traversePath(
+           $serendipity['serendipityPath'] . $serendipity['uploadPath'],
+           '',
+           false,
+           NULL,
+           1,
+           NULL,
+           FALSE,
+           $aExclude
+       );
+
+       foreach ($aResultSet AS $sKey => $sFile) {
+               if ($sFile['directory']) {
+                       array_push($paths, $sFile);
+               }
+               unset($aResultSet[$sKey]);
+       }
+    serendipity_directoryACL($paths, 'read');
+
+    usort($paths, 'serendipity_sortPath');
+
+    return $paths;
+}
\ No newline at end of file