Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

Image::CropHeight()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 8
Ratio 100 %
Metric Value
dl 8
loc 8
rs 9.2
cc 4
eloc 5
nc 3
nop 1
1
<?php
2
3
/**
4
 * Represents an Image
5
 *
6
 * @package framework
7
 * @subpackage filesystem
8
 */
9
class Image extends File implements Flushable {
10
11
	const ORIENTATION_SQUARE = 0;
12
	const ORIENTATION_PORTRAIT = 1;
13
	const ORIENTATION_LANDSCAPE = 2;
14
15
	private static $backend = "GDBackend";
16
17
	private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
18
		'Tag' => 'HTMLText',
19
	);
20
21
	/**
22
	 * @config
23
	 * @var int The width of an image thumbnail in a strip.
24
	 */
25
	private static $strip_thumbnail_width = 50;
26
27
	/**
28
	 * @config
29
	 * @var int The height of an image thumbnail in a strip.
30
	 */
31
	private static $strip_thumbnail_height = 50;
32
33
	/**
34
	 * @config
35
	 * @var int The width of an image thumbnail in the CMS.
36
	 */
37
	private static $cms_thumbnail_width = 100;
38
39
	/**
40
	 * @config
41
	 * @var int The height of an image thumbnail in the CMS.
42
	 */
43
	private static $cms_thumbnail_height = 100;
44
45
	/**
46
	 * @config
47
	 * @var int The width of an image thumbnail in the Asset section.
48
	 */
49
	private static $asset_thumbnail_width = 100;
50
51
	/**
52
	 * @config
53
	 * @var int The height of an image thumbnail in the Asset section.
54
	 */
55
	private static $asset_thumbnail_height = 100;
56
57
	/**
58
	 * @config
59
	 * @var int The width of an image preview in the Asset section.
60
	 */
61
	private static $asset_preview_width = 400;
62
63
	/**
64
	 * @config
65
	 * @var int The height of an image preview in the Asset section.
66
	 */
67
	private static $asset_preview_height = 200;
68
69
	/**
70
	 * @config
71
	 * @var bool Force all images to resample in all cases
72
	 */
73
	private static $force_resample = false;
74
75
	/**
76
	 * @config
77
	 * @var bool Regenerates images if set to true. This is set by {@link flush()}
78
	 */
79
	private static $flush = false;
80
81
	/**
82
	 * Triggered early in the request when someone requests a flush.
83
	 */
84
	public static function flush() {
85
		self::$flush = true;
86
	}
87
88
	public static function set_backend($backend) {
89
		self::config()->backend = $backend;
0 ignored issues
show
Documentation introduced by
The property backend does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
90
	}
91
92
	public static function get_backend() {
93
		return self::config()->backend;
94
	}
95
96
	/**
97
	 * Retrieve the original filename from the path of a transformed image.
98
	 * Any other filenames pass through unchanged.
99
	 *
100
	 * @param string $path
101
	 * @return string
102
	 */
103
	public static function strip_resampled_prefix($path) {
104
		return preg_replace('/_resampled\/(.+\/|[^-]+-)/', '', $path);
105
	}
106
107
	/**
108
	 * Set up template methods to access the transformations generated by 'generate' methods.
109
	 */
110
	public function defineMethods() {
111
		$methodNames = $this->allMethodNames();
112
		foreach($methodNames as $methodName) {
113
			if(substr($methodName,0,8) == 'generate') {
114
				$this->addWrapperMethod(substr($methodName,8), 'getFormattedImage');
115
			}
116
		}
117
118
		parent::defineMethods();
119
	}
120
121
	public function getCMSFields() {
122
		$fields = parent::getCMSFields();
123
124
		$urlLink = "<div class='field readonly'>";
125
		$urlLink .= "<label class='left'>"._t('AssetTableField.URL','URL')."</label>";
126
		$urlLink .= "<span class='readonly'><a href='{$this->Link()}'>{$this->RelativeLink()}</a></span>";
127
		$urlLink .= "</div>";
128
		// todo: check why the above code is here, since $urlLink is not used?
129
130
		//attach the addition file information for an image to the existing FieldGroup create in the parent class
131
		$fileAttributes = $fields->fieldByName('Root.Main.FilePreview')->fieldByName('FilePreviewData');
132
		$fileAttributes->push(new ReadonlyField("Dimensions", _t('AssetTableField.DIM','Dimensions') . ':'));
133
134
		return $fields;
135
	}
136
137
	/**
138
	 * Return an XHTML img tag for this Image,
139
	 * or NULL if the image file doesn't exist on the filesystem.
140
	 *
141
	 * @return string
142
	 */
143
	public function getTag() {
144
		if($this->exists()) {
145
			$url = $this->getURL();
146
			$title = ($this->Title) ? $this->Title : $this->Filename;
147
			if($this->Title) {
148
				$title = Convert::raw2att($this->Title);
149
			} else {
150
				if(preg_match("/([^\/]*)\.[a-zA-Z0-9]{1,6}$/", $title, $matches)) {
151
					$title = Convert::raw2att($matches[1]);
152
				}
153
			}
154
			return "<img src=\"$url\" alt=\"$title\" />";
155
		}
156
	}
157
158
	/**
159
	 * Return an XHTML img tag for this Image.
160
	 *
161
	 * @return string
162
	 */
163
	public function forTemplate() {
164
		return $this->getTag();
165
	}
166
167
	/**
168
	 * File names are filtered through {@link FileNameFilter}, see class documentation
169
	 * on how to influence this behaviour.
170
	 *
171
	 * @deprecated 4.0
172
	 */
173
	public function loadUploadedImage($tmpFile) {
174
		Deprecation::notice('4.0', 'Use the Upload::loadIntoFile()');
175
176
		if(!is_array($tmpFile)) {
177
			user_error("Image::loadUploadedImage() Not passed an array.  Most likely, the form hasn't got the right"
178
				. "enctype", E_USER_ERROR);
179
		}
180
181
		if(!$tmpFile['size']) {
182
			return;
183
		}
184
185
		$class = $this->class;
186
187
		// Create a folder
188
		if(!file_exists(ASSETS_PATH)) {
189
			mkdir(ASSETS_PATH, Config::inst()->get('Filesystem', 'folder_create_mask'));
190
		}
191
192
		if(!file_exists(ASSETS_PATH . "/$class")) {
193
			mkdir(ASSETS_PATH . "/$class", Config::inst()->get('Filesystem', 'folder_create_mask'));
194
		}
195
196
		// Generate default filename
197
		$nameFilter = FileNameFilter::create();
198
		$file = $nameFilter->filter($tmpFile['name']);
199
		if(!$file) $file = "file.jpg";
200
201
		$file = ASSETS_PATH . "/$class/$file";
202
203
		while(file_exists(BASE_PATH . "/$file")) {
204
			$i = $i ? ($i+1) : 2;
0 ignored issues
show
Bug introduced by
The variable $i does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
205
			$oldFile = $file;
206
			$file = preg_replace('/[0-9]*(\.[^.]+$)/', $i . '\\1', $file);
207
			if($oldFile == $file && $i > 2) user_error("Couldn't fix $file with $i", E_USER_ERROR);
208
		}
209
210
		if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], BASE_PATH . "/$file")) {
211
			// Remove the old images
212
213
			$this->deleteFormattedImages();
214
			return true;
215
		}
216
	}
217
218
	/**
219
	 * Scale image proportionally to fit within the specified bounds
220
	 *
221
	 * @param integer $width The width to size within
222
	 * @param integer $height The height to size within
223
	 * @return Image|null
224
	 */
225
	public function Fit($width, $height) {
226
		// Prevent divide by zero on missing/blank file
227
		if(!$this->getWidth() || !$this->getHeight()) return null;
228
229
		// Check if image is already sized to the correct dimension
230
		$widthRatio = $width / $this->getWidth();
231
		$heightRatio = $height / $this->getHeight();
232
233
		if( $widthRatio < $heightRatio ) {
234
			// Target is higher aspect ratio than image, so check width
235
			if($this->isWidth($width) && !Config::inst()->get('Image', 'force_resample')) return $this;
236
		} else {
237
			// Target is wider or same aspect ratio as image, so check height
238
			if($this->isHeight($height) && !Config::inst()->get('Image', 'force_resample')) return $this;
239
		}
240
241
		// Item must be regenerated
242
		return  $this->getFormattedImage('Fit', $width, $height);
243
	}
244
245
	/**
246
	 * Scale image proportionally to fit within the specified bounds
247
	 *
248
	 * @param Image_Backend $backend
249
	 * @param integer $width The width to size within
250
	 * @param integer $height The height to size within
251
	 * @return Image_Backend
252
	 * @deprecated 4.0 Generate methods are no longer applicable
253
	 */
254
	public function generateFit(Image_Backend $backend, $width, $height) {
255
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
256
		return $backend->resizeRatio($width, $height);
257
	}
258
259
	/**
260
	 * Proportionally scale down this image if it is wider or taller than the specified dimensions.
261
	 * Similar to Fit but without up-sampling. Use in templates with $FitMax.
262
	 *
263
	 * @uses Image::Fit()
264
	 * @param integer $width The maximum width of the output image
265
	 * @param integer $height The maximum height of the output image
266
	 * @return Image
267
	 */
268
	public function FitMax($width, $height) {
269
		// Temporary $force_resample support for 3.x, to be removed in 4.0
270
		if (Config::inst()->get('Image', 'force_resample') && $this->getWidth() <= $width && $this->getHeight() <= $height) return $this->Fit($this->getWidth(),$this->getHeight());
271
272
		return $this->getWidth() > $width || $this->getHeight() > $height
273
			? $this->Fit($width,$height)
274
			: $this;
275
	}
276
277
	/**
278
	 * Resize and crop image to fill specified dimensions.
279
	 * Use in templates with $Fill
280
	 *
281
	 * @param integer $width Width to crop to
282
	 * @param integer $height Height to crop to
283
	 * @return Image|null
284
	 */
285
	public function Fill($width, $height) {
286
		return $this->isSize($width, $height) && !Config::inst()->get('Image', 'force_resample')
287
			? $this
288
			: $this->getFormattedImage('Fill', $width, $height);
289
	}
290
291
	/**
292
	 * Resize and crop image to fill specified dimensions.
293
	 * Use in templates with $Fill
294
	 *
295
	 * @param Image_Backend $backend
296
	 * @param integer $width Width to crop to
297
	 * @param integer $height Height to crop to
298
	 * @return Image_Backend
299
	 * @deprecated 4.0 Generate methods are no longer applicable
300
	 */
301
	public function generateFill(Image_Backend $backend, $width, $height) {
302
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
303
		return $backend->croppedResize($width, $height);
304
	}
305
306
	/**
307
	 * Crop this image to the aspect ratio defined by the specified width and height,
308
	 * then scale down the image to those dimensions if it exceeds them.
309
	 * Similar to Fill but without up-sampling. Use in templates with $FillMax.
310
	 *
311
	 * @uses Image::Fill()
312
	 * @param integer $width The relative (used to determine aspect ratio) and maximum width of the output image
313
	 * @param integer $height The relative (used to determine aspect ratio) and maximum height of the output image
314
	 * @return Image
315
	 */
316
	public function FillMax($width, $height) {
317
		// Prevent divide by zero on missing/blank file
318
		if(!$this->getWidth() || !$this->getHeight()) return null;
319
320
		// Temporary $force_resample support for 3.x, to be removed in 4.0
321
		if (Config::inst()->get('Image', 'force_resample') && $this->isSize($width, $height)) return $this->Fill($width, $height);
322
323
		// Is the image already the correct size?
324
		if ($this->isSize($width, $height)) return $this;
325
326
		// If not, make sure the image isn't upsampled
327
		$imageRatio = $this->getWidth() / $this->getHeight();
328
		$cropRatio = $width / $height;
329
		// If cropping on the x axis compare heights
330
		if ($cropRatio < $imageRatio && $this->getHeight() < $height) return $this->Fill($this->getHeight()*$cropRatio, $this->getHeight());
331
		// Otherwise we're cropping on the y axis (or not cropping at all) so compare widths
332
		if ($this->getWidth() < $width) return $this->Fill($this->getWidth(), $this->getWidth()/$cropRatio);
333
334
		return $this->Fill($width, $height);
335
	}
336
337
	/**
338
	 * Fit image to specified dimensions and fill leftover space with a solid colour (default white). Use in templates with $Pad.
339
	 *
340
	 * @param integer $width The width to size to
341
	 * @param integer $height The height to size to
342
	 * @return Image|null
343
	 */
344
	public function Pad($width, $height, $backgroundColor='FFFFFF') {
345
		return $this->isSize($width, $height) && !Config::inst()->get('Image', 'force_resample')
346
			? $this
347
			: $this->getFormattedImage('Pad', $width, $height, $backgroundColor);
348
	}
349
350
	/**
351
	 * Fit image to specified dimensions and fill leftover space with a solid colour (default white). Use in templates with $Pad.
352
	 *
353
	 * @param Image_Backend $backend
354
	 * @param integer $width The width to size to
355
	 * @param integer $height The height to size to
356
	 * @return Image_Backend
357
	 * @deprecated 4.0 Generate methods are no longer applicable
358
	 */
359
	public function generatePad(Image_Backend $backend, $width, $height, $backgroundColor='FFFFFF') {
360
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
361
		return $backend->paddedResize($width, $height, $backgroundColor);
362
	}
363
364
	/**
365
	 * Scale image proportionally by width. Use in templates with $ScaleWidth.
366
	 *
367
	 * @param integer $width The width to set
368
	 * @return Image|null
369
	 */
370
	public function ScaleWidth($width) {
371
		return $this->isWidth($width) && !Config::inst()->get('Image', 'force_resample')
372
			? $this
373
			: $this->getFormattedImage('ScaleWidth', $width);
374
	}
375
376
	/**
377
	 * Scale image proportionally by width. Use in templates with $ScaleWidth.
378
	 *
379
	 * @param Image_Backend $backend
380
	 * @param int $width The width to set
381
	 * @return Image_Backend
382
	 * @deprecated 4.0 Generate methods are no longer applicable
383
	 */
384
	public function generateScaleWidth(Image_Backend $backend, $width) {
385
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
386
		return $backend->resizeByWidth($width);
387
	}
388
389
	/**
390
	 * Proportionally scale down this image if it is wider than the specified width.
391
	 * Similar to ScaleWidth but without up-sampling. Use in templates with $ScaleMaxWidth.
392
	 *
393
	 * @uses Image::ScaleWidth()
394
	 * @param integer $width The maximum width of the output image
395
	 * @return Image
396
	 */
397 View Code Duplication
	public function ScaleMaxWidth($width) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
		// Temporary $force_resample support for 3.x, to be removed in 4.0
399
		if (Config::inst()->get('Image', 'force_resample') && $this->getWidth() <= $width) return $this->ScaleWidth($this->getWidth());
400
401
		return $this->getWidth() > $width
402
			? $this->ScaleWidth($width)
403
			: $this;
404
	}
405
406
	/**
407
	 * Scale image proportionally by height. Use in templates with $ScaleHeight.
408
	 *
409
	 * @param integer $height The height to set
410
	 * @return Image|null
411
	 */
412
	public function ScaleHeight($height) {
413
		return $this->isHeight($height) && !Config::inst()->get('Image', 'force_resample')
414
			? $this
415
			: $this->getFormattedImage('ScaleHeight', $height);
416
	}
