Passed
Push — master ( 091b64...dd72cb )
by Nicolaas
09:12 queued 14s
created

ImageManipulations   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Importance

Changes 9
Bugs 2 Features 0
Metric Value
eloc 140
c 9
b 2
f 0
dl 0
loc 258
rs 4.5599
wmc 58

6 Methods

Rating   Name   Duplication   Size   Complexity  
A web_p_enabled() 0 13 5
F get_image_link() 0 99 25
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\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 =
42
            implode(
43
                '_',
44
                array_filter(
45
                    [
46
                        $image->ID,
47
                        $name,
48
                        ($useRetina ? 'Y' : 'N'),
49
                        ($forMobile ? 'MY' : 'MN'),
50
                        $resizeToWidth
51
                    ]
52
                )
53
            );
54
        if (empty(self::$imageLinkCache[$cacheKey])) {
55
            $item = DataObject::get_one(PerfectCMSImageCache::class, ['Code' => $cacheKey]);
0 ignored issues
show
Bug introduced by
The type Sunnysideup\PerfectCmsIm...pi\PerfectCMSImageCache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type Sunnysideup\PerfectCmsImages\Api\DataObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
56
            if ($item) {
57
                return $item->Link;
58
            }
59
            $link = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $link is dead and can be removed.
Loading history...
60
            //work out perfect width and height
61
            if (null === $useRetina) {
62
                $useRetina = PerfectCMSImages::use_retina($name);
0 ignored issues
show
Bug introduced by
The type Sunnysideup\PerfectCmsImages\Api\PerfectCMSImages was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
63
            }
64
65
            $crop = PerfectCMSImages::is_crop($name);
66
67
            $multiplier = PerfectCMSImages::get_multiplier($useRetina);
68
            $perfectWidth = (int) PerfectCMSImages::get_width($name, true);
69
            $perfectHeight = (int) PerfectCMSImages::get_height($name, true);
70
71
            if ($forMobile) {
72
                $perfectWidth = (int) PerfectCMSImages::get_mobile_width($name, true);
73
                $perfectHeight = (int) PerfectCMSImages::get_mobile_height($name, true);
74
            }
75
76
            $perfectWidth *= $multiplier;
77
            $perfectHeight *= $multiplier;
78
79
            //get current width and height
80
            $myWidth = $image->getWidth();
81
            $myHeight = $image->getHeight();
82
            //if we are trying to resize to a width that is small than the perfect width
83
            //and the resize width is small than the current width, then lets resize...
84
            if (0 !== (int) $resizeToWidth) {
85
                if ($resizeToWidth < $perfectWidth && $resizeToWidth < $myWidth) {
86
                    $perfectWidth = $resizeToWidth;
87
                }
88
            }
89
90
            $tmpImage = null;
91
            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...
92
                //if the height or the width are already perfect then we can not do anything about it.
93
                if ($myWidth === $perfectWidth && $myHeight === $perfectHeight) {
94
                    $tmpImage = $image;
95
                } elseif ($myWidth < $perfectWidth || $myHeight < $perfectHeight) {
96
                    $tmpImage = $image->Pad(
97
                        $perfectWidth,
98
                        $perfectHeight,
99
                        PerfectCMSImages::get_padding_bg_colour($name)
100
                    );
101
                } elseif ($crop) {
102
                    $tmpImage = $image->Fill($perfectWidth, $perfectHeight);
103
                } else {
104
                    $tmpImage = $image->FitMax($perfectWidth, $perfectHeight);
105
                }
106
            } 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...
107
                if ($myWidth === $perfectWidth) {
108
                    $tmpImage = $image;
109
                } elseif ($crop) {
110
                    $tmpImage = $image->Fill($perfectWidth, $myHeight);
111
                } else {
112
                    $tmpImage = $image->ScaleWidth($perfectWidth);
113
                }
114
            } elseif ($perfectHeight) {
115
                if ($myHeight === $perfectHeight) {
116
                    $tmpImage = $image;
117
                } elseif ($crop) {
118
                    $tmpImage = $image->Fill($myWidth, $perfectHeight);
119
                } else {
120
                    $tmpImage = $image->ScaleHeight($perfectHeight);
121
                }
122
            } elseif ($forMobile) {
123
                // todo: expplain this!
124
                // basically, it is for mobile and there is not perfect height nor width
125
                $tmpImage = null;
126
            } else {
127
                $tmpImage = $image;
128
            }
129
            $link = '';
130
            if($tmpImage) {
131
                $link = (string) $tmpImage->getUrl();
132
            }
133
            self::$imageLinkCache[$cacheKey] = $link;
134
            PerfectCMSImageCache::add_one($cacheKey, $link);
135
        }
136
137
        return self::$imageLinkCache[$cacheKey];
138
    }
139
140
    /**
141
     * back-up image.
142
     *
143
     * @return null|Image
144
     */
145
    public static function get_backup_image(string $name)
146
    {
147
        $image = null;
148
        $backupObject = SiteConfig::current_site_config();
149
        if ($backupObject->hasMethod($name)) {
150
            $image = $backupObject->{$name}();
151
        }
152
153
        return $image;
154
    }
155
156
    /**
157
     * placeholder image.
158
     */
159
    public static function get_placeholder_image_tag(string $name): string
160
    {
161
        $multiplier = (int) PerfectCMSImages::get_multiplier(true);
162
        $perfectWidth = (int) PerfectCMSImages::get_width($name, true);
163
        $perfectHeight = (int) PerfectCMSImages::get_height($name, true);
164
        $perfectWidth *= $multiplier;
165
        $perfectHeight *= $multiplier;
166
        if ($perfectWidth || $perfectHeight) {
167
            if (0 === $perfectWidth && $perfectHeight === 0) {
168
                $perfectWidth = 200;
169
            } elseif (0 === $perfectWidth) {
170
                $perfectWidth = $perfectHeight;
171
            } elseif ($perfectHeight === 0) {
172
                $perfectHeight = $perfectWidth;
173
            }
174
175
            $text = "{$perfectWidth} x {$perfectHeight} /2 = " . round($perfectWidth / 2) . ' x ' . round($perfectHeight / 2) . '';
176
177
            return 'https://placehold.it/' . $perfectWidth . 'x' . $perfectHeight . '?text=' . urlencode($text);
178
        }
179
180
        return 'https://placehold.it/1500x1500?text=' . urlencode('no size set');
181
    }
182
183
    public static function web_p_link(string $link): string
184
    {
185
        if (self::web_p_enabled() && $link) {
186
            $fileNameWithBaseFolder = Director::baseFolder() . '/public' . $link;
187
            $arrayOfLink = explode('.', $link);
188
            $extension = array_pop($arrayOfLink);
189
            $pathWithoutExtension = rtrim($link, '.' . $extension);
190
            $webPFileName = $pathWithoutExtension . '_' . $extension . '.webp';
191
            $webPFileNameWithBaseFolder = Director::baseFolder() . '/public' . $webPFileName;
192
            if (file_exists($fileNameWithBaseFolder)) {
193
                if (isset($_GET['flush']) && file_exists($webPFileNameWithBaseFolder)) {
194
                    unlink($webPFileNameWithBaseFolder);
195
                }
196
197
                if (file_exists($webPFileNameWithBaseFolder)) {
198
                    //todo: check that image is the same ...
199
                } else {
200
                    list($width, $height, $type) = getimagesize($fileNameWithBaseFolder);
201
                    $img = null;
202
                    if ($width && $height) {
203
                        if (2 === $type) {
204
                            $img = imagecreatefromjpeg($fileNameWithBaseFolder);
205
                        } elseif (3 === $type) {
206
                            $img = imagecreatefrompng($fileNameWithBaseFolder);
207
                            imagesavealpha($img, true);
208
                        }
209
210
                        if (null !== $img) {
211
                            $quality = Config::inst()->get(ImageManipulations::class, 'webp_quality');
212
                            imagewebp($img, $webPFileNameWithBaseFolder, $quality);
213
                        }
214
                    }
215
                }
216
217
                return $webPFileName;
218
            }
219
        }
220
221
        return $link;
222
    }
223
224
    public static function add_fake_parts($image, string $link): string
225
    {
226
        // first get the timestamp
227
        $time1 = strtotime((string) $image->LastEdited);
228
        $time2 = 0;
229
        $path = Controller::join_links(Director::baseFolder(), PUBLIC_DIR, $link);
230
        if (file_exists($path)) {
231
            $time2 = filemtime($path);
232
        }
233
234
        // first convert to hash extension
235
        if (class_exists('HashPathExtension')) {
236
            /** @var null|Controller $curr */
237
            $curr = Controller::curr();
238
            if ($curr) {
0 ignored issues
show
introduced by
$curr is of type SilverStripe\Control\Controller, thus it always evaluated to true.
Loading history...
239
                if ($curr->hasMethod('HashPath')) {
240
                    $link = $curr->HashPath($link, false);
241
                }
242
            }
243
        }
244
245
        // now you can add the time
246
        $link .= '?time=' . max($time1, $time2);
247
248
        // finally add the title
249
        if ($image->Title) {
250
            $imageClasses = Config::inst()->get(PerfectCMSImages::class, 'perfect_cms_images_append_title_to_image_links_classes');
251
            if (in_array($image->ClassName, $imageClasses, true)) {
252
                $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

252
                $link .= '&title=' . urlencode(/** @scrutinizer ignore-type */ Convert::raw2att($image->Title));
Loading history...
253
            }
254
        }
255
256
        return $link;
257
    }
258
259
    public static function web_p_enabled(): bool
260
    {
261
        if (Config::inst()->get(ImageManipulations::class, 'webp_enabled')) {
262
            if (function_exists('imagewebp')) {
263
                if (function_exists('imagecreatefromjpeg')) {
264
                    if (function_exists('imagecreatefrompng')) {
265
                        return true;
266
                    }
267
                }
268
            }
269
        }
270
271
        return false;
272
    }
273
}
274