| 1 |
<?php |
| 2 |
/** |
| 3 |
* Media handler |
| 4 |
* |
| 5 |
* @package WebberZone\Top_Ten\Frontend |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace WebberZone\Top_Ten\Frontend; |
| 9 |
|
| 10 |
if ( ! defined( 'WPINC' ) ) { |
| 11 |
die; |
| 12 |
} |
| 13 |
|
| 14 |
/** |
| 15 |
* Media Handler class. |
| 16 |
* |
| 17 |
* @since 3.5.0 |
| 18 |
*/ |
| 19 |
class Media_Handler { |
| 20 |
|
| 21 |
/** |
| 22 |
* Prefix. |
| 23 |
* |
| 24 |
* @var string $prefix Prefix. |
| 25 |
*/ |
| 26 |
protected static $prefix = 'tptn'; |
| 27 |
|
| 28 |
/** |
| 29 |
* Default thumbnail URL. |
| 30 |
* |
| 31 |
* @var string |
| 32 |
*/ |
| 33 |
protected static $default_thumb_url = TOP_TEN_DEFAULT_THUMBNAIL_URL; |
| 34 |
|
| 35 |
/** |
| 36 |
* Posts currently being processed to prevent infinite recursion. |
| 37 |
* |
| 38 |
* @var array |
| 39 |
*/ |
| 40 |
private static $processing_ids = array(); |
| 41 |
|
| 42 |
/** |
| 43 |
* Retrieve a plugin option value. |
| 44 |
* |
| 45 |
* Calls {@see tptn_get_option()} directly. Subclasses for other plugins |
| 46 |
* should override this method to point at their own options function. |
| 47 |
* |
| 48 |
* @param string $key Option key. |
| 49 |
* @param mixed $fallback Default value if option is not set. |
| 50 |
* @return mixed Option value. |
| 51 |
*/ |
| 52 |
protected static function get_option( string $key, $fallback = null ) { |
| 53 |
return tptn_get_option( $key, $fallback ); |
| 54 |
} |
| 55 |
|
| 56 |
/** |
| 57 |
* Add custom image size of thumbnail. Filters `init`. |
| 58 |
*/ |
| 59 |
public static function add_image_sizes() { |
| 60 |
if ( ! self::get_option( 'thumb_create_sizes' ) ) { |
| 61 |
return; |
| 62 |
} |
| 63 |
$thumb_size = self::get_option( 'thumb_size' ); |
| 64 |
$thumb_size_name = self::$prefix . '_thumbnail'; |
| 65 |
|
| 66 |
if ( ! in_array( $thumb_size, get_intermediate_image_sizes() ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict |
| 67 |
$thumb_size = $thumb_size_name; |
| 68 |
} |
| 69 |
|
| 70 |
// Add image sizes if $thumb_size_name is selected or the selected thumbnail size is no longer valid. |
| 71 |
if ( $thumb_size_name === $thumb_size ) { |
| 72 |
$width = self::get_option( 'thumb_width', 150 ); |
| 73 |
$height = self::get_option( 'thumb_height', 150 ); |
| 74 |
$crop = self::get_option( 'thumb_crop', true ); |
| 75 |
|
| 76 |
add_image_size( $thumb_size_name, $width, $height, $crop ); |
| 77 |
} |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Function to get the post thumbnail. |
| 82 |
* |
| 83 |
* @param string|array $args { |
| 84 |
* Optional. Array or string of Query parameters. |
| 85 |
* |
| 86 |
* @type int|\WP_Post $post Post ID or \WP_Post object. |
| 87 |
* @type string $size Thumbnail size. Should be a pre-defined image size. |
| 88 |
* @type string $thumb_meta Meta field that is used to store the location of default thumbnail image. |
| 89 |
* @type string $acf_field ACF field name (Image or Text field) to use as thumbnail source. |
| 90 |
* @type string $thumb_html Accepted arguments are `html` or `css`. |
| 91 |
* @type string $thumb_default Default thumbnail image. |
| 92 |
* @type bool $thumb_default_show Show default thumb if none found. |
| 93 |
* @type int $scan_images Get related posts for a specific post ID. |
| 94 |
* @type string $class Class of the thumbnail. |
| 95 |
* } |
| 96 |
* @return string Image tag |
| 97 |
*/ |
| 98 |
public static function get_the_post_thumbnail( $args = array() ) { |
| 99 |
$defaults = array( |
| 100 |
'post' => '', |
| 101 |
'size' => 'thumbnail', |
| 102 |
'thumb_meta' => 'post-image', |
| 103 |
'acf_field' => '', |
| 104 |
'thumb_html' => 'html', |
| 105 |
'thumb_default' => self::get_option( 'thumb_default', '' ), |
| 106 |
'thumb_default_show' => true, |
| 107 |
'scan_images' => true, |
| 108 |
'use_site_icon' => true, |
| 109 |
'class' => self::$prefix . '_thumb', |
| 110 |
'style' => '', |
| 111 |
); |
| 112 |
|
| 113 |
// Parse incomming $args into an array and merge it with $defaults. |
| 114 |
$args = wp_parse_args( $args, $defaults ); |
| 115 |
|
| 116 |
$result = get_post( $args['post'] ); |
| 117 |
|
| 118 |
if ( empty( $result ) ) { |
| 119 |
return ''; |
| 120 |
} |
| 121 |
|
| 122 |
// Recursion protection - prevent infinite loops when shortcodes trigger nested thumbnail generation. |
| 123 |
if ( isset( self::$processing_ids[ $result->ID ] ) ) { |
| 124 |
return ''; |
| 125 |
} |
| 126 |
self::$processing_ids[ $result->ID ] = true; |
| 127 |
|
| 128 |
try { |
| 129 |
|
| 130 |
if ( is_string( $args['size'] ) ) { |
| 131 |
list( $args['thumb_width'], $args['thumb_height'] ) = self::get_thumb_size( $args['size'] ); |
| 132 |
} else { |
| 133 |
$args['thumb_width'] = $args['size'][0]; |
| 134 |
$args['thumb_height'] = $args['size'][1]; |
| 135 |
$args['size'] = self::get_appropriate_image_size( $args['size'][0], $args['size'][1] ); |
| 136 |
} |
| 137 |
|
| 138 |
$post_title = esc_attr( $result->post_title ); |
| 139 |
|
| 140 |
$output = ''; |
| 141 |
$postimage = ''; |
| 142 |
$pick = ''; |
| 143 |
$attachment_id = 0; |
| 144 |
$extracted_alt = ''; |
| 145 |
$alt = ''; |
| 146 |
|
| 147 |
$strategies = array( |
| 148 |
fn() => self::get_thumbnail_from_meta( $result, $args ), |
| 149 |
fn() => self::get_thumbnail_from_acf( $result, $args ), |
| 150 |
fn() => self::get_thumbnail_from_fifu( $result ), |
| 151 |
fn() => self::get_thumbnail_from_featured_image( $result, $args ), |
| 152 |
fn() => self::get_thumbnail_from_content_scan( $result, $args ), |
| 153 |
fn() => self::get_thumbnail_from_first_child( $result, $args ), |
| 154 |
fn() => self::get_thumbnail_from_video_meta( $result ), |
| 155 |
fn() => self::get_thumbnail_from_default_thumb( $args ), |
| 156 |
fn() => self::get_thumbnail_from_site_icon( $args ), |
| 157 |
); |
| 158 |
|
| 159 |
foreach ( $strategies as $strategy ) { |
| 160 |
$thumb = $strategy(); |
| 161 |
if ( ! empty( $thumb['postimage'] ) ) { |
| 162 |
$postimage = $thumb['postimage']; |
| 163 |
$attachment_id = $thumb['attachment_id']; |
| 164 |
$pick = $thumb['pick']; |
| 165 |
$extracted_alt = $thumb['extracted_alt']; |
| 166 |
break; |
| 167 |
} |
| 168 |
} |
| 169 |
|
| 170 |
// Hopefully, we've found a thumbnail by now. If so, run it through the custom filter, check for SSL and create the image tag. |
| 171 |
if ( $postimage ) { |
| 172 |
|
| 173 |
/** |
| 174 |
* Filters the thumbnail image URL. |
| 175 |
* |
| 176 |
* Use this filter to modify the thumbnail URL that is automatically created |
| 177 |
* Before v2.1 this was used for cropping the post image using timthumb |
| 178 |
* |
| 179 |
* @param string $postimage URL of the thumbnail image |
| 180 |
* @param array $args Arguments array. |
| 181 |
* @param \WP_Post $result Post Object |
| 182 |
*/ |
| 183 |
$postimage = apply_filters( self::$prefix . '_thumb_url', $postimage, $args, $result ); |
| 184 |
|
| 185 |
if ( is_ssl() ) { |
| 186 |
$postimage = preg_replace( '~http://~', 'https://', $postimage ); |
| 187 |
} |
| 188 |
|
| 189 |
$class = self::$prefix . "_{$pick} {$args['class']} {$args['size']}"; |
| 190 |
|
| 191 |
if ( empty( $attachment_id ) && ! in_array( $pick, array( 'video_thumb', 'default_thumb', 'site_icon_max', 'site_icon_min', 'fifu', 'acf', 'acfcorrect' ), true ) ) { |
| 192 |
$attachment_id = self::get_cached_attachment_id( $postimage ); |
| 193 |
} |
| 194 |
|
| 195 |
/** |
| 196 |
* Flag to use the image's alt text as the thumbnail alt text. |
| 197 |
* |
| 198 |
* @param bool $use_image_alt Flag to use the image's alt text as the thumbnail alt text. |
| 199 |
*/ |
| 200 |
$use_image_alt = apply_filters( self::$prefix . '_thumb_use_image_alt', true ); |
| 201 |
|
| 202 |
/** |
| 203 |
* Flag to use the post title as the thumbnail alt text if no alt text is found. |
| 204 |
* |
| 205 |
* @param bool $alt_fallback Flag to use the post title as the thumbnail alt text if no alt text is found. |
| 206 |
*/ |
| 207 |
$alt_fallback = apply_filters( self::$prefix . '_thumb_alt_fallback_post_title', true ); |
| 208 |
|
| 209 |
if ( ! empty( $attachment_id ) && $use_image_alt ) { |
| 210 |
$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); |
| 211 |
} |
| 212 |
|
| 213 |
if ( empty( $alt ) && $extracted_alt ) { |
| 214 |
$alt = $extracted_alt; |
| 215 |
} |
| 216 |
|
| 217 |
// If empty alt then try to get the title of the attachment. |
| 218 |
if ( empty( $alt ) && ! empty( $attachment_id ) ) { |
| 219 |
$alt = get_post_field( 'post_title', $attachment_id ); |
| 220 |
} |
| 221 |
|
| 222 |
if ( empty( $alt ) ) { |
| 223 |
$alt = $alt_fallback ? $post_title : ''; |
| 224 |
} |
| 225 |
|
| 226 |
/** |
| 227 |
* Filters the thumbnail styles attribute. |
| 228 |
* |
| 229 |
* @param string $styles Thumbnail styles |
| 230 |
*/ |
| 231 |
$attr['style'] = apply_filters( self::$prefix . '_thumb_styles', $args['style'] ); |
| 232 |
|
| 233 |
/** |
| 234 |
* Filters the thumbnail classes and allows a filter function to add any more classes if needed. |
| 235 |
* |
| 236 |
* @param string $class Thumbnail Class |
| 237 |
*/ |
| 238 |
$attr['class'] = apply_filters( self::$prefix . '_thumb_class', $class ); |
| 239 |
|
| 240 |
/** |
| 241 |
* Filters the thumbnail alt. |
| 242 |
* |
| 243 |
* @param string $alt Thumbnail alt attribute |
| 244 |
*/ |
| 245 |
$attr['alt'] = apply_filters( self::$prefix . '_thumb_alt', $alt ); |
| 246 |
|
| 247 |
/** |
| 248 |
* Filters the thumbnail title. |
| 249 |
* |
| 250 |
* @param string $post_title Thumbnail title attribute |
| 251 |
*/ |
| 252 |
$attr['title'] = apply_filters( self::$prefix . '_thumb_title', $post_title ); |
| 253 |
|
| 254 |
$attr['thumb_html'] = $args['thumb_html']; |
| 255 |
$attr['thumb_width'] = $args['thumb_width']; |
| 256 |
$attr['thumb_height'] = $args['thumb_height']; |
| 257 |
|
| 258 |
$output .= self::get_image_html( $postimage, $attr, $attachment_id, $args['size'] ); |
| 259 |
|
| 260 |
if ( function_exists( 'wp_img_tag_add_srcset_and_sizes_attr' ) && ! empty( $attachment_id ) ) { |
| 261 |
$output = \wp_img_tag_add_srcset_and_sizes_attr( $output, $args['size'], $attachment_id ); |
| 262 |
} |
| 263 |
|
| 264 |
if ( function_exists( 'wp_img_tag_add_loading_optimization_attrs' ) ) { |
| 265 |
$output = \wp_img_tag_add_loading_optimization_attrs( $output, self::$prefix . '_thumbnail' ); |
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
/** |
| 270 |
* Filters post thumbnail HTML. |
| 271 |
* |
| 272 |
* @param string $output HTML output. |
| 273 |
* @param array $args Argument list |
| 274 |
* @param string $postimage Thumbnail URL |
| 275 |
*/ |
| 276 |
return apply_filters( self::$prefix . '_get_the_post_thumbnail', $output, $args, $postimage ); |
| 277 |
|
| 278 |
} finally { |
| 279 |
// Clean up recursion protection - guaranteed to run even if exceptions occur. |
| 280 |
unset( self::$processing_ids[ $result->ID ] ); |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
/** |
| 285 |
* Resize external image when attachment ID is not found. |
| 286 |
* |
| 287 |
* @param string $image_url Original image URL. |
| 288 |
* @param string $size Target image size. |
| 289 |
* @return string|false Resized image URL or false on failure. |
| 290 |
*/ |
| 291 |
private static function resize_external_image( $image_url, $size ) { |
| 292 |
// Check if this is a local image that can be resized. |
| 293 |
$upload_dir = wp_upload_dir(); |
| 294 |
if ( empty( $upload_dir['baseurl'] ) ) { |
| 295 |
return false; |
| 296 |
} |
| 297 |
$base_url = $upload_dir['baseurl']; |
| 298 |
|
| 299 |
// Only attempt resizing for local uploads directory images. |
| 300 |
if ( strpos( $image_url, $base_url ) !== 0 ) { |
| 301 |
return false; |
| 302 |
} |
| 303 |
|
| 304 |
// Strip any existing size suffix to get the base/original image URL. |
| 305 |
$base_image_url = self::get_base_image_url( $image_url ); |
| 306 |
|
| 307 |
// Convert URL to file path. |
| 308 |
$image_path = str_replace( $base_url, $upload_dir['basedir'], $base_image_url ); |
| 309 |
$image_path = urldecode( $image_path ); // Handle URL-encoded characters (spaces, special chars). |
| 310 |
|
| 311 |
// If base image doesn't exist, try the original URL path (might be the actual original). |
| 312 |
if ( ! file_exists( $image_path ) ) { |
| 313 |
$image_path = str_replace( $base_url, $upload_dir['basedir'], $image_url ); |
| 314 |
$image_path = urldecode( $image_path ); |
| 315 |
|
| 316 |
if ( ! file_exists( $image_path ) ) { |
| 317 |
return false; |
| 318 |
} |
| 319 |
} |
| 320 |
|
| 321 |
// Security: Validate path stays within uploads directory (after confirming file exists). |
| 322 |
$real_image_path = realpath( $image_path ); |
| 323 |
$real_upload_dir = realpath( $upload_dir['basedir'] ); |
| 324 |
|
| 325 |
if ( false === $real_image_path || false === $real_upload_dir || |
| 326 |
0 !== strpos( $real_image_path, $real_upload_dir ) ) { |
| 327 |
return false; |
| 328 |
} |
| 329 |
$image_path = $real_image_path; |
| 330 |
|
| 331 |
// Get image dimensions for the target size. |
| 332 |
$dimensions = self::get_thumb_size( $size ); |
| 333 |
$width = $dimensions[0]; |
| 334 |
$height = $dimensions[1]; |
| 335 |
|
| 336 |
// Generate resized filename from the BASE image (without any size suffix). |
| 337 |
$path_info = pathinfo( $image_path ); |
| 338 |
|
| 339 |
// Strip any existing size suffix from the filename to ensure clean base name. |
| 340 |
$base_filename = preg_replace( '/-\d+x\d+$/', '', $path_info['filename'] ); |
| 341 |
$resized_filename = $base_filename . "-{$width}x{$height}." . $path_info['extension']; |
| 342 |
$resized_path = $path_info['dirname'] . '/' . $resized_filename; |
| 343 |
$resized_url = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $resized_path ); |
| 344 |
|
| 345 |
// Security: Validate resized output path stays within uploads directory. |
| 346 |
$resized_dir = dirname( $resized_path ); |
| 347 |
$real_resized_dir = realpath( $resized_dir ); |
| 348 |
if ( false === $real_resized_dir || |
| 349 |
0 !== strpos( $real_resized_dir, $real_upload_dir ) ) { |
| 350 |
return false; |
| 351 |
} |
| 352 |
|
| 353 |
// Return existing resized image if it exists. |
| 354 |
if ( file_exists( $resized_path ) ) { |
| 355 |
return $resized_url; |
| 356 |
} |
| 357 |
|
| 358 |
// Attempt to create resized image from the original/base image. |
| 359 |
$image_editor = wp_get_image_editor( $image_path ); |
| 360 |
if ( is_wp_error( $image_editor ) ) { |
| 361 |
return false; |
| 362 |
} |
| 363 |
|
| 364 |
// Security: Check original image dimensions to prevent memory exhaustion. |
| 365 |
$original_size = $image_editor->get_size(); |
| 366 |
|
| 367 |
// Reject images larger than 10000x10000 pixels (adjustable via filter). |
| 368 |
$max_dimension = apply_filters( self::$prefix . '_max_image_dimension', 10000 ); |
| 369 |
if ( $original_size['width'] > $max_dimension || $original_size['height'] > $max_dimension ) { |
| 370 |
return false; |
| 371 |
} |
| 372 |
|
| 373 |
$resized = $image_editor->resize( $width, $height, true ); |
| 374 |
if ( is_wp_error( $resized ) ) { |
| 375 |
return false; |
| 376 |
} |
| 377 |
|
| 378 |
$saved = $image_editor->save( $resized_path ); |
| 379 |
if ( is_wp_error( $saved ) ) { |
| 380 |
return false; |
| 381 |
} |
| 382 |
|
| 383 |
return $resized_url; |
| 384 |
} |
| 385 |
|
| 386 |
/** |
| 387 |
* Resolve a thumbnail from an ACF Image or Text field. |
| 388 |
* |
| 389 |
* Handles all three ACF Image field return formats (Image Array, Image ID, |
| 390 |
* Image URL) as well as a plain Text field returning a URL string. |
| 391 |
* |
| 392 |
* @param string $acf_field ACF field name. |
| 393 |
* @param int $post_id Post ID to retrieve the field value from. |
| 394 |
* @param string $size Registered image size slug. |
| 395 |
* @return array { |
| 396 |
* @type string $postimage Image URL, or empty string if none found. |
| 397 |
* @type int $attachment_id Attachment ID when resolvable, otherwise 0. |
| 398 |
* @type string $pick Source identifier used for CSS class generation. |
| 399 |
* } |
| 400 |
*/ |
| 401 |
protected static function get_acf_thumbnail( string $acf_field, int $post_id, string $size ): array { |
| 402 |
$result = array( |
| 403 |
'postimage' => '', |
| 404 |
'attachment_id' => 0, |
| 405 |
'pick' => '', |
| 406 |
); |
| 407 |
|
| 408 |
$acf_value = get_field( $acf_field, $post_id ); |
| 409 |
|
| 410 |
if ( empty( $acf_value ) ) { |
| 411 |
return $result; |
| 412 |
} |
| 413 |
|
| 414 |
if ( is_array( $acf_value ) ) { |
| 415 |
// Image Array return format — prefer the attachment ID for full srcset support. |
| 416 |
if ( ! empty( $acf_value['id'] ) ) { |
| 417 |
$attachment_id = (int) $acf_value['id']; |
| 418 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $size ); |
| 419 |
if ( false !== $postthumb ) { |
| 420 |
$result['postimage'] = $postthumb[0]; |
| 421 |
$result['attachment_id'] = $attachment_id; |
| 422 |
$result['pick'] = 'acf'; |
| 423 |
} |
| 424 |
} elseif ( ! empty( $acf_value['url'] ) ) { |
| 425 |
$validated = filter_var( $acf_value['url'], FILTER_VALIDATE_URL ); |
| 426 |
if ( $validated ) { |
| 427 |
$result['postimage'] = $validated; |
| 428 |
$result['pick'] = 'acf'; |
| 429 |
} |
| 430 |
} |
| 431 |
} elseif ( is_numeric( $acf_value ) ) { |
| 432 |
// Image ID return format. |
| 433 |
$attachment_id = (int) $acf_value; |
| 434 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $size ); |
| 435 |
if ( false !== $postthumb ) { |
| 436 |
$result['postimage'] = $postthumb[0]; |
| 437 |
$result['attachment_id'] = $attachment_id; |
| 438 |
$result['pick'] = 'acf'; |
| 439 |
} |
| 440 |
} else { |
| 441 |
// Image URL return format, or a Text field containing a URL. |
| 442 |
$validated = filter_var( $acf_value, FILTER_VALIDATE_URL ); |
| 443 |
if ( $validated ) { |
| 444 |
$attachment_id = self::get_cached_attachment_id( $validated ); |
| 445 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $size ); |
| 446 |
|
| 447 |
$result['postimage'] = $postthumb ? $postthumb[0] : $validated; |
| 448 |
$result['attachment_id'] = $attachment_id; |
| 449 |
$result['pick'] = $postthumb ? 'acfcorrect' : 'acf'; |
| 450 |
} |
| 451 |
} |
| 452 |
|
| 453 |
return $result; |
| 454 |
} |
| 455 |
|
| 456 |
/** |
| 457 |
* Get thumbnail from the post meta field. |
| 458 |
* |
| 459 |
* @param \WP_Post $post Post object. |
| 460 |
* @param array $args Arguments array. |
| 461 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 462 |
*/ |
| 463 |
protected static function get_thumbnail_from_meta( \WP_Post $post, array $args ): array { |
| 464 |
$postimage = get_post_meta( $post->ID, $args['thumb_meta'], true ); |
| 465 |
$postimage = filter_var( $postimage, FILTER_VALIDATE_URL ); |
| 466 |
if ( ! $postimage ) { |
| 467 |
return array( |
| 468 |
'postimage' => '', |
| 469 |
'attachment_id' => 0, |
| 470 |
'pick' => '', |
| 471 |
'extracted_alt' => '', |
| 472 |
); |
| 473 |
} |
| 474 |
|
| 475 |
$pick = 'meta'; |
| 476 |
$attachment_id = self::get_cached_attachment_id( $postimage ); |
| 477 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); |
| 478 |
if ( false !== $postthumb ) { |
| 479 |
$postimage = $postthumb[0]; |
| 480 |
$pick .= 'correct'; |
| 481 |
} |
| 482 |
|
| 483 |
return array( |
| 484 |
'postimage' => $postimage, |
| 485 |
'attachment_id' => $attachment_id, |
| 486 |
'pick' => $pick, |
| 487 |
'extracted_alt' => '', |
| 488 |
); |
| 489 |
} |
| 490 |
|
| 491 |
/** |
| 492 |
* Get thumbnail from an ACF field. |
| 493 |
* |
| 494 |
* @param \WP_Post $post Post object. |
| 495 |
* @param array $args Arguments array. |
| 496 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 497 |
*/ |
| 498 |
protected static function get_thumbnail_from_acf( \WP_Post $post, array $args ): array { |
| 499 |
if ( ! $args['acf_field'] || ! function_exists( 'get_field' ) ) { |
| 500 |
return array( |
| 501 |
'postimage' => '', |
| 502 |
'attachment_id' => 0, |
| 503 |
'pick' => '', |
| 504 |
'extracted_alt' => '', |
| 505 |
); |
| 506 |
} |
| 507 |
|
| 508 |
$result = self::get_acf_thumbnail( $args['acf_field'], $post->ID, $args['size'] ); |
| 509 |
$result['extracted_alt'] = ''; |
| 510 |
return $result; |
| 511 |
} |
| 512 |
|
| 513 |
/** |
| 514 |
* Get thumbnail from the FIFU (Featured Image from URL) plugin. |
| 515 |
* |
| 516 |
* @param \WP_Post $post Post object. |
| 517 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 518 |
*/ |
| 519 |
protected static function get_thumbnail_from_fifu( \WP_Post $post ): array { |
| 520 |
/** |
| 521 |
* Filters the FIFU meta key used to store external image URLs. |
| 522 |
* |
| 523 |
* @param string $fifu_meta_key Meta key used by FIFU plugin. |
| 524 |
*/ |
| 525 |
$fifu_meta_key = apply_filters( self::$prefix . '_fifu_meta_key', 'fifu_image_url' ); |
| 526 |
$fifu_image_url = get_post_meta( $post->ID, $fifu_meta_key, true ); |
| 527 |
$fifu_image_url = filter_var( $fifu_image_url, FILTER_VALIDATE_URL ); |
| 528 |
|
| 529 |
if ( ! $fifu_image_url ) { |
| 530 |
return array( |
| 531 |
'postimage' => '', |
| 532 |
'attachment_id' => 0, |
| 533 |
'pick' => '', |
| 534 |
'extracted_alt' => '', |
| 535 |
); |
| 536 |
} |
| 537 |
|
| 538 |
return array( |
| 539 |
'postimage' => $fifu_image_url, |
| 540 |
'attachment_id' => 0, |
| 541 |
'pick' => 'fifu', |
| 542 |
'extracted_alt' => '', |
| 543 |
); |
| 544 |
} |
| 545 |
|
| 546 |
/** |
| 547 |
* Get thumbnail from the post's featured image. |
| 548 |
* |
| 549 |
* @param \WP_Post $post Post object. |
| 550 |
* @param array $args Arguments array. |
| 551 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 552 |
*/ |
| 553 |
protected static function get_thumbnail_from_featured_image( \WP_Post $post, array $args ): array { |
| 554 |
if ( false === get_post_thumbnail_id( $post->ID ) ) { |
| 555 |
return array( |
| 556 |
'postimage' => '', |
| 557 |
'attachment_id' => 0, |
| 558 |
'pick' => '', |
| 559 |
'extracted_alt' => '', |
| 560 |
); |
| 561 |
} |
| 562 |
|
| 563 |
$attachment_id = ( 'attachment' === $post->post_type ) ? $post->ID : get_post_thumbnail_id( $post->ID ); |
| 564 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); |
| 565 |
|
| 566 |
if ( false === $postthumb ) { |
| 567 |
return array( |
| 568 |
'postimage' => '', |
| 569 |
'attachment_id' => 0, |
| 570 |
'pick' => '', |
| 571 |
'extracted_alt' => '', |
| 572 |
); |
| 573 |
} |
| 574 |
|
| 575 |
return array( |
| 576 |
'postimage' => $postthumb[0], |
| 577 |
'attachment_id' => $attachment_id, |
| 578 |
'pick' => 'featured', |
| 579 |
'extracted_alt' => '', |
| 580 |
); |
| 581 |
} |
| 582 |
|
| 583 |
/** |
| 584 |
* Get thumbnail by scanning the post content for img tags. |
| 585 |
* |
| 586 |
* @param \WP_Post $post Post object. |
| 587 |
* @param array $args Arguments array. |
| 588 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 589 |
*/ |
| 590 |
protected static function get_thumbnail_from_content_scan( \WP_Post $post, array $args ): array { |
| 591 |
if ( ! $args['scan_images'] ) { |
| 592 |
return array( |
| 593 |
'postimage' => '', |
| 594 |
'attachment_id' => 0, |
| 595 |
'pick' => '', |
| 596 |
'extracted_alt' => '', |
| 597 |
); |
| 598 |
} |
| 599 |
|
| 600 |
// Skip content scanning for very large posts to prevent memory exhaustion. |
| 601 |
if ( strlen( $post->post_content ) > 50000 ) { // 50KB limit. |
| 602 |
$post_content = ''; |
| 603 |
} else { |
| 604 |
/** |
| 605 |
* Filters the post content that is used to scan for images. |
| 606 |
* |
| 607 |
* A filter function can be tapped into this to execute shortcodes, modify content, etc. |
| 608 |
* |
| 609 |
* @param string $post_content Post content. |
| 610 |
* @param \WP_Post $post Post object. |
| 611 |
*/ |
| 612 |
$post_content = apply_filters( self::$prefix . '_thumb_post_content', $post->post_content, $post ); |
| 613 |
} |
| 614 |
|
| 615 |
preg_match_all( '/<img\s[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>/i', $post_content, $matches ); |
| 616 |
if ( ! isset( $matches[1][0] ) || ! $matches[1][0] ) { |
| 617 |
return array( |
| 618 |
'postimage' => '', |
| 619 |
'attachment_id' => 0, |
| 620 |
'pick' => '', |
| 621 |
'extracted_alt' => '', |
| 622 |
); |
| 623 |
} |
| 624 |
|
| 625 |
$postimage = $matches[1][0]; |
| 626 |
$extracted_alt = self::get_alt_from_img_tag( $matches[0][0] ); |
| 627 |
$pick = 'first'; |
| 628 |
$attachment_id = self::get_cached_attachment_id( $postimage ); |
| 629 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); |
| 630 |
|
| 631 |
if ( false !== $postthumb ) { |
| 632 |
$postimage = $postthumb[0]; |
| 633 |
$pick .= 'correct'; |
| 634 |
} else { |
| 635 |
// Fallback: Try to resize the original URL if no attachment found. |
| 636 |
$resized_url = self::resize_external_image( $postimage, $args['size'] ); |
| 637 |
if ( $resized_url ) { |
| 638 |
$postimage = $resized_url; |
| 639 |
$pick .= 'resized'; |
| 640 |
} |
| 641 |
} |
| 642 |
|
| 643 |
return array( |
| 644 |
'postimage' => $postimage, |
| 645 |
'attachment_id' => $attachment_id, |
| 646 |
'pick' => $pick, |
| 647 |
'extracted_alt' => $extracted_alt, |
| 648 |
); |
| 649 |
} |
| 650 |
|
| 651 |
/** |
| 652 |
* Get thumbnail from the first attached child image. |
| 653 |
* |
| 654 |
* @param \WP_Post $post Post object. |
| 655 |
* @param array $args Arguments array. |
| 656 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 657 |
*/ |
| 658 |
protected static function get_thumbnail_from_first_child( \WP_Post $post, array $args ): array { |
| 659 |
$dimensions = self::get_thumb_size( $args['size'] ); |
| 660 |
$postimage = self::get_first_image( $post->ID, $dimensions[0], $dimensions[1] ); |
| 661 |
|
| 662 |
if ( ! $postimage ) { |
| 663 |
return array( |
| 664 |
'postimage' => '', |
| 665 |
'attachment_id' => 0, |
| 666 |
'pick' => '', |
| 667 |
'extracted_alt' => '', |
| 668 |
); |
| 669 |
} |
| 670 |
|
| 671 |
return array( |
| 672 |
'postimage' => $postimage, |
| 673 |
'attachment_id' => 0, |
| 674 |
'pick' => 'firstchild', |
| 675 |
'extracted_alt' => '', |
| 676 |
); |
| 677 |
} |
| 678 |
|
| 679 |
/** |
| 680 |
* Get thumbnail from the Video Thumbnails plugin meta field. |
| 681 |
* |
| 682 |
* @param \WP_Post $post Post object. |
| 683 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 684 |
*/ |
| 685 |
protected static function get_thumbnail_from_video_meta( \WP_Post $post ): array { |
| 686 |
$postimage = get_post_meta( $post->ID, '_video_thumbnail', true ); |
| 687 |
$postimage = filter_var( $postimage, FILTER_VALIDATE_URL ); |
| 688 |
|
| 689 |
if ( ! $postimage ) { |
| 690 |
return array( |
| 691 |
'postimage' => '', |
| 692 |
'attachment_id' => 0, |
| 693 |
'pick' => '', |
| 694 |
'extracted_alt' => '', |
| 695 |
); |
| 696 |
} |
| 697 |
|
| 698 |
return array( |
| 699 |
'postimage' => $postimage, |
| 700 |
'attachment_id' => 0, |
| 701 |
'pick' => 'video_thumb', |
| 702 |
'extracted_alt' => '', |
| 703 |
); |
| 704 |
} |
| 705 |
|
| 706 |
/** |
| 707 |
* Get thumbnail from the configured default thumbnail setting. |
| 708 |
* |
| 709 |
* @param array $args Arguments array. |
| 710 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 711 |
*/ |
| 712 |
protected static function get_thumbnail_from_default_thumb( array $args ): array { |
| 713 |
if ( ! $args['thumb_default_show'] || ! $args['thumb_default'] ) { |
| 714 |
return array( |
| 715 |
'postimage' => '', |
| 716 |
'attachment_id' => 0, |
| 717 |
'pick' => '', |
| 718 |
'extracted_alt' => '', |
| 719 |
); |
| 720 |
} |
| 721 |
|
| 722 |
$postimage = $args['thumb_default']; |
| 723 |
$pick = 'default_thumb'; |
| 724 |
$attachment_id = 0; |
| 725 |
|
| 726 |
if ( self::$default_thumb_url !== $postimage ) { |
| 727 |
$attachment_id = self::get_cached_attachment_id( $postimage ); |
| 728 |
$postthumb = wp_get_attachment_image_src( $attachment_id, $args['size'] ); |
| 729 |
if ( false !== $postthumb ) { |
| 730 |
$postimage = $postthumb[0]; |
| 731 |
$pick .= 'correct'; |
| 732 |
} |
| 733 |
} |
| 734 |
|
| 735 |
return array( |
| 736 |
'postimage' => $postimage, |
| 737 |
'attachment_id' => $attachment_id, |
| 738 |
'pick' => $pick, |
| 739 |
'extracted_alt' => '', |
| 740 |
); |
| 741 |
} |
| 742 |
|
| 743 |
/** |
| 744 |
* Get thumbnail from the site icon. |
| 745 |
* |
| 746 |
* @param array $args Arguments array. |
| 747 |
* @return array{postimage: string, attachment_id: int, pick: string, extracted_alt: string} |
| 748 |
*/ |
| 749 |
protected static function get_thumbnail_from_site_icon( array $args ): array { |
| 750 |
if ( ! $args['use_site_icon'] ) { |
| 751 |
return array( |
| 752 |
'postimage' => '', |
| 753 |
'attachment_id' => 0, |
| 754 |
'pick' => '', |
| 755 |
'extracted_alt' => '', |
| 756 |
); |
| 757 |
} |
| 758 |
|
| 759 |
$postimage = get_site_icon_url( max( $args['thumb_width'], $args['thumb_height'] ) ); |
| 760 |
$pick = 'site_icon_max'; |
| 761 |
|
| 762 |
// Fallback to min size if max size not available. |
| 763 |
if ( ! $postimage ) { |
| 764 |
$postimage = get_site_icon_url( min( $args['thumb_width'], $args['thumb_height'] ) ); |
| 765 |
$pick = 'site_icon_min'; |
| 766 |
} |
| 767 |
|
| 768 |
if ( ! $postimage ) { |
| 769 |
return array( |
| 770 |
'postimage' => '', |
| 771 |
'attachment_id' => 0, |
| 772 |
'pick' => '', |
| 773 |
'extracted_alt' => '', |
| 774 |
); |
| 775 |
} |
| 776 |
|
| 777 |
return array( |
| 778 |
'postimage' => $postimage, |
| 779 |
'attachment_id' => 0, |
| 780 |
'pick' => $pick, |
| 781 |
'extracted_alt' => '', |
| 782 |
); |
| 783 |
} |
| 784 |
|
| 785 |
/** |
| 786 |
* Get an HTML img element. |
| 787 |
* |
| 788 |
* When an attachment ID is available the function delegates to |
| 789 |
* {@see wp_get_attachment_image()} so that srcset, sizes and all |
| 790 |
* core image optimisations are applied automatically. |
| 791 |
* |
| 792 |
* When only a URL is available (external images, meta-key URLs, etc.) |
| 793 |
* the function builds the `<img>` tag manually using the configured |
| 794 |
* thumbnail dimensions and attributes. |
| 795 |
* |
| 796 |
* @param string $attachment_url Image URL. |
| 797 |
* @param array $attr Optional. Attributes for the image markup. |
| 798 |
* @param int $attachment_id Optional. Attachment ID. Default 0. |
| 799 |
* @param string|int[] $size Optional. Registered image size name or |
| 800 |
* array of width and height values in pixels. |
| 801 |
* Default empty string. |
| 802 |
* @return string HTML img element or empty string on failure. |
| 803 |
*/ |
| 804 |
public static function get_image_html( $attachment_url, $attr = array(), $attachment_id = 0, $size = '' ) { |
| 805 |
// If there is an attachment ID, delegate to wp_get_attachment_image(). |
| 806 |
if ( $attachment_id ) { |
| 807 |
$attr = self::ensure_loading_and_decoding_attrs( $attr ); |
| 808 |
$attr = self::sanitize_image_attrs( $attr ); |
| 809 |
|
| 810 |
return wp_get_attachment_image( $attachment_id, $size, false, $attr ); |
| 811 |
} |
| 812 |
|
| 813 |
// If there is no URL, return an empty string. |
| 814 |
if ( empty( $attachment_url ) ) { |
| 815 |
return ''; |
| 816 |
} |
| 817 |
|
| 818 |
// Define default attributes. |
| 819 |
$default_attr = array( |
| 820 |
'src' => $attachment_url, |
| 821 |
'alt' => '', |
| 822 |
'thumb_html' => self::get_option( 'thumb_html', 'html' ), |
| 823 |
'thumb_width' => self::get_option( 'thumb_width', 150 ), |
| 824 |
'thumb_height' => self::get_option( 'thumb_height', 150 ), |
| 825 |
'class' => "attachment-$size size-$size", |
| 826 |
); |
| 827 |
|
| 828 |
// Merge default attributes with provided attributes. |
| 829 |
$attr = wp_parse_args( $attr, $default_attr ); |
| 830 |
$attr = self::ensure_loading_and_decoding_attrs( $attr ); |
| 831 |
|
| 832 |
// Generate width and height string before thumb_* keys are stripped. |
| 833 |
$hwstring = self::get_image_hwstring( $attr ); |
| 834 |
|
| 835 |
// Add 'auto' to the sizes attribute for lazy-loaded images. |
| 836 |
if ( |
| 837 |
isset( $attr['loading'] ) && |
| 838 |
'lazy' === $attr['loading'] && |
| 839 |
isset( $attr['sizes'] ) && |
| 840 |
function_exists( 'wp_sizes_attribute_includes_valid_auto' ) && |
| 841 |
! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] ) |
| 842 |
) { |
| 843 |
$attr['sizes'] = 'auto, ' . $attr['sizes']; |
| 844 |
} |
| 845 |
|
| 846 |
// Sanitise and strip internal attributes. |
| 847 |
$attr = self::sanitize_image_attrs( $attr ); |
| 848 |
|
| 849 |
/** |
| 850 |
* Filters the list of attachment image attributes. |
| 851 |
* |
| 852 |
* @param array $attr Attributes for the image markup. |
| 853 |
* @param string $attachment_url Image URL. |
| 854 |
*/ |
| 855 |
$attr = apply_filters( self::$prefix . '_get_image_attributes', $attr, $attachment_url ); |
| 856 |
$attr = array_map( 'esc_attr', $attr ); |
| 857 |
|
| 858 |
// Construct the HTML img tag. |
| 859 |
$html = '<img ' . rtrim( $hwstring ); |
| 860 |
foreach ( $attr as $name => $value ) { |
| 861 |
if ( '' !== $value ) { |
| 862 |
$html .= " $name=" . '"' . $value . '"'; |
| 863 |
} |
| 864 |
} |
| 865 |
$html .= ' />'; |
| 866 |
|
| 867 |
/** |
| 868 |
* Filters the img tag. |
| 869 |
* |
| 870 |
* @param string $html HTML img element or empty string on failure. |
| 871 |
* @param string $attachment_url Image URL. |
| 872 |
* @param array $attr Attributes for the image markup. |
| 873 |
*/ |
| 874 |
return apply_filters( self::$prefix . '_get_image_html', $html, $attachment_url, $attr ); |
| 875 |
} |
| 876 |
|
| 877 |
/** |
| 878 |
* Ensures the loading/decoding attributes are set consistently for all thumbnails. |
| 879 |
* |
| 880 |
* @param array $attr Attributes array. |
| 881 |
* @return array |
| 882 |
*/ |
| 883 |
protected static function ensure_loading_and_decoding_attrs( array $attr ): array { |
| 884 |
if ( empty( $attr['loading'] ) ) { |
| 885 |
/** |
| 886 |
* Filters the default loading attribute applied to Contextual Related Posts thumbnails. |
| 887 |
* |
| 888 |
* @param string|null $loading Loading attribute value or null to omit. |
| 889 |
* @param array $attr Thumbnail attributes. |
| 890 |
*/ |
| 891 |
$attr['loading'] = apply_filters( self::$prefix . '_thumbnail_loading_attribute', 'lazy', $attr ); |
| 892 |
} |
| 893 |
|
| 894 |
if ( empty( $attr['decoding'] ) ) { |
| 895 |
/** |
| 896 |
* Filters the default decoding attribute applied to Contextual Related Posts thumbnails. |
| 897 |
* |
| 898 |
* @param string|null $decoding Decoding attribute value or null to omit. |
| 899 |
* @param array $attr Thumbnail attributes. |
| 900 |
*/ |
| 901 |
$attr['decoding'] = apply_filters( self::$prefix . '_thumbnail_decoding_attribute', 'async', $attr ); |
| 902 |
} |
| 903 |
|
| 904 |
return $attr; |
| 905 |
} |
| 906 |
|
| 907 |
/** |
| 908 |
* Sanitises optional image attributes before rendering. |
| 909 |
* |
| 910 |
* Removes `decoding`, `loading`, and `fetchpriority` when their values |
| 911 |
* are empty or invalid, and strips internal `thumb_*` keys that must |
| 912 |
* never appear in the final `<img>` tag. |
| 913 |
* |
| 914 |
* @param array $attr Image attributes. |
| 915 |
* @return array Cleaned attributes. |
| 916 |
*/ |
| 917 |
private static function sanitize_image_attrs( array $attr ): array { |
| 918 |
// Omit the `decoding` attribute if the value is invalid according to the spec. |
| 919 |
if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) { |
| 920 |
unset( $attr['decoding'] ); |
| 921 |
} |
| 922 |
|
| 923 |
/* |
| 924 |
* If the default value of `lazy` for the `loading` attribute is overridden |
| 925 |
* to omit the attribute for this image, ensure it is not included. |
| 926 |
*/ |
| 927 |
if ( isset( $attr['loading'] ) && ! $attr['loading'] ) { |
| 928 |
unset( $attr['loading'] ); |
| 929 |
} |
| 930 |
|
| 931 |
// If the `fetchpriority` attribute is overridden and set to false or an empty string. |
| 932 |
if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) { |
| 933 |
unset( $attr['fetchpriority'] ); |
| 934 |
} |
| 935 |
|
| 936 |
// Strip internal keys that must not appear in the final <img> tag. |
| 937 |
unset( $attr['thumb_html'], $attr['thumb_width'], $attr['thumb_height'] ); |
| 938 |
|
| 939 |
return $attr; |
| 940 |
} |
| 941 |
|
| 942 |
/** |
| 943 |
* Extract alt text from an image tag string. |
| 944 |
* |
| 945 |
* @param string $img_tag Image tag HTML. |
| 946 |
* @return string Sanitized alt text or empty string if none found. |
| 947 |
*/ |
| 948 |
private static function get_alt_from_img_tag( string $img_tag ): string { |
| 949 |
if ( ! preg_match( '/\salt=(\"|\')(.*?)\1/i', $img_tag, $matches ) ) { |
| 950 |
return ''; |
| 951 |
} |
| 952 |
|
| 953 |
$alt = wp_specialchars_decode( $matches[2], ENT_QUOTES ); |
| 954 |
$alt = sanitize_text_field( $alt ); |
| 955 |
|
| 956 |
return $alt; |
| 957 |
} |
| 958 |
|
| 959 |
/** |
| 960 |
* Retrieve width and height attributes using given width and height values. |
| 961 |
* |
| 962 |
* @param array $args Argument array. |
| 963 |
* @return string Height-width string. |
| 964 |
*/ |
| 965 |
public static function get_image_hwstring( $args = array() ) { |
| 966 |
$default_args = array( |
| 967 |
'thumb_html' => self::get_option( 'thumb_html', 'html' ), |
| 968 |
'thumb_width' => self::get_option( 'thumb_width', 150 ), |
| 969 |
'thumb_height' => self::get_option( 'thumb_height', 150 ), |
| 970 |
); |
| 971 |
|
| 972 |
$args = wp_parse_args( $args, $default_args ); |
| 973 |
|
| 974 |
if ( 'css' === $args['thumb_html'] ) { |
| 975 |
$thumb_html = ' style="max-width:' . $args['thumb_width'] . 'px;max-height:' . $args['thumb_height'] . 'px;" '; |
| 976 |
} elseif ( 'html' === $args['thumb_html'] ) { |
| 977 |
$thumb_html = ' width="' . $args['thumb_width'] . '" height="' . $args['thumb_height'] . '" '; |
| 978 |
} else { |
| 979 |
$thumb_html = ''; |
| 980 |
} |
| 981 |
|
| 982 |
/** |
| 983 |
* Filters the thumbnail HTML and allows a filter function to add any more HTML if needed. |
| 984 |
* |
| 985 |
* @param string $thumb_html Thumbnail HTML. |
| 986 |
* @param array $args Argument array. |
| 987 |
*/ |
| 988 |
return apply_filters( self::$prefix . '_thumb_html', $thumb_html, $args ); |
| 989 |
} |
| 990 |
|
| 991 |
/** |
| 992 |
* Get the first child image in the post. |
| 993 |
* |
| 994 |
* @param int|\WP_Post $postid Post ID or WP_Post object. |
| 995 |
* @param int $thumb_width Thumb width. |
| 996 |
* @param int $thumb_height Thumb height. |
| 997 |
* @return string Location of thumbnail. |
| 998 |
*/ |
| 999 |
public static function get_first_image( $postid, int $thumb_width, int $thumb_height ): string { |
| 1000 |
$args = array( |
| 1001 |
'numberposts' => 1, |
| 1002 |
'order' => 'ASC', |
| 1003 |
'post_mime_type' => 'image', |
| 1004 |
'post_parent' => $postid, |
| 1005 |
'post_status' => 'inherit', |
| 1006 |
'post_type' => 'attachment', |
| 1007 |
); |
| 1008 |
|
| 1009 |
$attachments = get_children( $args ); |
| 1010 |
|
| 1011 |
if ( empty( $attachments ) ) { |
| 1012 |
return ''; |
| 1013 |
} |
| 1014 |
|
| 1015 |
$attachment = reset( $attachments ); |
| 1016 |
$image_size = array( $thumb_width, $thumb_height ); |
| 1017 |
|
| 1018 |
if ( 0 < $attachment->ID ) { |
| 1019 |
$image_attributes = wp_get_attachment_image_src( $attachment->ID, $image_size ); |
| 1020 |
|
| 1021 |
if ( empty( $image_attributes ) ) { |
| 1022 |
$image_attributes = wp_get_attachment_image_src( $attachment->ID, 'full' ); |
| 1023 |
} |
| 1024 |
|
| 1025 |
if ( ! empty( $image_attributes ) ) { |
| 1026 |
/** |
| 1027 |
* Filter the first child image URL. |
| 1028 |
* |
| 1029 |
* @param string $image_url URL of the image. |
| 1030 |
* @param int|\WP_Post $postid Post ID or WP_Post object. |
| 1031 |
* @param int $thumb_width Thumb width. |
| 1032 |
* @param int $thumb_height Thumb height. |
| 1033 |
*/ |
| 1034 |
return apply_filters( |
| 1035 |
self::$prefix . '_get_first_image', |
| 1036 |
$image_attributes[0], |
| 1037 |
$postid, |
| 1038 |
$thumb_width, |
| 1039 |
$thumb_height |
| 1040 |
); |
| 1041 |
} |
| 1042 |
} |
| 1043 |
|
| 1044 |
return ''; |
| 1045 |
} |
| 1046 |
|
| 1047 |
/** |
| 1048 |
* Get cached attachment ID from URL to prevent database exhaustion. |
| 1049 |
* |
| 1050 |
* @param string $attachment_url Attachment URL. |
| 1051 |
* @return int Attachment ID. |
| 1052 |
*/ |
| 1053 |
public static function get_cached_attachment_id( $attachment_url = '' ) { |
| 1054 |
$attachment_id = 0; |
| 1055 |
|
| 1056 |
// If there is no URL, return. |
| 1057 |
if ( ! $attachment_url ) { |
| 1058 |
return $attachment_id; |
| 1059 |
} |
| 1060 |
|
| 1061 |
// Check cache first. |
| 1062 |
$cache_key = self::$prefix . '_attachment_id_' . get_current_blog_id() . '_' . hash( 'sha256', $attachment_url ); |
| 1063 |
$cached_id = wp_cache_get( $cache_key, self::$prefix . '_media' ); |
| 1064 |
|
| 1065 |
if ( false !== $cached_id ) { |
| 1066 |
return (int) $cached_id; |
| 1067 |
} |
| 1068 |
|
| 1069 |
// Attempt to retrieve the attachment ID from the URL. |
| 1070 |
$attachment_id = attachment_url_to_postid( $attachment_url ); |
| 1071 |
|
| 1072 |
// If not found, try stripping the size suffix (e.g., -150x150, -1024x768) and lookup base URL. |
| 1073 |
if ( 0 === $attachment_id ) { |
| 1074 |
$base_url = self::get_base_image_url( $attachment_url ); |
| 1075 |
if ( $base_url !== $attachment_url ) { |
| 1076 |
$attachment_id = attachment_url_to_postid( $base_url ); |
| 1077 |
} |
| 1078 |
} |
| 1079 |
|
| 1080 |
// Cache the result for 1 hour. |
| 1081 |
wp_cache_set( $cache_key, $attachment_id, self::$prefix . '_media', HOUR_IN_SECONDS ); |
| 1082 |
|
| 1083 |
/** |
| 1084 |
* Filter the cached attachment ID from the attachment URL. |
| 1085 |
* |
| 1086 |
* @param int $attachment_id Attachment ID. |
| 1087 |
* @param string $attachment_url Attachment URL. |
| 1088 |
*/ |
| 1089 |
return apply_filters( self::$prefix . '_get_cached_attachment_id', $attachment_id, $attachment_url ); |
| 1090 |
} |
| 1091 |
|
| 1092 |
/** |
| 1093 |
* Get the base image URL by stripping WordPress size suffixes. |
| 1094 |
* |
| 1095 |
* Converts URLs like image-150x150.jpg or image-1024x768.jpg to image.jpg |
| 1096 |
* |
| 1097 |
* @param string $url Image URL potentially with size suffix. |
| 1098 |
* @return string Base image URL without size suffix. |
| 1099 |
*/ |
| 1100 |
public static function get_base_image_url( $url ) { |
| 1101 |
// Remove WordPress size suffix (e.g., -150x150) while retaining filename and extension. |
| 1102 |
return preg_replace( '/-\d+x\d+(?=\.[^.]+$)/', '', $url ); |
| 1103 |
} |
| 1104 |
|
| 1105 |
/** |
| 1106 |
* Function to get the correct height and width of the thumbnail. |
| 1107 |
* |
| 1108 |
* @param string $size Image size. |
| 1109 |
* @return array Width and height. If no width and height is found, then 150 is returned for each. |
| 1110 |
*/ |
| 1111 |
public static function get_thumb_size( $size = 'thumbnail' ) { |
| 1112 |
|
| 1113 |
// Get thumbnail size. |
| 1114 |
$thumb_size_array = self::get_all_image_sizes( $size ); |
| 1115 |
|
| 1116 |
if ( isset( $thumb_size_array['width'] ) ) { |
| 1117 |
$thumb_width = $thumb_size_array['width']; |
| 1118 |
$thumb_height = $thumb_size_array['height']; |
| 1119 |
} |
| 1120 |
|
| 1121 |
if ( isset( $thumb_width ) && isset( $thumb_height ) ) { |
| 1122 |
$thumb_size = array( $thumb_width, $thumb_height ); |
| 1123 |
} else { |
| 1124 |
$thumb_size = array( 150, 150 ); |
| 1125 |
} |
| 1126 |
|
| 1127 |
/** |
| 1128 |
* Filter array of thumbnail size. |
| 1129 |
* |
| 1130 |
* @param array $thumb_size Array with width and height of thumbnail. |
| 1131 |
*/ |
| 1132 |
return apply_filters( self::$prefix . '_get_thumb_size', $thumb_size ); |
| 1133 |
} |
| 1134 |
|
| 1135 |
/** |
| 1136 |
* Get all image sizes. |
| 1137 |
* |
| 1138 |
* @param string|int[] $size Image size. |
| 1139 |
* @return array If a single size is specified, then the array with width, height and crop status |
| 1140 |
* or an empty array if size is not found; |
| 1141 |
* If no size is specified then an Associative array of the registered image sub-sizes. |
| 1142 |
*/ |
| 1143 |
public static function get_all_image_sizes( $size = '' ) { |
| 1144 |
|
| 1145 |
if ( is_array( $size ) ) { |
| 1146 |
$size = self::get_appropriate_image_size( $size[0], $size[1] ); |
| 1147 |
} |
| 1148 |
|
| 1149 |
$sizes = wp_get_registered_image_subsizes(); |
| 1150 |
|
| 1151 |
/* Get only 1 size if found */ |
| 1152 |
if ( $size ) { |
| 1153 |
if ( isset( $sizes[ $size ] ) ) { |
| 1154 |
return $sizes[ $size ]; |
| 1155 |
} |
| 1156 |
return array(); |
| 1157 |
} |
| 1158 |
|
| 1159 |
/** |
| 1160 |
* Filters array of image sizes. |
| 1161 |
* |
| 1162 |
* @param array $sizes Image sizes. |
| 1163 |
*/ |
| 1164 |
return apply_filters( self::$prefix . '_get_all_image_sizes', $sizes ); |
| 1165 |
} |
| 1166 |
|
| 1167 |
/** |
| 1168 |
* Get the most appropriate image size based on the given thumbnail width and height. |
| 1169 |
* |
| 1170 |
* @param int $thumb_width Thumbnail width. |
| 1171 |
* @param int $thumb_height Thumbnail height. |
| 1172 |
* @return string|bool Image size name if found, false otherwise. |
| 1173 |
*/ |
| 1174 |
public static function get_appropriate_image_size( $thumb_width, $thumb_height ) { |
| 1175 |
$sizes = wp_get_registered_image_subsizes(); |
| 1176 |
|
| 1177 |
$closest_size = false; |
| 1178 |
$closest_distance = PHP_INT_MAX; |
| 1179 |
|
| 1180 |
foreach ( $sizes as $size_name => $size_info ) { |
| 1181 |
$size_width = $size_info['width']; |
| 1182 |
$size_height = $size_info['height']; |
| 1183 |
$distance = sqrt( pow( $thumb_width - $size_width, 2 ) + pow( $thumb_height - $size_height, 2 ) ); |
| 1184 |
|
| 1185 |
if ( $distance < $closest_distance ) { |
| 1186 |
$closest_distance = $distance; |
| 1187 |
$closest_size = $size_name; |
| 1188 |
} |
| 1189 |
} |
| 1190 |
|
| 1191 |
return $closest_size; |
| 1192 |
} |
| 1193 |
} |
| 1194 |
|