417
418
	/**
419
	 * Scale image proportionally by height. Use in templates with $ScaleHeight.
420
	 *
421
	 * @param Image_Backend $backend
422
	 * @param integer $height The height to set
423
	 * @return Image_Backend
424
	 * @deprecated 4.0 Generate methods are no longer applicable
425
	 */
426
	public function generateScaleHeight(Image_Backend $backend, $height){
427
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
428
		return $backend->resizeByHeight($height);
429
	}
430
431
	/**
432
	 * Proportionally scale down this image if it is taller than the specified height.
433
	 * Similar to ScaleHeight but without up-sampling. Use in templates with $ScaleMaxHeight.
434
	 *
435
	 * @uses Image::ScaleHeight()
436
	 * @param integer $height The maximum height of the output image
437
	 * @return Image
438
	 */
439 View Code Duplication
	public function ScaleMaxHeight($height) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440
		// Temporary $force_resample support for 3.x, to be removed in 4.0
441
		if (Config::inst()->get('Image', 'force_resample') && $this->getHeight() <= $height) return $this->ScaleHeight($this->getHeight());
442
443
		return $this->getHeight() > $height
444
			? $this->ScaleHeight($height)
445
			: $this;
446
	}
447
448
	/**
449
	 * Crop image on X axis if it exceeds specified width. Retain height.
450
	 * Use in templates with $CropWidth. Example: $Image.ScaleHeight(100).$CropWidth(100)
451
	 *
452
	 * @uses Image::Fill()
453
	 * @param integer $width The maximum width of the output image
454
	 * @return Image
455
	 */
456 View Code Duplication
	public function CropWidth($width) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
457
		// Temporary $force_resample support for 3.x, to be removed in 4.0
458
		if (Config::inst()->get('Image', 'force_resample') && $this->getWidth() <= $width) return $this->Fill($this->getWidth(), $this->getHeight());
459
460
		return $this->getWidth() > $width
461
			? $this->Fill($width, $this->getHeight())
462
			: $this;
463
	}
464
465
	/**
466
	 * Crop image on Y axis if it exceeds specified height. Retain width.
467
	 * Use in templates with $CropHeight. Example: $Image.ScaleWidth(100).CropHeight(100)
468
	 *
469
	 * @uses Image::Fill()
470
	 * @param integer $height The maximum height of the output image
471
	 * @return Image
472
	 */
473 View Code Duplication
	public function CropHeight($height) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
474
		// Temporary $force_resample support for 3.x, to be removed in 4.0
475
		if (Config::inst()->get('Image', 'force_resample') && $this->getHeight() <= $height) return $this->Fill($this->getWidth(), $this->getHeight());
476
477
		return $this->getHeight() > $height
478
			? $this->Fill($this->getWidth(), $height)
479
			: $this;
480
	}
481
482
	/**
483
	 * Resize the image by preserving aspect ratio, keeping the image inside the
484
	 * $width and $height
485
	 *
486
	 * @param integer $width The width to size within
487
	 * @param integer $height The height to size within
488
	 * @return Image
489
	 * @deprecated 4.0 Use Fit instead
490
	 */
491
	public function SetRatioSize($width, $height) {
492
		Deprecation::notice('4.0', 'Use Fit instead');
493
		return $this->Fit($width, $height);
494
	}
495
496
	/**
497
	 * Resize the image by preserving aspect ratio, keeping the image inside the
498
	 * $width and $height
499
	 *
500
	 * @param Image_Backend $backend
501
	 * @param integer $width The width to size within
502
	 * @param integer $height The height to size within
503
	 * @return Image_Backend
504
	 * @deprecated 4.0 Generate methods are no longer applicable
505
	 */
506
	public function generateSetRatioSize(Image_Backend $backend, $width, $height) {
507
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
508
		return $backend->resizeRatio($width, $height);
509
	}
510
511
	/**
512
	 * Resize this Image by width, keeping aspect ratio. Use in templates with $SetWidth.
513
	 *
514
	 * @param integer $width The width to set
515
	 * @return Image
516
	 * @deprecated 4.0 Use ScaleWidth instead
517
	 */
518
	public function SetWidth($width) {
519
		Deprecation::notice('4.0', 'Use ScaleWidth instead');
520
		return $this->ScaleWidth($width);
521
	}
522
523
	/**
524
	 * Resize this Image by width, keeping aspect ratio. Use in templates with $SetWidth.
525
	 *
526
	 * @param Image_Backend $backend
527
	 * @param int $width The width to set
528
	 * @return Image_Backend
529
	 * @deprecated 4.0 Generate methods are no longer applicable
530
	 */
531
	public function generateSetWidth(Image_Backend $backend, $width) {
532
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
533
		return $backend->resizeByWidth($width);
534
	}
535
536
	/**
537
	 * Resize this Image by height, keeping aspect ratio. Use in templates with $SetHeight.
538
	 *
539
	 * @param integer $height The height to set
540
	 * @return Image
541
	 * @deprecated 4.0 Use ScaleHeight instead
542
	 */
543
	public function SetHeight($height) {
544
		Deprecation::notice('4.0', 'Use ScaleHeight instead');
545
		return $this->ScaleHeight($height);
546
	}
