Reinstated GDBackend->checkAvailableMemory()

This was removed as part of the SS4 assets refactor:
be239896d3
Presumably @tractorcow didn’t feel it’s possible to retain this,
because we don’t have local file handles required for getimagesize().
Since there’s getimagesizefromstring() as well (added in PHP 5.4),
we just needed a different logic branch.

Also makes the logic more resilient against missing GD resources on a backend.
Lack of available memory for a resize is only one (new) reason,
other edge cases were already causing these missing resources (e.g. an invalid file string).

Original discussion: https://groups.google.com/forum/m/#!topic/silverstripe-dev/B57a3KYeAVQ
Pull request for 3.x: https://github.com/silverstripe/silverstripe-framework/pull/2859
More context: https://github.com/silverstripe/silverstripe-framework/pull/2569
This commit is contained in:
Ingo Schommer 2017-01-06 15:20:05 +13:00
parent 6e561f00bd
commit fcb511b1c0

View File

@ -99,7 +99,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
// We use getimagesize instead of extension checking, because sometimes extensions are wrong.
$meta = getimagesize($path);
if ($meta === false) {
if ($meta === false || !$this->checkAvailableMemory($meta)) {
$this->markFailed($path, $mtime);
return;
}
@ -147,7 +147,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
return;
}
// Skip if failed before
// Skip if failed before, or image is too large
$filename = $assetContainer->getFilename();
$hash = $assetContainer->getHash();
$variant = $assetContainer->getVariant();
@ -155,8 +155,16 @@ class GDBackend extends Object implements Image_Backend, Flushable
return;
}
// Mark as potentially failed prior to creation, resetting this on success
$content = $assetContainer->getString();
// We use getimagesizefromstring instead of extension checking, because sometimes extensions are wrong.
$meta = getimagesizefromstring($content);
if ($meta === false || !$this->checkAvailableMemory($meta)) {
$this->markFailed($filename, $hash, $variant);
return;
}
// Mark as potentially failed prior to creation, resetting this on success
$image = imagecreatefromstring($content);
if ($image === false) {
$this->markFailed($filename, $hash, $variant);
@ -214,6 +222,35 @@ class GDBackend extends Object implements Image_Backend, Flushable
return (bool)$this->cache->load($key);
}
/**
* Check if we've got enough memory available for resampling this image. This check is rough,
* so it will not catch all images that are too large - it also won't work accurately on large,
* animated GIFs as bits per pixel can't be calculated for an animated GIF with a global color
* table.
*
* @param array $imageInfo Value from getimagesize() or getimagesizefromstring()
* @return boolean
*/
protected function checkAvailableMemory($imageInfo)
{
$limit = translate_memstring(ini_get('memory_limit'));
if ($limit < 0) {
return true; // memory_limit == -1
}
// bits per channel (rounded up, default to 1)
$bits = isset($imageInfo['bits']) ? ($imageInfo['bits'] + 7) / 8 : 1;
// channels (default 4 rgba)
$channels = isset($imageInfo['channels']) ? $imageInfo['channels'] : 4;
$bytesPerPixel = $bits * $channels;
// width * height * bytes per pixel
$memoryRequired = $imageInfo[0] * $imageInfo[1] * $bytesPerPixel;
return $memoryRequired + memory_get_usage() < $limit;
}
/**
* Mark a file as failed
*
@ -622,16 +659,31 @@ class GDBackend extends Object implements Image_Backend, Flushable
if ($extension = pathinfo($filename, PATHINFO_EXTENSION)) {
$path .= "." . $extension;
}
$this->writeTo($path);
$writeSuccess = $this->writeTo($path);
if (!$writeSuccess) {
return null;
}
$result = $assetStore->setFromLocalFile($path, $filename, $hash, $variant, $config);
unlink($path);
return $result;
}
/**
* @param string $filename
* @return boolean
*/
public function writeTo($filename)
{
if (!$filename) {
return;
return false;
}
// The GD resource might not exist if the image is too large to be processed, see checkAvailableMemory().
if (!$this->gd) {
return false;
}
// Get current image data
@ -660,7 +712,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
}
}
// if $this->interlace != 0, the output image will be interlaced
// If $this->interlace != 0, the output image will be interlaced.
imageinterlace($this->gd, $this->interlace);
// if the extension does not exist, the file will not be created!
@ -679,9 +731,14 @@ class GDBackend extends Object implements Image_Backend, Flushable
imagepng($this->gd, $filename);
break;
}
if (file_exists($filename)) {
@chmod($filename, 0664);
if (!file_exists($filename)) {
return false;
}
@chmod($filename, 0664);
return true;
}
/**