Passed
Push — master ( edcc7a...f020d6 )
by Nicolaas
02:53
created

ImageManipulations   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Importance

Changes 9
Bugs 2 Features 0
Metric Value
eloc 153
c 9
b 2
f 0
dl 0
loc 276
rs 5.04
wmc 57

6 Methods

Rating   Name   Duplication   Size   Complexity  
A web_p_enabled() 0 13 5
F get_image_link() 0 117 24
C web_p_link() 0 39 12
B get_placeholder_image_tag() 0 22 7
A get_backup_image() 0 9 2
B add_fake_parts() 0 33 7

How to fix   Complexity   

Complex Class

Complex classes like ImageManipulations often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ImageManipulations, and based on these observations, apply Extract Interface, too.

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\ORM\DataObject;
13
use SilverStripe\SiteConfig\SiteConfig;
14
use Sunnysideup\PerfectCmsImages\Model\PerfectCMSImageCache;
15
16
class ImageManipulations
17
{
18
    use Configurable;
19
    use Injectable;
20
21
    private static $webp_enabled = true;
22
23
    private static $webp_quality = 77;
24
25
    private static $imageLinkCache = [];
26
27
    /**
28
     * work out the best image link.
29
     *
30
     * There are basically three options:
31
     * a. if the height and/or width matches or is smaller than it should be
32
     *    then just return natural image
33
     * b. if crop is set to true then Fill
34
     * c. otherwise resize Height/Width/Both
35
     *
36
     * @param Image     $image
37
     * @param null|bool $useRetina     optional
38
     * @param null|bool $forMobile     optional
39
     * @param null|int  $resizeToWidth optional
40
     */
41
    public static function get_image_link($image, string $name, ?bool $useRetina = null, ?bool $forMobile = null, ?int $resizeToWidth = 0): string
42
    {
43
        $cacheKey =
44
            implode(
45
                '_',
46
                array_filter(
47
                    [
48
                        $image->ID,
49
                        $name,
50
                        ($useRetina ? 'Y' : 'N'),
51
                        ($forMobile ? 'MY' : 'MN'),
52
                        $resizeToWidth
53
                    ]
54
                )
55
            );
56
        if (empty(self::$imageLinkCache[$cacheKey])) {
57
            $item = DataObject::get_one(PerfectCMSImageCache::class, ['Code' => $cacheKey, 'ImageID' => $image->ID]);
58
            if ($item) {
59
                return $item->Link;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $item->Link could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
60
            }
61
            $link = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $link is dead and can be removed.
Loading history...
62
            //work out perfect width and height
63
            if (null === $useRetina) {
64
                $useRetina = PerfectCMSImages::use_retina($name);
65
            }
66
67
            $crop = PerfectCMSImages::is_crop($name);
68
69
            $multiplier = PerfectCMSImages::get_multiplier($useRetina);
70
            $perfectWidth = (int) PerfectCMSImages::get_width($name, true);
71
            $perfectHeight = (int) PerfectCMSImages::get_height($name, true);
72
73
            if ($forMobile) {
74
                $perfectWidth = (int) PerfectCMSImages::get_mobile_width($name, true);
75
                $perfectHeight = (int) PerfectCMSImages::get_mobile_height($name, true);
76
            }
77
78
            $perfectWidth *= $multiplier;
79
            $perfectHeight *= $multiplier;
80
81
            //get current width and height
82
            $myWidth = $image->getWidth();
83
            $myHeight = $image->getHeight();
84
            //if we are trying to resize to a width that is small than the perfect width
85
            //and the resize width is small than the current width, then lets resize...
86
            if (0 !== (int) $resizeToWidth) {
87
                if ($resizeToWidth < $perfectWidth && $resizeToWidth < $myWidth) {
88
                    $perfectWidth = $resizeToWidth;
89
                }
90
            }
91
92
            $link = null;
93
            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...
94
                //if the height or the width are already perfect then we can not do anything about it.
95
                if ($myWidth === $perfectWidth && $myHeight === $perfectHeight) {
96
                    $link = $image->getURL();
97
                } elseif ($myWidth < $perfectWidth || $myHeight < $perfectHeight) {
98
                    $link = $image->getImageLinkCachedIfExists(
99
                        'Pad',
100
                        $perfectWidth,
101
                        $perfectHeight,
102
                        PerfectCMSImages::get_padding_bg_colour($name)
103
                    );
104
                } elseif ($crop) {
105
                    $link = $image->getImageLinkCachedIfExists(
106
                        'Fill',
107
                        $perfectWidth,
108
                        $perfectHeight
109
                    );
110
                } else {
111
                    $link = $image->getImageLinkCachedIfExists(
112
                        'FitMax',
113
                        $perfectWidth,
114
                        $perfectHeight
115
                    );
116
                }
117
            } 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...
118
                if ($myWidth === $perfectWidth) {
119
                    $link = $image->getURL();
120
                } elseif ($crop) {
121
                    $link = $image->getImageLinkCachedIfExists(
122
                        'Fill',
123
                        $perfectWidth,
124
                        $myHeight
125
                    );
126
                } else {
127
                    $link = $image->getImageLinkCachedIfExists(
128
                        'ScaleWidth',
129
                        $perfectWidth
130
                    );
131
                }
132
            } elseif ($perfectHeight) {
133
                if ($myHeight === $perfectHeight) {
134
                    $link = $image->getUrl();
135
                } elseif ($crop) {
136
                    $link = $image->getImageLinkCachedIfExists(
137
                        'Fill',
138
                        $myWidth,
139
                        $perfectHeight
140
                    );
141
                } else {
142
                    $link = $image->getImageLinkCachedIfExists(
143
                        'ScaleHeight',
144
                        $perfectHeight
145
                    );
146
                }
147
            } elseif ($forMobile) {
148
                // basically, it is for mobile and there is not perfect height nor perfect width
149
                $link = '';
150
            } else {
151
                // not for mobile, we definitely want to have some sort of link!
152
                $link = $image->getUrl();
153
            }
154
            self::$imageLinkCache[$cacheKey] = (string) $link;
155
        }
156
157
        return self::$imageLinkCache[$cacheKey];
158
    }
159
160
    /**
161
     * back-up image.
162
     *
163
     * @return null|Image
164
     */
165
    public static function get_backup_image(string $name)
166
    {
167
        $image = null;
168
        $backupObject = SiteConfig::current_site_config();
169
        if ($backupObject->hasMethod($name)) {
170
            $image = $backupObject->{$name}();
171
        }
172
173
        return $image;
174
    }
175
176
    /**
177
     * placeholder image.
178
     */
179
    public static function get_placeholder_image_tag(string $name): string
180
    {
181
        $multiplier = (int) PerfectCMSImages::get_multiplier(true);
182
        $perfectWidth = (int) PerfectCMSImages::get_width($name, true);
183
        $perfectHeight = (int) PerfectCMSImages::get_height($name, true);
184
        $perfectWidth *= $multiplier;
185
        $perfectHeight *= $multiplier;
186
        if ($perfectWidth || $perfectHeight) {
187
            if (0 === $perfectWidth && $perfectHeight === 0) {
188
                $perfectWidth = 200;
189
            } elseif (0 === $perfectWidth) {
190
                $perfectWidth = $perfectHeight;
191
            } elseif ($perfectHeight === 0) {
192
                $perfectHeight = $perfectWidth;
193
            }
194
195
            $text = "{$perfectWidth} x {$perfectHeight} /2 = " . round($perfectWidth / 2) . ' x ' . round($perfectHeight / 2) . '';
196
197
            return 'https://placehold.it/' . $perfectWidth . 'x' . $perfectHeight . '?text=' . urlencode($text);
198
        }
199
200
        return 'https://placehold.it/1500x1500?text=' . urlencode('no size set');
201
    }
202
203
    public static function web_p_link(string $link): string
204
    {
205
        if (self::web_p_enabled() && $link) {
206
            $fileNameWithBaseFolder = Director::baseFolder() . '/public' . $link;
207
            $arrayOfLink = explode('.', $link);
208
            $extension = array_pop($arrayOfLink);
209
            $pathWithoutExtension = rtrim($link, '.' . $extension);
210
            $webPFileName = $pathWithoutExtension . '_' . $extension . '.webp';
211
            $webPFileNameWithBaseFolder = Director::baseFolder() . '/public' . $webPFileName;
212
            if (file_exists($fileNameWithBaseFolder)) {
213
                if (isset($_GET['flush']) && file_exists($webPFileNameWithBaseFolder)) {
214
                    unlink($webPFileNameWithBaseFolder);
215
                }
216
217
                if (file_exists($webPFileNameWithBaseFolder)) {
218
                    //todo: check that image is the same ...
219
                } else {
220
                    list($width, $height, $type) = getimagesize($fileNameWithBaseFolder);
221
                    $img = null;
222
                    if ($width && $height) {
223
                        if (2 === $type) {
224
                            $img = imagecreatefromjpeg($fileNameWithBaseFolder);
225
                        } elseif (3 === $type) {
226
                            $img = imagecreatefrompng($fileNameWithBaseFolder);
227
                            imagesavealpha($img, true);
228
                        }
229
230
                        if (null !== $img) {
231
                            $quality = Config::inst()->get(ImageManipulations::class, 'webp_quality');
232
                            imagewebp($img, $webPFileNameWithBaseFolder, $quality);
233
                        }
234
                    }
235
                }
236
237
                return $webPFileName;
238
            }
239
        }
240
241
        return $link;
242
    }
243
244
    public static function add_fake_parts($image, string $link): string
245
    {
246
        // first get the timestamp
247
        $time1 = strtotime((string) $image->LastEdited);
248
        $time2 = 0;
249
        $path = Controller::join_links(Director::baseFolder(), PUBLIC_DIR, $link);
250
        if (file_exists($path)) {
251
            $time2 = filemtime($path);
252
        }
253
254
        // first convert to hash extension
255
        if (class_exists('HashPathExtension')) {
256
            /** @var null|Controller $curr */
257
            $curr = Controller::curr();
258
            if ($curr) {
0 ignored issues
show
introduced by
$curr is of type SilverStripe\Control\Controller, thus it always evaluated to true.
Loading history...
259
                if ($curr->hasMethod('HashPath')) {
260
                    $link = $curr->HashPath($link, false);
261
                }
262
            }
263
        }
264
265
        // now you can add the time
266
        $link .= '?time=' . max($time1, $time2);
267
268
        // finally add the title
269
        if ($image->Title) {
270
            $imageClasses = Config::inst()->get(PerfectCMSImages::class, 'perfect_cms_images_append_title_to_image_links_classes');
271
            if (in_array($image->ClassName, $imageClasses, true)) {
272
                $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

272
                $link .= '&title=' . urlencode(/** @scrutinizer ignore-type */ Convert::raw2att($image->Title));
Loading history...
273
            }
274
        }
275
276
        return $link;
277
    }
278
279
    public static function web_p_enabled(): bool
280
    {
281
        if (Config::inst()->get(ImageManipulations::class, 'webp_enabled')) {
282
            if (function_exists('imagewebp')) {
283
                if (function_exists('imagecreatefromjpeg')) {
284
                    if (function_exists('imagecreatefrompng')) {
285
                        return true;
286
                    }
287
                }
288
            }
289
        }
290
291
        return false;
292
    }
293
}
294