547
548
	/**
549
	 * Resize this Image by height, keeping aspect ratio. Use in templates with $SetHeight.
550
	 *
551
	 * @param Image_Backend $backend
552
	 * @param integer $height The height to set
553
	 * @return Image_Backend
554
	 * @deprecated 4.0 Generate methods are no longer applicable
555
	 */
556
	public function generateSetHeight(Image_Backend $backend, $height){
557
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
558
		return $backend->resizeByHeight($height);
559
	}
560
561
	/**
562
	 * Resize this Image by both width and height, using padded resize. Use in templates with $SetSize.
563
	 * @see Image::PaddedImage()
564
	 *
565
	 * @param integer $width The width to size to
566
	 * @param integer $height The height to size to
567
	 * @return Image
568
	 * @deprecated 4.0 Use Pad instead
569
	 */
570
	public function SetSize($width, $height) {
571
		Deprecation::notice('4.0', 'Use Pad instead');
572
		return $this->Pad($width, $height);
573
	}
574
575
	/**
576
	 * Resize this Image by both width and height, using padded resize. Use in templates with $SetSize.
577
	 *
578
	 * @param Image_Backend $backend
579
	 * @param integer $width The width to size to
580
	 * @param integer $height The height to size to
581
	 * @return Image_Backend
582
	 * @deprecated 4.0 Generate methods are no longer applicable
583
	 */
584
	public function generateSetSize(Image_Backend $backend, $width, $height) {
585
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
586
		return $backend->paddedResize($width, $height);
587
	}
588
589
	/**
590
	 * Resize this image for the CMS. Use in templates with $CMSThumbnail
591
	 *
592
	 * @return Image_Cached|null
593
	 */
594
	public function CMSThumbnail() {
595
		return $this->Pad($this->stat('cms_thumbnail_width'),$this->stat('cms_thumbnail_height'));
596
	}
597
598
	/**
599
	 * Resize this image for the CMS. Use in templates with $CMSThumbnail.
600
	 *
601
	 * @return Image_Backend
602
	 * @deprecated 4.0 Generate methods are no longer applicable
603
	 */
604
	public function generateCMSThumbnail(Image_Backend $backend) {
605
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
606
		return $backend->paddedResize($this->stat('cms_thumbnail_width'),$this->stat('cms_thumbnail_height'));
607
	}
608
609
	/**
610
	 * Resize this image for preview in the Asset section. Use in templates with $AssetLibraryPreview.
611
	 *
612
	 * @return Image_Backend
613
	 * @deprecated 4.0 Generate methods are no longer applicable
614
	 */
615
	public function generateAssetLibraryPreview(Image_Backend $backend) {
616
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
617
		return $backend->paddedResize($this->stat('asset_preview_width'),$this->stat('asset_preview_height'));
618
	}
619
620
	/**
621
	 * Resize this image for thumbnail in the Asset section. Use in templates with $AssetLibraryThumbnail.
622
	 *
623
	 * @return Image_Backend
624
	 * @deprecated 4.0 Generate methods are no longer applicable
625
	 */
626
	public function generateAssetLibraryThumbnail(Image_Backend $backend) {
627
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
628
		return $backend->paddedResize($this->stat('asset_thumbnail_width'),$this->stat('asset_thumbnail_height'));
629
	}
630
631
	/**
632
	 * Resize this image for use as a thumbnail in a strip. Use in templates with $StripThumbnail.
633
	 *
634
	 * @return Image_Cached|null
635
	 */
636
	public function StripThumbnail() {
637
		return $this->Fill($this->stat('strip_thumbnail_width'),$this->stat('strip_thumbnail_height'));
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->Fill($this->stat(...ip_thumbnail_height')); of type Image|null adds the type Image to the return on line 637 which is incompatible with the return type of the parent method File::StripThumbnail of type HTMLVarchar.
Loading history...
638
	}
639
640
	/**
641
	 * Resize this image for use as a thumbnail in a strip. Use in templates with $StripThumbnail.
642
	 *
643
	 * @return Image_Backend
644
	 * @deprecated 4.0 Generate methods are no longer applicable
645
	 */
646
	public function generateStripThumbnail(Image_Backend $backend) {
647
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
648
		return $backend->croppedResize($this->stat('strip_thumbnail_width'),$this->stat('strip_thumbnail_height'));
649
	}
650
651
	/**
652
	 * Resize this Image by both width and height, using padded resize. Use in templates with $PaddedImage.
653
	 * @see Image::SetSize()
654
	 *
655
	 * @param integer $width The width to size to
656
	 * @param integer $height The height to size to
657
	 * @return Image
658
	 * @deprecated 4.0 Use Pad instead
659
	 */
660
	public function PaddedImage($width, $height, $backgroundColor='FFFFFF') {
661
		Deprecation::notice('4.0', 'Use Pad instead');
662
		return $this->Pad($width, $height, $backgroundColor);
663
	}
664
665
	/**
666
	 * Resize this Image by both width and height, using padded resize. Use in templates with $PaddedImage.
667
	 *
668
	 * @param Image_Backend $backend
669
	 * @param integer $width The width to size to
670
	 * @param integer $height The height to size to
671
	 * @return Image_Backend
672
	 * @deprecated 4.0 Generate methods are no longer applicable
673
	 */
674
	public function generatePaddedImage(Image_Backend $backend, $width, $height, $backgroundColor='FFFFFF') {
675
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
676
		return $backend->paddedResize($width, $height, $backgroundColor);
677
	}
678
679
	/**
680
	 * Determine if this image is of the specified size
681
	 *
682
	 * @param integer $width Width to check
683
	 * @param integer $height Height to check
684
	 * @return boolean
685
	 */
686
	public function isSize($width, $height) {
687
		return $this->isWidth($width) && $this->isHeight($height);
688
	}
689
690
	/**
691
	 * Determine if this image is of the specified width
692
	 *
693
	 * @param integer $width Width to check
694
	 * @return boolean
695
	 */
696
	public function isWidth($width) {
697
		return !empty($width) && $this->getWidth() == $width;
698
	}
699
700
	/**
701
	 * Determine if this image is of the specified width
702
	 *
703
	 * @param integer $height Height to check
704
	 * @return boolean
705
	 */
706
	public function isHeight($height) {
707
		return !empty($height) && $this->getHeight() == $height;
708
	}
709
710
	/**
711
	 * Return an image object representing the image in the given format.
712
	 * This image will be generated using generateFormattedImage().
713
	 * The generated image is cached, to flush the cache append ?flush=1 to your URL.
714
	 *
715
	 * Just pass the correct number of parameters expected by the working function
716
	 *
717
	 * @param string $format The name of the format.
718
	 * @return Image_Cached|null
719
	 */
720
	public function getFormattedImage($format) {
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
721
		$args = func_get_args();
722
723
		if($this->exists()) {
724
			$cacheFile = call_user_func_array(array($this, "cacheFilename"), $args);
725
726
			if(!file_exists(Director::baseFolder()."/".$cacheFile) || self::$flush) {
727
				call_user_func_array(array($this, "generateFormattedImage"), $args);
728
			}
729
730
			$cached = new Image_Cached($cacheFile, false, $this);
731
			return $cached;
732
		}
733
	}
734
735
	/**
736
	 * Return the filename for the cached image, given its format name and arguments.
737
	 * @param string $format The format name.
738
	 * @return string
739
	 * @throws InvalidArgumentException
740
	 */
741
	public function cacheFilename($format) {
742
		$args = func_get_args();
743
		array_shift($args);
744
745
		// Note: $folder holds the *original* file, while the Image we're working with
746
		// may be a formatted image in a child directory (this happens when we're chaining formats)
747
		$folder = $this->ParentID ? $this->Parent()->Filename : ASSETS_DIR . "/";
748
749
		$format = $format . Convert::base64url_encode($args);
750
		$filename = $format . "/" . $this->Name;
751
752
		$pattern = $this->getFilenamePatterns($this->Name);
753
754
		// Any previous formats need to be derived from this Image's directory, and prepended to the new filename
755
		$prepend = array();
756
		preg_match_all($pattern['GeneratorPattern'], $this->Filename, $matches, PREG_SET_ORDER);
757
		foreach($matches as $formatdir) {
758
			$prepend[] = $formatdir[0];
759
		}
760
		$filename = implode($prepend) . $filename;
761
762
		if (!preg_match($pattern['FullPattern'], $filename)) {
763
			throw new InvalidArgumentException('Filename ' . $filename
764
				. ' that should be used to cache a resized image is invalid');
765
		}
766
767
		return $folder . "_resampled/" . $filename;
768
	}
769
770
	/**
771
	 * Generate an image on the specified format. It will save the image
772
	 * at the location specified by cacheFilename(). The image will be generated
773
	 * using the specific 'generate' method for the specified format.
774
	 *
775
	 * @param string $format Name of the format to generate.
776
	 */
777
	public function generateFormattedImage($format) {
778
		$args = func_get_args();
779
780
		$cacheFile = call_user_func_array(array($this, "cacheFilename"), $args);
781
782
		$backend = Injector::inst()->createWithArgs(self::config()->backend, array(
783
			Director::baseFolder()."/" . $this->Filename,
784
			$args
785
		));
786
787
		if($backend->hasImageResource()) {
788
789
			$generateFunc = "generate$format";
790
			if($this->hasMethod($generateFunc)){
791
792
				array_shift($args);
793
				array_unshift($args, $backend);
794
795
				$backend = call_user_func_array(array($this, $generateFunc), $args);
796
				if($backend){
797
					$backend->writeTo(Director::baseFolder()."/" . $cacheFile);
798
				}
799
800
			} else {
801
				user_error("Image::generateFormattedImage - Image $format public function not found.",E_USER_WARNING);
802
			}
803
		}
804
	}
805
806
	/**
807
	 * Generate a resized copy of this image with the given width & height.
808
	 * This can be used in templates with $ResizedImage but should be avoided,
809
	 * as it's the only image manipulation function which can skew an image.
810
	 *
811
	 * @param integer $width Width to resize to
812
	 * @param integer $height Height to resize to
813
	 * @return Image
814
	 */
815
	public function ResizedImage($width, $height) {
816
		return $this->isSize($width, $height) && !Config::inst()->get('Image', 'force_resample')
817
			? $this
818
			: $this->getFormattedImage('ResizedImage', $width, $height);
819
	}
820
821
	/**
822
	 * Generate a resized copy of this image with the given width & height.
823
	 * Use in templates with $ResizedImage.
824
	 *
825
	 * @param Image_Backend $backend
826
	 * @param integer $width Width to resize to
827
	 * @param integer $height Height to resize to
828
	 * @return Image_Backend|null
829
	 */
830
	public function generateResizedImage(Image_Backend $backend, $width, $height) {
831
		if(!$backend){
832
			user_error("Image::generateFormattedImage - generateResizedImage is being called by legacy code"
833
				. " or Image::\$backend is not set.",E_USER_WARNING);
834
		}else{
835
			return $backend->resize($width, $height);
836
		}
837
	}
838
839
	/**
840
	 * Generate a resized copy of this image with the given width & height, cropping to maintain aspect ratio.
841
	 * Use in templates with $CroppedImage
842
	 *
843
	 * @param integer $width Width to crop to
844
	 * @param integer $height Height to crop to
845
	 * @return Image
846
	 * @deprecated 4.0 Use Fill instead
847
	 */
848
	public function CroppedImage($width, $height) {
849
		Deprecation::notice('4.0', 'Use Fill instead');
850
		return $this->Fill($width, $height);
851
	}
852
853
	/**
854
	 * Generate a resized copy of this image with the given width & height, cropping to maintain aspect ratio.
855
	 * Use in templates with $CroppedImage
856
	 *
857
	 * @param Image_Backend $backend
858
	 * @param integer $width Width to crop to
859
	 * @param integer $height Height to crop to
860
	 * @return Image_Backend
861
	 * @deprecated 4.0 Generate methods are no longer applicable
862
	 */
863
	public function generateCroppedImage(Image_Backend $backend, $width, $height) {
864
		Deprecation::notice('4.0', 'Generate methods are no longer applicable');
865
		return $backend->croppedResize($width, $height);
866
	}
867
868
	/**
869
	 * Generate patterns that will help to match filenames of cached images
870
	 * @param string $filename Filename of source image
871
	 * @return array
872
	 */
873
	private function getFilenamePatterns($filename) {
874
		$methodNames = $this->allMethodNames(true);
875
		$generateFuncs = array();
876 View Code Duplication
		foreach($methodNames as $methodName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
877
			if(substr($methodName, 0, 8) == 'generate') {
878
				$format = substr($methodName, 8);
879
				$generateFuncs[] = preg_quote($format);
880
			}
881
		}
882
		// All generate functions may appear any number of times in the image cache name.
883
		$generateFuncs = implode('|', $generateFuncs);
884
		$base64url_match = "[a-zA-Z0-9_~]*={0,2}";
885
		return array(
886
				'FullPattern' => "/^((?P<Generator>{$generateFuncs})(?P<Args>" . $base64url_match . ")\/)+"
887
									. preg_quote($filename) . "$/i",
888
				'GeneratorPattern' => "/(?P<Generator>{$generateFuncs})(?P<Args>" . $base64url_match . ")\//i"
889
		);
890
	}
891
892
	/**
893
	 * Generate a list of images that were generated from this image
894
	 */
895
	private function getGeneratedImages() {
896
		$generatedImages = array();
897
		$cachedFiles = array();
898
899
		$folder = $this->ParentID ? $this->Parent()->Filename : ASSETS_DIR . '/';
900
		$cacheDir = Director::getAbsFile($folder . '_resampled/');
901
902
		// Find all paths with the same filename as this Image (the path contains the transformation info)
903
		if(is_dir($cacheDir)) {
904
			$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cacheDir));
905
			foreach($files as $path => $file){
906
				if ($file->getFilename() == $this->Name) {
907
					$cachedFiles[] = $path;
908
				}
909
			}
910
		}
911
912
		$pattern = $this->getFilenamePatterns($this->Name);
913
914
		// Reconstruct the image transformation(s) from the format-folder(s) in the path
915
		// (if chained, they contain the transformations in the correct order)
916
		foreach($cachedFiles as $cf_path) {
917
			preg_match_all($pattern['GeneratorPattern'], $cf_path, $matches, PREG_SET_ORDER);
918
919
			$generatorArray = array();
920
			foreach ($matches as $singleMatch) {
921
				$generatorArray[] = array(
922
					'Generator' => $singleMatch['Generator'],
923
					'Args' => Convert::base64url_decode($singleMatch['Args'])
924
				);
925
			}
926
927
			$generatedImages[] = array(
928
				'FileName' => $cf_path,
929
				'Generators' => $generatorArray
930
			);
931
		}
932
933
		return $generatedImages;
934
	}
