$mime, 'sha1' => sha1_file($fs_path), 'size' => filesize($fs_path), 'format' => '', 'width' => 0, 'height' => 0, ]; $flag = FileUtils::ONLY_BINARY; $props = NULL; if ($mime == 'application/x-shockwave-flash') { $props = self::getSWFProps($fs_path); if ($props) $flag = FileUtils::ONLY_SWF; } elseif (substr($mime, 0, 6) == 'image/') { $props = self::getImageProps($fs_path); if ($props) $flag = FileUtils::ONLY_IMAGES; } elseif (substr($mime, 0, 6) == 'video/') { $props = VideoUtils::getVideoProps($fs_path); if ($props) $flag = FileUtils::ONLY_VIDEO; } if (!($allowed_types & $flag)) { throw new UserException('file-type-denied', [ 'allowed' => $allowed_types, 'type' => $flag ]); } $props = ($props ?: []) + $stdProps; if (!$props['format']) { if (!empty($props['filename'])) $orig_name = preg_replace('#^.*/#is', '', $props['filename']); $p = strrpos($orig_name, '.'); if ($p) $props['format'] = strtolower(substr($orig_name, $p+1)); } unset($props['filename']); return $props; } /** * Get image properties + compress uploaded BMP images to JPEG */ protected static function getImageProps($fn) { $format = NULL; $props = []; try { $exif = @exif_read_data($fn); if ($exif) { foreach (self::$exifProps as $p) { if (isset($exif[$p])) { $props[$p] = $exif[$p]; } } } // FIXME Сначала проверять ширину/высоту, не читая саму картинку в память $im = static::magick(); $im->readImage($fn); $format = strtolower($im->getImageFormat()); if ($format == 'jpeg') { $format = 'jpg'; } if ($format != 'png' && $format != 'gif' && $format != 'jpeg' && $format != 'jpg' && $format != 'pdf' && $format != 'djvu' && $format != 'djv' && ($format != 'ico' || $im->getImageWidth() > 48 || $im->getImageHeight() > 48)) // bmp, tiff, psd... { $im->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); if (!empty($props['Orientation'])) { self::fixOrientation($im, $props['Orientation']); unset($props['Orientation']); } $im->setCompressionQuality(static::$quality); $im->setImageFormat('jpeg'); $format = 'jpg'; $fn = tempnam(sys_get_temp_dir(), 'imguniq'); $im->writeImage($fn); } $width = $im->getImageWidth(); $height = $im->getImageHeight(); if (!empty($props['Orientation']) && $props['Orientation'] >= 5) { $t = $width; $width = $height; $height = $t; } } catch (Exception $e) { // Not an image return NULL; } return [ 'filename' => $fn, 'width' => $width, 'height' => $height, 'format' => $format, 'props' => $props, ]; } /** * Get MIME type of a file */ protected static function checkMime($filename) { $mime = false; if (class_exists('finfo')) { static $finfo; if (!$finfo) { $finfo = new finfo(FILEINFO_MIME_TYPE); } if ($finfo) { $mime = $finfo->file($filename); } } elseif (function_exists('mime_content_type')) { $mime = mime_content_type($filename); } else { $mime = shell_exec("file ".escapeshellarg($filename)); } return $mime; } protected static function getSWFProps($fn) { $size = shell_exec("swfdump -X -Y ".escapeshellarg($fn)); if (preg_match('/-X (\d+) -Y (\d+)/', $size, $m)) { return [ 'format' => 'swf', 'width' => $m[1], 'height' => $m[2], ]; } return NULL; } /** * Fix ImageMagick image $im orientation based on EXIF orientation $orient */ public static function fixOrientation($im, $orient) { $orient = intval($orient); if ($orient > 1 && $orient <= 8) { if ($orient == 2) { $im->flopImage(); } elseif ($orient == 3) { $im->rotateImage($im instanceof Imagick ? new ImagickPixel() : new GmagickPixel(), 180); } elseif ($orient == 4) { $im->flipImage(); } elseif ($orient == 5) { if (method_exists($im, 'transposeImage')) { $im->transposeImage(); } else { // GraphicsMagick doesn't have Transpose $im->rotateImage($im instanceof Imagick ? new ImagickPixel() : new GmagickPixel(), 90); $im->flopImage(); } } elseif ($orient == 6) { $im->rotateImage($im instanceof Imagick ? new ImagickPixel() : new GmagickPixel(), 90); } elseif ($orient == 7) { if (method_exists($im, 'transverseImage')) { $im->transverseImage(); } else { // GraphicsMagick doesn't have Transverse $im->rotateImage($im instanceof Imagick ? new ImagickPixel() : new GmagickPixel(), 270); $im->flopImage(); } } elseif ($orient == 8) { $im->rotateImage($im instanceof Imagick ? new ImagickPixel() : new GmagickPixel(), 270); } } } public static function getGPS($file) { $props = $file['props']; if (empty($props['GPSLatitude']) && empty($props['GPSLongitude'])) { return NULL; } $latitude = FileUtils::exifGPS($props['GPSLatitude'], $props['GPSLatitudeRef']); $longitude = FileUtils::exifGPS($props['GPSLongitude'], $props['GPSLongitudeRef']); return [ $latitude, $longitude ]; } public static function exifGPS($coordinate, $hemisphere) { for ($i = 0; $i < 3; $i++) { $part = explode('/', $coordinate[$i]); if (count($part) == 1) { $coordinate[$i] = $part[0]; } elseif (count($part) == 2) { $coordinate[$i] = floatval($part[0])/floatval($part[1]); } else { $coordinate[$i] = 0; } } list($degrees, $minutes, $seconds) = $coordinate; $sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1; return $sign * ($degrees + $minutes/60 + $seconds/3600); } /** * Calculate thumbnail image width and height */ public static function thumbnailSize($cw, $ch, $maxw, $maxh) { if ($maxw <= 0 && $maxh <= 0 || $cw <= 0 || $ch <= 0) { return [ $cw, $ch ]; } if ($maxw > 0 && ($maxh <= 0 || $maxh/$ch >= $maxw/$cw)) { $nw = $maxw; $nh = $ch * $maxw / $cw; } elseif ($maxh > 0 && ($maxw <= 0 || $maxh/$ch < $maxw/$cw)) { $nw = $cw * $maxh / $ch; $nh = $maxh; } return [ intval($nw), intval($nh) ]; } /** * Return file size description */ static $sizeSuffix = [ 'Кб' => [ 'bytes' => 'bytes', 'Kb' => 'Кб', 'Mb' => 'Мб', 'Gb' => 'Гб', ], ]; public static function sizeString($bytes, $lang = 'ru') { $r = $bytes; if (is_numeric($r)) { if ($r >= 0 && $r < 1024) $r = $r . ' ' . (isset(self::$sizeString[$lang]['bytes']) ? self::$sizeString[$lang]['bytes'] : 'bytes'); elseif ($r >= 1024 && $r < 1024*1024) $r = sprintf('%.2f ', $r/1024) . (isset(self::$sizeString[$lang]['Kb']) ? self::$sizeString[$lang]['Kb'] : 'Kb'); elseif ($r >= 1024*1024 && $r < 1024*1024*1024) $r = sprintf('%.2f ', $r/1024/1024) . (isset(self::$sizeString[$lang]['Mb']) ? self::$sizeString[$lang]['Mb'] : 'Mb'); elseif ($r >= 1024*1024*1024) $r = sprintf('%.2f ', $r/1024/1024/1024) . (isset(self::$sizeString[$lang]['Gb']) ? self::$sizeString[$lang]['Gb'] : 'Gb'); elseif ($r < 0) $r = sprintf('%.2f ', 2-($r/1024/1024/1024)) . (isset(self::$sizeString[$lang]['Gb']) ? self::$sizeString[$lang]['Gb'] : 'Gb'); } return $r; } /** * Recursively create a directory */ public static function mkpath($path, $throw_error = true) { if (is_dir($path) || @mkdir($path, 0777, true)) { return true; } if ($throw_error) { $error = error_get_last(); throw new Exception("Failed to create path $path: ".$error['message']); } return false; } /** * Get thumbnail size for $file */ public static function getThumbSize($file, $width, $height, $crop = false) { if ($width < 0) $width = 0; if ($height < 0) $height = 0; if (!$width || !$height) $crop = false; if (!$file['width'] || !$width && !$height || (!$width || $file['width'] <= $width) && (!$height || $file['height'] < $height) && !$crop || ($file['format'] == 'swf' || $file['format'] == 'video')) return NULL; if (!$crop) return self::thumbnailSize($file['width'], $file['height'], $width, $height); return [ $width, $height ]; } /** * Convert ImageMagick image $im to a thumbnail */ public static function makeThumb($im, $width, $height, $crop, $orientation, $alignY) { $cl = get_class($im); if ($crop) { $iw = $im->getImageWidth(); $ih = $im->getImageHeight(); if ($crop == FileUtils::CROP_Y && $ih*$width/$iw < $height) { $height = $ih*$width/$iw; } elseif ($crop == FileUtils::CROP_X && $iw*$height/$ih < $width) { $width = $iw*$height/$ih; } if ($width/$height < $iw/$ih) { $cw = intval($width*$ih/$height); $im->cropImage($cw, $ih, intval(($iw-$cw)/2), 0); } elseif ($width/$height > $iw/$ih) { $ch = intval($height*$iw/$width); $im->cropImage($iw, $ch, 0, intval(($ih-$ch)*$alignY)); } $im->resizeImage($width, $height, $cl::FILTER_LANCZOS, 1); $im->setImagePage($width, $height, 0, 0); /* for gif cropping */ $iw = $im->getImageWidth(); $ih = $im->getImageHeight(); } else { $im->resizeImage($width, $height, $cl::FILTER_LANCZOS, 1); } $im->stripImage(); if ($orientation !== NULL) { self::fixOrientation($im, $orientation); } } /** * Get thumbnail type */ public static function getThumbType($file, $width, $height, $crop = false, $alignY = 0.5) { $size = FileUtils::getThumbSize($file, $width, $height, $crop); if (!$size) { return NULL; } list($width, $height) = $size; if (!$crop) { $type = intval($width); } else { $p = $crop == FileUtils::CROP_Y ? 'cy' : ($crop == FileUtils::CROP_X ? 'cx' : 'c'); $type = intval($width).'x'.intval($height).'_'.$p.$alignY; } return [ 'width' => $width, 'height' => $height, 'type' => $type ]; } public static function generateThumbnail($sourcefn, $thumbfn, $file, $width, $height, $crop = false, $alignY = 0.5) { try { $im = FileUtils::magick(); $im->readImage($sourcefn); $props = $file['props']; if (!empty($props['Orientation']) && $props['Orientation'] > 5) { /* swap width & height */ $t = $width; $width = $height; $height = $t; if ($crop == FileUtils::CROP_X || $crop == FileUtils::CROP_Y) $crop = 5-$crop; } FileUtils::makeThumb($im, $width, $height, $crop, isset($props['Orientation']) ? $props['Orientation'] : NULL, $alignY); $im->setCompressionQuality(FileUtils::$quality); FileUtils::mkpath(dirname($thumbfn)); $im->writeImage($thumbfn); } catch (Exception $e) { trigger_error("$e"); return false; } return true; } }