Skip to content

Instantly share code, notes, and snippets.

@ThatStevensGuy
Created May 16, 2021 23:04
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ThatStevensGuy/7020010fe667106f79d2556f386933d0 to your computer and use it in GitHub Desktop.
Automatic Featured Image for WordPress Posts
<?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