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