The following script will automatically set the first image in a WordPress post as the Featured Image when the post is saved. You can override the functionality by selecting a Featured Image.
/** * @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); }
https://gist.github.com/ThatStevensGuy/7020010fe667106f79d2556f386933d0