Passed
Push — master ( 7b46b2...9520f5 )
by Nicolaas
02:20
created

ImageManipulations::add_fake_parts()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 7
eloc 16
c 3
b 0
f 0
nc 24
nop 2
dl 0
loc 33
rs 8.8333
1
<?php
2
3
namespace Sunnysideup\PerfectCmsImages\Api;
4
5
use SilverStripe\Assets\Image;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Core\Config\Config;
9
use SilverStripe\Core\Config\Configurable;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Core\Injector\Injectable;
12
use SilverStripe\SiteConfig\SiteConfig;
13
14
class ImageManipulations
15
{
16
    use Configurable;
17
    use Injectable;
18
19
    private static $webp_enabled = true;
20
21
    private static $webp_quality = 77;
22
23
    private static $imageLinkCache = [];
24
25
    /**
26
     * work out the best image link.
27
     *
28
     * There are basically three options:
29
     * a. if the height and/or width matches or is smaller than it should be
30
     *    then just return natural image
31
     * b. if crop is set to true then Fill
32
     * c. otherwise resize Height/Width/Both
33
     *
34
     * @param Image     $image
35
     * @param null|bool $useRetina     optional
36
     * @param null|bool $forMobile     optional
37
     * @param null|int  $resizeToWidth optional
38
     */
39
    public static function get_image_link($image, string $name, ?bool $useRetina = null, ?bool $forMobile = null, ?int $resizeToWidth = 0): string
40
    {
41
        $cacheKey = $image->ClassName . '_' . $image->ID . '_' . $name . '_' . ($useRetina ? 'Y' : 'N') . '_' . ($forMobile ? 'MY' : 'MN');
42
        if (empty(self::$imageLinkCache[$cacheKey])) {
43
            //work out perfect width and height
44
            if (null === $useRetina) {
45
                $useRetina = PerfectCMSImages::use_retina($name);
46
            }
47
48
            $crop = PerfectCMSImages::is_crop($name);
49
50
            $multiplier = PerfectCMSImages::get_multiplier($useRetina);
51
            $perfectWidth = PerfectCMSImages::get_width($name, true);
52
            $perfectHeight = PerfectCMSImages::get_height($name, true);
53
54
            if ($forMobile) {
55
                $perfectWidth = PerfectCMSImages::get_mobile_width($name, true);
56
                $perfectHeight = PerfectCMSImages::get_mobile_height($name, true);
57
            }
58
59
            $perfectWidth *= $multiplier;
60
            $perfectHeight *= $multiplier;
61
62
            //get current width and height
63
            $myWidth = $image->getWidth();
64
            $myHeight = $image->getHeight();
65
            //if we are trying to resize to a width that is small than the perfect width
66
            //and the resize width is small than the current width, then lets resize...
67
            if (0 !== (int) $resizeToWidth) {
68
                if ($resizeToWidth < $perfectWidth && $resizeToWidth < $myWidth) {
69
                    $perfectWidth = $resizeToWidth;
70
                }
71
            }
72
73
            $tmpImage = null;
74
            if ($perfectWidth && $perfectHeight) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $perfectWidth of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
75
                //if the height or the width are already perfect then we can not do anything about it.
76
                if ($myWidth === $perfectWidth && $myHeight === $perfectHeight) {
77
                    $tmpImage = $image;
78
                } elseif ($myWidth < $perfectWidth || $myHeight < $perfectHeight) {
79
                    $tmpImage = $image->Pad(
80
                        $perfectWidth,
81
                        $perfectHeight,
82
                        PerfectCMSImages::get_padding_bg_colour($name)
83
                    );
84
                } elseif ($crop) {
85
                    $tmpImage = $image->Fill($perfectWidth, $perfectHeight);
86
                } else {
87
                    $tmpImage = $image->FitMax($perfectWidth, $perfectHeight);
88
                }
89
            } elseif ($perfectWidth) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $perfectWidth of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
90
                if ($myWidth === $perfectWidth) {
91
                    $tmpImage = $image;
92
                } elseif ($crop) {
93
                    $tmpImage = $image->Fill($perfectWidth, $myHeight);
94
                } else {
95
                    $tmpImage = $image->ScaleWidth($perfectWidth);
96
                }
97
            } elseif ($perfectHeight) {
98
                if ($myHeight === $perfectHeight) {
99
                    $tmpImage = $image;
100
                } elseif ($crop) {
101
                    $tmpImage = $image->Fill($myWidth, $perfectHeight);
102
                } else {
103
                    $tmpImage = $image->ScaleHeight($perfectHeight);
104
                }
105
            } elseif ($forMobile) {
106
                // todo: expplain this!
107
                // basically, it is for mobile and there is not perfect height nor width
108
                $tmpImage = null;
109
            } else {
110
                $tmpImage = $image;
111
            }
112
            if($tmpImage) {
113
                $link = $tmpImage->Link();
114
            }
115
116
            self::$imageLinkCache[$cacheKey] = (string) $link;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $link does not seem to be defined for all execution paths leading up to this point.
Loading history...
117
        }
118
119
        return self::$imageLinkCache[$cacheKey];
120
    }
121
122
    /**
123
     * back-up image.
124
     *
125
     * @return null|Image
126
     */
127
    public static function get_backup_image(string $name)
128
    {
129
        $image = null;
130
        $backupObject = SiteConfig::current_site_config();
131
        if ($backupObject->hasMethod($name)) {
132
            $image = $backupObject->{$name}();
133
        }
134
135
        return $image;
136
    }
137
138
    /**
139
     * placeholder image.
140
     */
141
    public static function get_placeholder_image_tag(string $name): string
142
    {
143
        $multiplier = PerfectCMSImages::get_multiplier(true);
144
        $perfectWidth = PerfectCMSImages::get_width($name, true);
145
        $perfectHeight = PerfectCMSImages::get_height($name, true);
146
        $perfectWidth *= $multiplier;
147
        $perfectHeight *= $multiplier;
148
        if ($perfectWidth || $perfectHeight) {
149
            if (0 === $perfectWidth) {
150
                $perfectWidth = $perfectHeight;
151
            }
152
153
            if (! $perfectHeight) {
154
                $perfectHeight = $perfectWidth;
155
            }
156
157
            $text = "{$perfectWidth} x {$perfectHeight} /2 = " . round($perfectWidth / 2) . ' x ' . round($perfectHeight / 2) . '';
158
159
            return 'https://placehold.it/' . $perfectWidth . 'x' . $perfectHeight . '?text=' . urlencode($text);
160
        }
161
162
        return 'https://placehold.it/1500x1500?text=' . urlencode('no size set');
163
    }
164
165
    public static function web_p_link(string $link): string
166
    {
167
        if (self::web_p_enabled() && $link) {
168
            $fileNameWithBaseFolder = Director::baseFolder() . '/public' . $link;
169
            $arrayOfLink = explode('.', $link);
170
            $extension = array_pop($arrayOfLink);
171
            $pathWithoutExtension = rtrim($link, '.' . $extension);
172
            $webPFileName = $pathWithoutExtension . '_' . $extension . '.webp';
173
            $webPFileNameWithBaseFolder = Director::baseFolder() . '/public' . $webPFileName;
174
            if (file_exists($fileNameWithBaseFolder)) {
175
                if (isset($_GET['flush']) && file_exists($webPFileNameWithBaseFolder)) {
176
                    unlink($webPFileNameWithBaseFolder);
177
                }
178
179
                if (file_exists($webPFileNameWithBaseFolder)) {
180
                    //todo: check that image is the same ...
181
                } else {
182
                    list($width, $height, $type) = getimagesize($fileNameWithBaseFolder);
183
                    $img = null;
184
                    if ($width && $height) {
185
                        if (2 === $type) {
186
                            $img = imagecreatefromjpeg($fileNameWithBaseFolder);
187
                        } elseif (3 === $type) {
188
                            $img = imagecreatefrompng($fileNameWithBaseFolder);
189
                            imagesavealpha($img, true);
190
                        }
191
192
                        if (null !== $img) {
193
                            $quality = Config::inst()->get(ImageManipulations::class, 'webp_quality');
194
                            imagewebp($img, $webPFileNameWithBaseFolder, $quality);
195
                        }
196
                    }
197
                }
198
199
                return $webPFileName;
200
            }
201
        }
202
203
        return $link;
204
    }
205
206
    public static function add_fake_parts($image, string $link): string
207
    {
208
        // first get the timestamp
209
        $time1 = strtotime($image->LastEdited);
210
        $time2 = 0;
211
        $path = Controller::join_links(Director::baseFolder(),  PUBLIC_DIR, $link);
212
        if(file_exists($path)) {
213
            $time2 = filemtime($path);
214
        }
215
216
        // first convert to hash extension
217
        if (class_exists('HashPathExtension')) {
218
            /** @var null|Controller $curr */
219
            $curr = Controller::curr();
220
            if ($curr) {
0 ignored issues
show
introduced by
$curr is of type SilverStripe\Control\Controller, thus it always evaluated to true.
Loading history...
221
                if ($curr->hasMethod('HashPath')) {
222
                    $link = $curr->HashPath($link, false);
223
                }
224
            }
225
        }
226
227
        // now you can add the time
228
        $link .= '?time='.max($time1, $time2);
229
230
        // finally add the title
231
        if ($image->Title) {
232
            $imageClasses = Config::inst()->get(PerfectCMSImages::class, 'perfect_cms_images_append_title_to_image_links_classes');
233
            if (in_array($image->ClassName, $imageClasses, true)) {
234
                $link .= '&title=' . urlencode(Convert::raw2att($image->Title));
0 ignored issues
show
Bug introduced by
It seems like SilverStripe\Core\Convert::raw2att($image->Title) can also be of type array and array; however, parameter $string of urlencode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
                $link .= '&title=' . urlencode(/** @scrutinizer ignore-type */ Convert::raw2att($image->Title));
Loading history...
235
            }
236
        }
237
238
        return $link;
239
    }
240
241
    public static function web_p_enabled(): bool
242
    {
243
        if (Config::inst()->get(ImageManipulations::class, 'webp_enabled')) {
244
            if (function_exists('imagewebp')) {
245
                if (function_exists('imagecreatefromjpeg')) {
246
                    if (function_exists('imagecreatefrompng')) {
247
                        return true;
248
                    }
249
                }
250
            }
251
        }
252
253
        return false;
254
    }
255
}
256