<?php
/*
* Copyright (C) 2017 Karmabunny Pty Ltd.
*
* This file is a part of SproutCMS.
*
* SproutCMS is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either
* version 2 of the License, or (at your option) any later version.
*
* For more information, visit <http://getsproutcms.com>.
*/
namespace Sprout\Helpers;
use Exception;
use InvalidArgumentException;
use Kohana;
use karmabunny\pdb\Exceptions\RowMissingException;
/**
* Methods for working with files, including images
*/
{
/**
* Gets the details of a file using its id.
*
* Uses a prepared statement for speed when doing repeated queries.
*
* N.B. If the file entry doesn't exist, a reference to 'missing.png' is returned
*
* @param int $id The ID in the files table
* @return array Contains keys 'filename' and 'date_file_modified'
*/
public static function getDetails($id)
{
static $prepared_q = null;
if (!$prepared_q) {
$q = "SELECT filename, date_file_modified FROM ~files WHERE id = ?";
$prepared_q = Pdb::prepare($q);
}
try {
return Pdb::execute($prepared_q, [$id], 'row');
} catch (RowMissingException $ex) {
return ['filename' => 'missing.png', 'date_file_modified' => '1970-01-01 00:00:00'];
}
}
/**
* For a given file, returns the filename to use to retrieve a resized version of the file
*
* @param string $original Original image name, e.g. 123_example.jpg
* @param string $size_name One of the 'file.image_transformations' config options, e.g. 'small'
* @param string $force_ext If set, extension is set to this
* @return string Name of resized file, e.g. 123_example.small.jpg
*/
static function getResizeFilename($original, $size_name, $force_ext = null)
{
$file_noext = implode('.', $parts);
if ($force_ext) {
$ext = $force_ext;
}
return "{$file_noext}.{$size_name}.{$ext}";
}
/**
* Gets the (final) extension from a file name, in lowercase
* @param string $filename Full filename, e.g. 'image.large.jpg', '/path/to/image.png'
* @return string Extension, excluding leading dot, e.g. 'jpg', 'png'
*/
static function getExt($filename)
{
}
/**
* Determines the file type from a file name by examining its extension
* @param string $filename The file name
* @return int One of the FileConstants::TYPE_* values, see {@see FileConstants}.
* If the type couldn't be determined, FileConstants::TYPE_OTHER is returned.
*/
{
$ext = self::getExt($filename);
foreach (FileConstants::$type_exts as $type => $exts) {
if (in_array($ext, $exts)) return $type; }
return FileConstants::TYPE_OTHER;
}
/**
* For a given file, returns the name without an ext
*
* @param string Full filename
* @return string Base part of filename
**/
static function getNoext($original)
{
}
/**
* Converts a file size, in bytes, into a human readable form (with trailing kb, mb, etc)
*
* @param int $size Size in bytes
* @return string
**/
static function humanSize($size)
{
static
$types = array(' bytes', ' kb', ' mb', ' gb', ' tb');
$type = 0;
while ($size > 1024) {
$size /= 1024;
$type++;
if ($type > 5) break;
}
return round($size, 1) . $types[$type]; }
/**
* Make a filename sane - strip lots of characters which create problems
*
* @param string $filename Filename which may or may not already be sane
* @return string Sane filename
**/
static function filenameMakeSane($filename)
{
$ext = '';
$filename = trim($filename, '_');
if ($filename == '') $filename = time();
return $filename;
}
/**
* Return the backend library to use for many file operations
*
* @return FilesBackend
**/
private static function backend()
{
static $backend;
if ($backend === null) {
$backend = new FilesBackendDirectory();
}
return $backend;
}
/**
* Returns the public URL for a given file. Does not include domain.
*
* @param string $filename The name of the file in the repository
* @return string
**/
public static function url($filename)
{
return self::backend()->absUrl($filename);
}
/**
* Returns the relative public URL for a given file.
* Doesn't contain ROOT/ or domain. Use for content areas.
*
* @param string $filename The name of the file in the repository
* @return string
**/
public static function relUrl($filename)
{
return self::backend()->relUrl($filename);
}
/**
* Returns the public URL for a given file, including domain.
*
* @param string $filename The name of the file in the repository
* @return string
**/
public static function absUrl($filename)
{
return self::backend()->absUrl($filename);
}
/**
* Returns the relative URL for a dynamically resized image.
*
* Size formatting is as per {@see File::parseSizeString}, e.g. c400x300
*
* @param int $id ID or filename from record in files table
* @param string $size A code as per {@see File::parseSizeString}
* @return string HTML-safe relative URL, e.g. file/resize/c400x300/123_example.jpg
*/
public static function resizeUrl($id, $size)
{
return self::backend()->resizeUrl($id, $size);
}
/**
* Gets the relative URL for a fixed or dynamically resized image
*
* @param int $id ID or filename from record in files table
* @param string $size_name The size you want, e.g. 'small', 'banner', 'c100x100', etc.
* The value can either be a size name from the 'file.image_transformations' config option,
* or be a resize code as per {@see File::parseSizeString}
* @param string $force_ext Force the ext to a specific value, e.g. 'jpg'
* @param bool $create_if_missing For numeric size names (e.g. 'c100x100'), causes a resize for any missing files
* @return string File URL, e.g. 'file/download/123/small' or 'files/123_test.c100x100.jpg'
*/
public static function sizeUrl($id, $size_name, $force_ext = null, $create_if_missing = false)
{
return "file/download/{$id}/{$size_name}";
}
$file_details = File::getDetails($id); $filename = $file_details['filename'];
} else {
$filename = $id;
if (!self::exists($filename)) {
try {
$filename = File::lookupReplacementName($filename); } catch (Exception $ex) {
return 'files/missing.png';
}
}
}
$url = File::getResizeFilename($filename, $size_name, $force_ext);
$pattern = '/^[crm][0-9]+x[0-9]+(?:-[lcr][tcb](?:~[0-9]+)?)?$/';
if ($create_if_missing and
preg_match($pattern, $size_name) and
!File::exists($url)) { File::createSize($filename, $size_name, $force_ext); }
return File::relUrl($url); }
/**
* Returns TRUE if the file exists, and FALSE if it does not
*
* @param string $filename The name of the file in the repository. Deprecated: an ID value is also accepted in order
* to support older modules; such modules need to be updated to avoid an extra database lookup.
* @return bool TRUE if the file exists, and FALSE if it does not
**/
public static function exists($filename)
{
$id = $filename;
try {
$filename = Pdb::q('SELECT filename FROM ~files WHERE id = ?', [$id], 'val');
} catch (RowMissingException $ex) {
return false;
}
}
return self::backend()->exists($filename);
}
/**
* Returns the size, in bytes, of the specified file
*
* @param string $filename The name of the file in the repository
* @return int File size in bytes
**/
public static function size($filename)
{
if (!self::exists($filename)) {
try {
$filename = File::lookupReplacementName($filename); } catch (RowMissingException $ex) {
// No problem, return original (broken) URL
}
}
return self::backend()->size($filename);
}
/**
* Returns the modified time, in unix timestamp format, of the specified file
*
* @param string $filename The name of the file in the repository
* @return int Modified time as a unix timestamp
**/
public static function mtime($filename)
{
return self::backend()->mtime($filename);
}
/**
* Sets access and modification time of file
* @return bool True if successful
**/
public static
function touch($filename) {
return self::backend()->touch($filename); }
/**
* Returns the size of an image, or false on failure.
*
* Output format is the same as getimagesize, but will be at a minimum:
* [0] => width, [1] => height, [2] => type
*
* @param string $filename The name of the file in the repository
* @return array
**/
public static function imageSize($filename)
{
return self::backend()->imageSize($filename);
}
/**
* Delete a file
* If the file is an image, any resized variants (e.g. 'small', 'medium' etc.) are deleted too
* @param string $filename The name of the file in the repository, e.g. '123_some_image.jpg'
* @return bool True if the deletion of the main file succeeded
*/
public static function delete($filename)
{
File::deleteCache($filename); $ext = File::getExt($filename); $base = File::getNoExt($filename); $transforms = Kohana::config('file.image_transformations');
foreach ($transforms as $type => $params) {
self::backend()->delete("{$base}.{$type}.{$ext}");
}
return self::backend()->delete($filename);
}
/**
* Delete cached versions of a file
*
* @param string $filename The name of the file in the repository
**/
public static function deleteCache($filename)
{
$files = glob(APPPATH
. 'cache/resize-*-' . $filename); foreach ($files as $f) {
}
}
/**
* Returns all files which match the specified mask.
* I have a feeling this returns other sizes (e.g. .small) as well - which may not be ideal.
*
* @param string $mask Files to find. Supports wildcards such as * and ?
**/
public static
function glob($mask) {
return self::backend()->glob($mask); }
/**
* This is the equivalent of the php readfile function
*
* @param string $filename The name of the file in the repository
**/
public static
function readfile($filename) {
return self::backend()->readfile($filename); }
/**
* Returns file content as a string. Basically the same as file_get_contents
*
* @param string $filename The name of the file in the repository
* @return string $content The content
**/
public static function getString($filename)
{
return self::backend()->getString($filename);
}
/**
* Saves file content as a string. Basically the same as file_put_contents
*
* @param string $filename The name of the file in the repository
* @param string $content The content
* @return bool True on success, false on failure
**/
public static function putString($filename, $content)
{
return self::backend()->putString($filename, $content);
}
/**
* Saves file content from a stream. Basically just fopen/stream_copy_to_stream/fclose
*
* @param string $filename The name of the file in the repository
* @param resource $stream The stream to copy content from
* @return bool True on success, false on failure
**/
public static function putStream($filename, $stream)
{
return self::backend()->putStream($filename, $stream);
}
/**
* Saves file content from an existing file
*
* @param string $filename The name of the file in the repository
* @param string $existing The existing file on disk
* @return bool True on success, false on failure
**/
public static function putExisting($filename, $existing)
{
return self::backend()->putExisting($filename, $existing);
}
/**
* Moves an uploaded file into the repository.
* Returns TRUE on success, FALSE on failure.
*
* @param string $src Source filename
* @param string $filename The name of the file in the repository
* @return bool True on success, false on failure
**/
public static function moveUpload($src, $filename)
{
return self::backend()->moveUpload($src, $filename);
}
/**
* Create a copy of the file in a temporary directory.
* Don't forget to File::destroy_local_copy($temp_filename) when you're done!
*
* @param string $filename The file to copy into a temporary location
* @return string Temp filename or NULL on error
**/
public static function createLocalCopy($filename)
{
return self::backend()->createLocalCopy($filename);
}
/**
* Remove a local copy of a file
* Call this once you're done with the local copy
*
* @param string $temp_filename The filename returned by createLocalCopy
**/
public static function cleanupLocalCopy($temp_filename)
{
return self::backend()->cleanupLocalCopy($temp_filename);
}
/**
* Searches the whole database to find all records in all columns
* which contain a given filename.
*
* The search looks in VARCHAR columns with more than 200 chars (exact match)
* and in TEXT columns (contains match)
*
* Return value is an array of matches, in the format:
* [0] => table
* [1] => record id
* [2] => record name, if available
*
* @param string $filename The name of the file to search
**/
public static function findUsage($filename)
{
$pf = Pdb::prefix();
$all_params = [
'filename' => $filename,
'like_filename' => Pdb::likeEscape($filename),
];
$size_names = Kohana::config('file.image_transformations');
foreach ($size_names as $size_name => $transform) {
Pdb::validateIdentifier($size_name);
$sizes[] = $size_name;
$all_params["resize_{$size_name}"] = Pdb
::likeEscape(File::getResizeFilename($filename, $size_name)); }
// Tables to not show results for
$pf . 'files',
$pf . 'history_items',
$pf . 'cronjobs',
$pf . 'workerjobs',
$pf . 'pages',
$pf . 'page_revisions',
$pf . 'page_widgets',
$pf . 'exception_log',
);
// Iterate the tables
$q = "SHOW TABLE STATUS";
$db_tables = Pdb::q($q, [], 'arr');
foreach ($db_tables as $tbl) {
if (strpos($tbl['Name'], $pf) !== 0) continue; if (in_array($tbl['Name'], $badtables)) continue;
// Grab the columns
$q = "SHOW COLUMNS FROM {$tbl['Name']}";
$db_cols = Pdb::q($q, [], 'arr');
// Build a where clause
$cols = [];
$where = [];
$params = [];
foreach ($db_cols as $col) {
if ($col['Field'] === 'id') $cols[] = 'id';
if ($col['Field'] === 'name') $cols[] = 'name';
if (preg_match('/VARCHAR\(([0-9]+)\)/i', $col['Type'], $matches) and
$matches[1] >= 200) { $where[] = "{$col['Field']} = :filename";
if (!isset($params['filename'])) $params['filename'] = $all_params['filename'];
$where[] = "{$col['Field']} LIKE CONCAT('%', :like_filename, '%')";
if (!isset($params['like_filename'])) $params['like_filename'] = $all_params['like_filename'];
foreach ($sizes as $size_name) {
$param_name = "resize_{$size_name}";
$where[] = "{$col['Field']} LIKE CONCAT('%', :{$param_name}, '%')";
if (!isset($params[$param_name])) $params[$param_name] = $all_params[$param_name]; }
}
}
if (count($cols) == 0 or
count($where) == 0) continue;
$q = 'SELECT ' . implode(', ', $cols) . ' FROM ' . $tbl['Name'] . ' WHERE ' . implode(' OR ', $where); $queries[$tbl['Name']] = [$q, $params];
}
// Spekky query for page revisions
$where = [];
$params = $all_params;
unset($params['filename']);
$where[] = "widget.settings LIKE CONCAT('%', :like_filename, '%')";
foreach ($sizes as $size_name) {
$param_name = "resize_{$size_name}";
$where[] = "widget.settings LIKE CONCAT('%', :{$param_name}, '%')";
}
$where[] = "page.banner LIKE CONCAT('%', :like_filename, '%')";
$q = "SELECT DISTINCT page.id, page.name
FROM ~page_revisions AS rev
INNER JOIN ~page_widgets AS widget ON rev.id = widget.page_revision_id
AND widget.area_id = 1 AND widget.type = 'RichText'
INNER JOIN ~pages AS page ON rev.page_id = page.id
WHERE (" . implode(' OR ', $where) . ') AND rev.status = :live';
$params['live'] = 'live';
$queries['sprout_pages'] = [$q, $params];
// Spekky query for gallery images
if (Sprout::moduleInstalled('galleries2')) {
$where = [];
$params = $all_params;
unset($params['filename']); $where[] = 'f.filename LIKE :like_filename';
foreach ($sizes as $size_name) {
$param_name = "resize_{$size_name}";
$where[] = "f.filename LIKE :{$param_name}";
}
$q = "SELECT g.id, g.name
FROM ~galleries AS g
INNER JOIN ~gallery_sources AS src ON src.gallery_id = g.id AND src.type = :type_image
INNER JOIN ~files_cat_join AS joiner ON joiner.cat_id = src.category
INNER JOIN ~files AS f ON joiner.file_id = f.id
WHERE (" . implode(' OR ', $where) . ')'; $params['type_image'] = GalleryConstants::SOURCE_FILES_IMAGE;
$queries['sprout_galleries'] = [$q, $params];
}
// Run the queries
foreach ($queries as $table => $q_and_params) {
list($q, $params) = $q_and_params; $res = Pdb::q($q, $params, 'pdo');
// Save results
foreach ($res as $row) {
$row['id'],
(isset($row['name']) ?
$row['name'] : 'Record #' . $row['id']), );
}
$res->closeCursor();
}
return $output;
}
/**
* Return the mimetype for a given filename.
*
* Only uses the extension - doesn't actually check the file
* If you need deep checking, take a look at {@see File::checkFileContentsExtension}
* If the extension is unrecognised, returns 'application/octet-stream'.
*
* @param string $filename
* @return string E.g. 'image/png'
**/
public static function mimetype($filename)
{
$ext = File::getExt($filename); return (isset(Constants
::$mimetypes[$ext]) ? Constants
::$mimetypes[$ext] : 'application/octet-stream'); }
/**
* Checks file contents match the extension
*
* @param $filename string The full path/filename of the file to check
* @param $ext string The supplied file extension
* @return boolean True if the file matches, false if it doesn't
* @return null If there isn't a check for the supplied extension
*/
public static function checkFileContentsExtension($filename, $ext)
{
switch ($ext) {
case 'jpg':
case 'jpeg':
case 'jpe':
case 'jif':
case 'jfif':
case 'jfi':
return ($size[2] == IMAGETYPE_JPEG);
case 'png':
return ($size[2] == IMAGETYPE_PNG);
case 'gif':
return ($size[2] == IMAGETYPE_GIF);
case 'pdf':
$fp = fopen($filename, 'r'); return ($magic == '%PDF');
}
return null;
}
/**
* Get the content-type of a file using magic mime.
*
* This is _NOT_ limited to the whitelist of mime types described in the
* Constants. Use this with care.
*
* Note mime_content_type() inspects file contents and can't always
* determine css/js files correctly, this is a hack fix for that.
*
* https://stackoverflow.com/a/17736797/7694753
*
* @param string $path
* @return string|null null if unknown
*/
public static function mimetypeExtended($path)
{
$extension = pathinfo($path, PATHINFO_EXTENSION
);
switch($extension) {
case 'css':
return 'text/css; charset=UTF-8';
case 'js':
return 'application/javascript; charset=UTF-8';
case 'svg':
return 'image/svg+xml; charset=UTF-8';
}
if (!$info) return null;
}
/**
* Prompts a user to download a file, and terminates the script
* Sets all the right headers and stuff, doesn't set caching/expires/etc headers though.
*
* @param string $filename The name of the file in the repository
* @param string $download_name An optional alternate name to provide to the user
**/
public static function download($filename, $download_name = null)
{
$size = File::size($filename); $mime = File::mimetype($filename);
// Set some general headers
header('Content-type: ' . $mime); header('Content-length: ' . $size); header('Content-disposition: attachment; filename="' . addslashes($download_name ?
$download_name : $filename) . '"');
// MSIE needs "public" when under SSL - http://support.microsoft.com/kb/316431
header('Cache-Control: public, max-age=1');
Kohana::closeBuffers();
}
/**
* Parse the size string used in file/resize and some helpers.
*
* Syntax: [crm]{number}x{number}(-[lcr][tcb])(~{number})
* Type Width Height Crop X Y Quality
*
* Returns an array.
* [0] type, either 'r', 'c' or 'm'
* r = resize, up or down, try to fill the area requested
* c = crop, resulting file will always be the width and height requested
* m = resize down only
* [1] width
* [2] height
* [3] x position, 'left', 'center' or 'right'
* [4] y position, 'top', 'center' or 'bottom'
* [5] jpeg quality, 0 = worst, 100 = best
*
* Returns an empty array on error (so you can use list() safely)
*
* @param $str Size string
* @return array
**/
public static function parseSizeString($str)
{
$result = preg_match('/^([crm])([0-9]+)x([0-9]+)(?:-([lcr])([tcb]))?(?:~([0-9]+))?$/', $str, $matches); if (! $result) return array();
$matches[1] = (int) $matches[1];
$matches[2] = (int) $matches[2];
if (empty($matches[3])) { $matches[3] = 'center';
$matches[4] = 'center';
} else {
if ($matches[3] == 'l') $matches[3] = 'left';
if ($matches[3] == 'c') $matches[3] = 'center';
if ($matches[3] == 'r') $matches[3] = 'right';
if ($matches[4] == 't') $matches[4] = 'top';
if ($matches[4] == 'c') $matches[4] = 'center';
if ($matches[4] == 'b') $matches[4] = 'bottom';
}
if (empty($matches[5])) $matches[5] = null;
return $matches;
}
/**
* Create a resized version of the specified file at a given size.
*
* The size is specified the same as the file/resize method (rXXXxYYY or cXXXxYYY)
* The output filename will be basename.size.ext
*
* The files can be used with `size_url` or `get_resize_filename` on the front-end.
*
* @param string $filename The original filename
* @param string $size The size in on-the-fly resize format
* @param string $force_ext Force a different ext on save, such as jpg for banners
* @return bool True on success, false on failure
**/
public static function createSize($filename, $size, $force_ext = null)
{
if (! File::exists($filename)) { return false;
}
$temp_filename = File::createLocalCopy($filename); if (! $temp_filename) {
return false;
}
$img = new Image($temp_filename);
// Determine the size
list($type, $width, $height, $crop_x, $crop_y, $quality) = File::parseSizeString($size);
if ($type == 'm') {
// Max size
$file_size = File::imageSize($filename);
if ($width == 0) $width = PHP_INT_MAX;
if ($height == 0) $height = PHP_INT_MAX;
if ($file_size[0] > $width or $file_size[1] > $height) {
$img->resize($width, $height);
}
} else if ($type == 'r') {
// Resize
$img->resize($width, $height);
} else if ($type == 'c') {
// Crop
if ($width / $img->width > $height / $img->height) {
$master = Image::WIDTH;
} else {
$master = Image::HEIGHT;
}
$img->resize($width, $height, $master);
$img->crop($width, $height, $crop_y, $crop_x);
} else {
// What?
File::cleanupLocalCopy($temp_filename); throw new Exception('Incorrect resize type');
}
if ($quality) {
$img->quality($quality);
}
$sized_filename = self::getResizeFilename($filename, $size, $force_ext);
$result = $img->save();
if (! $result) return false;
$result = File::putExisting($sized_filename, $temp_filename); if (! $result) return false;
File::cleanupLocalCopy($temp_filename);
$result = Replication::postFileUpdate($sized_filename);
if (! $result) return false;
return true;
}
/**
* Will we have enough RAM to do the resizes?
*
* @throws Exception If we don't
* @param array $dimensions Dimensions of the original image; 0 = width, 1 => height
* @return void
*/
public static
function calculateResizeRam
(array $dimensions) {
$origin_ram = $dimensions[0] * $dimensions[1] * 4;
$memory_limit = Sprout::getMemoryLimit();
$sizes = Kohana::config('file.image_transformations');
foreach ($sizes as $size_name => $transform) {
$size_ram = 0;
foreach ($transform as $t) {
$size_ram += $t->estimateRamRequirement();
}
$total_ram_req = $origin_ram + $size_ram;
if ($total_ram_req > $memory_limit) {
$total_ram_req = str_replace(' ', ' ', File::humanSize($total_ram_req)); $memory_limit = str_replace(' ', ' ', File::humanSize($memory_limit));
throw new Exception(
"Unable to create size '{$size_name}'; expected RAM requirements "
. "of {$total_ram_req} exceeds memory limit of {$memory_limit}"
);
}
}
}
/**
* Create default image sizes as per the config parameter 'file.image_transformations'
*
* The transformed files get saved onto the server.
* If any of the transformations in a transform-group fails,
* the whole group will fail and the file will not be saved.
*
* @throw Exception
* @param string $filename The file to create sizes for
* @param string $specific_size Optional parameter to process only a single size
* @return void
*/
public static function createDefaultSizes($filename, $specific_size = null)
{
$file_noext = implode('.', $parts);
$sizes = Kohana::config('file.image_transformations');
if ($specific_size) {
if (!isset($sizes[$specific_size])) { throw new Exception('Invalid param $specific_size; size doesn\'t exist.');
}
$sizes = array($specific_size => $sizes[$specific_size]); }
// Look up image in DB and see if it needs author attribution
$q = "SELECT author, embed_author
FROM ~files
WHERE filename = ?";
$row = Pdb::q($q, [$filename], 'row');
if ($row['author'] and $row['embed_author']) {
$embed_text = $row['author'];
} else {
$embed_text = false;
}
foreach ($sizes as $size_name => $transform) {
$temp_filename = File::createLocalCopy($filename); if (!$temp_filename) {
throw new Exception('Unable to create temporary copy for processing');
}
$img = new Image($temp_filename);
$resize_filename = "{$file_noext}.{$size_name}.{$ext}";
// Do the transforms
$width = $height = 0;
foreach ($transform as $t) {
$res = $t->transform($img);
if ($t instanceof ResizeImageTransform) {
$dims = $t->getDimensions();
$width = $dims['width'];
$height = $dims['height'];
}
// If an individual transform fails,
// cancel the transforming for this group
// The other transform groups will still be processed though
if ($res == false) {
File::cleanupLocalCopy($temp_filename); continue 2;
}
}
if ($embed_text) $img->addText($embed_text);
$result = $img->save();
if (! $result) {
throw new Exception('Save of new image failed');
}
// Import temp file into media repo
$result = File::putExisting($resize_filename, $temp_filename); if (! $result) {
throw new Exception('Image copy of new file into repository failed');
}
File::cleanupLocalCopy($temp_filename); }
}
/**
* Do post-processing after a file upload
*
* @throw Exception
* @param string $filename The name of hte new file
* @param int $file_id The ID of the new file
* @param int $file_type The new file type - e.g. DOCUMENT or IMAGE; see FileConstants
**/
public static function postUploadProcessing($filename, $file_id, $file_type)
{
$file_id = (int) $file_id;
$file_type = (int) $file_type;
$update_data = ['filename' => $filename];
Pdb::update('files', $update_data, ['id' => $file_id]);
switch ($file_type) {
case FileConstants::TYPE_DOCUMENT:
$ext = FileIndexing::getExt($filename);
if (FileIndexing::isExtSupported($ext)) {
$update_data = [];
$update_data['plaintext'] = FileIndexing::getPlaintext($filename, $ext);
Pdb::update('files', $update_data, ['id' => $file_id]);
}
break;
case FileConstants::TYPE_IMAGE:
File::createDefaultSizes($filename); break;
}
}
/**
* Generates a cropped, base-64 encoded thumbnail of an image
* @param string $file_path Path to the original image
* @param int $width Width to use for thumbnail
* @param int $height Height to use for thumbnail
* @return array Has the following keys:
* 'encoded_thumbnail': Base-64 encoded thumbnail
* 'original_width': width of the original image
* 'original_height': height of the original image
* @return false If file doesn't exist or can't be recognised as an image
* @throws Exception if not enough RAM to generate thumbnail
*/
public static function base64Thumb($file_path, $width, $height)
{
if (!$size) return false;
$current_size = new ResizeImageTransform($w, $h);
$resize = new ResizeImageTransform($width, $height);
$resize_ram = $current_size->estimateRamRequirement();
$resize_ram += $resize->estimateRamRequirement();
if ($resize_ram > Sprout::getMemoryLimit()) {
throw new Exception('Not enough RAM to generate thumbnail');
}
return [
'encoded_thumbnail' => Image::base64($file_path, $resize),
'original_width' => $w,
'original_height' => $h,
];
}
/**
* Checks the database for an updated URL path for a file.
* I.e. a file which has been replaced by the admin 'replace file' tool.
* @param string $filename Name of file, with no path, e.g. 123_image.jpg
* @return string Updated URL (relative to root), e.g. 'files/123_new_image.png' or 'file/download/123'
* @throws RowMissingException If no updated path was found
* @throws InvalidArgumentException If linkspec in DB invalid
*/
public static function lookupReplacementUrl($filename)
{
$q = "SELECT destination
FROM ~redirects
WHERE path_exact = ?";
$dest_spec_json = Pdb::q($q, ['files/' . $filename], 'val');
if ($dest_spec['class'] != '\\Sprout\\Helpers\\LinkSpecInternal') {
throw new InvalidArgumentException("Link spec doesn't match expected value");
}
return $dest_spec['data'];
}
/**
* Checks the database for an updated name for a file.
*
* This only works for full-sized images, e.g. 123_example.jpg, not 123_example.small.jpg
*
* @param string $filename Name of file, with no path, e.g. 123_image.jpg
* @return string Updated filename, e.g. '123_new_image.png'
* @throws RowMissingException If no updated path was found
* @throws InvalidArgumentException If linkspec in DB invalid
*/
public static function lookupReplacementName($filename)
{
$replacement = self::lookupReplacementUrl($filename);
if (preg_match('#^file/download/([0-9]+)#', $replacement)) { $file_details = self::getDetails($id);
return $file_details['filename'];
} else {
throw new InvalidArgumentException("Redirect target doesn't match expected value");
}
}
/**
* Replaces a set of files to be stored in a single field; this acts as a backend for {@see Fb::chunkedUpload}
*
* Files are saved as '{prefix}{num}.{ext}', with 'num' starting at 1
*
* @param string $session_key Session key used for this file field, used for {@see FileUpload::verify}
* @param string $field_name Name of the field used on the form, e.g. 'photos'
* @param array $exts Allowed file extensions, e.g. ['jpg', 'jpeg', 'png', 'gif']
* @param string $prefix The start of the new file name, e.g. 'user-gallery-123-'
* @return array List of newly saved filenames
*/
public static function replaceSet($session_key, $field_name, $exts, $prefix)
{
$files_saved = [];
$i = 0;
while (isset($_POST[$field_name][$i])) { try {
$src_file = FileUpload::verify($session_key, $field_name, $i, $exts);
$ext = File::getExt($_POST[$field_name][$i]); $dest = $prefix . ($i + 1) . '.' . $ext;
$res = File::moveUpload($src_file, $dest);
$files_saved[] = $dest;
}
}
} catch (Exception $ex) {
}
++$i;
}
foreach ($old_files as $file) {
}
}
return $files_saved;
}
}