Passed
Pull Request — master (#258)
by Dominik
05:19
created

TimberDynamicResize   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Importance

Changes 10
Bugs 2 Features 2
Metric Value
wmc 36
eloc 152
c 10
b 2
f 2
dl 0
loc 288
rs 9.52

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getRelativeUploadDir() 0 7 2
A generateImage() 0 17 1
A toggleDynamic() 0 13 2
A addDynamicHooks() 0 5 1
A changeRelativeUploadPath() 0 4 1
A resizeDynamic() 0 23 3
A removeRewriteTag() 0 4 1
A getDefaultRelativeUploadDir() 0 12 3
A checkAndGenerateImage() 0 38 4
A registerRewriteRule() 0 8 1
A storeResizedUrls() 0 11 1
A getTableName() 0 4 1
A parseRequest() 0 4 2
A addRewriteTag() 0 4 1
A addImageSeparatorToUploadUrl() 0 7 1
A addHooks() 0 14 2
A getUploadsBasedir() 0 4 1
A createTable() 0 26 3
A __construct() 0 8 2
A getUploadsBaseurl() 0 4 1
A addImageSeparatorToUploadPath() 0 7 2
1
<?php
2
3
namespace Flynt\Utils;
4
5
use Twig\TwigFilter;
6
use Timber\ImageHelper;
7
use Timber\Image\Operation\Resize;
8
9
class TimberDynamicResize
10
{
11
    const DB_VERSION = '2.0';
12
    const TABLE_NAME = 'resized_images';
13
    const IMAGE_QUERY_VAR = 'resized-images';
14
    const IMAGE_PATH_SEPARATOR = 'resized';
15
16
    public $flyntResizedImages = [];
17
18
    protected $enabled = false;
19
20
    public function __construct()
21
    {
22
        $this->enabled = get_field('field_global_TimberDynamicResize_dynamicImageGeneration', 'option');
0 ignored issues
show
Bug introduced by
The function get_field was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

22
        $this->enabled = /** @scrutinizer ignore-call */ get_field('field_global_TimberDynamicResize_dynamicImageGeneration', 'option');
Loading history...
23
        if ($this->enabled) {
24
            $this->createTable();
25
            $this->addDynamicHooks();
26
        }
27
        $this->addHooks();
28
    }
29
30
    protected function createTable()
31
    {
32
        $optionName = static::TABLE_NAME . '_db_version';
33
34
        $installedVersion = get_option($optionName);
35
36
        if ($installedVersion !== static::DB_VERSION) {
37
            global $wpdb;
38
            $tableName = self::getTableName();
0 ignored issues
show
Bug Best Practice introduced by
The method Flynt\Utils\TimberDynamicResize::getTableName() is not static, but was called statically. ( Ignorable by Annotation )

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

38
            /** @scrutinizer ignore-call */ 
39
            $tableName = self::getTableName();
Loading history...
39
40
            $charsetCollate = $wpdb->get_charset_collate();
41
42
            $sql = "CREATE TABLE $tableName (
43
                width int(11) NOT NULL,
44
                height int(11) NOT NULL,
45
                crop varchar(32) NOT NULL
46
            ) $charsetCollate;";
47
48
            require_once ABSPATH . 'wp-admin/includes/upgrade.php';
49
            dbDelta($sql);
50
51
            if (version_compare($installedVersion, '2.0', '<=')) {
0 ignored issues
show
Bug introduced by
It seems like $installedVersion can also be of type false; however, parameter $version1 of version_compare() 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

51
            if (version_compare(/** @scrutinizer ignore-type */ $installedVersion, '2.0', '<=')) {
Loading history...
52
                $wpdb->query("ALTER TABLE {$tableName} ADD PRIMARY KEY(`width`, `height`, `crop`);");
53
            }
54
55
            update_option($optionName, static::DB_VERSION);
56
        }
57
    }
58
59
    protected function addDynamicHooks()
60
    {
61
        add_filter('init', [$this, 'addRewriteTag']);
62
        add_action('generate_rewrite_rules', [$this, 'registerRewriteRule']);
63
        add_action('parse_request', [$this, 'parseRequest']);
64
    }
65
66
    public function parseRequest($wp)
67
    {
68
        if (isset($wp->query_vars[static::IMAGE_QUERY_VAR])) {
69
            $this->checkAndGenerateImage($wp->query_vars[static::IMAGE_QUERY_VAR]);
70
        }
71
    }
72
73
    protected function addHooks()
74
    {
75
        add_action('timber/twig/filters', function ($twig) {
76
            $twig->addFilter(
77
                new TwigFilter('resizeDynamic', [$this, 'resizeDynamic'])
78
            );
79
            return $twig;
80
        });
81
        if ($this->enabled) {
82
            add_action('after_switch_theme', function () {
83
                add_action('shutdown', 'flush_rewrite_rules');
84
            });
85
            add_action('switch_theme', function () {
86
                flush_rewrite_rules(true);
87
            });
88
        }
89
    }
90
91
    public function getTableName()
92
    {
93
        global $wpdb;
94
        return $wpdb->prefix . static::TABLE_NAME;
95
    }
96
97
    public static function getDefaultRelativeUploadDir()
98
    {
99
        require_once(ABSPATH . 'wp-admin/includes/file.php');
100
        $uploadDir = wp_upload_dir();
101
        $homePath = get_home_path();
102
        if (!empty($homePath) && $homePath !== '/') {
103
            $baseDir = str_replace('\\', '/', $uploadDir['basedir']);
104
            $relativeUploadDir = str_replace($homePath, '', $baseDir);
105
        } else {
106
            $relativeUploadDir = $uploadDir['relative'];
107
        }
108
        return $relativeUploadDir;
109
    }
110
111
    public function getRelativeUploadDir()
112
    {
113
        $relativeUploadPath = get_field('field_global_TimberDynamicResize_relativeUploadPath', 'option');
0 ignored issues
show
Bug introduced by
The function get_field was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

113
        $relativeUploadPath = /** @scrutinizer ignore-call */ get_field('field_global_TimberDynamicResize_relativeUploadPath', 'option');
Loading history...
114
        if (empty($relativeUploadPath)) {
115
            return static::getDefaultRelativeUploadDir();
116
        } else {
117
            return $relativeUploadPath;
118
        }
119
    }
120
121
    public function getUploadsBaseurl()
122
    {
123
        $uploadDir = wp_upload_dir();
124
        return $uploadDir['baseurl'];
125
    }
126
127
    public function getUploadsBasedir()
128
    {
129
        $uploadDir = wp_upload_dir();
130
        return $uploadDir['basedir'];
131
    }
132
133
    public function resizeDynamic(
134
        $src,
135
        $w,
136
        $h = 0,
137
        $crop = 'default',
138
        $force = false
139
    ) {
140
        if ($this->enabled) {
141
            $resizeOp = new Resize($w, $h, $crop);
142
            $fileinfo = pathinfo($src);
143
            $resizedUrl = $resizeOp->filename(
144
                $fileinfo['dirname'] . '/' . $fileinfo['filename'],
145
                $fileinfo['extension']
146
            );
147
148
            if (empty($this->flyntResizedImages)) {
149
                add_action('shutdown', [$this, 'storeResizedUrls'], -1);
150
            }
151
            $this->flyntResizedImages[$w . '-' . $h . '-' . $crop] = [$w, $h, $crop];
152
153
            return $this->addImageSeparatorToUploadUrl($resizedUrl);
154
        } else {
155
            return $this->generateImage($src, $w, $h, $crop, $force);
156
        }
157
    }
158
159
    public function registerRewriteRule($wpRewrite)
160
    {
161
        $routeName = static::IMAGE_QUERY_VAR;
162
        $relativeUploadDir = $this->getRelativeUploadDir();
163
        $relativeUploadDir = trailingslashit($relativeUploadDir) . static::IMAGE_PATH_SEPARATOR;
164
        $wpRewrite->rules = array_merge(
165
            ["^{$relativeUploadDir}/?(.*?)/?$" => "index.php?{$routeName}=\$matches[1]"],
166
            $wpRewrite->rules
167
        );
168
    }
169
170
    public function addRewriteTag()
171
    {
172
        $routeName = static::IMAGE_QUERY_VAR;
173
        add_rewrite_tag("%{$routeName}%", "([^&]+)");
174
    }
175
176
    public function removeRewriteTag()
177
    {
178
        $routeName = static::IMAGE_QUERY_VAR;
179
        remove_rewrite_tag("%{$routeName}%");
180
    }
181
182
    public function checkAndGenerateImage($relativePath)
183
    {
184
        $matched = preg_match('/(.+)-(\d+)x(\d+)-c-(.+)(\..*)$/', $relativePath, $matchedSrc);
185
        $exists = false;
186
        if ($matched) {
187
            $originalRelativePath = $matchedSrc[1] . $matchedSrc[5];
188
            $originalPath = trailingslashit($this->getUploadsBasedir()) . $originalRelativePath;
189
            $originalUrl = trailingslashit($this->getUploadsBaseurl()) . $originalRelativePath;
190
            $exists = file_exists($originalPath);
191
            $w = (int) $matchedSrc[2];
192
            $h = (int) $matchedSrc[3];
193
            $crop = $matchedSrc[4];
194
        }
195
196
        if ($exists) {
197
            global $wpdb;
198
            $tableName = $this->getTableName();
199
            $resizedImage = $wpdb->get_row(
200
                $wpdb->prepare("SELECT * FROM {$tableName} WHERE width = %d AND height = %d AND crop = %s", [
201
                    $w,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $w does not seem to be defined for all execution paths leading up to this point.
Loading history...
202
                    $h,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $h does not seem to be defined for all execution paths leading up to this point.
Loading history...
203
                    $crop,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $crop does not seem to be defined for all execution paths leading up to this point.
Loading history...
204
                ])
205
            );
206
        }
207
208
        if (empty($resizedImage)) {
209
            global $wp_query;
210
            $wp_query->set_404();
211
            status_header(404);
212
            nocache_headers();
213
            include get_404_template();
214
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
215
        } else {
216
            $resizedUrl = $this->generateImage($originalUrl, $w, $h, $crop);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $originalUrl does not seem to be defined for all execution paths leading up to this point.
Loading history...
217
218
            wp_redirect($resizedUrl);
219
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
220
        }
221
    }
222
223
    protected function generateImage($url, $w, $h, $crop, $force = false)
224
    {
225
        add_filter('timber/image/new_url', [$this, 'addImageSeparatorToUploadUrl']);
226
        add_filter('timber/image/new_path', [$this, 'addImageSeparatorToUploadPath']);
227
228
        $resizedUrl = ImageHelper::resize(
229
            $url,
230
            $w,
231
            $h,
232
            $crop,
233
            $force
234
        );
235
236
        remove_filter('timber/image/new_url', [$this, 'addImageSeparatorToUploadUrl']);
237
        remove_filter('timber/image/new_path', [$this, 'addImageSeparatorToUploadPath']);
238
239
        return $resizedUrl;
240
    }
241
242
    public function addImageSeparatorToUploadUrl($url)
243
    {
244
        $baseurl = $this->getUploadsBaseurl();
245
        return str_replace(
246
            $baseurl,
247
            trailingslashit($baseurl) . static::IMAGE_PATH_SEPARATOR,
248
            $url
249
        );
250
    }
251
252
    public function addImageSeparatorToUploadPath($path = '')
253
    {
254
        $basepath = $this->getUploadsBasedir();
255
        return str_replace(
256
            $basepath,
257
            trailingslashit($basepath) . static::IMAGE_PATH_SEPARATOR,
258
            empty($path) ? $basepath : $path
259
        );
260
    }
261
262
    public function storeResizedUrls()
263
    {
264
        global $wpdb;
265
        $tableName = $this->getTableName();
266
        $values = array_values($this->flyntResizedImages);
267
        $placeholders = array_fill(0, count($values), '(%d, %d, %s)');
268
        $placeholdersString = implode(', ', $placeholders);
269
        $wpdb->query(
270
            $wpdb->prepare(
271
                "INSERT IGNORE INTO {$tableName} (width, height, crop) VALUES {$placeholdersString}",
272
                call_user_func_array('array_merge', $values)
273
            )
274
        );
275
    }
276
277
    public function toggleDynamic($enable)
278
    {
279
        if ($enable) {
280
            $this->addRewriteTag();
281
            add_action('generate_rewrite_rules', [$this, 'registerRewriteRule']);
282
            add_action('parse_request', [$this, 'parseRequest']);
283
        } else {
284
            $this->removeRewriteTag();
285
            remove_action('generate_rewrite_rules', [$this, 'registerRewriteRule']);
286
            remove_action('parse_request', [$this, 'parseRequest']);
287
        }
288
        add_action('shutdown', function () {
289
            flush_rewrite_rules(false);
290
        });
291
    }
292
293
    public function changeRelativeUploadPath($relativeUploadPath)
0 ignored issues
show
Unused Code introduced by
The parameter $relativeUploadPath is not used and could be removed. ( Ignorable by Annotation )

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

293
    public function changeRelativeUploadPath(/** @scrutinizer ignore-unused */ $relativeUploadPath)

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

Loading history...
294
    {
295
        add_action('shutdown', function () {
296
            flush_rewrite_rules(false);
297
        });
298
    }
299
}
300