Version: current

      1 <?php
      2 /**
      3  * Represents an image attached to a page.
      4  * @package sapphire
      5  * @subpackage filesystem
      6  */
      7 class Image extends File {
      8
      9     const ORIENTATION_SQUARE = 0;
     10     const ORIENTATION_PORTRAIT = 1;
     11     const ORIENTATION_LANDSCAPE = 2;
     12
     13     static $casting = array(
     14         'Tag' => 'HTMLText',
     15     );
     16
     17     // форматы преобразования для которых при равенстве размеров картинки совпадают
     18     static $safeGenerateFormats = array('SetRatioSize', 'SetSize', 'SetWidth', 'SetHeight');
     19
     20     /**
     21      * The width of an image thumbnail in a strip.
     22      * @var int
     23      */
     24     public static $strip_thumbnail_width = 50;
     25
     26     /**
     27      * The height of an image thumbnail in a strip.
     28      * @var int
     29      */
     30     public static $strip_thumbnail_height = 50;
     31
     32     /**
     33      * The width of an image thumbnail in the CMS.
     34      * @var int
     35      */
     36     public static $cms_thumbnail_width = 100;
     37
     38     /**
     39      * The height of an image thumbnail in the CMS.
     40      */
     41     public static $cms_thumbnail_height = 100;
     42
     43     /**
     44      * The width of an image thumbnail in the Asset section.
     45      */
     46     public static $asset_thumbnail_width = 100;
     47
     48     /**
     49      * The height of an image thumbnail in the Asset section.
     50      */
     51     public static $asset_thumbnail_height = 100;
     52
     53     /**
     54      * The width of an image preview in the Asset section.
     55      */
     56     public static $asset_preview_width = 400;
     57
     58     /**
     59      * The height of an image preview in the Asset section.
     60      */
     61     public static $asset_preview_height = 200;
     62
     63     /**
     64      * Height and width of an asset in a popup
     65      */
     66     public static $asset_popup_size = 800;
     67
     68     /**
     69      * Set up template methods to access the transformations generated by 'generate' methods.
     70      */
     71     public function defineMethods() {
     72         $methodNames = $this->allMethodNames();
     73         foreach($methodNames as $methodName) {
     74             if(substr($methodName,0,8) == 'generate') {
     75                 $this->addWrapperMethod(substr($methodName,8), 'getFormattedImage');
     76             }
     77         }
     78
     79         parent::defineMethods();
     80     }
     81
     82     /**
     83      * An image exists if it has a filename.
     84      * @return boolean
     85      */
     86     public function exists() {
     87         if(isset($this->record["Filename"])) {
     88             return true;
     89         }
     90     }
     91
     92     /**
     93      * Get the relative URL for this Image.
     94      * Overwrites File->URL() which returns an absolute URL.
     95      *
     96      * @todo Refactor to return absolute URL like {@link File}
     97      * @uses Director::baseURL()
     98      * @return string
     99      */
    100 /* !!! absent in 2.4.0
    101     function getURL() {
    102         return $this->Filename;//Director::baseURL() . $this->Filename;
    103     }
    104 */
    105     /**
    106      * Return an XHTML img tag for this Image.
    107      * @return string
    108      */
    109     function getTag() {
    110         if(file_exists(Director::baseFolder() . '/' . $this->Filename)) {
    111             $url = $this->getURL(); // !!! absolute url
    112             $title = ($this->Title) ? $this->Title : $this->Filename;
    113             if($this->Title) {
    114                 $title = Convert::raw2att($this->Title);
    115             } else {
    116                 if(preg_match("/([^\/]*)\.[a-zA-Z0-9]{1,6}$/", $title, $matches)) $title = Convert::raw2att($matches[1]);
    117             }
    118             return "<img src=\"$url\" alt=\"$title\" />";
    119         }
    120     }
    121
    122     /**
    123      * Return an XHTML img tag for this Image.
    124      * @return string
    125      */
    126     function forTemplate() {
    127         return $this->getTag();
    128     }
    129
    130     function loadUploadedImage($tmpFile) {
    131         if(!is_array($tmpFile)) {
    132             user_error("Image::loadUploadedImage() Not passed an array.  Most likely, the form hasn't got the right enctype", E_USER_ERROR);
    133         }
    134
    135         if(!$tmpFile['size']) {
    136             return;
    137         }
    138
    139         $class = $this->class;
    140
    141         // Create a folder
    142         if(!file_exists(ASSETS_PATH)) {
    143             mkdir(ASSETS_PATH, Filesystem::$folder_create_mask);
    144         }
    145
    146         if(!file_exists(ASSETS_PATH . "/$class")) {
    147             mkdir(ASSETS_PATH . "/$class", Filesystem::$folder_create_mask);
    148         }
    149
    150         // Generate default filename
    151         $file = Convert::rus2lat($tmpFile['name']);
    152         $file = str_replace(' ', '-',$file);
    153         $file = ereg_replace('[^A-Za-z0-9+.-]+','',$file);
    154         $file = ereg_replace('-+', '-',$file);
    155         if(!$file) {
    156             $file = "file.jpg";
    157         }
    158
    159         $file = ASSETS_PATH . "/$class/$file";
    160
    161         while(file_exists(BASE_PATH . "/$file")) {
    162             $i = $i ? ($i+1) : 2;
    163             $oldFile = $file;
    164             $file = ereg_replace('[0-9]*(\.[^.]+$)',$i . '\\1', $file);
    165             if($oldFile == $file && $i > 2) user_error("Couldn't fix $file with $i", E_USER_ERROR);
    166         }
    167
    168         if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], BASE_PATH . "/$file")) {
    169             // Remove the old images
    170
    171             $this->deleteFormattedImages();
    172             return true;
    173         }
    174     }
    175
    176     public function SetWidth($width) {
    177         return $this->getFormattedImage('SetWidth', $width);
    178     }
    179
    180     public function SetHeight($height) {
    181         return $this->getFormattedImage('SetHeight', $height);
    182     }
    183
    184     public function SetSize($width, $height) {
    185         return $this->getFormattedImage('SetSize', $width, $height);
    186     }
    187
    188     public function SetRatioSize($width, $height) {
    189         return $this->getFormattedImage('SetRatioSize', $width, $height);
    190     }
    191
    192     public function generateSetRatioSize(GD $gd, $width, $height) {
    193         return $gd->resizeRatio($width, $height);
    194     }
    195
    196     /**
    197      * Resize this Image by width, keeping aspect ratio. Use in templates with $SetWidth.
    198      * @return GD
    199      */
    200     public function generateSetWidth(GD $gd, $width) {
    201         return $gd->resizeByWidth($width);
    202     }
    203
    204     /**
    205      * Resize this Image by height, keeping aspect ratio. Use in templates with $SetHeight.
    206      * @return GD
    207      */
    208     public function generateSetHeight(GD $gd, $height){
    209         return $gd->resizeByHeight($height);
    210     }
    211
    212     /**
    213      * Resize this Image by both width and height, using padded resize. Use in templates with $SetSize.
    214      * @return GD
    215      */
    216     public function generateSetSize(GD $gd, $width, $height) {
    217         return $gd->paddedResize($width, $height);
    218     }
    219
    220     public function CMSThumbnail() {
    221         return $this->getFormattedImage('CMSThumbnail');
    222     }
    223
    224     /**
    225      * Resize this image for the CMS. Use in templates with $CMSThumbnail.
    226      * @return GD
    227      */
    228     function generateCMSThumbnail(GD $gd) {
    229         return $gd->paddedResize($this->stat('cms_thumbnail_width'),$this->stat('cms_thumbnail_height'));
    230     }
    231
    232     /**
    233      * Resize this image for preview in the Asset section. Use in templates with $AssetLibraryPreview.
    234      * @return GD
    235      */
    236     function generateAssetLibraryPreview(GD $gd) {
    237         return $gd->paddedResize($this->stat('asset_preview_width'),$this->stat('asset_preview_height'));
    238     }
    239
    240     /**
    241      * Resize this image for thumbnail in the Asset section. Use in templates with $AssetLibraryThumbnail.
    242      * @return GD
    243      */
    244     function generateAssetLibraryThumbnail(GD $gd) {
    245         return $gd->paddedResize($this->stat('asset_thumbnail_width'),$this->stat('asset_thumbnail_height'));
    246     }
    247
    248     /**
    249      * Resize this image for use as a thumbnail in a strip. Use in templates with $StripThumbnail.
    250      * @return GD
    251      */
    252     function generateStripThumbnail(GD $gd) {
    253         return $gd->croppedResize($this->stat('strip_thumbnail_width'),$this->stat('strip_thumbnail_height'));
    254     }
    255
    256     function generatePaddedImage(GD $gd, $width, $height) {
    257         return $gd->paddedResize($width, $height);
    258     }
    259
    260     /**
    261      * Return an image object representing the image in the given format.
    262      * This image will be generated using generateFormattedImage().
    263      * The generated image is cached, to flush the cache append ?flush=1 to your URL.
    264      * @param string $format The name of the format.
    265      * @param string $arg1 An argument to pass to the generate function.
    266      * @param string $arg2 A second argument to pass to the generate function.
    267      * @return Image_Cached
    268      */
    269     function getFormattedImage($format, $arg1 = null, $arg2 = null) {
    270         if($this->ID && $this->Filename && Director::fileExists($this->Filename)) {
    271             $cacheFile = $this->cacheFilename($format, $arg1, $arg2);
    272
    273             if(!file_exists(Director::baseFolder().'/'.$cacheFile) || isset($_GET['flush'])) {
    274                 $this->generateFormattedImage($format, $arg1, $arg2);
    275             }
    276
    277             $cached = new Image_Cached($cacheFile);
    278             // Pass through the title so the templates can use it
    279             $cached->Title = $this->Title;
    280             return $cached;
    281         }
    282     }
    283
    284     /**
    285      * Return the filename for the cached image, given it's format name and arguments.
    286      * @param string $format The format name.
    287      * @param string $arg1 The first argument passed to the generate function.
    288      * @param string $arg2 The second argument passed to the generate function.
    289      * @return string
    290      */
    291     function cacheFilename($format, $arg1 = null, $arg2 = null) {
    292         $folder = $this->ParentID ? $this->Parent()->Filename : ASSETS_DIR . "/";
    293
    294         $format = $format.$arg1.$arg2;
    295
    296         return $folder . "_resampled/$format-" . $this->Name;
    297     }
    298
    299     /**
    300      * Generate an image on the specified format. It will save the image
    301      * at the location specified by cacheFilename(). The image will be generated
    302      * using the specific 'generate' method for the specified format.
    303      * @param string $format Name of the format to generate.
    304      * @param string $arg1 Argument to pass to the generate method.
    305      * @param string $arg2 A second argument to pass to the generate method.
    306      */
    307     function generateFormattedImage($format, $arg1 = null, $arg2 = null) {
    308         $cacheFile = $this->cacheFilename($format, $arg1, $arg2);
    309
    310         $gd = new GD(Director::baseFolder().'/' . $this->Filename);
    311
    312         if($gd->hasGD()){
    313             $generateFunc = "generate$format";
    314             if($this->hasMethod($generateFunc)){
    315                 $gd2 = $this->$generateFunc($gd, $arg1, $arg2);
    316                 if($gd2){
    317                     // если картинки совпадают - просто копируем (на unix-like можно делать symlink)
    318                     if (array_search($format, Image::$safeGenerateFormats) !== false
    319                         && $gd->getWidth() == $gd2->getWidth() && $gd->getHeight() == $gd2->getHeight()
    320                         ) {
    321                         $gd2->makeDir(dirname(Director::baseFolder().'/' . $cacheFile));
    322                         copy(Director::baseFolder().'/' . $this->Filename, Director::baseFolder().'/' . $cacheFile);
    323                         @chmod(Director::baseFolder().'/' . $cacheFile, 0664);
    324                     }
    325                     else {
    326                         $gd2->writeTo(Director::baseFolder().'/' . $cacheFile);
    327                     }
    328                 }
    329
    330             } else {
    331                 USER_ERROR("Image::generateFormattedImage - Image $format function not found.",E_USER_WARNING);
    332             }
    333         }
    334     }
    335
    336     /**
    337      * Generate a resized copy of this image with the given width & height.
    338      * Use in templates with $ResizedImage.
    339      */
    340     function generateResizedImage($gd, $width, $height) {
    341         if(is_numeric($gd) || !$gd){
    342             USER_ERROR("Image::generateFormattedImage - generateResizedImage is being called by legacy code or gd is not set.",E_USER_WARNING);
    343         }else{
    344             return $gd->resize($width, $height);
    345         }
    346     }
    347
    348     /**
    349      * Generate a resized copy of this image with the given width & height, cropping to maintain aspect ratio.
    350      * Use in templates with $CroppedImage
    351      */
    352     function generateCroppedImage($gd, $width, $height) {
    353         return $gd->croppedResize($width, $height);
    354     }
    355
    356     /**
    357      * Generate a rotateв Clockwise image
    358      * Use in templates with $RotateClockwise
    359      */
    360     public function generateRotateClockwise(GD $gd) {
    361         return $gd->rotate(90);
    362     }
    363
    364     /**
    365      * Generate a rotateв CounterClockwise image
    366      * Use in templates with $RotateCounterClockwise
    367      */
    368     public function generateRotateCounterClockwise(GD $gd) {
    369         return $gd->rotate(270);
    370     }
    371
    372     /**
    373      * Remove all of the formatted cached images for this image.
    374      * @return int The number of formatted images deleted
    375      */
    376     public function deleteFormattedImages() {
    377
    378         if(!$this->Filename) return 0;
    379         $numDeleted = 0;
    380         $folder = $this->ParentID ? $this->Parent()->Filename : ASSETS_DIR . '/';
    381         $name = $this->Name;
    382
    383         if ($this->isChanged('ParentID')) {
    384             $folder = dirname($this->getField('Filename')) . '/';
    385             $name = basename($this->getField('Filename'));
    386         }
    387
    388         $cacheDir = Director::getAbsFile($folder . '_resampled/');
    389         if(!is_dir($cacheDir)) return 0;
    390
    391         $this->extend('deleteFormattedImages', $numDeleted);
    392
    393         $cachedFiles = array();
    394         if($handle = opendir($cacheDir)) {
    395             while(($file = readdir($handle)) !== false) {
    396                 // save files not starting with a dot && ending with $this->Name
    397                 if(substr($file, 0, 1) != '.'
    398                     && substr_compare($file, $name, -strlen($name), strlen($name)) == 0
    399                     && is_file($cacheDir . $file)
    400                 ) {
    401                     $cachedFiles[] = $file;
    402                 }
    403             }
    404             closedir($handle);
    405         }
    406         if (count($cachedFiles) == 0) return 0;
    407
    408         $methodNames = $this->allMethodNames();
    409         $generateFuncs = array();
    410         foreach($methodNames as $methodName) {
    411             if(substr($methodName, 0, 8) == 'generate') {
    412                 $format = substr($methodName, 8);
    413                 $generateFuncs[] = $format;
    414             }
    415         }
    416         // All generate functions may appear any number of times in the image cache name.
    417         $generateFuncs = implode('|', $generateFuncs);
    418         $pattern = "/^(({$generateFuncs})(\d+)?-)+".preg_quote($name)."$/i";
    419
    420         foreach(preg_grep($pattern, $cachedFiles) as $cfile) {
    421             if(file_exists($cacheDir . $cfile)) {
    422                 unlink($cacheDir . $cfile);
    423                 $numDeleted++;
    424             }
    425         }
    426         return $numDeleted;
    427     }
    428
    429     /**
    430      * Get the dimensions of this Image.
    431      * @param string $dim If this is equal to "string", return the dimensions in string form,
    432      * if it is 0 return the height, if it is 1 return the width.
    433      * @return string|int
    434      */
    435     function getDimensions($dim = "string") {
    436         if($this->getField('Filename')) {
    437             $imagefile = Director::baseFolder() . '/' . $this->getField('Filename');
    438             if(file_exists($imagefile)) {
    439                 $size = getimagesize($imagefile);
    440                 return ($dim === "string") ? "$size[0]x$size[1]" : $size[$dim];
    441             } else {
    442                 return ($dim === "string") ? "file '$imagefile' not found" : null;
    443             }
    444         }
    445     }
    446
    447     /**
    448      * Get the width of this image.
    449      * @return int
    450      */
    451     function getWidth() {
    452         return $this->getDimensions(0);
    453     }
    454
    455     /**
    456      * Get the height of this image.
    457      * @return int
    458      */
    459     function getHeight() {
    460         return $this->getDimensions(1);
    461     }
    462
    463     /**
    464      * Get the orientation of this image.
    465      * @return ORIENTATION_SQUARE | ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE
    466      */
    467     function getOrientation() {
    468         $width = $this->getWidth();
    469         $height = $this->getHeight();
    470         if($width > $height) {
    471             return self::ORIENTATION_LANDSCAPE;
    472         } elseif($height > $width) {
    473             return self::ORIENTATION_PORTRAIT;
    474         } else {
    475             return self::ORIENTATION_SQUARE;
    476         }
    477     }
    478
    479     /**
    480      * Возвращает содержимое файла
    481      * Требуется для SVG-формата
    482      *
    483      * @return string
    484      */
    485     function getSVGContent() {
    486         if (strtoupper($this->getExtension()) == 'SVG') {
    487             if(file_exists($this->getFullPath())) {
    488                 return file_get_contents($this->getFullPath());
    489             }
    490         }
    491         return false;
    492     }
    493
    494     protected function onBeforeDelete() {
    495         parent::onBeforeDelete();
    496         $this->deleteFormattedImages();
    497     }
    498
    499     //При перемещении Image удаляем его Formatted Images
    500     protected function resetFilename($renamePhysicalFile = true) {
    501         $this->deleteFormattedImages();
    502         parent::resetFilename($renamePhysicalFile);
    503     }
    504 }
    505
    506 /**
    507  * A resized / processed {@link Image} object.
    508  * When Image object are processed or resized, a suitable Image_Cached object is returned, pointing to the
    509  * cached copy of the processed image.
    510  * @package sapphire
    511  * @subpackage filesystem
    512  */
    513 class Image_Cached extends Image {
    514     /**
    515      * Create a new cached image.
    516      * @param string $filename The filename of the image.
    517      * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.  Singletons
    518      * don't have their defaults set.
    519      */
    520     public function __construct($filename = null, $isSingleton = false) {
    521         parent::__construct(array(), $isSingleton);
    522         $this->Filename = $filename;
    523     }
    524
    525     public function getRelativePath() {
    526         return $this->getField('Filename');
    527     }
    528
    529     // Prevent this from doing anything
    530     public function requireTable() {
    531
    532     }
    533
    534     public function debug() {
    535         return "Image_Cached object for $this->Filename";
    536     }
    537 }
    538
    539

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -