PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 6.2.0
WP Popular Posts v6.2.0
4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 5.0.0 5.0.1 5.0.2 5.1.0 5.2.0 5.2.1 5.2.2 5.2.3 5.2.4 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.2.0 6.2.1 6.3.0 6.3.1 6.3.2 6.3.3 6.3.4 6.4.0 6.4.1 6.4.2 7.0.0 7.0.1 7.1.0 7.2.0 7.3.0 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.3.7 7.3.8 7.4.0 trunk 2.3.7 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.2 4.0.3 4.0.5 4.0.6
wordpress-popular-posts / src / Image.php
wordpress-popular-posts / src Last commit date
Activation 2 years ago Admin 2 years ago Block 2 years ago Container 2 years ago Front 2 years ago Rest 2 years ago Traits 2 years ago Widget 2 years ago Bootstrap.php 2 years ago Cache.php 2 years ago Helper.php 2 years ago I18N.php 2 years ago Image.php 2 years ago Output.php 2 years ago Query.php 2 years ago Settings.php 2 years ago Themer.php 2 years ago Translate.php 2 years ago WordPressPopularPosts.php 2 years ago deprecated.php 2 years ago template-tags.php 2 years ago
Image.php
955 lines
1 <?php
2 /**
3 * This class builds/retrieves the thumbnail image of each popular posts.
4 *
5 *
6 * @package WordPressPopularPosts
7 * @author Hector Cabrera <me@cabrerahector.com>
8 */
9
10 namespace WordPressPopularPosts;
11
12 class Image {
13
14 /**
15 * Default thumbnail.
16 *
17 * @since 2.2.0
18 * @var string
19 */
20 private $default_thumbnail = '';
21
22 /**
23 * Plugin uploads directory.
24 *
25 * @since 3.0.4
26 * @var array
27 */
28 private $uploads_dir = [];
29
30 /**
31 * Admin settings.
32 *
33 * @since 5.0.0
34 * @var array
35 */
36 private $admin_options = [];
37
38 /**
39 * Available image sizes.
40 *
41 * @since 5.0.0
42 * @var array
43 */
44 private $available_sizes = [];
45
46 /**
47 * Available image descriptors.
48 *
49 * @since 5.3.0
50 * @var array
51 */
52 private $descriptors = [];
53
54 /**
55 * Construct.
56 *
57 * @since 4.0.0
58 * @param array $admin_options
59 */
60 public function __construct(array $admin_options)
61 {
62 $this->admin_options = $admin_options;
63
64 // Set default thumbnail
65 $this->default_thumbnail = plugins_url('assets/images/no_thumb.jpg', dirname(__FILE__, 1));
66
67 if ( Helper::is_image_url($this->admin_options['tools']['thumbnail']['default']) ) {
68 $this->default_thumbnail = $this->admin_options['tools']['thumbnail']['default'];
69 }
70
71 // Set uploads folder
72 $wp_upload_dir = wp_get_upload_dir();
73 $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'] . '/wordpress-popular-posts';
74 $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'] . '/wordpress-popular-posts';
75
76 if ( ! is_dir($this->uploads_dir['basedir']) ) {
77 // Couldn't create the folder, store thumbnails in Uploads
78 if ( ! wp_mkdir_p($this->uploads_dir['basedir']) ) {
79 $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'];
80 $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'];
81 }
82 }
83
84 // Set descriptors
85 $this->descriptors = ['1.5', '2', '2.5', '3'];
86 }
87
88 /**
89 * Get WPP's uploads folder.
90 *
91 * @since 4.0.0
92 * @access public
93 * @return array|bool
94 */
95 public function get_plugin_uploads_dir()
96 {
97 if ( is_array($this->uploads_dir) && ! empty($this->uploads_dir) ) {
98 return $this->uploads_dir;
99 }
100 return false;
101 }
102
103 /**
104 * Returns an image.
105 *
106 * @since 5.0.0
107 * @param int $post_id Post ID
108 * @param array $size Image size (width & height)
109 * @param string $source Image source
110 * @param bool $crop Whether to crop the image or not
111 * @param string $build Whether to build the image or get an existing one
112 * @return string
113 */
114 public function get(int $post_id, array $size, string $source, bool $crop = true, ?string $build = 'manual')
115 {
116 // Bail, $post_id is not an integer
117 if ( ! is_numeric($post_id) ) {
118 return '';
119 }
120
121 $alt = '';
122 $classes = ['wpp-thumbnail', 'wpp_' . $source];
123 $filename = $post_id . '-' . $source . '-' . $size[0] . 'x' . $size[1];
124 $cached = $this->exists($filename);
125
126 // We have a thumbnail already, return it
127 if ( $cached ) {
128 $classes[] = 'wpp_cached_thumb';
129
130 /**
131 * Filters CSS classes assigned to the thumbnail
132 *
133 * @since 5.0.0
134 * @param array CSS classes
135 * @param int The post ID
136 * @return array The new CSS classes
137 */
138 $classes = apply_filters(
139 'wpp_thumbnail_class_attribute',
140 $classes,
141 $post_id
142 );
143
144 /**
145 * Filters ALT attribute assigned to the thumbnail
146 *
147 * @since 5.0.0
148 * @param string Original ALT attribute
149 * @param int The post ID
150 * @return string The new ALT attribute
151 */
152 $alt = apply_filters(
153 'wpp_thumbnail_alt_attribute',
154 $this->get_alt_attribute($post_id, $source),
155 $post_id
156 );
157
158 return $this->render(
159 $cached,
160 $size,
161 is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
162 is_string($alt) ? $alt : ''
163 );
164 }
165
166 $thumb_url = null;
167
168 // Return image as-is, no need to create a new thumbnail
169 if (
170 ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] )
171 || ( 'featured' == $source && 'predefined' == $build )
172 ){
173 // Get custom field image URL
174 if ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] ) {
175 $thumb_url = get_post_meta(
176 $post_id,
177 $this->admin_options['tools']['thumbnail']['field'],
178 true
179 );
180
181 if ( ! $thumb_url || ! Helper::is_image_url($thumb_url) ) {
182 // Is this an attachment ID instead of an image URL?
183 if ( Helper::is_number($thumb_url) ) {
184 $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
185 $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
186 } else {
187 $thumb_url = null;
188 }
189 }
190 }
191 // Get Post Thumbnail
192 else {
193 if (
194 current_theme_supports('post-thumbnails')
195 && has_post_thumbnail($post_id)
196 ) {
197 // Find corresponding image size
198 $stock_size = null;
199 $images_sizes = $this->get_sizes(null);
200
201 foreach ( $images_sizes as $name => $attr ) :
202 if (
203 $attr['width'] == $size[0]
204 && $attr['height'] == $size[1]
205 && $attr['crop'] == $crop
206 ) {
207 $stock_size = $name;
208 break;
209 }
210 endforeach;
211
212 // Couldn't find a matching size so let's go with width/height combo instead
213 // (this should never happen but better safe than sorry!)
214 if ( null == $stock_size ) {
215 $stock_size = $size;
216 }
217
218 /**
219 * Filters CSS classes assigned to the thumbnail
220 *
221 * @since 5.0.0
222 * @param array CSS classes
223 * @param int The post ID
224 * @return array The new CSS classes
225 */
226 $classes = apply_filters(
227 'wpp_thumbnail_class_attribute',
228 $classes,
229 $post_id
230 );
231
232 $featured_image = get_the_post_thumbnail(
233 $post_id,
234 $stock_size
235 );
236
237 if ( strpos($featured_image, 'class="') && is_array($classes) && ! empty($classes) ) {
238 $featured_image = str_replace('class="', 'class="' . esc_attr(implode(' ', $classes)) . ' ', $featured_image);
239 }
240
241 if ( $this->admin_options['tools']['thumbnail']['lazyload'] && false == strpos($featured_image, 'loading="lazy"') ) {
242 $featured_image = str_replace('src="', 'loading="lazy" src="', $featured_image);
243 }
244
245 return $featured_image;
246 }
247 }
248 }
249 // Build a new thumbnail and return it
250 else {
251 $file_path = null;
252
253 if ( 'custom_field' == $source && $this->admin_options['tools']['thumbnail']['resize'] ) {
254 $thumb_url = get_post_meta(
255 $post_id,
256 $this->admin_options['tools']['thumbnail']['field'],
257 true
258 );
259
260 if ( ! $thumb_url || ! Helper::is_image_url($thumb_url) ) {
261 // Is this an attachment ID instead of an image URL?
262 // If so, try to fetch the image
263 if ( Helper::is_number($thumb_url) ) {
264 $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
265 $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
266 } else {
267 $thumb_url = null;
268 }
269 }
270
271 if ( $thumb_url && Helper::is_image_url($thumb_url) ) {
272 $file_path = $this->url_to_path($thumb_url, $post_id);
273 }
274 } else {
275 $file_meta = $this->get_file_meta($post_id, $source);
276
277 if ( is_array($file_meta) && isset($file_meta['path']) ) {
278 $alt = isset($file_meta['alt']) ? $file_meta['alt'] : '';
279 $file_path = $file_meta['path'];
280 }
281 }
282
283 if ( $file_path ) {
284 $extension = pathinfo($file_path, PATHINFO_EXTENSION);
285 $thumb_url = $this->resize(
286 $file_path,
287 $filename . '.' . $extension,
288 $size,
289 $crop
290 );
291 }
292 }
293
294 if ( ! $thumb_url ) {
295 $classes[] = 'wpp_def_no_src';
296 $thumb_url = $this->get_default_url($post_id);
297 }
298
299 /**
300 * Filters CSS classes assigned to the thumbnail
301 *
302 * @since 5.0.0
303 * @param array CSS classes
304 * @param int The post ID
305 * @return array The new CSS classes
306 */
307 $classes = apply_filters(
308 'wpp_thumbnail_class_attribute',
309 $classes,
310 $post_id
311 );
312
313 /**
314 * Filters ALT attribute assigned to the thumbnail
315 *
316 * @since 5.0.0
317 * @param string Original ALT attribute
318 * @param int The post ID
319 * @return string The new ALT attribute
320 */
321 $alt = apply_filters(
322 'wpp_thumbnail_alt_attribute',
323 $this->get_alt_attribute($post_id, $source),
324 $post_id
325 );
326
327 return $this->render(
328 $thumb_url,
329 $size,
330 is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
331 is_string($alt) ? $alt : ''
332 );
333 }
334
335 /**
336 * Checks whether a given thumbnail exists.
337 *
338 * @since 5.0.0
339 * @access private
340 * @param string $filename
341 * @return string|bool Full URL to image
342 */
343 private function exists(string $filename)
344 {
345 // Do we have thumbnail already?
346 $file = $this->resolve(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
347
348 if ( $file && is_file($file) ) {
349 $extension = pathinfo($file, PATHINFO_EXTENSION);
350 return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename . '.' . $extension;
351 }
352
353 return false;
354 }
355
356 /**
357 * Resolves filename.
358 *
359 * @since 5.0.0
360 * @access private
361 * @author Ioan Chiriac
362 * @link https://stackoverflow.com/a/29468093/9131961
363 * @param string $name
364 * @return string|bool Resolved path, or false if not found
365 */
366 private function resolve(string $name)
367 {
368 $info = pathinfo($name);
369
370 // File already contains an extension, return it
371 if ( isset($info['extension']) && ! empty($info['extension']) ) {
372 return $name;
373 }
374
375 $filename = $info['filename'];
376 $len = strlen($filename);
377
378 // open the folder
379 $dh = opendir($info['dirname']);
380
381 if ( ! $dh ) {
382 return false;
383 }
384
385 // scan each file in the folder
386 while ( ($file = readdir($dh)) !== false ) {
387 if ( strncmp($file, $filename, $len) === 0 ) {
388 if ( strlen($name) > $len ) {
389 // if name contains a directory part
390 $name = substr($name, 0, strlen($name) - $len) . $file;
391 } else {
392 // if the name is at the path root
393 $name = $file;
394 }
395
396 closedir($dh);
397 return $name;
398 }
399 }
400
401 // file not found
402 closedir($dh);
403 return false;
404 }
405
406 /**
407 * Retrieves local path to image.
408 *
409 * @since 5.0.0
410 * @access private
411 * @param string $url
412 * @param integer $post_ID
413 * @return string|boolean Path to image, or false if not found
414 */
415 private function url_to_path(string $url, ?int $post_ID)
416 {
417 if ( Helper::is_image_url($url) ) {
418 $attachment_id = $this->get_attachment_id($url);
419
420 // Image is hosted locally
421 if ( $attachment_id ) {
422 return get_attached_file($attachment_id);
423 }
424
425 // Image hosted elsewhere?
426 if ( $post_ID && Helper::is_number($post_ID) ) {
427 return $this->fetch_external_image($post_ID, $url);
428 }
429 }
430
431 return false;
432 }
433
434 /**
435 * Gets image meta.
436 *
437 * @since 5.0.0
438 * @access private
439 * @param int $id Post ID
440 * @param string $source Image source
441 * @return array|bool
442 */
443 private function get_file_meta(int $id, string $source)
444 {
445 // get thumbnail path from the Featured Image
446 if ( 'featured' == $source ) {
447 $thumbnail_id = get_post_thumbnail_id($id);
448
449 if ( $thumbnail_id ) {
450 // image path
451 return [
452 'path' => get_attached_file($thumbnail_id),
453 'alt' => get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true)
454 ];
455 }
456 }
457 // get thumbnail path from first image attachment
458 elseif ( 'first_attachment' == $source ) {
459 $args = [
460 'numberposts' => 1,
461 'order' => 'ASC',
462 'post_parent' => $id,
463 'post_type' => 'attachment',
464 'post_mime_type' => 'image'
465 ];
466 $post_attachments = get_children($args);
467
468 if ( ! empty($post_attachments) ) {
469 $first_img = array_shift($post_attachments);
470
471 return [
472 'path' => get_attached_file($first_img->ID),
473 'alt' => get_post_meta($first_img->ID, '_wp_attachment_image_alt', true)
474 ];
475 }
476 }
477 // get thumbnail path from post content
478 elseif ( 'first_image' == $source ) {
479 /** @var wpdb $wpdb */
480 global $wpdb;
481
482 $content = $wpdb->get_var(
483 $wpdb->prepare(
484 "SELECT post_content FROM {$wpdb->posts} WHERE ID = %d;",
485 $id
486 )
487 );
488
489 if ( $content ) {
490 // at least one image has been found
491 if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
492 // get img src attribute from the first image found
493 preg_match('/(src)="([^"]*)"/i', $img[0], $src_attr);
494
495 if ( isset($src_attr[2]) && ! empty($src_attr[2]) ) {
496 // get img alt attribute from the first image found
497 $alt = '';
498 preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
499
500 if ( isset($alt_attr[2]) && ! empty($alt_attr[2]) ) {
501 $alt = $alt_attr[2];
502 }
503
504 // image from Media Library
505 $attachment_id = $this->get_attachment_id($src_attr[2]);
506
507 if ( $attachment_id ) {
508 return [
509 'path' => get_attached_file($attachment_id),
510 'alt' => $alt
511 ];
512 } // external image?
513 else {
514 return [
515 'path' => $this->fetch_external_image($id, $src_attr[2]),
516 'alt' => $alt
517 ];
518 }
519 }
520 }
521 }
522 }
523
524 return false;
525 }
526
527 /**
528 * Gets image ALT attribute.
529 *
530 * @since 5.0.0
531 * @access private
532 * @param int $id Post ID
533 * @param string $source Image source
534 * @return string
535 */
536 private function get_alt_attribute(int $id, string $source)
537 {
538 $alt = '';
539
540 // get thumbnail path from the Featured Image
541 if ( 'featured' == $source ) {
542 $thumbnail_id = get_post_thumbnail_id($id);
543
544 if ( $thumbnail_id ) {
545 // image path
546 $alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
547 }
548 }
549 // get thumbnail path from first image attachment
550 elseif ( 'first_attachment' == $source ) {
551 $args = [
552 'numberposts' => 1,
553 'order' => 'ASC',
554 'post_parent' => $id,
555 'post_type' => 'attachment',
556 'post_mime_type' => 'image'
557 ];
558 $post_attachments = get_children($args);
559
560 if ( ! empty($post_attachments) ) {
561 $first_img = array_shift($post_attachments);
562 $alt = get_post_meta($first_img->ID, '_wp_attachment_image_alt', true);
563 }
564 }
565 // get thumbnail path from post content
566 elseif ( 'first_image' == $source ) {
567 /** @var wpdb $wpdb */
568 global $wpdb;
569
570 $content = $wpdb->get_var(
571 $wpdb->prepare(
572 "SELECT post_content FROM {$wpdb->posts} WHERE ID = %d;",
573 $id
574 )
575 );
576
577 if ( $content ) {
578 // at least one image has been found
579 if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
580 // get img alt attribute from the first image found
581 preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
582
583 if ( isset($alt_attr[2]) && ! empty($alt_attr[2]) ) {
584 $alt = $alt_attr[2];
585 }
586 }
587 }
588 }
589
590 return $alt;
591 }
592
593 /**
594 * Get the Attachment ID for a given image URL.
595 *
596 * @since 3.0.0
597 * @access private
598 * @author Frankie Jarrett
599 * @link http://frankiejarrett.com/get-an-attachment-id-by-url-in-wordpress/
600 * @param string $url
601 * @return int|null
602 */
603 private function get_attachment_id(string $url)
604 {
605 $url = Helper::add_scheme(
606 $url,
607 is_ssl() ? 'https://' : 'http://'
608 );
609
610 // Split the $url into two parts with the wp-content directory as the separator.
611 $parse_url = explode(parse_url(WP_CONTENT_URL, PHP_URL_PATH), $url);
612
613 // Get the host of the current site and the host of the $url, ignoring www.
614 $this_host = str_ireplace('www.', '', parse_url(home_url(), PHP_URL_HOST));
615 $file_host = str_ireplace('www.', '', parse_url($url, PHP_URL_HOST));
616
617 // Return nothing if there aren't any $url parts or if the current host and $url host do not match.
618 if (
619 ! isset($parse_url[1])
620 || empty($parse_url[1])
621 || ($this_host != $file_host)
622 ) {
623 return false;
624 }
625
626 // Now we're going to quickly search the DB for any attachment GUID with a partial path match.
627 // Example: /uploads/2013/05/test-image.jpg
628 global $wpdb;
629
630 $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1]));
631
632 if ( ! $attachment ) {
633 // Maybe it's a resized image, so try to get the full one
634 $parse_url[1] = preg_replace('/-[0-9]{1,4}x[0-9]{1,4}\.(jpg|jpeg|png|gif|bmp)$/i', '.$1', $parse_url[1]);
635 $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1]));
636 }
637
638 // Returns null if no attachment is found.
639 return isset($attachment[0]) ? $attachment[0] : null;
640 }
641
642 /**
643 * Fetchs external images.
644 *
645 * @since 2.3.3
646 * @access private
647 * @param int $id Post ID.
648 * @param string $url Image url.
649 * @return string|bool Image path, or false on failure.
650 */
651 private function fetch_external_image(int $id, string $url)
652 {
653 if ( ! Helper::is_image_url($url) ) {
654 return false;
655 }
656
657 $full_image_path = trailingslashit($this->get_plugin_uploads_dir()['basedir']) . "{$id}_" . sanitize_file_name(rawurldecode(wp_basename($url)));
658
659 // if the file exists already, return URL and path
660 if ( file_exists($full_image_path) ) {
661 return $full_image_path;
662 }
663
664 $url = Helper::add_scheme(
665 $url,
666 is_ssl() ? 'https://' : 'http://'
667 );
668
669 $accepted_status_codes = [200, 301, 302];
670 $response = wp_remote_head($url, ['timeout' => 5, 'sslverify' => false]);
671
672 if (
673 ! is_wp_error($response)
674 && in_array(wp_remote_retrieve_response_code($response), $accepted_status_codes)
675 ) {
676 require_once ABSPATH . 'wp-admin/includes/file.php';
677
678 $url = str_replace('https://', 'http://', $url);
679 $tmp = download_url($url);
680
681 // File was downloaded successfully
682 if ( ! is_wp_error($tmp) ) {
683 // Determine image type
684 if ( function_exists('exif_imagetype') ) {
685 $image_type = exif_imagetype($tmp);
686 } else {
687 $image_type = getimagesize($tmp);
688 $image_type = ( isset($image_type[2]) ) ? $image_type[2] : null;
689 }
690
691 // Valid image, save it
692 if ( in_array($image_type, [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]) ) {
693 // move file to Uploads
694 if ( @rename($tmp, $full_image_path) ) {
695 // borrowed from WP - set correct file permissions
696 $stat = stat(dirname($full_image_path));
697 $perms = $stat['mode'] & 0000644;
698 @chmod($full_image_path, $perms);
699
700 return $full_image_path;
701 }
702 }
703
704 // Invalid file, remove it
705 @unlink($tmp);
706 }
707 }
708
709 return false;
710 }
711
712 /**
713 * Creates thumbnails.
714 *
715 * @since 3.0.0
716 * @access private
717 * @param string $path Image path
718 * @param string $filename Image filename
719 * @param array $size Image size
720 * @param bool $crop Whether to crop the image or not
721 * @return string|bool Image URL on success, false on error
722 */
723 private function resize(string $path, string $filename, array $size, bool $crop = true)
724 {
725 $image = wp_get_image_editor($path);
726
727 // valid image, create thumbnails
728 if ( ! is_wp_error($image) ) {
729 $original_size = $image->get_size();
730 $sizes = [
731 '1x' => $size
732 ];
733 $thumbnail = '';
734
735 /**
736 * Hook to enable/disable retina support.
737 * @since 5.3.0
738 */
739 $retina_support = apply_filters('wpp_retina_support', true);
740
741 if ( $retina_support ) {
742 // Calculate thumbnail sizes
743 foreach( $this->descriptors as $descriptor ) {
744 $new_size_width = floor($descriptor * $size[0]);
745 $new_size_height = floor($descriptor * $size[1]);
746
747 if (
748 $new_size_width <= $original_size['width']
749 && $new_size_height <= $original_size['height']
750 ) {
751 $sizes[$descriptor . 'x'] = [$new_size_width, $new_size_height];
752 }
753 }
754 }
755
756 $path_parts = null;
757
758 // Generate thumbnails
759 foreach( $sizes as $d => $s ) {
760 if ( '1x' == $d ) {
761 $thumbnail = $this->generate_thumbnail($path, $filename, $s, $crop);
762
763 // Image could not be generated, let's bail early.
764 if ( ! $thumbnail ) {
765 break;
766 }
767 } else {
768 if ( ! $path_parts ) {
769 $path_parts = pathinfo($filename);
770 }
771
772 $filename_with_descriptor = $path_parts['filename'] . "@{$d}." . $path_parts['extension'];
773 $this->generate_thumbnail($path, $filename_with_descriptor, $s, $crop);
774 }
775 }
776
777 return $thumbnail;
778 }
779
780 return false;
781 }
782
783 /**
784 * Creates image.
785 *
786 * @since 5.3.0
787 * @access private
788 * @param string $path Image path
789 * @param string $filename Image filename
790 * @param array $size Image size
791 * @param bool $crop Whether to crop the image or not
792 * @return string|bool Image URL on success, false on error
793 */
794 private function generate_thumbnail(string $path, string $filename, array $size, bool $crop = true)
795 {
796 $image = wp_get_image_editor($path);
797
798 // valid image, create thumbnail
799 if ( ! is_wp_error($image) ) {
800 /**
801 * Hook to change the image compression quality of WPP's thumbnails.
802 * @since 4.2.1
803 */
804 $quality = apply_filters('wpp_thumbnail_compression_quality', null);
805
806 if ( ! ctype_digit($quality) ) {
807 $quality = null; // Fallback to core's default
808 }
809
810 $image->set_quality($quality);
811
812 $image->resize($size[0], $size[1], $crop);
813 $new_img = $image->save(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
814
815 if ( ! is_wp_error($new_img) ) {
816 return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename;
817 }
818 }
819
820 return false;
821 }
822
823 /**
824 * Generates srcset attribute for this image.
825 *
826 * @since 5.3.0
827 * @param string $src
828 * @return string
829 */
830 private function get_srcset(string $src)
831 {
832 /**
833 * Hook to enable/disable retina support.
834 * @since 5.3.0
835 */
836 $retina_support = apply_filters('wpp_retina_support', true);
837
838 if ( ! $retina_support ) {
839 return '';
840 }
841
842 $path_parts = pathinfo($src);
843 $srcset = [$src];
844
845 foreach( $this->descriptors as $descriptor ) {
846 $d = "{$descriptor}x";
847 $filename = $path_parts['filename'] . "@{$d}." . $path_parts['extension'];
848
849 if ( @file_exists(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename) ) {
850 $srcset[] = $path_parts['dirname'] . '/' . $filename . ' ' . $d;
851 }
852 }
853
854 return ( count($srcset) > 1 ) ? ' srcset="' . implode(', ', $srcset) . '" ' : '';
855 }
856
857 /**
858 * Render image tag.
859 *
860 * @since 3.0.0
861 * @access public
862 * @param string $src Image URL
863 * @param array $dimension Image's width and height
864 * @param string $class CSS class
865 * @param object $alt Alternative text
866 * @param string $error Error, if the image could not be created
867 * @return string
868 */
869 public function render(string $src, array $size, string $class, string $alt = '', string $error = '')
870 {
871 $img_tag = '';
872
873 if ( $error ) {
874 $img_tag = '<!-- ' . $error . ' --> ';
875 }
876
877 // Make sure we use the right protocol
878 $src = esc_url(is_ssl() ? str_ireplace('http://', 'https://', $src) : $src);
879 // Get srcset, if available
880 $srcset = $this->get_srcset($src);
881
882 $src = 'src="' . $src . '"' . $srcset;
883
884 // Lazy Load attribute, if enabled
885 $lazyload = ( $this->admin_options['tools']['thumbnail']['lazyload'] ) ? ' loading="lazy"' : '';
886
887 $img_tag .= '<img ' . $src . ' width="' . esc_attr($size[0]) . '" height="' . esc_attr($size[1]) . '" alt="' . esc_attr($alt) . '" class="' . esc_attr($class) . '" decoding="async" ' . $lazyload . ' />';
888
889 return apply_filters('wpp_render_image', $img_tag);
890 }
891
892 /**
893 * Gets list of available thumbnails sizes
894 *
895 * @since 3.2.0
896 * @link http://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes
897 * @param string $size
898 * @return array|bool
899 */
900 public function get_sizes(?string $size)
901 {
902 if ( ! is_array($this->available_sizes) || empty($this->available_sizes) ) {
903 global $_wp_additional_image_sizes;
904
905 $this->available_sizes = [];
906 $get_intermediate_image_sizes = get_intermediate_image_sizes();
907
908 // Create the full array with sizes and crop info
909 foreach( $get_intermediate_image_sizes as $_size ) {
910 if ( in_array($_size, ['thumbnail', 'medium', 'large']) ) {
911 $this->available_sizes[$_size]['width'] = get_option($_size . '_size_w');
912 $this->available_sizes[$_size]['height'] = get_option($_size . '_size_h');
913 $this->available_sizes[$_size]['crop'] = (bool) get_option($_size . '_crop');
914 } elseif ( isset($_wp_additional_image_sizes[$_size]) ) {
915 $this->available_sizes[$_size] = [
916 'width' => $_wp_additional_image_sizes[$_size]['width'],
917 'height' => $_wp_additional_image_sizes[$_size]['height'],
918 'crop' => $_wp_additional_image_sizes[$_size]['crop']
919 ];
920 }
921 }
922 }
923
924 // Get only 1 size if found
925 if ( $size ) {
926 if ( isset($this->available_sizes[$size]) ) {
927 return $this->available_sizes[$size];
928 }
929 return false;
930 }
931
932 return $this->available_sizes;
933 }
934
935 /**
936 * Returns the URL of the default thumbnail image.
937 *
938 * @since 5.0.0
939 * @param int|null
940 * @return string
941 */
942 public function get_default_url(?int $post_ID = null)
943 {
944 if ( has_filter('wpp_default_thumbnail_url') ) {
945 $default_thumbnail_url = apply_filters('wpp_default_thumbnail_url', $this->default_thumbnail, $post_ID);
946
947 if ( $default_thumbnail_url != $this->default_thumbnail && Helper::is_image_url($default_thumbnail_url) ) {
948 return $default_thumbnail_url;
949 }
950 }
951
952 return $this->default_thumbnail;
953 }
954 }
955