From 4a4f35c1021d379370a2bc3aa8f7e9ce6f8e97fa Mon Sep 17 00:00:00 2001 From: garvinhicking Date: Sat, 5 Aug 2006 21:43:19 +0000 Subject: [PATCH] fix sorting media paths --- docs/NEWS | 2 + include/functions_images.inc.php | 2225 ++++++++++++++++++++++++++---- 2 files changed, 1970 insertions(+), 257 deletions(-) diff --git a/docs/NEWS b/docs/NEWS index be26179..b87a6cd 100644 --- 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) diff --git a/include/functions_images.inc.php b/include/functions_images.inc.php index c45e9f0..ca45056 100644 --- a/include/functions_images.inc.php +++ b/include/functions_images.inc.php @@ -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 '
' . $rs . '
'; + 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 . '
', $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 . '
'; + 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 . '
'; $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 '
'. sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) .'
'; $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.
"; + array_push($paths, $sFile); + } else { + if ($debug) echo "{$sFile['relpath']} is a file.
"; + // Store the file in our array, remove any ending slashes + $aFilesOnDisk[$sFile['relpath']] = 1; + } + unset($aResultSet[$sKey]); + } + + usort($paths, 'serendipity_sortPath'); + + if ($debug) echo "

Got files:

" . print_r($aFilesOnDisk, true) . "

"; + $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 "

Got images:

" . print_r($aResultSet, true) . "

"; + 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 "

File name is $sFileName,
thumbnail is $sThumbNailFile

"; + unset($aResultSet[$sKey]); + + if (isset($aFilesOnDisk[$sFileName])){ + unset($aFilesOnDisk[$sFileName]); + } else { + if ($debug) "Deleting Image {$sFile['id']}
\n"; + serendipity_deleteImage($sFile['id']); + ++$nCount; + } + + unset($aFilesOnDisk[$sThumbNailFile]); + } + } + + if ($nCount > 0){ + if ($debug) echo "

Cleaned up ".$nCount." database entries

"; + } + + serendipity_set_config_var('last_image_hash', $serendipity['current_image_hash'], 0); + $aUnmatchedOnDisk = array_keys($aFilesOnDisk); + if ($debug) echo "

Got unmatched files:

" . print_r($aUnmatchedOnDisk, true) . "

"; + $nCount = 0; + foreach ($aUnmatchedOnDisk AS $sFile) { + if (preg_match('@\.' . $serendipity['thumbSuffix'] . '\.@', $sFile)) { + if ($debug) echo "

Skipping thumbnailed file $sFile

"; + continue; + } else { + if ($debug) echo "

Checking $sFile

"; + } + + // 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 "

Inserting image $sFileName from $sDirectory

" . print_r($aImageData, true) . "
into database

"; + # 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 "

Inserted ".$nCount." images into the database

"; + } + } else { + if ($debug) echo "

Media Gallery database is up to date

"; + } + + /* + $nTimeEnd = microtime_float ( ); + $nDifference = $nTimeEnd - $nTimeStart; + echo "

total time taken was " . $nDifference . "

"; + */ + ## 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)); } -?> -
- $g_val) { - if ( !is_array($g_val) && $g_key != 'page' ) { - echo ''; - } - } -?> - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - -
-
- -
- -
- - - - - - - $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 .= ''. $file['name'] . ''; - if ($url) { - $preview = ''. $preview .''; - } - } elseif ($is_image && $file['hotlink']) { - $sizes = serendipity_calculate_aspect_size($file['dimensions_width'], $file['dimensions_height'], $serendipity['thumbSize']); - $preview .= ''. $file['name'] . ''; - if ($url) { - $preview = ''. $preview .''; - } - /* If it's not an image, or the thumbnail does not exist */ - } else { - $preview .= ''. $file['mime'] .'
- ' . (($file['hotlink']) ? MEDIA_HOTLINKED : $file['mime']) .' -'; - if ($url) { - $preview .= '
' . $file['name'] . '.' . $file['extension'] . ''; - } - $preview .= ''; - } + serendipity_prepareMedia($serendipity['imageList'][$k], $url); -?> - - - - - - -
- - - - - -
- - - - - -
-
- - - - - - - - - - - - - -
- - <?php echo MEDIA_FULLSIZE; ?>
- <?php echo MEDIA_RENAME; ?>
- <?php echo IMAGE_RESIZE; ?>
- <?php echo IMAGE_ROTATE_LEFT; ?>
-
<?php echo IMAGE_ROTATE_RIGHT; ?>
-
<?php echo MEDIA_DELETE; ?>
- -
-', 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']); } -?> -
- -
- $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'.
\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.
\n"; + } + unset($paths[$idx]); + } else { + if ($debug) { + echo "ACL for " . $info['relpath'] . " granted.
\n"; + } + } + } + + if (count($paths) < $startCount) { + if ($debug) { + echo "ACL denied all.
\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 .= ' ' . "\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'] .= ''. $file['realname'] . ''; + if ($url) { + $file['preview_url'] = $url .'&serendipity[image]='. $file['id']; + $file['preview'] = ''. $file['preview'] .''; + } + } 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'] .= ''. $file['realname'] . ''; + if ($url) { + $file['preview_url'] = $url .'&serendipity[image]='. $file['id']; + $file['preview'] = ''. $file['preview'] .''; + } + /* 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'] .= ''. $file['mime'] .'
- ' . (($file['hotlink']) ? MEDIA_HOTLINKED : $file['mime']) .' -'; + if ($url) { + $file['preview_url'] = $url .'&serendipity[image]='. $file['id']; + $file['preview'] .= '
' . $file['name'] . '.' . $file['extension'] . ''; + } + } + + $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, '
', 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 .= ''; + } + } + + 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('@]*>(.*)@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 . '>(.*)@', $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 . '
', 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 . '
', (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 . '
', (int)$serendipity['maxImgWidth'], (int)$serendipity['maxImgHeight']); + return false; + } + } + + if (!empty($serendipity['maxImgHeight'])) { + if ($dim[1] > $serendipity['maxImgHeight']) { + printf(MEDIA_UPLOAD_DIMERROR . '
', (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 . '
', $oldDir); + return false; + } + + if (is_dir($real_newDir)) { + printf(ERROR_FILE_EXISTS . '
', $newDir); + return false; + } + + if (!rename($real_oldDir, $real_newDir)) { + printf(MEDIA_DIRECTORY_MOVE_ERROR . '
', $newDir); + return false; + } + + printf(MEDIA_DIRECTORY_MOVED . '
', $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 . '
'; + 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 . '
', 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 -- 2.39.5