PluginProbe
Visual Composer Website Builder / 45.16.4
Visual Composer Website Builder v45.16.4
45.16.4 45.16.3 45.16.2 45.16.1 45.2-beta.1 45.2-beta.2 45.2.0 45.3-beta.1 45.3.0 45.4-beta.1 45.4.0 45.4.1 45.4.1-beta.2 45.5-beta.1 45.5.0 45.6-beta.1 45.6.0 45.7-beta.1 45.7.0 45.8-beta.1 45.8.0 45.9.0 trunk 45.0 45.0-beta.1 All 42 releases
visualcomposer / visualcomposer / Helpers / Image.php

Image.php in Visual Composer Website Builder 45.16.4, at visualcomposer/Helpers/Image.php

487 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace VisualComposer\Helpers;
4
5 if (!defined('ABSPATH')) {
6 header('Status: 403 Forbidden');
7 header('HTTP/1.1 403 Forbidden');
8 exit;
9 }
10
11 use VisualComposer\Framework\Illuminate\Support\Helper;
12
13 class Image implements Helper
14 {
15 public function parseImage($matches)
16 {
17 if (isset($matches[1])) {
18 preg_match('(\ssrc=["|\'](.*?)["|\'])', $matches[1], $matchesUrl);
19 if (isset($matchesUrl[1])) {
20 $src = $matchesUrl[1];
21 }
22
23 preg_match('(data-height=["|\']([0-9]{0,4})["|\'])', $matches[1], $matchesHeight);
24 if (isset($matchesHeight[1])) {
25 $height = (int)$matchesHeight[1];
26 }
27
28 preg_match('(data-width=["|\']([0-9]{0,4})["|\'])', $matches[1], $matchesWidth);
29 if (isset($matchesWidth[1])) {
30 $width = (int)$matchesWidth[1];
31 }
32
33 $dynamic = false;
34 if (vcvenv('VCV_JS_FT_DYNAMIC_FIELDS')) {
35 $isMatches = preg_match('(\sdata-dynamic=["|\'](.*?)["|\'])', $matches[1], $matchesDynamic);
36 if ($isMatches) {
37 $dynamic = $matchesDynamic[1];
38 }
39 }
40
41 $attachmentId = 0;
42 preg_match('(data-attachment-id=["|\'](\d+)["|\'])', $matches[1], $matchesAttachmentId);
43 if (isset($matchesAttachmentId[1])) {
44 $attachmentId = (int)$matchesAttachmentId[1];
45 }
46
47 if (isset($src, $width, $height)) {
48 return $this->generateImage($matches, $src, $width, $height, $dynamic, $attachmentId);
49 }
50 }
51
52 return $matches[0];
53 }
54
55 public function getImageData($url)
56 {
57 $contentUrl = content_url();
58 $contentUrl = str_replace(['http://', 'https://'], '', $contentUrl);
59 if (strpos($url, $contentUrl) !== false) {
60 $path = str_replace(['http://', 'https://'], '', $url);
61 $path = str_replace($contentUrl, basename($contentUrl), $path);
62 } else {
63 $path = wp_make_link_relative($url);
64 }
65
66 $pathinfo = pathinfo($path);
67
68 $imageData = [
69 'path' => rtrim(ABSPATH, '/\\') . '/' . ltrim($path, '/\\'),
70 'filename' => $pathinfo['filename'],
71 'extension' => $pathinfo['extension'],
72 ];
73
74 return $imageData;
75 }
76
77 /**
78 * Generate <img> with provided attributes
79 *
80 * @param array $content
81 * @param string $src
82 * @param int|bool $width
83 * @param int|bool $height
84 * @param bool $dynamic
85 * @param int $attachmentId
86 *
87 * @return string
88 */
89 protected function generateImage($content, $src, $width = false, $height = false, $dynamic = false, $attachmentId = 0)
90 {
91 $src = $this->getLazyLoadSrc($content[1], $src);
92 $imageData = $this->getImageData($src);
93
94 // Add some additional data for the image
95 $imageData['width'] = $width;
96 $imageData['height'] = $height;
97 $imageData['attachmentId'] = $attachmentId;
98
99 $image = wp_get_image_editor($imageData['path']);
100
101 $newSrc = '';
102 if (!$dynamic && $width && $height && !is_wp_error($image)) {
103 $originalSize = $image->get_size();
104 $imageData['originalWidth'] = $originalSize['width'];
105 $imageData['originalHeight'] = $originalSize['height'];
106
107 $sizes = $this->getSizes($imageData);
108 $images = $this->getImages($sizes, $image, $imageData);
109
110 // Update the src with a resized image (with provided $width and $height).
111 // Should be already in a list of $images, as we tried to resize images
112 // of all provided sizes in $sizes list.
113 $resizedImageSrc = $this->getImageSrc($images, $width, $image, $imageData);
114 if (!empty($resizedImageSrc)) {
115 $src = $resizedImageSrc;
116 }
117 unset($resizedImageSrc);
118
119 // Create the srcset
120 $srcset = $this->getImageSrcset($images);
121 if (!empty($srcset)) {
122 // Note the whitespace before `srcset` attribute
123 $newSrc .= sprintf(' srcset="%s"', $srcset);
124 }
125 }
126
127 $attributes = $content[1];
128 $isLazyload = strpos($content[1], 'data-src=') !== false;
129 $attributes = $this->getImageAttributes($isLazyload, $newSrc, $src, $attributes, $dynamic);
130
131 return '<img ' . $attributes . '/>';
132 }
133
134 /**
135 * @param string $attributes
136 * @param $src
137 *
138 * @return string
139 */
140 public function getLazyLoadSrc($attributes, $src)
141 {
142 $isLazyload = strpos($attributes, 'data-src=') !== false;
143 if ($isLazyload) {
144 preg_match('(\sdata-src=["|\'](.*?)["|\'])', $attributes, $matchesUrl);
145 if (isset($matchesUrl[1])) {
146 $src = set_url_scheme($matchesUrl[1]);
147 }
148 }
149
150 return $src;
151 }
152
153 /**
154 * Get a value for image `src` attribute
155 *
156 * @param array $images A list of resized images
157 * @param int $width A width we are looking for
158 * @param \WP_Image_Editor $image
159 * @param array $imageData Image data
160 *
161 * @return string
162 */
163 public function getImageSrc($images, $width, $image = null, array $imageData = [])
164 {
165 $key = "{$width}w";
166 if (!empty($images) && is_array($images) && array_key_exists($key, $images)) {
167 return $images[ $key ];
168 }
169
170 if (!$image instanceof \WP_Image_Editor || empty($imageData)) {
171 return '';
172 }
173
174 // Oops, image is not resized yet. Try to get an image again.
175 $images = $this->getImages([$key => $width], $image, $imageData);
176 if (!empty($images) && array_key_exists($key, $images)) {
177 return $images[ $key ];
178 }
179
180 // Something definitely goes wrong
181 return '';
182 }
183
184 /**
185 * Get a value for image `srcset` attribute
186 *
187 * @param array $images
188 *
189 * @return string
190 */
191 public function getImageSrcset($images)
192 {
193 if (empty($images)) {
194 return '';
195 }
196
197 $srcset = [];
198 foreach ($images as $widthAttr => $imageSrc) {
199 $srcset[] = sprintf('%s %s', esc_url($imageSrc), esc_attr($widthAttr));
200 }
201
202 return implode(', ', $srcset);
203 }
204
205 /**
206 * @param $isLazyload
207 * @param $newSrc
208 * @param $src
209 * @param $attributes
210 * @param $dynamic
211 *
212 * @return string
213 */
214 protected function getImageAttributes($isLazyload, $newSrc, $src, $attributes, $dynamic)
215 {
216 if ($isLazyload) {
217 $newSrc .= ' src=""';
218 $newDataSrc = ' data-src="' . set_url_scheme($src) . '"';
219 $attributes = preg_replace('(\sdata-src=["|\'](.*?)["|\'])', $newDataSrc, $attributes);
220 } else {
221 $urlSchemeSrc = set_url_scheme($src);
222 $newSrc .= ' src="' . $urlSchemeSrc . '"';
223 }
224 $attributes = preg_replace('(\ssrc=["|\'](.*?)["|\'])', $newSrc, $attributes);
225 $attributes = preg_replace('(data-default-image=["|\'](true|false)["|\'])', '', $attributes);
226 if (!$dynamic) {
227 $attributes = str_replace(['data-height', 'data-width'], ['height', 'width'], $attributes);
228 }
229
230 return $attributes;
231 }
232
233 /**
234 * Calculate aspect ratio
235 *
236 * @param int $width
237 * @param int $height
238 *
239 * @return float
240 */
241 public function getAspectRatio($width, $height)
242 {
243 return $width / $height;
244 }
245
246 /**
247 * Get base URL
248 *
249 * @return string
250 */
251 public function getBaseUrl()
252 {
253 $uploadDir = wp_get_upload_dir();
254 $baseUrl = trailingslashit($uploadDir['baseurl']);
255
256 /*
257 * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
258 * (which is to say, when they share the domain name of the current request).
259 */
260 if (
261 is_ssl()
262 && strpos($baseUrl, 'https') !== 0
263 && wp_parse_url($baseUrl, PHP_URL_HOST) === $_SERVER['HTTP_HOST']
264 ) {
265 $baseUrl = set_url_scheme($baseUrl, 'https');
266 }
267
268 return $baseUrl;
269 }
270
271 /**
272 * Get sizes
273 *
274 * @param array $imageData Image data. Keys "width" and "originalWidth" are required.
275 *
276 * @return array
277 */
278 public function getSizes(array $imageData)
279 {
280 if (empty($imageData['originalWidth']) || empty($imageData['width'])) {
281 return [];
282 }
283
284 $originalWidth = $imageData['originalWidth'];
285 $resizedWidth = $imageData['width'];
286
287 // Pre-defined sizes
288 $sizes = [
289 '320w' => 320,
290 '480w' => 480,
291 '800w' => 800,
292 ];
293
294 // Calculate a size, based on a $width that come from the editor
295 $sizes[ $resizedWidth . 'w' ] = (int)$resizedWidth;
296
297 // Calculate retina size
298 if ($resizedWidth * 2 <= $originalWidth) {
299 $retinaImage = $resizedWidth * 2;
300 $sizes['2x'] = (int)$retinaImage;
301 }
302
303 return $sizes;
304 }
305
306 /**
307 * Get images by provided list of sizes
308 *
309 * If an attachment id is provided this method first checks images
310 * already resized by WordPress.
311 *
312 * Performs resize for the rest of the images (or all if an attachment ID
313 * is not provided or required sizes are not found in a list of WordPress
314 * images).
315 *
316 * Returns a list of resized image, where key is a width attribute
317 * (for srcset) and value is a relative path to image, for example:
318 *
319 * ```
320 * [
321 * '320w' => '2022/06/Portfolio-4-320x213.jpg',
322 * '480w' => '2022/06/Portfolio-4-480x320.jpg',
323 * '2x' => '2022/06/Portfolio-4-2048x1366.jpg',
324 * ]
325 * ```
326 *
327 * @param array $sizes A list of sizes in format [320w => 320, ...]
328 * @param \WP_Image_Editor $image Image
329 * @param array $imageData Image data (width, height, attachmentId, etc)
330 *
331 * @return array
332 *
333 */
334 public function getImages($sizes, $image, $imageData)
335 {
336 if (empty($sizes)) {
337 return [];
338 }
339
340 // TODO: maybe check for cached images
341
342 if (!empty($imageData['attachmentId'])) {
343 $images = $this->getImagesFromAttachmentSizes($sizes, $imageData['attachmentId']);
344
345 // Make sure we have all sizes
346 $restOfSizes = array_diff_key($sizes, $images);
347 if (!empty($restOfSizes)) {
348 $resizedImages = $this->resizeImageBySizes($restOfSizes, $image, $imageData);
349 $images = array_merge($images, $resizedImages);
350 }
351 } else {
352 // Resize all images
353 $images = $this->resizeImageBySizes($sizes, $image, $imageData);
354 }
355
356 // TODO: this is a good place to cache generated images
357
358 // Images should be with a relative path without leading slash,
359 // e.g. "2022/06/Image.jpg", so prepend them with a base URL
360 $baseUrl = $this->getBaseUrl();
361 foreach ($images as &$relativePathToImage) {
362 $relativePathToImage = $baseUrl . $relativePathToImage;
363 }
364 unset($relativePathToImage);
365
366 return $images;
367 }
368
369 /**
370 * Get images from attachment sizes
371 *
372 * Try to look through a list of already resized images.
373 * There may already be what we are looking for.
374 *
375 * @param array $sizes A list of sizes to search
376 * @param int $attachmentId Attachment ID
377 *
378 * @return array
379 */
380 public function getImagesFromAttachmentSizes($sizes, $attachmentId)
381 {
382 $attachmentMeta = wp_get_attachment_metadata($attachmentId);
383 if (empty($attachmentMeta) || empty($attachmentMeta['sizes'])) {
384 return [];
385 }
386
387 $dirname = _wp_get_attachment_relative_path($attachmentMeta['file']);
388 if ($dirname) {
389 $dirname = trailingslashit($dirname);
390 }
391
392 $attachmentSizes = $attachmentMeta['sizes'];
393 $images = [];
394 foreach ($attachmentSizes as $image) {
395 // Check if image meta isn't corrupted.
396 if (!is_array($image)) {
397 continue;
398 }
399
400 // Loop through the list of already resized images and try to find
401 // a required "width". Once found - we can use it!
402 if (in_array($image['width'], $sizes, true)) {
403 $key = array_search($image['width'], $sizes, true);
404 $images[ $key ] = $dirname . $image['file'];
405 }
406 }
407
408 return $images;
409 }
410
411 /**
412 * Resize a single image by provided list of sizes
413 *
414 * @param array $sizes A list of sizes in format [320w => 320]
415 * @param \WP_Image_Editor $image Image
416 * @param array $imageData Image data (width, height, attachmentId, etc)
417 *
418 * @return array
419 */
420 public function resizeImageBySizes($sizes, $image, $imageData)
421 {
422 $images = [];
423 $uploadDir = wp_upload_dir();
424
425 $dirname = $uploadDir['subdir'];
426 if ($dirname) {
427 $dirname = trailingslashit($dirname);
428 }
429
430 $aspectRatio = $this->getAspectRatio($imageData['width'], $imageData['height']);
431 $retinaImage = array_key_exists('2x', $sizes);
432 $fileHelper = vchelper('File');
433 foreach ($sizes as $widthAttr => $width) {
434 if ($width > $imageData['width'] && !$retinaImage) {
435 continue;
436 }
437
438 $height = (int)round($width / $aspectRatio);
439 $filename = "{$imageData['filename']}-{$width}x{$height}.{$imageData['extension']}";
440 $absolutePath = "{$uploadDir['path']}/{$filename}";
441
442 // Check if file already exists. No need to resize twice.
443 if (!$fileHelper->exists($absolutePath)) {
444 $resizedImage = $this->resizeImage($image, $imageData, $absolutePath, $width, $height, true);
445 if (is_wp_error($resizedImage)) {
446 continue;
447 }
448
449 $filename = $resizedImage['file'];
450 }
451
452 // For consistency with "getImagesFromAttachmentSizes" method
453 // return the relative path to the filename.
454 $images[ $widthAttr ] = ltrim($dirname . $filename, '/\\');
455 }
456
457 return $images;
458 }
459
460 /**
461 * Resize and save image
462 *
463 * @param \WP_Image_Editor $image Image editor object.
464 * @param array $imageData Image data.
465 * @param string $path Absolute path where to save an image.
466 * @param int $width
467 * @param int $height
468 * @param bool $crop
469 *
470 * @return array|\WP_Error
471 */
472 public function resizeImage($image, $imageData, $path, $width = null, $height = null, $crop = false)
473 {
474 // For GD need to create a new object, because GD has a reference to a resource.
475 // In case of ImageMagick it is possible to clone the object.
476 if ($image instanceof \WP_Image_Editor_GD) {
477 $newImage = wp_get_image_editor($imageData['path']);
478 } else {
479 $newImage = clone $image;
480 }
481
482 $newImage->resize($width, $height, $crop);
483
484 return $newImage->save($path);
485 }
486 }
487