Here is a little more advanced solution. I made this because the featured image guthenburg block puts custom attributes on the image tag. I also wanted to take in account all the other info an uploaded image would have. Like srcset, and full image meta info. This class takes an image in your themes directory, copies it to uploads directory in a "{theme-slug}-defaults" folder and produces all the responsive sizes. Then build the tag just like wordpress would.
It would be rather easy to expand this filter function and put different images for different categories, post types for example. Or create a random generator for an array of default images.
So here it is (put in theme's functions.php):
function my_filter_thumbnail(string $html, int $post_id, int $attachment_id, $size, $attr)
{
// Shows only for posts and only in list views loops, not in single view
if(!$attachment_id && get_post_type($post_id) == 'post' && !is_single())
{
$default = new DefaultFeaturedImage(
get_stylesheet_directory() . '/assets/img/default-post-thumbnail.jpg',
$size,
$attr
);
$html = $default->generate();
}
return $html;
}
add_filter( 'post_thumbnail_html', 'my_filter_thumbnail', 20, 5 );
class DefaultFeaturedImage
{
private $source, $src, $size, $attr, $image_meta, $width, $height, $folder, $basedir, $path;
public function __construct($source, $size = 'medium', $attr = []) {
$this->source = $source;
$this->size = $size;
$this->attr = $attr;
$this->folder = get_stylesheet() . '_defaults';
list( 'basedir' => $this->basedir ) = wp_get_upload_dir();
$this->path = $this->basedir . '/' . $this->folder; // Uploads path for thumbnail
$this->src = $this->path . '/' . basename($this->source);
if ( wp_mkdir_p( $this->path ) ) {
if ( !file_exists($this->src) ) {
copy($this->source, $this->src);
}
}
list( $this->width, $this->height ) = getimagesize( $this->src );
list( $this->width, $this->height ) = image_constrain_size_for_editor( $this->width, $this->height, $this->size );
$this->generateImageMeta();
$this->buildAttr();
}
/*
* Largely borrowed from the plugin https://wordpress.org/plugins/regenerate-thumbnails/
*/
private function getThumbnailSizes() {
global $_wp_additional_image_sizes;
$thumbnail_sizes = array();
foreach ( get_intermediate_image_sizes() as $size ) {
$thumbnail_sizes[ $size ]['label'] = $size;
if ( in_array( $size, array( 'thumbnail', 'medium', 'medium_large', 'large' ) ) ) {
$thumbnail_sizes[ $size ]['width'] = (int) get_option( $size . '_size_w' );
$thumbnail_sizes[ $size ]['height'] = (int) get_option( $size . '_size_h' );
$thumbnail_sizes[ $size ]['crop'] = ( 'thumbnail' == $size ) ? (bool) get_option( 'thumbnail_crop' ) : false;
} elseif ( ! empty( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes[ $size ] ) ) {
$thumbnail_sizes[ $size ]['width'] = (int) $_wp_additional_image_sizes[ $size ]['width'];
$thumbnail_sizes[ $size ]['height'] = (int) $_wp_additional_image_sizes[ $size ]['height'];
$thumbnail_sizes[ $size ]['crop'] = (bool) $_wp_additional_image_sizes[ $size ]['crop'];
}
}
return $thumbnail_sizes;
}
private function generateImageMeta()
{
$images = [];
$editor = wp_get_image_editor( $this->src );
$generate_new = false;
foreach ( $this->getThumbnailSizes() as $size )
{
$thumb = $this->getThumbnail($editor, $size);
if ( $thumb ) {
$images[] = $thumb;
if ( !file_exists($thumb['file']) )
$generate_new = true;
}
}
if ( $generate_new )
$images = $editor->multi_resize($this->getThumbnailSizes());
$this->image_meta = [
'width' => $this->width,
'height' => $this->height,
'file' => $this->folder . '/' . basename($this->src),
'filesize' => filesize($this->src),
'sizes' => $images,
'image_meta' => null
];
}
/*
* Largely borrowed from the plugin https://wordpress.org/plugins/regenerate-thumbnails/
*/
private function getThumbnail( $editor, $size )
{
$dims = image_resize_dimensions(
$this->width,
$this->height,
$size['width'],
$size['height'],
$size['crop']
);
if ( ! $dims ) {
return false;
}
list( , , , , $dst_w, $dst_h ) = $dims;
$suffix = "{$dst_w}x{$dst_h}";
$filename = $editor->generate_filename( $suffix );
$filesize = 0;
if(file_exists($filename))
$filesize = filesize($filename);
return [
'file' => basename($filename),
'width' => $dst_w,
'height' => $dst_h,
'mime-type' => mime_content_type($this->source),
'filesize' => $filesize,
];
}
/*
* Mostly copied from wp_get_attachment_image()
*/
private function buildAttr()
{
$size_class = $this->size;
if ( is_array( $size_class ) ) {
$size_class = implode( 'x', $size_class );
}
$default_attr = array(
'src' => $this->src,
'class' => "attachment-$size_class size-$size_class",
'alt' => '',
);
$this->attr = wp_parse_args( $this->attr, $default_attr );
$loading_attr = $this->attr;
$loading_attr['width'] = $this->width;
$loading_attr['height'] = $this->height;
$loading_optimization_attr = wp_get_loading_optimization_attributes(
'img',
$loading_attr,
current_filter()
);
// Add loading optimization attributes if not available.
$this->attr = array_merge( $this->attr, $loading_optimization_attr );
// Omit the `decoding` attribute if the value is invalid according to the spec.
if ( empty( $this->attr['decoding'] ) || ! in_array( $this->attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) {
unset( $this->attr['decoding'] );
}
/*
* If the default value of `lazy` for the `loading` attribute is overridden
* to omit the attribute for this image, ensure it is not included.
*/
if ( isset( $this->attr['loading'] ) && ! $this->attr['loading'] ) {
unset( $this->attr['loading'] );
}
// If the `fetchpriority` attribute is overridden and set to false or an empty string.
if ( isset( $this->attr['fetchpriority'] ) && ! $this->attr['fetchpriority'] ) {
unset( $this->attr['fetchpriority'] );
}
// Generate 'srcset' and 'sizes' if not already present.
if ( empty( $this->attr['srcset'] ) ) {
if ( is_array( $this->image_meta ) ) {
$size_array = array( absint( $this->width ), absint( $this->height ) );
$srcset = wp_calculate_image_srcset( $size_array, $this->src, $this->image_meta );
$sizes = wp_calculate_image_sizes( $size_array, $this->src, $this->image_meta );
if ( $srcset && ( $sizes || ! empty( $this->attr['sizes'] ) ) ) {
$this->attr['srcset'] = $srcset;
if ( empty( $this->attr['sizes'] ) ) {
$this->attr['sizes'] = $sizes;
}
}
}
}
$this->attr = apply_filters( 'wp_get_attachment_image_attributes', $this->attr, null, $this->size );
$this->attr = array_map( 'esc_attr', $this->attr );
}
public function generate()
{
$hwstring = image_hwstring( $this->width, $this->height );
$html = rtrim( "<img $hwstring" );
foreach ( $this->attr as $name => $value ) {
$html .= " $name=" . '"' . $value . '"';
}
$html .= ' />';
return $html;
}
}