935
936
	/**
937
	 * Regenerate all of the formatted cached images for this image.
938
	 *
939
	 * @return int The number of formatted images regenerated
940
	 */
941
	public function regenerateFormattedImages() {
942
		if(!$this->Filename) return 0;
943
944
		// Without this, not a single file would be written
945
		// caused by a check in getFormattedImage()
946
		$this->flush();
947
948
		$numGenerated = 0;
949
		$generatedImages = $this->getGeneratedImages();
950
		$doneList = array();
951
		foreach($generatedImages as $singleImage) {
952
			$cachedImage = $this;
953
			if (in_array($singleImage['FileName'], $doneList) ) continue;
954
955
			foreach($singleImage['Generators'] as $singleGenerator) {
956
				$args = array_merge(array($singleGenerator['Generator']), $singleGenerator['Args']);
957
				$cachedImage = call_user_func_array(array($cachedImage, "getFormattedImage"), $args);
958
			}
959
			$doneList[] = $singleImage['FileName'];
960
			$numGenerated++;
961
		}
962
963
		return $numGenerated;
964
	}
965
966
	/**
967
	 * Remove all of the formatted cached images for this image.
968
	 *
969
	 * @return int The number of formatted images deleted
970
	 */
971
	public function deleteFormattedImages() {
972
		if(!$this->Filename) return 0;
973
974
		$numDeleted = 0;
975
		$generatedImages = $this->getGeneratedImages();
976
		foreach($generatedImages as $singleImage) {
977
			$path = $singleImage['FileName'];
978
			unlink($path);
979
			$numDeleted++;
980
			do {
981
				$path = dirname($path);
982
			}
983
			// remove the folder if it's empty (and it's not the assets folder)
984
			while(!preg_match('/assets$/', $path) && Filesystem::remove_folder_if_empty($path));
985
		}
986
987
		return $numDeleted;
988
	}
989
990
	/**
991
	 * Get the dimensions of this Image.
992
	 * @param string $dim If this is equal to "string", return the dimensions in string form,
993
	 * if it is 0 return the height, if it is 1 return the width.
994
	 * @return string|int|null
995
	 */
996
	public function getDimensions($dim = "string") {
997
		if($this->getField('Filename')) {
998
999
			$imagefile = $this->getFullPath();
1000
			if($this->exists()) {
1001
				$size = getimagesize($imagefile);
1002
				return ($dim === "string") ? "$size[0]x$size[1]" : $size[$dim];
1003
			} else {
1004
				return ($dim === "string") ? "file '$imagefile' not found" : null;
1005
			}
1006
		}
1007
	}
1008
1009
	/**
1010
	 * Get the width of this image.
1011
	 * @return int
1012
	 */
1013
	public function getWidth() {
1014
		return $this->getDimensions(0);
1015
	}
1016
1017
	/**
1018
	 * Get the height of this image.
1019
	 * @return int
1020
	 */
1021
	public function getHeight() {
1022
		return $this->getDimensions(1);
1023
	}
1024
1025
	/**
1026
	 * Get the orientation of this image.
1027
	 * @return ORIENTATION_SQUARE | ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE
1028
	 */
1029
	public function getOrientation() {
1030
		$width = $this->getWidth();
1031
		$height = $this->getHeight();
1032
		if($width > $height) {
1033
			return self::ORIENTATION_LANDSCAPE;
1034
		} elseif($height > $width) {
1035
			return self::ORIENTATION_PORTRAIT;
1036
		} else {
1037
			return self::ORIENTATION_SQUARE;
1038
		}
1039
	}
1040
1041
	public function onAfterUpload() {
1042
		$this->deleteFormattedImages();
1043
		parent::onAfterUpload();
1044
	}
1045
1046
	protected function onBeforeDelete() {
1047
		$backend = Injector::inst()->create(self::get_backend());
1048
		$backend->onBeforeDelete($this);
1049
1050
		$this->deleteFormattedImages();
1051
1052
		parent::onBeforeDelete();
1053
	}
1054
}
1055
1056
/**
1057
 * A resized / processed {@link Image} object.
1058
 * When Image object are processed or resized, a suitable Image_Cached object is returned, pointing to the
1059
 * cached copy of the processed image.
1060
 *
1061
 * @package framework
1062
 * @subpackage filesystem
1063
 */
1064
class Image_Cached extends Image {
1065
1066
	/**
1067
	 * Create a new cached image.
1068
	 * @param string $filename The filename of the image.
1069
	 * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.
1070
	 *                             Singletons don't have their defaults set.
1071
	 */
1072
	public function __construct($filename = null, $isSingleton = false, Image $sourceImage = null) {
1073
		parent::__construct(array(), $isSingleton);
1074
		if ($sourceImage) {
1075
			// Copy properties from source image, except unsafe ones
1076
			$properties = $sourceImage->toMap();
1077
			unset($properties['RecordClassName'], $properties['ClassName']);
1078
			$this->update($properties);
1079
		}
1080
		$this->ID = -1;
1081
		$this->Filename = $filename;
1082
	}
1083
1084
	/**
1085
	 * Override the parent's exists method becuase the ID is explicitly set to -1 on a cached image we can't use the
1086
	 * default check
1087
	 *
1088
	 * @return bool Whether the cached image exists
1089
	 */
1090
	public function exists() {
1091
		return file_exists($this->getFullPath());
1092
	}
1093
1094
	public function getRelativePath() {
1095
		return $this->getField('Filename');
1096
	}
1097
1098
	/**
1099
	 * Prevent creating new tables for the cached record
1100
	 *
1101
	 * @return false
1102
	 */
1103
	public function requireTable() {
1104
		return false;
1105
	}
1106
1107
	/**
1108
	 * Prevent writing the cached image to the database
1109
	 *
1110
	 * @throws Exception
1111
	 */
1112
	public function write($showDebug = false, $forceInsert = false, $forceWrite = false, $writeComponents = false) {
1113
		throw new Exception("{$this->ClassName} can not be written back to the database.");
1114
	}
1115
}
1116