<?php # Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team) # All rights reserved. See LICENSE file for licensing details if (IN_serendipity !== true) { die ("Don't hack!"); } /** * Check if an uploaded file is "evil" * * @access public * @param string Input filename * @return boolean */ function serendipity_isActiveFile($file) { if (preg_match('@^\.@', $file)) { return true; } $core = preg_match('@\.(php.*|[psj]html?|pht|aspx?|cgi|jsp|py|pl)$@i', $file); if ($core) { return true; } $eventData = false; serendipity_plugin_api::hook_event('backend_media_check', $eventData, $file); return $eventData; } /** * Gets a list of media items from our media database * * LONG * * @access public * @param int The offset to start fetching media files * @param int How many items to fetch * @param int The number (referenced variable) of fetched items * @param string The "ORDER BY" sql part when fetching items * @param string Order by DESC or ASC * @param string Only fetch files from a specific directory * @param string Only fetch specific filenames (including check for realname match) * @param string Only fetch media with specific keyword * @param array An array of restricting filter sets * @param boolean Apply strict directory checks, or include subdirectories? * @return array Resultset of images */ function serendipity_fetchImagesFromDatabase($start=0, $limit=0, &$total=null, $order = false, $ordermode = false, $directory = '', $filename = '', $keywords = '', $filter = array(), $hideSubdirFiles = false) { global $serendipity; $cond = array( 'joinparts' => array(), 'parts' => array(), ); $orderfields = serendipity_getImageFields(); if (empty($order) || !isset($orderfields[$order])) { $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)); } else { $limitsql = ''; } if ($hideSubdirFiles == false) { if (!empty($directory)) { $cond['parts']['directory'] = " AND i.path LIKE '" . serendipity_db_escape_string($directory) . "%'\n"; } // if in root, having no directory set, the query fetches all files by default, meaning we are done } else { if (!empty($directory)) { $cond['parts']['directory'] = " AND i.path LIKE '" . serendipity_db_escape_string($directory) . "%'\n"; } else { $cond['parts']['directory'] = " AND i.path = ''\n"; } } if (!empty($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 { $keywords = array(); } } if (count($keywords) > 0) { $cond['parts']['keywords'] = " AND (mk.property IN ('" . serendipity_db_implode("', '", $keywords, 'string') . "'))\n"; $cond['joinparts']['keywords'] = true; } foreach($filter AS $f => $fval) { if (! (isset($orderfields[$f]) || $f == "fileCategory") || empty($fval)) { continue; } $cond['parts']['filter'] = ''; 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'] ?? null) == '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"; } } elseif ($f == 'fileCategory') { switch ($fval) { case 'image': $cond['parts']['filter'] .= " AND (i.mime LIKE 'image/%')\n"; break; case 'video': $cond['parts']['filter'] .= " AND (i.mime LIKE 'video/%')\n"; break; } } else { 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"; } } $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"; } $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'] ?? false) { $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'] ?? false) { $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'] ?? false) { $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'] ?? false) { $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' || $serendipity['dbType'] == 'pdo-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) && $rs !== true && $rs !== 1) { echo '<div>' . $rs . '</div>'; return array(); } elseif (!is_array($rs)) { return array(); } $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 = count($total_rs); } return $rs; } /** * Fetch a specific media item from the mediadatabase * * @access public * @param int The ID of an media item * @return array The media info data */ function serendipity_fetchImageFromDatabase($id, $mode = 'read') { global $serendipity; if (is_array($id)) { $cond = array( 'and' => "WHERE i.id IN (" . serendipity_db_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' || $serendipity['dbType'] == 'pdo-postgres') { $cond['group'] = ''; $cond['distinct'] = 'DISTINCT'; } else { $cond['group'] = 'GROUP BY i.id'; $cond['distinct'] = ''; } if ($mode != 'discard') { 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; } /** * Update a media item * * @access public * @param array An array of columns to update * @param int The ID of an media item to update * @return boolean */ function serendipity_updateImageInDatabase($updates, $id) { global $serendipity; $admin = ''; if (!serendipity_checkPermission('adminImagesAdd')) { $admin = ' AND (authorid = ' . $serendipity['authorid'] . ' OR authorid = 0)'; } $i = 0; if (sizeof($updates) > 0) { $imageBeforeChange = serendipity_fetchImageFromDatabase($id); foreach ($updates AS $k => $v) { $q[] = $k ." = '" . serendipity_db_escape_string($v) . "'"; } serendipity_db_query("UPDATE {$serendipity['dbPrefix']}images SET ". implode(',', $q) ." WHERE id = " . (int)$id . " $admin"); $i++; // Check if this update changes important directory or filename attributes. // If yes, the plugin API will forward this change of files to plugins like // staticpage, so that they can update their contents. if (isset($updates['path']) || isset($updates['realname'])) { if (! isset($updates['path'])) { $updates['path'] = $imageBeforeChange['path']; } if (! isset($updates['realname'])) { $updates['realname'] = $imageBeforeChange['realname']; } // NOTE: Previously, the API supported multiple rename tasks like dir, filedir and file // Now the core will ALWAYS propagate each file change distinctly, never will a directory // be submitted to the API. $eventData = array( // array in array because the event api expects that array( 'type' => 'file', 'oldDir' => $imageBeforeChange['path'] . $imageBeforeChange['realname'], 'newDir' => $updates['path'] . $updates['realname'] ) ); serendipity_plugin_api::hook_event('backend_media_rename', $eventData); } // If the user manually saved the image properties, some values might need updating now // Name and realname are saved there as well, and the title is set to the name by default if (isset($updates['realname'])) { $q = "UPDATE {$serendipity['dbPrefix']}mediaproperties SET value = '" . serendipity_db_escape_string($updates['realname']) . "' WHERE mediaid = " . (int)$imageBeforeChange['id'] . ' AND property = "realname" AND value = "' . $imageBeforeChange['realname'] . '"'; serendipity_db_query($q); $q = "UPDATE {$serendipity['dbPrefix']}mediaproperties SET value = '" . serendipity_db_escape_string($updates['realname']) . "' WHERE mediaid = " . (int)$imageBeforeChange['id'] . ' AND property = "TITLE" AND value = "' . $imageBeforeChange['realname'] .'"'; serendipity_db_query($q); } if (isset($updates['name'])) { $q = "UPDATE {$serendipity['dbPrefix']}mediaproperties SET value = '" . $updates['name'] . "' WHERE mediaid = " . (int)$imageBeforeChange['id'] . ' AND property = "name" AND value = "' . $imageBeforeChange['name'] .'"'; serendipity_db_query($q); } } return $i; } /** * Delete a media item * * @access public * @param int The ID of a media item to delete * @return */ function serendipity_deleteImage($id) { global $serendipity; $dThumb = array(); $messages = ''; $file = serendipity_fetchImageFromDatabase($id); if (!is_array($file)) { $messages .= sprintf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . FILE_NOT_FOUND . "</span>\n", $id); //return false; } else { $dFile = $file['path'] . $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $dThumb = array(array( 'fthumb' => $file['thumbnail_name'] )); if (!serendipity_checkPermission('adminImagesDelete')) { return; } if (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid']) { // A non-admin user may not delete private files from other users. return; } if (!$file['hotlink']) { if (file_exists($serendipity['serendipityPath'] . $serendipity['uploadPath'] . $dFile)) { if (@unlink($serendipity['serendipityPath'] . $serendipity['uploadPath'] . $dFile)) { $messages .= sprintf('<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> ' . DELETE_FILE . "</span>\n", $dFile); } else { $messages .= sprintf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . DELETE_FILE_FAIL . "</span>\n", $dFile); } serendipity_plugin_api::hook_event('backend_media_delete', $dThumb); foreach($dThumb AS $thumb) { $dfnThumb = $file['path'] . $file['name'] . (!empty($thumb['fthumb']) ? '.' . $thumb['fthumb'] : '') . (empty($file['extension']) ? '' : '.' . $file['extension']); $dfThumb = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $dfnThumb; if (@unlink($dfThumb)) { $messages .= sprintf('<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> ' . DELETE_THUMBNAIL . "</span>\n", $dfnThumb); } } } else { $messages .= sprintf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . FILE_NOT_FOUND . "</span>\n", $dFile); } } else { $messages .= sprintf('<span class="msg_hint"><span class="icon-help-circled" aria-hidden="true"></span> ' . DELETE_HOTLINK_FILE . "</span>\n", $file['name']); } serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}images WHERE id = ". (int)$id); serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}mediaproperties WHERE mediaid = ". (int)$id); } return $messages; } /** * Open a directory and fetch all existing media items * * @access public * @param boolean deprecated * @param int deprecated * @param int deprecated * @param array Array list of found items * @param string sub-directory to investigate [recursive use] * @return array List of media items */ function serendipity_fetchImages($group = false, $start = 0, $end = 20, $images = '', $odir = '') { global $serendipity; // Open directory $basedir = $serendipity['serendipityPath'] . $serendipity['uploadPath']; $images = array(); if ($dir = @opendir($basedir . $odir)) { $aTempArray = array(); while (($file = @readdir($dir)) !== false) { if ($file == '.svn' || $file == 'CVS' || $file == '.htaccess' || strtolower($file) == 'thumbs.db' || $file == '.' || $file == '..') { continue; // 2013/06/05 added exclude .htaccess for ckeditor/kcfinder usage and 2013/12/25 added thumbs.db } 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); } } } natsort($images); /* BC */ $serendipity['imageList'] = $images; return $images; } /** * Inserts a hotlinked media file * * hotlinks are files that are only linked in your database, and not really stored on your server * * @access public * @param string The filename to hotlink * @param string The URL to hotlink with * @param int The owner of the hotlinked media item * @param int The timestamp of insertion (unix second) * @param string A temporary filename for fetching the file to investigate it * @return int The ID of the inserted media item */ function serendipity_insertHotlinkedImageInDatabase($filename, $url, $authorid = 0, $time = NULL, $tempfile = NULL) { global $serendipity; if (is_null($time)) { $time = time(); } list($filebase, $extension) = serendipity_parseFileName($filename); if ($tempfile && file_exists($tempfile)) { $filesize = @filesize($tempfile); $fdim = @serendipity_getimagesize($tempfile, '', $extension); $width = $fdim[0]; $height = $fdim[1]; $mime = $fdim['mime']; @unlink($tempfile); } $query = sprintf( "INSERT INTO {$serendipity['dbPrefix']}images ( name, date, authorid, extension, mime, size, dimensions_width, dimensions_height, path, hotlink, realname ) VALUES ( '%s', %s, %s, '%s', '%s', %s, %s, %s, '%s', 1, '%s' )", serendipity_db_escape_string($filebase), (int)$time, (int)$authorid, serendipity_db_escape_string($extension), serendipity_db_escape_string($mime), (int)$filesize, (int)$width, (int)$height, serendipity_db_escape_string($url), serendipity_db_escape_string($filename) ); $sql = serendipity_db_query($query); if (is_string($sql)) { echo '<span class="block_level">' . $query . "</span>\n"; echo '<span class="block_level">' . $sql . "</span>\n"; } $image_id = serendipity_db_insert_id('images', 'id'); if ($image_id > 0) { return $image_id; } return 0; } /** * Insert a media item in the database * * @access public * @param string The filename of the media item * @param string The path to the media item * @param int The owner author of the item * @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, $realname = NULL) { global $serendipity; if (is_null($time)) { $time = time(); } if (is_null($realname)) { $realname = $filename; } $filepath = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $filename; $filesize = @filesize($filepath); list($filebase, $extension) = serendipity_parseFileName($filename); $thumbpath = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $filebase . '.'. $serendipity['thumbSuffix'] . (empty($extension) ? '' : '.' . $extension); $thumbnail = (file_exists($thumbpath) ? $serendipity['thumbSuffix'] : ''); $fdim = @serendipity_getimagesize($filepath, '', $extension); $width = $fdim[0]; $height = $fdim[1]; $mime = $fdim['mime']; $query = sprintf( "INSERT INTO {$serendipity['dbPrefix']}images ( name, extension, mime, size, dimensions_width, dimensions_height, thumbnail_name, date, authorid, path, realname ) VALUES ( '%s', '%s', '%s', %s, %s, %s, '%s', %s, %s, '%s', '%s' )", serendipity_db_escape_string($filebase), serendipity_db_escape_string($extension), serendipity_db_escape_string($mime), (int)$filesize, (int)$width, (int)$height, serendipity_db_escape_string($thumbnail), (int)$time, (int)$authorid, serendipity_db_escape_string($directory), serendipity_db_escape_string($realname) ); $sql = serendipity_db_query($query); if (is_string($sql)) { echo '<span class="block_level">' . $query . "</span>\n"; echo '<span class="block_level">' . $sql . "</span>\n"; } $image_id = serendipity_db_insert_id('images', 'id'); if ($image_id > 0) { return $image_id; serendipity_cleanCache(); } return 0; } /** * Create a thumbnail for an image * * LONG * * @access public * @param string The input image filename * @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, $is_temporary = false, $force_resize = false) { global $serendipity; if ($size === false) { $size = $serendipity['thumbSize']; } if ($size < 1) { return array(0,0); } if ($thumbname === false) { $thumbname = $serendipity['thumbSuffix']; } $t = serendipity_parseFileName($file); $f = $t[0]; $suf = $t[1]; $infile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $directory . $file; #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; } $serendipity['last_outfile'] = $outfile; #echo 'To: ' . $outfile . '<br />'; $fdim = @serendipity_getimagesize($infile, '', $suf); if (isset($fdim['noimage'])) { $r = array(0, 0); } else { if ($serendipity['magick'] !== true) { if (is_array($size)) { // The caller wants a thumbnail with a specific size $r = serendipity_resize_image_gd($infile, $outfile, $size['width'], $size['height']); } else { // The caller wants a thumbnail constrained in the dimension set by config $calc = serendipity_calculate_aspect_size($fdim[0], $fdim[1], $size, $serendipity['thumbConstraint']); $r = serendipity_resize_image_gd($infile, $outfile, $calc[0], $calc[1]); } } else { if (is_array($size)) { if ($fdim[0] > $size['width'] && $fdim[1] > $size['height']) { $r = $size; } else { return array(0,0); // do not create any thumb, if image is smaller than defined sizes } } else { $calc = serendipity_calculate_aspect_size($fdim[0], $fdim[1], $size, $serendipity['thumbConstraint']); $r = array('width' => $calc[0], 'height' => $calc[1]); } $newSize = $r['width'] . 'x' . $r['height']; if ($fdim['mime'] == 'application/pdf') { $cmd = escapeshellcmd($serendipity['convert']) . ' -antialias -flatten -scale '. serendipity_escapeshellarg($newSize) .' '. serendipity_escapeshellarg($infile . '[0]') . ' ' . serendipity_escapeshellarg($outfile . '.png'); } else { 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 \>) } if (!$serendipity['imagemagick_nobang']) $newSize .= '!'; // force the first run image geometry exactly to given sizes, if there were rounding differences (see https://github.com/s9y/Serendipity/commit/94881ba4c0e3bdd4b5fac510e93977e239171c1c and comments) $cmd = escapeshellcmd($serendipity['convert'] . ' ' . $serendipity['imagemagick_thumb_parameters']) . ' -antialias -resize ' . serendipity_escapeshellarg($newSize) . ' ' . serendipity_escapeshellarg($infile) .' '. serendipity_escapeshellarg($outfile); } exec($cmd, $output, $result); if ($result != 0) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) ."</span>\n"; $r = false; // return failure } else { touch($outfile); } unset($output, $result); } } return $r; } /** * Scale an image * * LONG * * @access public * @param int The ID of an image * @param int The target width * @param int The target height * @return true */ 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']) { return; } $infile = $outfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); if ($serendipity['magick'] !== true) { serendipity_resize_image_gd($infile, $outfile, $width, $height); } else { $cmd = escapeshellcmd($serendipity['convert']) . ' -scale ' . serendipity_escapeshellarg($width . 'x' . $height) . ' ' . serendipity_escapeshellarg($infile) . ' ' . serendipity_escapeshellarg($outfile); exec($cmd, $output, $result); if ( $result != 0 ) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) ."</span>\n"; return false; } unset($output, $result); } if ($result == 0) { serendipity_updateImageInDatabase(array('dimensions_width' => $width, 'dimensions_height' => $height, 'size' => @filesize($outfile)), $id); return true; } return false; } /** * Rotate an image * * LONG * * @access public * @param int The ID of an image * @param int Number of degrees to rotate * @return boolean */ 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']) { // A non-admin user may not delete private files from other users. return false; } $infile = $outfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $thumbnails = serendipity_getThumbnails($id); if ($serendipity['magick'] !== true) { serendipity_rotate_image_gd($infile, $outfile, $degrees); foreach($thumbnails as $thumbnail) { $infileThumb = $outfileThumb = $thumbnail; serendipity_rotate_image_gd($infileThumb, $outfileThumb, $degrees); } } else { /* Why can't we just all agree on the rotation direction? */ $degrees = (360 - $degrees); /* rotate main image */ $cmd = escapeshellcmd($serendipity['convert']) . ' -rotate ' . serendipity_escapeshellarg($degrees) . ' ' . serendipity_escapeshellarg($infile) . ' ' . serendipity_escapeshellarg($outfile); exec($cmd, $output, $result); if ( $result != 0 ) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) ."</span>\n"; } unset($output, $result); /* rotate thumbnail */ foreach($thumbnails as $thumbnail) { $infileThumb = $outfileThumb = $thumbnail; $cmd = escapeshellcmd($serendipity['convert']) . ' -rotate ' . serendipity_escapeshellarg($degrees) . ' ' . serendipity_escapeshellarg($infileThumb) . ' ' . serendipity_escapeshellarg($outfileThumb); exec($cmd, $output, $result); if ( $result != 0 ) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . sprintf(IMAGICK_EXEC_ERROR, $cmd, $output[0], $result) ."</span>\n"; } unset($output, $result); } } $fdim = @getimagesize($outfile); serendipity_updateImageInDatabase(array('dimensions_width' => $fdim[0], 'dimensions_height' => $fdim[1]), $id); return true; } /** * Creates thumbnails for all images in the upload dir * * @access public * @return int Number of created thumbnails */ function serendipity_generateThumbs() { global $serendipity; $i = 0; $serendipity['imageList'] = serendipity_fetchImagesFromDatabase(0, 0, $total); $msg_printed = false; foreach ($serendipity['imageList'] AS $k => $file) { $is_image = serendipity_isImage($file); if ($is_image && !$file['hotlink']) { $update = false; $filename = $file['path'] . $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $ffull = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $filename; if (!file_exists($ffull)) { serendipity_deleteImage($file['id']); continue; } if (empty($file['thumbnail_name'])) { $file['thumbnail_name'] = $serendipity['thumbSuffix']; } $oldThumb = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . '.' . $file['thumbnail_name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $newThumb = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . '.' . $serendipity['thumbSuffix'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $sThumb = $file['path'] . $file['name'] . '.' . $serendipity['thumbSuffix'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $fdim = @getimagesize($ffull); if (!file_exists($oldThumb) && !file_exists($newThumb) && ($fdim[0] > $serendipity['thumbSize'] || $fdim[1] > $serendipity['thumbSize'])) { $returnsize = serendipity_makeThumbnail($file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']), $file['path']); if ($returnsize !== false ) { // Only print the resize message the first time if (!$msg_printed) { $resizemedia = sprintf(RESIZE_BLAHBLAH, THUMBNAIL_SHORT); printf('<span class="msg_notice"><span class="icon-info-circled" aria-hidden="true"></span> ' . $resizemedia . "</span>\n"); echo "\n" . '<ul class="serendipityFileList">' . "\n"; $msg_printed = true; } echo '<li>' . $sThumb . ': ' . $returnsize['width'] . 'x' . $returnsize['height'] . "</li>\n"; if (!file_exists($newThumb)) { printf('<li><span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . THUMBNAIL_FAILED_COPY . '</span></li>' . "\n", $sThumb); } else { $update = true; } } } elseif (!file_exists($oldThumb) && !file_exists($newThumb) && $fdim[0] <= $serendipity['thumbSize'] && $fdim[1] <= $serendipity['thumbSize']) { if (!$msg_printed) { $resizethumb = sprintf(RESIZE_BLAHBLAH, THUMB); printf('<span class="msg_notice"><span class="icon-info-circled" aria-hidden="true"></span> ' . $resizethumb . "</span>\n"); echo "\n" . '<ul class="serendipityFileList">' . "\n"; $msg_printed = true; } $res = @copy($ffull, $newThumb); if (@$res === true) { printf('<li>' . THUMBNAIL_USING_OWN . '</li>' . "\n", $sThumb); $update = true; } else { printf('<li><span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . THUMBNAIL_FAILED_COPY . '</span></li>' . "\n", $sThumb); } } if ($update) { $i++; $updates = array('thumbnail_name' => $serendipity['thumbSuffix']); serendipity_updateImageInDatabase($updates, $file['id']); } } else { // Currently, non-image files have no thumbnail. } } // Close the list, if it was created if ($msg_printed) { echo "</ul>\n"; } return $i; } /** * Guess the MIME type of a file * * @access public * @param string Filename extension * @return string Mimetype */ function serendipity_guessMime($extension) { $mime = ''; switch (strtolower($extension)) { case 'jpg': case 'jpeg': $mime = 'image/jpeg'; break; case 'aiff': case 'aif': $mime = 'audio/x-aiff'; break; case 'gif': $mime = 'image/gif'; break; case 'png': $mime = 'image/png'; break; case 'pdf': $mime = 'application/pdf'; break; case 'doc': $mime = 'application/msword'; break; case 'rtf': $mime = 'application/rtf'; break; case 'wav': case 'wave': $mime = 'audio/x-wav'; break; case 'mp2': case 'mpg': case 'mpeg': $mime = 'video/x-mpeg'; break; case 'avi': $mime = 'video/x-msvideo'; break; case 'mp3': $mime = 'audio/x-mpeg3'; break; case 'xlm': case 'xlb': case 'xll': case 'xla': case 'xlw': case 'xlc': case 'xls': case 'xlt': $mime = 'application/vnd.ms-excel'; break; case 'ppt': case 'pps': $mime = 'application/vnd.ms-powerpoint'; break; case 'html': case 'htm': $mime = 'text/html'; break; case 'xsl': case 'xslt': case 'xml': case 'wsdl': case 'xsd': $mime = 'text/xml'; break; case 'zip': $mime = 'application/zip'; break; case 'tar': $mime = 'application/x-tar'; break; case 'tgz': case 'gz': $mime = 'application/x-gzip'; break; case 'swf': $mime = 'application/x-shockwave-flash'; break; case 'rm': case 'ra': case 'ram': $mime = 'application/vnd.rn-realaudio'; break; case 'exe': $mime = 'application/octet-stream'; break; case 'mov': case 'mp4': case 'qt': $mime = 'video/x-quicktime'; break; case 'midi': case 'mid': $mime = 'audio/x-midi'; break; case 'txt': $mime = 'text/plain'; break; case 'qcp': $mime = 'audio/vnd.qcelp'; break; case 'emf': $mime = 'image/x-emf'; break; case 'wmf': $mime = 'image/x-wmf'; break; case 'snd': $mime = 'audio/basic'; break; case 'pmd': $mime = 'application/x-pmd'; break; case 'wbmp': $mime = 'image/vnd.wap.wbmp'; break; case 'gcd': $mime = 'text/x-pcs-gcd'; break; case 'mms': $mime = 'application/vnd.wap.mms-message'; break; case 'ogg': case 'ogm': $mime = 'application/ogg'; break; case 'rv': $mime = 'video/vnd.rn-realvideo'; break; case 'wmv': $mime = 'video/x-ms-wmv'; break; case 'wma': $mime = 'audio/x-ms-wma'; break; case 'qcp': $mime = 'audio/vnd.qcelp'; break; case 'jad': $mime = 'text/vnd.sun.j2me.app-descriptor'; break; case '3g2': case '3gp': $mime = 'video/3gpp'; break; case 'jar': $mime = 'application/java-archive'; break; case 'ico': $mime = 'image/x-icon'; break; case 'svg': $mime = 'image/svg'; break; default: $mime = 'application/octet-stream'; break; } return $mime; } /** * Check all existing thumbnails if they are the right size, insert missing thumbnails * * LONG * * @access public * @return int Number of updated thumbnails */ function serendipity_syncThumbs($deleteThumbs = false) { global $serendipity; $i = 0; $files = serendipity_fetchImages(); $fcount = count($files); for ($x = 0; $x < $fcount; $x++) { $update = array(); $f = serendipity_parseFileName($files[$x]); if (empty($f[1]) || $f[1] == $files[$x]) { // No extension means bad file most probably. Skip it. printf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . SKIPPING_FILE_EXTENSION . "</span>\n", $files[$x]); continue; } $ffull = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $files[$x]; $fthumb = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $f[0] . '.' . $serendipity['thumbSuffix'] . '.' . $f[1]; $sThumb = $f[0] . '.' . $serendipity['thumbSuffix'] . '.' . $f[1]; $fbase = basename($f[0]); $fdir = dirname($f[0]) . '/'; if ($fdir == './') { $fdir = ''; } if (!is_readable($ffull) || filesize($ffull) == 0) { printf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . SKIPPING_FILE_UNREADABLE . "</span>\n", $files[$x]); continue; } $ft_mime = serendipity_guessMime($f[1]); $fdim = serendipity_getimagesize($ffull, $ft_mime); // If we're supposed to delete thumbs, this is the easiest place. Leave messages plain unstiled. if (is_readable($fthumb)) { if ($deleteThumbs === true) { if (@unlink($fthumb)) { printf(DELETE_THUMBNAIL . "<br />\n", $sThumb); $i++; } } else if ($deleteThumbs == 'checksize') { // Find existing thumbnail dimensions $tdim = serendipity_getimagesize($fthumb); if ($tdim['noimage']) { // Delete it so it can be regenerated if (@unlink($fthumb)) { printf(DELETE_THUMBNAIL . "<br />\n", $sThumb); $i++; } } else { // Calculate correct thumbnail size from original image $expect = serendipity_calculate_aspect_size($fdim[0], $fdim[1], $serendipity['thumbSize'], $serendipity['thumbConstraint']); // Check actual thumbnail size if ($tdim[0] != $expect[0] || $tdim[1] != $expect[1]) { // This thumbnail is incorrect; delete it so // it can be regenerated if (@unlink($fthumb)) { printf(DELETE_THUMBNAIL . "<br />\n", $sThumb); $i++; } } } } // else the option is to keep all existing thumbs; do nothing. } // end if thumb exists $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']) . "' AND extension = '" . serendipity_db_escape_string($f[1]) . "'" ); serendipity_ACL_SQL($cond, false, 'directory'); $rs = serendipity_db_query("SELECT * FROM {$serendipity['dbPrefix']}images AS i {$cond['joins']} {$cond['and']}", true, 'assoc'); // Leave messages plain unstiled if (is_array($rs)) { // This image is in the database. Check our calculated data against the database data. $update = array(); // Is the width correct? if (isset($fdim[0]) && $rs['dimensions_width'] != $fdim[0]) { $update['dimensions_width'] = $fdim[0]; } // Is the height correct? if (isset($fdim[1]) && $rs['dimensions_height'] != $fdim[1]) { $update['dimensions_height'] = $fdim[1]; } // Is the image size correct? if ($rs['size'] != filesize($ffull)) { $update['size'] = filesize($ffull); } // Has the thumbnail suffix changed? $checkfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $rs['path'] . $rs['name'] . '.' . $rs['thumbnail_name'] . (empty($rs['extension']) ? '' : '.' . $rs['extension']); if (!file_exists($checkfile) && file_exists($fthumb)) { $update['thumbnail_name'] = $serendipity['thumbSuffix']; } /* Do the database update, if needed */ if (sizeof($update) != 0) { printf(FOUND_FILE . ' (2)<br />', $files[$x]); serendipity_updateImageInDatabase($update, $rs['id']); $i++; } } else { printf(FOUND_FILE . ' (1)<br />', $files[$x]); serendipity_insertImageInDatabase($fbase . '.' . $f[1], $fdir, 0, filemtime($ffull)); $i++; } } return $i; } /** * Wrapper for GDLib functions * * @access public * @param string Filename to operate on * @return string Functionname to execute */ function serendipity_functions_gd($infilename) { if (!function_exists('imagecopyresampled')) { return false; } $func = array(); $inf = pathinfo(strtolower($infilename)); switch ($inf['extension']) { case 'gif': $func['load'] = 'imagecreatefromgif'; $func['save'] = 'imagegif'; $func['qual'] = 100; break; case 'jpeg': case 'jpg': case 'jfif': $func['load'] = 'imagecreatefromjpeg'; $func['save'] = 'imagejpeg'; $func['qual'] = 100; break; case 'png': $func['load'] = 'imagecreatefrompng'; $func['save'] = 'imagepng'; $func['qual'] = 9; break; default: return false; } /* If our loader does not exist, we are doomed */ if (!function_exists($func['load'])) { return false; } /* If the save function does not exist (i.e. read-only GIF), we want to output it as PNG */ if (!function_exists($func['save'])) { if (function_exists('imagepng')) { $func['save'] = 'imagepng'; } else { return false; } } return $func; } /** * Rotate an image (GDlib) * * @access public * @param string Source Filename to rotate * @param string Target file * @param int Degress to rotate * @return array New width/height of the image */ function serendipity_rotate_image_gd($infilename, $outfilename, $degrees) { $func = serendipity_functions_gd($infilename); if (!is_array($func)) { return false; } $in = $func['load']($infilename); $out = imagerotate($in, $degrees, 0); $func['save']($out, $outfilename, $func['qual']); $newwidth = imagesx($out); $newheight = imagesy($out); $out = null; $in = null; return array($newwidth, $newheight); } /** * Resize an image (GDLib) * * @access public * @param string Source Filename to resize * @param string Target file * @param int New width * @return int New height (can be autodetected) * @return array New image size */ function serendipity_resize_image_gd($infilename, $outfilename, $newwidth, $newheight=null) { $func = serendipity_functions_gd($infilename); if (!is_array($func)) { return false; } try { // if an image exist that can not be loaded (invalid gif for example), the page shall still be rendered $in = $func['load']($infilename); } catch (Exception $e) { echo 'Could not create thumbnail: ', $e->getMessage(), "\n"; return false; } $width = imagesx($in); $height = imagesy($in); if (is_null($newheight)) { $newsizes = serendipity_calculate_aspect_size($width, $height, $newwidth, 'width'); $newwidth = $newsizes[0]; $newheight = $newsizes[1]; } if (is_null($newwidth)) { $newsizes = serendipity_calculate_aspect_size($width, $height, $newheight, 'height'); $newwidth = $newsizes[0]; $newheight = $newsizes[1]; } $out = imagecreatetruecolor($newwidth, $newheight); /* Attempt to copy transparency information, this really only works for PNG */ if (function_exists('imagesavealpha')) { imagealphablending($out, false); imagesavealpha($out, true); } imagecopyresampled($out, $in, 0, 0, 0, 0, $newwidth, $newheight, $width, $height); @umask(0000); touch($outfilename); // safe_mode requirement $func['save']($out, $outfilename, $func['qual']); @chmod($outfilename, 0664); $out = null; $in = null; return array($newwidth, $newheight); } /** * Calculate new size for an image, considering aspect ratio and constraint * * @access public * @param int Image width * @param int Image height * @param int Target dimension size * @param string Dimension to constrain ('width', 'height', 'largest', * 'smallest'; defaults to original behavior, 'largest') * @return array An array with the scaled width and height */ function serendipity_calculate_aspect_size($width, $height, $size, $constraint = null) { // Allow for future constraints (idea: 'percent') $known_constraints = array('width', 'height', 'largest', 'smallest'); // Rearrange params for calls from old imageselectorplus plugin if ($size == null) { $size = $constraint; $constraint = 'smallest'; } // Normalize relative constraint types if ($constraint == 'largest' || !in_array($constraint, $known_constraints)) { // Original default behavior, included for backwards compatibility // Constrains largest dimension if ($width >= $height) { $constraint = 'width'; } else { $constraint = 'height'; } } else if ($constraint == 'smallest') { // Only ever called from imageselectorplus plugin, included for // backwards compatibility with its older versions if ($width >= $height) { $constraint = 'height'; } else { $constraint = 'width'; } } // Constraint is now definitely one of the known absolute types, // either 'width' or 'height' if ($constraint == 'height') { // Is the image big enough to resize? if ($height > $size) { // Calculate new size $ratio = $width / $height; $newwidth = round($size * $ratio); // Limit calculated dimension to at least 1px if ($newwidth <= 0) { $newwidth = 1; } $newsize = array($newwidth, $size); } else { // Image is too small to be resized; use original dimensions $newsize = array($width, $height); } } else { // Default constraint is width if ($width > $size) { // Image is big enough to resize $ratio = $height / $width; $newheight = round($size * $ratio); // Limit calculated dimension to at least 1px if ($newheight <= 0) { $newheight = 1; } $newsize = array($size, $newheight); } else { // Do not scale small images $newsize = array($width, $height); } } return $newsize; } /** * Display the list of images in our database * * @access public * @param int The current page number * @param string The HTML linebreak to use after a row of images * @param boolean Is this the ML-Version for managing everything (true), or is it about selecting one image for the editor? (false) * @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 * @param array Map of smarty vars transported into all following templates * @return string Generated HTML */ function serendipity_displayImageList($page = 0, $lineBreak = NULL, $manage = false, $url = NULL, $show_upload = false, $limit_path = NULL, $smarty_vars = array()) { global $serendipity; static $debug = false; $extraParems = serendipity_generateImageSelectorParems(); $serendipity['GET']['only_path'] = serendipity_uploadSecure($limit_path . $serendipity['GET']['only_path'], true); $serendipity['GET']['only_filename'] = serendipity_specialchars(str_replace(array('*', '?'), array('%', '_'), $serendipity['GET']['only_filename'])); $perPage = (!empty($serendipity['GET']['sortorder']['perpage']) ? (int)$serendipity['GET']['sortorder']['perpage'] : 8); while ($perPage % $lineBreak !== 0) { $perPage++; } $start = ($page-1) * $perPage; if ($manage && $limit_path == NULL) { ## SYNCH START ## $aExclude = array("CVS" => true, ".svn" => true, "_vti_cnf" => true); // _vti_cnf to exclude possible added servers frontpage extensions 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 "<span class='block_level'>{$sFile['relpath']} is a directory.</span>"; array_push($paths, $sFile); } else { if ($debug) echo "<span class='block_level'>{$sFile['relpath']} is a file.</span>"; // 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 ($debug) { echo "<p>Image Sync Right: " . serendipity_checkPermission('adminImagesSync') . " Onthefly Sync: " . $serendipity['onTheFlySynch'] . " Hash: " . $serendipity['current_image_hash'] . "!=" . $serendipity['last_image_hash']. "</p>"; } if ($serendipity['onTheFlySynch'] && serendipity_checkPermission('adminImagesSync') && ($debug || ($serendipity['current_image_hash'] != ($serendipity['last_image_hash'] ?? null)))) { $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'] . (empty($sFile['extension']) ? '' : '.' . $sFile['extension']); } $sFileName = $sFile['path'] . $sFile['name'] . (empty($sFile['extension']) ? '' : '.' . $sFile['extension']); if ($debug) { echo "<p>File name is $sFileName, thumbnail is $sThumbNailFile</p>"; } unset($aResultSet[$sKey]); if (isset($aFilesOnDisk[$sFileName])) { unset($aFilesOnDisk[$sFileName]); } else { if ($debug) { echo "<span class='block_level'>Deleting Image {$sFile['id']}</span>"; } 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, false, '(image)|(video)|(audio)/')) { $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'); // set remember filter settings for SetCookie if (!isset($serendipity['GET']['filter'])) { serendipity_restoreVar($serendipity['COOKIE']['filter'], $serendipity['GET']['filter']); } $serendipity['imageList'] = serendipity_fetchImagesFromDatabase( $start, $perPage, $totalImages, // Passed by ref (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']['keywords']) ? $serendipity['GET']['keywords'] : ''), (isset($serendipity['GET']['filter']) ? $serendipity['GET']['filter'] : ''), isset($serendipity['GET']['hideSubdirFiles']) ); $pages = ceil($totalImages / $perPage); $linkPrevious = '?' . $extraParems . '&serendipity[page]=' . ($page-1); $linkNext = '?' . $extraParems . '&serendipity[page]=' . ($page+1); // Keep the inner to be build first. Now add first and last. Has to do with adding $param to $extraParems. $linkFirst = '?' . $extraParems . '&serendipity[page]=' . 1; $linkLast = '?' . $extraParems . '&serendipity[page]=' . $pages; if (is_null($lineBreak)) { $lineBreak = floor(750 / ($serendipity['thumbSize'] + 20)); } $dprops = $keywords = array(); if ($serendipity['parseMediaOverview'] ?? false) { $ids = array(); foreach ($serendipity['imageList'] AS $k => $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; } serendipity_prepareMedia($serendipity['imageList'][$k], $url); if ($serendipity['parseMediaOverview'] ?? false) { $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 { $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']); } serendipity_parseMediaProperties($dprops, $keywords, $serendipity['imageList'][$k], $serendipity['imageList'][$k]['props'], 3, false); } } } $smarty_vars = array_merge($smarty_vars, array( 'limit_path' => $limit_path, 'perPage' => $perPage, 'show_upload' => $show_upload, 'page' => $page, 'pages' => $pages, 'linkFirst' => $linkFirst, 'linkNext' => $linkNext, 'linkPrevious' => $linkPrevious, 'linkLast' => $linkLast, 'extraParems' => $extraParems, 'totalImages' => $totalImages )); return serendipity_showMedia( $serendipity['imageList'], $paths, $url, $manage, $lineBreak, true, $smarty_vars ); } // End serendipity_displayImageList() /** * Generate the url-parameters needed when generating the ML to select an image to add to the editor, * to store the relevant options (like which textarea to add it to) * * @param string Url or Form format */ function serendipity_generateImageSelectorParems($format = 'url') { global $serendipity; $sortParams = array('perpage', 'order', 'ordermode'); $importParams = array('adminModule', 'htmltarget', 'filename_only', 'textarea', 'subpage', 'keywords', 'noBanner', 'noSidebar', 'noFooter', 'showUpload','showMediaToolbar'); $extraParems = ''; $filterParams = $serendipity['GET']['filter'] ?? array(); $standaloneFilterParams = array('only_path', 'only_filename'); $parems = array(); foreach($importParams AS $importParam) { if (isset($serendipity['GET'][$importParam])) { $parems['serendipity[' . $importParam . ']'] = $serendipity['GET'][$importParam]; } } foreach($sortParams AS $sortParam) { serendipity_restoreVar($serendipity['COOKIE']['sortorder_' . $sortParam], $serendipity['GET']['sortorder'][$sortParam]); $parems['serendipity[sortorder]['. $sortParam .']'] = $serendipity['GET']['sortorder'][$sortParam]; } foreach($standaloneFilterParams AS $filterParam) { serendipity_restoreVar($serendipity['COOKIE'][$filterParam], $serendipity['GET'][$filterParam]); if (!empty($serendipity['GET'][$filterParam]) && $serendipity['GET'][$filterParam] != "undefined") { $parems['serendipity[' . $filterParam . ']'] = $serendipity['GET'][$filterParam]; } } foreach($filterParams AS $filterParam => $filterValue) { serendipity_restoreVar($serendipity['COOKIE']['filter'][$filterParam], $serendipity['GET']['filter'][$filterParam]); if (!empty($serendipity['GET']['filter'][$filterParam]) && $serendipity['GET']['filter'][$filterParam] != "undefined") { if (is_array($filterValue)) { foreach($filterValue as $key => $value) { $parems['serendipity[filter][' . $filterParam . '][' . $key . ']'] = $value; } } else { $parems['serendipity[filter][' . $filterParam . ']'] = $filterValue; } } } foreach ($parems as $param => $value) { if ($format == "form") { $extraParems .= '<input type="hidden" name="'. $param .'" value="'. serendipity_specialchars($value) .'">'."\n"; } else { $extraParems .= $param.'='. serendipity_specialchars($value) .'&'; } } return rtrim($extraParems, '&'); } /** * Check if a media item is an image * * @access public * @param array File information * @param boolean Use a strict check that does not list PDFs as an image? * @return boolean True if the file is an image */ function serendipity_isImage(&$file, $strict = false, $allowed = 'image/') { global $serendipity; $file['displaymime'] = $file['mime']; if (! isset($file['imgsrc'])) { return false; } // Strip HTTP path out of imgsrc $file['location'] = $serendipity['serendipityPath'] . preg_replace('@^(' . preg_quote($serendipity['serendipityHTTPPath']) . ')@i', '', $file['imgsrc']); // File is PDF -> Thumb is PNG // Detect PDF thumbs if ($file['mime'] == 'application/pdf' && file_exists($file['location'] . '.png') && $strict == false) { $file['imgsrc'] .= '.png'; $file['displaymime'] = 'image/png'; } return preg_match('@' . $allowed . '@i', $file['displaymime']); } /** * Recursively delete a directory tree * * @access public * @param string The originating directory * @param string The subdirectory * @param boolean Force deleting an directory even if there are files left in it? * @return true */ function serendipity_killPath($basedir, $directory = '', $forceDelete = false) { static $serious = true; if ($handle = @opendir($basedir . $directory)) { $filestack = []; while (false !== ($file = @readdir($handle))) { if ($file != '.' && $file != '..') { if (is_dir($basedir . $directory . $file)) { serendipity_killPath($basedir, $directory . $file . '/', $forceDelete); } else { $filestack[$file] = $directory . $file; } } } @closedir($handle); echo '<span class="msg_notice"><span class="icon-info-circled" aria-hidden="true"></span> '; printf(CHECKING_DIRECTORY, $directory); echo "</span>"; // No, we just don't kill files the easy way. We sort them out properly from the database // and preserve files not entered therein. $files = serendipity_fetchImagesFromDatabase(0, 0, $total, false, false, $directory); if (is_array($files)) { echo "<ul class='plainList'>\n"; foreach($files AS $f => $file) { echo "<li>\n"; if ($serious) { echo serendipity_deleteImage($file['id']); } else { echo $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); } echo "</li>\n"; unset($filestack[$file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension'])]); unset($filestack[$file['name'] . (!empty($file['thumbnail_name']) ? '.' . $file['thumbnail_name'] : '') . (empty($file['extension']) ? '' : '.' . $file['extension'])]); } echo "</ul>\n"; } if (count($filestack) > 0) { if ($forceDelete) { echo "<ul class='plainList'>\n"; foreach($filestack AS $f => $file) { if ($serious && @unlink($basedir . $file)) { printf('<li><span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> ' . DELETING_FILE . ' ' . DONE . "</span></li>\n", $file); } else { printf('<li><span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . DELETING_FILE . ' ' . ERROR . "</span></li>\n", $file); } } echo "</ul>\n"; } else { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . ERROR_DIRECTORY_NOT_EMPTY . "</span>\n"; echo "<ul>\n"; foreach($filestack AS $f => $file) { echo '<li>' . $file . "</li>\n"; } echo "</ul>\n"; } } if ($serious && !empty($directory) && !preg_match('@^.?/?$@', $directory) && @rmdir($basedir . $directory)) { echo '<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> '; printf(DIRECTORY_DELETE_SUCCESS, $directory); echo "</span>\n"; } else { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '; printf(DIRECTORY_DELETE_FAILED, $directory); echo "</span>\n"; } } return true; } /** * Recursively walk a directory tree * * * @access public * @param string The core directory * @param string The subdirectory * @param boolean Only return directories instead of files as well? * @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, $apply_ACL = false, $aExcludeDirs = NULL) { global $serendipity; if ($serendipity['useInternalCache']) { $key = md5($basedir . $dir . $onlyDirs . $pattern . $depth . $max_depth . $apply_ACL . serialize($aExcludeDirs) . $serendipity['serendipityUser']); $files = serendipity_getCacheItem($key); if ($files && $files !== false) { return unserialize($files); } } if ($aExcludeDirs === null) { // add _vti_cnf to exclude possible added servers frontpage extensions // add ckeditor/kcfinders .thumb dir to exclude, since no hook $aExcludeDirs = array("CVS" => true, ".svn" => true, ".thumbs" => true, "_vti_cnf" => true, ".git" => true); } $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 != '..') { $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('\\', '/', serendipity_dirSlash('end', $dir)) . basename($file) . ($bIsDir ? '/' : ''), '/'), 'directory' => $bIsDir ); } } 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); } if ($serendipity['useInternalCache']) { $key = md5($basedir . $dir . $onlyDirs . $pattern . $depth . $max_depth . $apply_ACL . serialize($aExcludeDirs) . $serendipity['serendipityUser']); serendipity_cacheItem($key, serialize($files)); } 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 * * @access public * @param string The directory to delete * @return */ function serendipity_deletePath($dir) { $d = dir($dir); if ($d) { while ($f = $d->read() ){ if ($f != '.' && $f != '..') { if (is_dir($dir . $f)){ serendipity_deletePath($dir . $f . '/'); rmdir($dir . $f); } if (is_file($dir . $f)) { unlink($dir . $f); } } } $d->close(); } } /** * Transform a filename into a valid upload path * * @access public * @param string The input filename * @param boolean Shall all paths be stripped? * @param boolean Shall a trailing slash be appended? * @return string The valid filename */ function serendipity_uploadSecure($var, $strip_paths = true, $append_slash = false) { $var = str_replace(' ', '_', $var); $var = preg_replace('@[^0-9a-z\._/-]@i', '', $var); $var = preg_replace('@\.+$@i', '', $var); # remove trailing dots if ($strip_paths) { $var = preg_replace('@(\.+[/\\\\]+)@', '/', $var); } $var = preg_replace('@^(/+)@', '', $var); if ($append_slash) { if (!empty($var) && substr($var, -1, 1) != '/') { $var .= '/'; } } # truncate extensions to 5 chars # 1) (\.[^./\\\]{5}) up to five chars after a dot are captured # 2) expression matches only if followed by anything that is no dot # and has no / or \ at the end # 3) if epxression matches, everything after the capture group is deleted $var = preg_replace('@(\.[^./\\\]{5})[^./\\\]+$@', '$1', $var); return $var; } /** * Get the imagesize for a file * * @access public * @param string The filename of the image * @param string The mimetype of an image (can be autodetected) * @param string The file extension of an image * @return array The width/height of the file */ function serendipity_getimagesize($file, $ft_mime = '', $suf = '') { if (empty($ft_mime) && !empty($suf)) { $ft_mime = serendipity_guessMime($suf); } if ($ft_mime == 'application/pdf') { $fdim = array(1000,1000,24, '', 'bits'=> 24, 'channels' => '3', 'mime' => 'application/pdf'); } else { $fdim = @getimagesize($file); } if (is_array($fdim)) { if (empty($fdim['mime'])) { $fdim['mime'] = $ft_mime; } if ($fdim['mime'] == 'image/vnd.wap.wbmp' && $ft_mime == 'video/x-quicktime') { // PHP Versions prior to 4.3.9 reported .mov files wrongly as WAP. Fix this and mark the file as 'non-image' with 0x0 dimensions $fdim['mime'] = $ft_mime; } } else { // The file is no image. Return a fake array so that files are inserted (but without a thumb) $fdim = array( 0 => 0, 1 => 0, 'mime' => $ft_mime, 'noimage' => true ); } return $fdim; } /** * Get the available fields of the media database * * @access public * @return array Array with available, sortable fields */ function serendipity_getImageFields() { global $serendipity; if (($serendipity['simpleFilters'] ?? true) !== false) { $x = array( 'i.date' => array('desc' => SORT_ORDER_DATE, 'type' => 'date' ), 'i.name' => array('desc' => SORT_ORDER_NAME, 'type' => 'text' ), ); } else { $x = array( 'i.date' => array('desc' => SORT_ORDER_DATE, 'type' => 'date' ), 'i.name' => array('desc' => SORT_ORDER_NAME, 'type' => 'text' ), 'i.authorid' => array('desc' => AUTHOR, 'type' => 'authors' ), 'i.extension' => array('desc' => SORT_ORDER_EXTENSION, 'type' => 'text' ), '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) : serendipity_specialchars($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; } /** * Escape a shell argument for imagemagick use * * @access public * @param string Input argument * @return string Output argument */ function serendipity_escapeshellarg($string) { return escapeshellarg(str_replace('%', '', $string)); } /** * Move file with all thumbnails to given directory and update database and entries * */ function serendipity_moveFileTo($id, $dir) { global $serendipity; $file = serendipity_fetchImageFromDatabase($id); serendipity_renameFile($id, $file['name'], $dir); serendipity_updateImageInDatabase(array('path' => $dir), $id); serendipity_updateImageInEntries($id, $file); return true; } /** * Rename a media directory * * @access public * @param string Old directory name * @param string New directory name */ function serendipity_renameDir($oldDir, $newDir) { global $serendipity; # Plan: 1. Get id of all images under $oldDir (including those in subdirs) # 2. Move $oldDir to newDir # 3. Update image (path) in database # 3. Update image in entries via serendipity_updateImageInEntries $imgBase = serendipity_dirSlash('end', $serendipity['serendipityPath'] . $serendipity['uploadPath']); $total = null; $images = serendipity_fetchImagesFromDatabase(0, 0, $total, false, false, $oldDir); // Perform ACL renames $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 (! file_exists("${imgBase}${newDir}")) { rename("${imgBase}${oldDir}", "${imgBase}${newDir}"); foreach($images as $image) { serendipity_updateImageInDatabase( array('path' => preg_replace('@' . preg_quote(serendipity_dirSlash('end', $oldDir)) . '@', serendipity_dirSlash('end', $newDir), $image['path'], 1)), // we use preg_replace and not str_replace to be able to limit to exacty one replacement, preventing issues when a path has loops in it $image['id'] ); serendipity_updateImageInEntries($image['id'], $image); } serendipity_cleanCache(); return true; } return false; } /** * Rename a file in the ML (upload folder and database). Also edit entries to use the new name, and move the thumbnails * * @param Id of the image to be renamed * @param The new name (without extension) * @path The new path to be prepended to the new name, if the file is also to be moved * */ function serendipity_renameFile($id, $newName, $path = null) { global $serendipity; $file = serendipity_fetchImageFromDatabase($id); if (!is_array($file) || (!serendipity_checkPermission('adminImagesMaintainOthers') && $file['authorid'] != '0' && $file['authorid'] != $serendipity['authorid'])) { return; } $oldName = $file['name']; if (LANG_CHARSET == 'UTF-8') { $newName = utf8_encode($newName); } if ($path === null) { $path = $file['path']; } $newName = serendipity_uploadSecure(serendipity_makeFilename($newName), true); $imgBase = $serendipity['serendipityPath'] . $serendipity['uploadPath']; $newPath = $imgBase . $path . $newName . (empty($file['extension']) ? '' : '.' . $file['extension']); if (serendipity_isActiveFile($newPath)) { return sprintf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . ERROR_FILE_FORBIDDEN . "</span>\n", $newName); } if (file_exists($newPath)) { return sprintf('<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . ERROR_FILE_EXISTS . "</span>\n", $newName); } if (rename("{$imgBase}{$file['path']}{$file['realname']}", $newPath)) { # if renaming was successfull, rename thumbnails and update # databases and entries serendipity_renameThumbnails($id, "{$path}$newName"); serendipity_updateImageInDatabase(array('name' => $newName, 'realname' => basename($newPath)), $id); serendipity_updateImageInEntries($id, $file); } else { return '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ' . MEDIA_RENAME_FAILED . "</span>\n"; } serendipity_cleanCache(); return TRUE; } /** * Rename thumbnails linked to $id * * */ function serendipity_renameThumbnails($id, $newName) { global $serendipity; $file = serendipity_fetchImageFromDatabase($id); $thumbnails = serendipity_getThumbnails($id); foreach($thumbnails as $thumbnail) { $newThumbnail = str_replace("{$file['path']}{$file['name']}", $newName, $thumbnail); rename($thumbnail, $newThumbnail); $eventData = array( // array in array because the event api expects that array( 'type' => 'file', // TODO: Use proper preg_quote 'oldDir' => preg_replace('@' . $serendipity['serendipityPath'] . $serendipity['uploadPath'] . '@', '', $thumbnail), 'newDir' => preg_replace('@' . $serendipity['serendipityPath'] . $serendipity['uploadPath'] . '@', '', $newThumbnail) ) ); serendipity_plugin_api::hook_event('backend_media_rename', $eventData); } return true; } /** * Get an array of existing thumbnails linked to the image with the given id * * */ function serendipity_getThumbnails($id) { global $serendipity; $file = serendipity_fetchImageFromDatabase($id); $name = $file['name']; $imagePath = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['realname']; $thumbnailBase = str_replace($file['extension'], '', $imagePath); $uploadDir = dirname($imagePath); return glob("$thumbnailBase*{$file['thumbnail_name']}.{$file['extension']}"); } /** * Set image references to current path in all articles linking to them via the ML * * @id is the id of the image, used for identification * @old is the old file property array of the image, used to keep a manual set link * */ function serendipity_updateImageInEntries($id, $old) { global $serendipity; $file = serendipity_fetchImageFromDatabase($id); $imageHTTPPath = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $file['path'] . $file['realname']; $oldImageHTTPPath = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $old['path'] . $old['realname']; $thumbnailHTTPPath = str_replace(".{$file['extension']}", ".{$file['thumbnail_name']}.{$file['extension']}", $imageHTTPPath); $thumbSuffix = $serendipity['thumbSuffix']; $q = "SELECT id, body, extended FROM {$serendipity['dbPrefix']}entries WHERE body LIKE '%<!-- s9ymdb:$id -->%' OR extended LIKE '%<!-- s9ymdb:$id -->%'"; $entries = serendipity_db_query($q, false, 'assoc'); if (is_array($entries) && !empty($entries)) { foreach($entries as $entry) { # First change the img element, be it a thumbnail or an image $pattern = "@(<!-- s9ymdb:$id -->[^>]*) src=[\"']([^'\"]+)[\"']@"; $callback = function($matches) use ($imageHTTPPath, $thumbnailHTTPPath, $file, $thumbSuffix) { if (strpos($matches[2], "$thumbSuffix.{$file['extension']}") === false) { // the image showed the full size image return $matches[1] . ' src="' . $imageHTTPPath . '"'; } else { return $matches[1] . ' src="' . $thumbnailHTTPPath . '"'; } }; $entry['body'] = preg_replace_callback($pattern, $callback, $entry['body']); $entry['extended'] = preg_replace_callback($pattern, $callback, $entry['extended']); # But we should not forget to update the a element $pattern = "@href=[\"']([^'\"]+)[\"']([^>]*)>(<!-- s9ymdb:$id -->)@"; $callback = function($matches) use ($imageHTTPPath, $thumbnailHTTPPath, $oldImageHTTPPath) { # We only update the link if it is not a manual link, if it pointed to the old image location if ($matches[1] === $oldImageHTTPPath) { return 'href="' . $imageHTTPPath . '"' . $matches[2] . '>' . $matches[3]; } else { return 'href="' . $matches[1] . '"' . $matches[2] . '>' . $matches[3]; } }; $entry['body'] = preg_replace_callback($pattern, $callback, $entry['body']); $entry['extended'] = preg_replace_callback($pattern, $callback, $entry['extended']); $uq = "UPDATE {$serendipity['dbPrefix']}entries SET body = '" . serendipity_db_escape_string($entry['body']) . "' , extended = '" . serendipity_db_escape_string($entry['extended']) . "' WHERE id = " . serendipity_db_escape_string($entry['id']); serendipity_db_query($uq); } } } /** * 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 "<span class='block_level'>Applying ACL for mode '$type'.</span>"; } if (!is_array($paths)) { return true; } $startCount = count($paths); if (!isset($serendipity['enableACL']) || $serendipity['enableACL'] == true) { // Check if we are a cool superuser. Bail out if we are. $logged_in = serendipity_userLoggedIn(); if ($logged_in && 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. if ($logged_in) { $my_groups =& serendipity_getGroups($serendipity['authorid']); $acl_allowed_groups = array(); foreach($my_groups AS $my_group) { $acl_allowed_groups[$my_group['id']] = true; } } else { // Only the 'ALL AUTHORS' group is valid for non-logged in authors. $acl_allowed_groups = array(0 => true); } // Iterate every ACL and check if we are allowed to use it. Parse that data into a workable array. $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 '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ACL for ' . $info['relpath'] . " DENIED.</span>"; } unset($paths[$idx]); } else { if ($debug) { echo '<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> ACL for ' . $info['relpath'] . " granted.</span>"; } } } if (count($paths) < $startCount) { if ($debug) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> ACL denied all.</span>'; } 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 { if ($sExtension) { $sThumbNailPath = $sRelativePath . $sName . (!empty($sThumbName) ? '.' . $sThumbName : '') . '.' . $sExtension; } else { $sThumbNailPath = $sRelativePath . $sName . (!empty($sThumbName) ? '.' . $sThumbName : ''); } } 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 string Generated HTML * */ 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[' . serendipity_specialchars($imgsel_key) . ']" value="' . serendipity_specialchars($imgsel_val) . '">' . "\n"; } } $dprops = explode(';', $serendipity['mediaProperties']); $keywords = explode(';', $serendipity['mediaKeywords']); $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'] ?? null)) { $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, 'case_add' => is_array($new_media[0]['created_thumbnail'] ?? null) // created_thumbnail is only set when viewing properties after adding an image ); return serendipity_showMedia( $show, $mirror, '', 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]] ?? null, true); $propkey = serendipity_specialchars($parts[0]) . ($idx ?? ''); $media['base_property'][$propkey] = array( 'label' => serendipity_specialchars(defined('MEDIA_PROPERTY_' . strtoupper($parts[0])) ? constant('MEDIA_PROPERTY_' . strtoupper($parts[0])) : $parts[0]), 'type' => $type, 'val' => $val, 'title' => serendipity_specialchars($parts[0]) ); if (!is_array($GLOBALS['IPTC'] ?? null)) { // Your templates config.inc.php or any of the language files can declare this variable, // if you want to use other default settings for this. No interface ability to declare this // yet, sorry. $GLOBALS['IPTC'] = array( 'DATE' => array('DateCreated'), 'RUN_LENGTH' => array('RunLength'), 'DPI' => array('XResolution'), 'COPYRIGHT' => array('Creator'), 'TITLE' => array('Title', 'ObjectName'), 'COMMENT1' => array('Description'), 'ALT' => array('Title', 'ObjectName'), 'COMMENT2' => array('Keywords', 'PhotoLocation') ); } $default_iptc_val = null; if (empty($val)) { switch($parts[0]) { case 'DATE': $default_iptc_val = serendipity_serverOffsetHour(); case 'RUN_LENGTH': if (!isset($default_iptc_val)) { $default_iptc_val = '00:00:00.00'; } case 'DPI': if (!isset($default_iptc_val)) { $default_iptc_val = '72'; } case 'COPYRIGHT': if (!isset($default_iptc_val)) { $default_iptc_val = $serendipity['serendipityUser']; } case 'TITLE': if (!isset($default_iptc_val)) { $default_iptc_val = $media['realname']; } case 'ALT': if (!isset($default_iptc_val)) { $default_iptc_val = ''; } case 'COMMENT1': if (!isset($default_iptc_val)) { $default_iptc_val = ''; } case 'COMMENT2': if (!isset($default_iptc_val)) { $default_iptc_val = ''; } $media['base_property'][$propkey]['val'] = serendipity_pickKey($media['metadata'], 'Keywords', ''); $new_iptc_val = false; foreach($GLOBALS['IPTC'][$parts[0]] AS $iptc_key) { if (empty($new_iptc_val)) { $new_iptc_val = serendipity_pickKey($media['metadata'], $iptc_key, ''); } } if (empty($new_iptc_val)) { $new_iptc_val = $default_iptc_val; } if ($parts[0] == 'DATE') { $media['base_property'][$propkey]['val'] = serendipity_strftime(DATE_FORMAT_SHORT, $new_iptc_val); } else { $media['base_property'][$propkey]['val'] = $new_iptc_val; } 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' => serendipity_specialchars($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($use_property_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']); } } if (is_array($serendipity['POST']['mediaKeywords'])) { 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) ? serendipity_db_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, set when using the ML for inserting images * @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']); if (! $file['hotlink']) { $file['full_thumb'] = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $sThumbSource; $file['full_thumbHTTP'] = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $sThumbSource; } $file['url'] = $url; 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'] . (empty($file['extension']) ? '' : '.' . $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'] : '') . (empty($file['extension']) ? '' : '.' . $file['extension']); } } // Detect PDF thumbs if (file_exists($file['full_thumb'] . '.png')) { $file['full_thumb'] .= '.png'; $file['full_thumbHTTP'] .= '.png'; $file['show_thumb'] .= '.png'; $sThumbSource .= '.png'; } if (empty($file['realname'])) { $file['realname'] = $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); } $file['diskname'] = $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $file['links'] = array('imagelinkurl' => $file['full_file']); $file['realfile'] = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . (empty($file['extension']) ? '' : '.' . $file['extension']); $file['dim'] = @getimagesize($file['full_thumb'], $file['thumb_header']); $file['dim_orig'] = @getimagesize($file['realfile'], $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'; } 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]; } elseif ($file['is_image'] && $file['hotlink']) { $sizes = serendipity_calculate_aspect_size($file['dimensions_width'], $file['dimensions_height'], $serendipity['thumbSize'], $serendipity['thumbConstraint']); $file['thumbWidth'] = $sizes[0]; $file['thumbHeight'] = $sizes[1]; /* 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['mimeicon'] = $mimeicon; } $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 * @return string Generated HTML * */ function serendipity_showMedia(&$file, &$paths, $url = '', $manage = false, $lineBreak = 3, $enclose = true, $smarty_vars = array()) { global $serendipity; $form_hidden = ''; // do not add, if not for the default media list form if (($serendipity['GET']['adminAction'] == 'default' || empty($serendipity['GET']['adminAction'])) && ! ($serendipity['GET']['fid'] ?? null)) { foreach($serendipity['GET'] AS $g_key => $g_val) { // do not add token, since this is assigned separately to properties and list forms if (!is_array($g_val) && $g_key != 'page' && $g_key != 'token') { $form_hidden .= ' <input type="hidden" name="serendipity[' . $g_key . ']" value="' . serendipity_specialchars($g_val) . '">'."\n"; } } } // Add variables to the hidden form, so they persist in the ML popup after an item as been uploaded and the // form filter gets used if ($serendipity['GET']['adminAction'] == 'add') { $target_fields = ['textarea', 'htmltarget', 'filename_only', 'noBanner', 'noSidebar', 'noFooter', 'showMediaToolbar', 'multiselect', 'showUpload']; foreach ($target_fields as $target_field) { if (isset($serendipity['GET'][$target_field])) { $form_hidden .= ' <input type="hidden" name="serendipity[' . $target_field . ']" value="' . serendipity_specialchars($serendipity['GET'][$target_field]) . '">'."\n"; } } $form_hidden .= ' <input type="hidden" name="serendipity[adminModule]" value="media">'."\n"; } if (!is_object($serendipity['smarty'])) { serendipity_smarty_init(); } $order_fields = serendipity_getImageFields(); $media = array( 'manage' => $manage, 'multiperm' => serendipity_checkPermission('adminImagesDirectories'), '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' => empty($smarty_vars['limit_path']) ? '' : basename($smarty_vars['limit_path']), 'only_path' => $serendipity['GET']['only_path'] ?? null, 'only_filename' => $serendipity['GET']['only_filename'] ?? null, 'sortorder' => $serendipity['GET']['sortorder'] ?? null, 'keywords_selected' => $serendipity['GET']['keywords'] ?? null, 'filter' => $serendipity['GET']['filter'] ?? ['fileCategory' => null], 'sort_order' => $order_fields, 'simpleFilters' => $serendipity['simpleFilters'] ?? true, 'hideSubdirFiles' => $serendipity['GET']['hideSubdirFiles'] ?? null, 'authors' => serendipity_fetchUsers(), 'sort_row_interval' => array(8, 16, 50, 100), 'nr_files' => count($file), 'keywords' => explode(';', $serendipity['mediaKeywords']), 'thumbSize' => $serendipity['thumbSize'], 'multiselect' => isset($serendipity['GET']['multiselect']) ? serendipity_db_bool($serendipity['GET']['multiselect']) : true, 'sortParams' => array('perpage', 'order', 'ordermode') ); $media = array_merge($media, $smarty_vars); $media['files'] =& $file; if (is_array($paths) && count($paths) > 0) { $media['paths'] =& $paths; } else { $media['paths'] =& serendipity_getMediaPaths(); } if (! isset($media['filter']['fileCategory']) ) { $media['filter']['fileCategory'] = null; } $serendipity['smarty']->assignByRef('media', $media); if ($enclose) { serendipity_smarty_fetch('MEDIA_ITEMS', 'admin/media_items.tpl'); return serendipity_smarty_show('admin/media_pane.tpl'); } else { serendipity_smarty_fetch('MEDIA_ITEMS', 'admin/media_items.tpl'); return serendipity_smarty_show('admin/media_properties.tpl'); } } /** * 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[1] > 0) ? ($parts[0] / $parts[1]) : 0; break; case 'or': if ($item == '1') { return 'Landscape'; } else { return 'Portrait'; } case 'date': return strtotime($item); break; case 'date2': $parts = preg_split('&[ :]&', $item); return mktime($parts[3], $parts[4], $parts[5], $parts[1], $parts[2], $parts[0]); break; case 'IPTCdate': preg_match('@(\d{4})(\d{2})(\d{2})@',$item,$parts); return mktime(0, 0, 0, intval($parts[2]), intval($parts[3]), intval($parts[1])); break; case 'IPTCtime': preg_match('@(\d{2})(\d{2})(\d{2})([\+-])(\d{2})(\d{2})@',$item,$parts); $time = serendipity_strftime("%H:%M",mktime(intval($parts[1]), intval($parts[2]), intval($parts[3]), 0, 0, 0)); $timezone = serendipity_strftime("%H:%M",mktime(intval($parts[5]), intval($parts[6]), 0, 0, 0, 0)); return $time." GMT".$parts[4].$timezone; 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' => 'IPTCDateCreated', '2#060' => 'IPTCTimeCreated', '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]); } switch ($desc) { case 'IPTCDateCreated': $ret['IPTC'][$desc] = serendipity_metaFieldConvert($ret['IPTC'][$desc],'IPTCdate'); break; case 'IPTCTimeCreated': $ret['IPTC'][$desc] = serendipity_metaFieldConvert($ret['IPTC'][$desc],'IPTCtime'); break; } } } } 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 . (empty($extension) ? '' : '.' . $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 . (empty($extension) ? '' : '.' . $extension))) { $cnum = md5(time() . $filebase); } // Those variables are passed by reference! $tfile = $filebase . $cnum . (empty($extension) ? '' : '.' . $extension); $target = $dir . $tfile; if ($echo) { echo '<span class="msg_success"><span class="icon-ok-circled" aria-hidden="true"></span> <strong>'; printf(FILENAME_REASSIGNED . '<br />', serendipity_specialchars($tfile)); echo "</strong></span>\n"; } 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']) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '; printf(MEDIA_UPLOAD_SIZEERROR . '<br />', (int)$serendipity['maxFileSize']); echo "</span>\n"; 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']) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '; printf(MEDIA_UPLOAD_DIMERROR . '<br />', (int)$serendipity['maxImgWidth'], (int)$serendipity['maxImgHeight']); echo "</span>\n"; return false; } } if (!empty($serendipity['maxImgHeight'])) { if ($dim[1] > $serendipity['maxImgHeight']) { echo '<span class="msg_error"><span class="icon-attention-circled" aria-hidden="true"></span> '; printf(MEDIA_UPLOAD_DIMERROR . '<br />', (int)$serendipity['maxImgWidth'], (int)$serendipity['maxImgHeight']); echo "</span>\n"; return false; } } } return true; } /** * DEPRECATED. Use the functions serendipity_renameDir and serendipity_moveFile instead. This is only * a wrapper around those, for backwards compatibility * * Moves a media directory * * @param string The old directory. * This can be NULL or (an empty / a) STRING for re-name/multiCheck move comparison events * @param string The new directory * @param string The type of what to remove (dir|file|filedir) * @param string An item id of a file * @param array Result of serendipity_fetchImageFromDatabase($id) * @return boolean * */ function serendipity_moveMediaDirectory($oldDir, $newDir, $type = 'dir', $item_id = null, $file = null) { global $serendipity; if ($type == 'dir') { serendipity_renameDir($oldDir, $newDir); } else { return serendipity_moveFileTo($item_id, $newDir); } } /** * Show the Media Library * * @access public * @param bool default false * @param array $smarty_vars * @return string Image list */ function showMediaLibrary($addvar_check = false, $smarty_vars = array()) { global $serendipity; if (!serendipity_checkPermission('adminImagesView')) { return; } $output = ''; // After upload, do not show the list to be able to proceed to // media selection. if ($addvar_check && !empty($GLOBALS['image_selector_addvars'])) { return true; } if (!isset($serendipity['thumbPerPage'])) { $serendipity['thumbPerPage'] = 2; } $smarty_vars = array( 'textarea' => isset($serendipity['GET']['textarea']) ? $serendipity['GET']['textarea'] : false, 'htmltarget' => isset($serendipity['GET']['htmltarget']) ? $serendipity['GET']['htmltarget'] : '', 'filename_only' => isset($serendipity['GET']['filename_only']) ? $serendipity['GET']['filename_only'] : false, ); if (isset($serendipity['GET']['showUpload'])) { $show_upload = $serendipity['GET']['showUpload']; } else { $show_upload = $serendipity['GET']['showUpload'] = false; } $output .= serendipity_displayImageList( isset($serendipity['GET']['page']) ? $serendipity['GET']['page'] : 1, $serendipity['thumbPerPage'], isset($serendipity['GET']['showMediaToolbar']) ? serendipity_db_bool($serendipity['GET']['showMediaToolbar']) : true, NULL, $show_upload, NULL, $smarty_vars ); return $output; } /** * Gets all available media directories * * @return array * */ function &serendipity_getMediaPaths() { global $serendipity; $aExclude = array("CVS" => true, ".svn" => true, "_vti_cnf" => true); // add _vti_cnf to exclude possible added servers frontpage extensions 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; } /** * Checks whether a user has access to write into a directory * * @access public * @param string Directory to check * @return boolean */ function serendipity_checkDirUpload($dir) { global $serendipity; /* if (serendipity_checkPermission('adminImagesMaintainOthers')) { return true; } */ $allowed = serendipity_ACLGet(0, 'directory', 'write', $dir); $mygroups = serendipity_checkPermission(null, null, true); // Usergroup "0" always means that access is granted. If no array exists, no ACL restrictions have been set and all is fine. if (!is_array($allowed) || isset($allowed[0])) { return true; } if (!is_array($mygroups)) { return true; } foreach($mygroups AS $grpid => $grp) { if (isset($allowed[$grpid])) { return true; break; } } return false; }