Support thumbnailing non-image files and straighten out setup for 'gd'
Summary: Make 'gd' an explicit optional dependency, test for it in setup, and make the software behave correctly if it is not available. When generating file thumnails, provide reasonable defaults and behavior for non-image files. Test Plan: Uploaded text files, pdf files, etc., and got real thumbnails instead of a broken image. Simulated setup and gd failures and walked through setup process and image fallback for thumbnails. Reviewed By: aran Reviewers: toulouse, jungejason, tuomaspelkonen, aran CC: aran, epriestley Differential Revision: 446
|
@ -43,12 +43,10 @@ class PhabricatorFileTransformController extends PhabricatorFileController {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$file->isViewableInBrowser()) {
|
$type = $file->getMimeType();
|
||||||
return new Aphront400Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$file->isTransformableImage()) {
|
if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) {
|
||||||
return new Aphront400Response();
|
return $this->buildDefaultTransformation($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->transform) {
|
switch ($this->transform) {
|
||||||
|
@ -78,6 +76,39 @@ class PhabricatorFileTransformController extends PhabricatorFileController {
|
||||||
return $this->buildTransformedFileResponse($xform);
|
return $this->buildTransformedFileResponse($xform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildDefaultTransformation(PhabricatorFile $file) {
|
||||||
|
static $regexps = array(
|
||||||
|
'@application/zip@' => 'zip',
|
||||||
|
'@image/@' => 'image',
|
||||||
|
'@application/pdf@' => 'pdf',
|
||||||
|
'@.*@' => 'default',
|
||||||
|
);
|
||||||
|
|
||||||
|
$type = $file->getMimeType();
|
||||||
|
$prefix = 'default';
|
||||||
|
foreach ($regexps as $regexp => $implied_prefix) {
|
||||||
|
if (preg_match($regexp, $type)) {
|
||||||
|
$prefix = $implied_prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->transform) {
|
||||||
|
case 'thumb-160x120':
|
||||||
|
$suffix = '160x120';
|
||||||
|
break;
|
||||||
|
case 'thumb-60x45':
|
||||||
|
$suffix = '60x45';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unsupported transformation type!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = "/rsrc/image/icon/fatcow/thumbnails/{$prefix}{$suffix}.png";
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($path);
|
||||||
|
}
|
||||||
|
|
||||||
private function buildTransformedFileResponse(
|
private function buildTransformedFileResponse(
|
||||||
PhabricatorTransformedFile $xform) {
|
PhabricatorTransformedFile $xform) {
|
||||||
|
|
||||||
|
@ -123,9 +154,25 @@ class PhabricatorFileTransformController extends PhabricatorFileController {
|
||||||
$dx, $dy,
|
$dx, $dy,
|
||||||
$scale * $dx, $scale * $dy);
|
$scale * $dx, $scale * $dy);
|
||||||
|
|
||||||
ob_start();
|
$img = null;
|
||||||
imagejpeg($dst);
|
|
||||||
return ob_get_clean();
|
if (function_exists('imagejpeg')) {
|
||||||
|
ob_start();
|
||||||
|
imagejpeg($dst);
|
||||||
|
$img = ob_get_clean();
|
||||||
|
} else if (function_exists('imagepng')) {
|
||||||
|
ob_start();
|
||||||
|
imagepng($dst);
|
||||||
|
$img = ob_get_clean();
|
||||||
|
} else if (function_exists('imagegif')) {
|
||||||
|
ob_start();
|
||||||
|
imagegif($dst);
|
||||||
|
$img = ob_get_clean();
|
||||||
|
} else {
|
||||||
|
throw new Exception("No image generation functions exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $img;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,7 +216,35 @@ class PhabricatorFile extends PhabricatorFileDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isTransformableImage() {
|
public function isTransformableImage() {
|
||||||
return preg_match('@^image/(gif|png|jpe?g)@', $this->getViewableMimeType());
|
|
||||||
|
// NOTE: The way the 'gd' extension works in PHP is that you can install it
|
||||||
|
// with support for only some file types, so it might be able to handle
|
||||||
|
// PNG but not JPEG. Try to generate thumbnails for whatever we can. Setup
|
||||||
|
// warns you if you don't have complete support.
|
||||||
|
|
||||||
|
$matches = null;
|
||||||
|
$ok = preg_match(
|
||||||
|
'@^image/(gif|png|jpe?g)@',
|
||||||
|
$this->getViewableMimeType(),
|
||||||
|
$matches);
|
||||||
|
if (!$ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($matches[1]) {
|
||||||
|
case 'jpg';
|
||||||
|
case 'jpeg':
|
||||||
|
return function_exists('imagejpeg');
|
||||||
|
break;
|
||||||
|
case 'png':
|
||||||
|
return function_exists('imagepng');
|
||||||
|
break;
|
||||||
|
case 'gif':
|
||||||
|
return function_exists('imagegif');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception('Unknown type matched as image MIME type.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getViewableMimeType() {
|
public function getViewableMimeType() {
|
||||||
|
|
|
@ -165,6 +165,39 @@ class PhabricatorSetup {
|
||||||
|
|
||||||
self::write("[OKAY] Basic configuration OKAY\n");
|
self::write("[OKAY] Basic configuration OKAY\n");
|
||||||
|
|
||||||
|
|
||||||
|
$issue_gd_warning = false;
|
||||||
|
self::writeHeader('GD LIBRARY');
|
||||||
|
if (extension_loaded('gd')) {
|
||||||
|
self::write(" okay Extension 'gd' is loaded.\n");
|
||||||
|
$image_type_map = array(
|
||||||
|
'imagepng' => 'PNG',
|
||||||
|
'imagegif' => 'GIF',
|
||||||
|
'imagejpeg' => 'JPEG',
|
||||||
|
);
|
||||||
|
foreach ($image_type_map as $function => $image_type) {
|
||||||
|
if (function_exists($function)) {
|
||||||
|
self::write(" okay Support for '{$image_type}' is available.\n");
|
||||||
|
} else {
|
||||||
|
self::write(" warn Support for '{$image_type}' is not available!\n");
|
||||||
|
$issue_gd_warning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self::write(" warn Extension 'gd' is not loaded.\n");
|
||||||
|
$issue_gd_warning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issue_gd_warning) {
|
||||||
|
self::write(
|
||||||
|
"[WARN] The 'gd' library is missing or lacks full support. ".
|
||||||
|
"Phabricator will not be able to generate image thumbnails without ".
|
||||||
|
"gd.\n");
|
||||||
|
} else {
|
||||||
|
self::write("[OKAY] 'gd' loaded and has full image type support.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
self::writeHeader('FACEBOOK INTEGRATION');
|
self::writeHeader('FACEBOOK INTEGRATION');
|
||||||
$fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
|
$fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
|
||||||
if (!$fb_auth) {
|
if (!$fb_auth) {
|
||||||
|
|
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/default160x120.png
Normal file
After Width: | Height: | Size: 1,006 B |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/default60x45.png
Normal file
After Width: | Height: | Size: 762 B |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/image160x120.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/image60x45.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/pdf160x120.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/pdf60x45.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/zip160x120.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
webroot/rsrc/image/icon/fatcow/thumbnails/zip60x45.png
Normal file
After Width: | Height: | Size: 1.1 KiB |