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