-
-
Save ThatStevensGuy/7020010fe667106f79d2556f386933d0 to your computer and use it in GitHub Desktop.
Automatic Featured Image for WordPress Posts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @package WordPress | |
* @subpackage Automatic Featured Image for WordPress Posts | |
* @author That Stevens Guy | |
* @phpcs:disable PSR1.Files.SideEffects | |
*/ | |
/** | |
* Transition post status action. | |
* | |
* @param string $new_status | |
* @param string $old_status | |
* @param WP_Post $post | |
* @return void | |
*/ | |
add_action('transition_post_status', function (string $new_status, string $old_status, WP_Post $post): void { | |
if (defined('REST_REQUEST') && REST_REQUEST) { | |
$published_post = $post; | |
/** | |
* REST requests need to postpone changes until "rest_after_insert_{$post->post_type}". | |
* | |
* @param WP_Post $post | |
* @param WP_REST_Request $request | |
* @param bool $creating | |
* @return void | |
*/ | |
add_action("rest_after_insert_{$post->post_type}", function ( | |
WP_Post $post, | |
WP_REST_Request $request, | |
bool $creating | |
) use ( | |
$new_status, | |
$old_status, | |
$published_post | |
): void { | |
if ($published_post->ID !== $post->ID) { | |
return; | |
} | |
tsg_transition_post_status($new_status, $old_status, $post); | |
}, 10, 3); | |
} else { | |
tsg_transition_post_status($new_status, $old_status, $post); | |
} | |
}, 10, 3); | |
/** | |
* Transition post status function. | |
* | |
* @param string $new_status | |
* @param string $old_status | |
* @param WP_Post $post | |
* @return void | |
*/ | |
function tsg_transition_post_status(string $new_status, string $old_status, WP_Post $post): void | |
{ | |
// Set the first image in post_content as the Featured Image. If one wasn't set. | |
tsg_set_featured_image($post); | |
} | |
/** | |
* Set the Featured Image automatically. | |
* | |
* @param WP_Post $post | |
* @return void | |
*/ | |
function tsg_set_featured_image(WP_Post $post): void | |
{ | |
if (!in_array($post->post_type, [ 'post' ])) { | |
return; | |
} | |
// Bypass automatic featured image if the post thumbnail was set manually. | |
if (has_post_thumbnail($post)) { | |
return; | |
} | |
$attachment_ids = tsg_get_image_attachment_ids_from_post_content( | |
$post, | |
[ | |
'get_first_attachment_id' => true, | |
'check_aspect_ratio' => true | |
] | |
); | |
if (!empty($attachment_ids[ 0 ])) { | |
update_post_meta($post->ID, '_thumbnail_id', $attachment_ids[ 0 ]); | |
} | |
} | |
/** | |
* Get image attachment ids from post content. | |
* | |
* @param WP_Post $post | |
* @param array $args | |
* @return array | |
*/ | |
function tsg_get_image_attachment_ids_from_post_content(WP_Post $post, array $args = []): array | |
{ | |
$args = array_merge([ | |
'get_first_attachment_id' => false, | |
'check_aspect_ratio' => false, | |
'horizontal_aspect_ratio' => 2.5, | |
'vertical_aspect_ratio' => 2.5 | |
], $args); | |
$attachment_ids = []; | |
$images = tsg_get_images_from_post_content($post); | |
if (empty($images)) { | |
return $attachment_ids; | |
} | |
$site_url = parse_url(site_url()); | |
foreach ($images as $image) { | |
// If the image is NOT from the current site, skip it. | |
if (strpos($image[ 'src' ], $site_url[ 'host' ] . '/' . explode('/', $image[ 'src' ])[ 3 ]) === false) { | |
continue; | |
} | |
$guid = tsg_get_original_image_src($image[ 'src' ]); | |
if (empty($guid)) { | |
continue; | |
} | |
$attachment_id = tsg_get_post_id_by_guid($guid); | |
if (empty($attachment_id)) { | |
continue; | |
} | |
if ($args[ 'check_aspect_ratio' ]) { | |
$attachment_metadata = get_metadata('post', $attachment_id, '_wp_attachment_metadata', true); | |
if ( | |
!tsg_check_image_aspect_ratio( | |
$attachment_metadata, | |
$args[ 'horizontal_aspect_ratio' ], | |
$args[ 'vertical_aspect_ratio' ] | |
) | |
) { | |
continue; | |
} | |
} | |
$attachment_ids[] = $attachment_id; | |
if ($args[ 'get_first_attachment_id' ]) { | |
break; | |
} | |
} | |
return $attachment_ids; | |
} | |
/** | |
* Get the original image source, size 'full'. | |
* | |
* @param string $url | |
* @param array $args | |
* @return string | |
*/ | |
function tsg_get_original_image_src(string $url, array $args = []): string | |
{ | |
if (!$url) { | |
return $url; | |
} | |
$args = array_merge([ | |
'check_exists' => false, | |
'check_filesize' => false, | |
'filesize_limit' => 4000000, | |
'strip_edit' => false | |
], $args); | |
// Strip the thumbnail size at the end of the URL so that we end up with what | |
// potentially could be the full size original image source. | |
// | |
// There is an edge case where the original URL has dimensions in the filename | |
// with the same format. These will be skipped, this is unaviodable, particularly | |
// for an offsite URL. | |
$url = preg_replace("/\-\d{2,4}[xX]\d{2,4}(\.[a-zA-Z]{2,4})$/", '$1', $url); | |
// Strip the edit timestamp for WordPress edited images. | |
// Turns out this isn't the best idea, end up with unedited images. But can be used for some things. | |
if (!empty($args[ 'strip_edit' ])) { | |
if (strpos($url, '-e') !== false) { | |
$pathinfo = pathinfo($url); | |
if ( | |
!empty($pathinfo[ 'dirname' ]) && | |
!empty($pathinfo[ 'filename' ]) && | |
!empty($pathinfo[ 'extension' ]) | |
) { | |
$filename_split = array_reverse(explode('-e', $pathinfo[ 'filename' ])); | |
if (!empty($filename_split[ 0 ]) && is_int((int)$filename_split[ 0 ])) { | |
unset($filename_split[ 0 ]); | |
} | |
$url = $pathinfo[ 'dirname' ] . '/' . | |
implode('-e', array_reverse($filename_split)) . '.' . $pathinfo [ 'extension' ]; | |
} | |
} | |
} | |
// Because we've chopped the URL up so much, we may want to check if the image even exists. | |
if (!empty($args[ 'check_exists' ]) || !empty($args[ 'check_filesize' ])) { | |
$stream_options = [ | |
'http' => [ | |
'user_agent' => | |
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6" | |
], | |
// 'ssl' => [ | |
// 'verify_peer' => false, | |
// 'verify_peer_name' => false | |
// ] | |
]; | |
$stream_context = stream_context_create($stream_options); | |
$headers = @get_headers($url, true, $stream_context); | |
if (!empty($args[ 'check_exists' ])) { | |
if (empty($headers[ 0 ]) || strpos($headers[ 0 ], '404') !== false) { | |
$url = ''; | |
} | |
} | |
if ($url && !empty($args[ 'check_filesize' ]) && !empty($args[ 'filesize_limit' ])) { | |
if ( | |
empty($headers[ 'Content-Length' ]) || | |
(int)$headers[ 'Content-Length' ] > (int)$args[ 'filesize_limit' ] | |
) { | |
$url = ''; | |
} | |
} | |
} | |
return $url; | |
} | |
/** | |
* Get all images from the post content. | |
* | |
* @param WP_Post $post | |
* @return array | |
*/ | |
function tsg_get_images_from_post_content(WP_Post $post): array | |
{ | |
$images = []; | |
if (empty($post->post_content)) { | |
return $images; | |
} | |
$content = apply_filters('the_content', $post->post_content); | |
preg_match_all('/<img\b[^>]+src=[\'"]([^\'"]+\.(?:jpg|png|jpeg))[\'"][^>]*>/i', $content, $matchesImages); | |
if (!empty($matchesImages[ 0 ])) { | |
foreach ($matchesImages[ 0 ] as $key => $img) { | |
$images[ $key ][ 'img' ] = $img; | |
$images[ $key ][ 'src' ] = $matchesImages[ 1 ][ $key ]; | |
preg_match_all( | |
'/(<img\b|(?!^)\G)[^>]*?\b(alt|width|height|srcset|sizes)=([\'"]?)([^>]*?)\3/i', | |
$img, | |
$matchesAttr | |
); | |
if (!empty($matchesAttr[ 2 ])) { | |
foreach ($matchesAttr[ 2 ] as $attr_key => $attr) { | |
if (!empty($matchesAttr[ 4 ][ $attr_key ])) { | |
$images[ $key ][ $attr ] = $matchesAttr[ 4 ][ $attr_key ]; | |
} | |
} | |
} | |
} | |
} | |
$images = apply_filters('tsg_get_images_from_post_content', $images, $post); | |
return $images; | |
} | |
/** | |
* Get check if an image fits within a suitable aspect ratio. | |
* | |
* @param array $image [ 'height' => int, 'width' => int ] | |
* @param float $horizontal_aspect_ratio | |
* @param float $vertical_aspect_ratio | |
* @return bool | |
*/ | |
function tsg_check_image_aspect_ratio( | |
array $image, | |
float $horizontal_aspect_ratio = 2.5, | |
float $vertical_aspect_ratio = 2.5 | |
): bool { | |
if (empty($image[ 'width' ]) || empty($image[ 'height' ])) { | |
return false; | |
} | |
$calculated_horizontal_aspect_ratio = (int)$image[ 'width' ] / (int)$image[ 'height' ]; | |
$calculated_vertical_aspect_ratio = (int)$image[ 'height' ] / (int)$image[ 'width' ]; | |
if ( | |
$calculated_horizontal_aspect_ratio > $horizontal_aspect_ratio || | |
$calculated_vertical_aspect_ratio > $vertical_aspect_ratio | |
) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Get post ID by guid. | |
* | |
* @param string $guid | |
* @return int ID if found, 0 if not | |
*/ | |
function tsg_get_post_id_by_guid(string $guid): int | |
{ | |
global $wpdb; | |
$post_id = $wpdb->get_var( | |
$wpdb->prepare(" | |
SELECT ID | |
FROM $wpdb->posts | |
WHERE instr( guid, '%s' ) > 0 | |
", $guid) | |
); | |
return intval($post_id); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment