Set Post Image Using Shortcode

The Shortcode API is more geared for altering post content rather than performing actions in response to content, and as such tend to require more creativity to alter other post data – though it can still be done. Ultimately, using a shortcode to set a featured image is kind of bizarre considering that you can do the same thing by clicking a button in the post editor UI.

I emphasize that my solutions below are untested hacks that rely on using WordPress components in non-standard ways


Solution

Use the Plugin API to write a plugin that uses the Shortcode API to register your shortcode.

In order to set a remote image as the featured image, you’ll need to download the image, move it to a permanent location (such as wp-content/uploads), attach it to the post, then set the featured image. There are a few ways to accomplish this, but I think the most straightforward would be to

  1. Use download_url() to download the remote image to the temporary directory.
  2. Pass the temporary filepath to handle_media_sideload() to move the file to wp-content/uploads, generate attachment metadata, and attach it to the post.
  3. Pass the returned attachment ID to set_post_thumbnail() to specify it as the featured image.

The tricky part about applying the above in a shortcode’s callback function is that handle_media_sideload() and set_post_thumbnail() both require the post’s ID, and shortcode callbacks are not passed the post ID nor the post object itself. The have access to this data when shortcodes are applied to posts getting displayed in The Loop by way of the global $post object, however applying the above operation at that point could result in the image being downloaded and set every time the post was displayed.

One possible work-around to achieve the hack would be to use a class property to pass the post ID, and return early from the shortcode callback the shortcode’s contents weren’t modified.


Alternate Solution

A simpler hack may be to manually extract the remote image URL from the post’s contents on an action hook that fires when a post is saved (such as save_post). A shortcode could still be registered to return a blank string. Something along the lines of

$featured_image_shortcode="post_image";

wpse_211802_featured_image_shortcode( $atts, $content ) {
  return '';
}

add_shortcode( $featured_image_shortcode, 'wpse_211802_featured_image_shortcode' );

wpse_211802_featured_image_from_shortcode_url( $post_id ) {
  $content           = get_the_content( $post_id );
  $shortcode_matches = array();
  
  if( ! has_shortcode( $content, 'post_image' ) )
    return;
  
  preg_match_all( '\[' . $featured_image_shortcode . '\](.*)\[\/' . $featured_image_shortcode . '\]', $content, $shortcode_matches );
  
  $url      = $shortcode_matches[ 1 ][ 0 ];
  $tempfile = download_url( $url );
  
  if( is_wp_error( $tempfile ) ){
    // download failed, handle error
    return;
  }

  // Set variables for storage
  preg_match( '/[^\?]+\.(jpg|jpe|jpeg|gif|png)/i', $url, $matches );

  $desc="Featured Image for Post " . $post_id;
  $file_array = array(
    'name'     => basename( $matches[0] ),
    'tmp_name' => $tempfile
  );

  // Sideload the downloaded file as an attachment to this post
  $attachment_id = media_handle_sideload( $file_array, $post_id, $desc );

  // If errors encountered while sideloading, delete the downloaded file
  if ( is_wp_error( $attachment_id ) ) {
    @unlink( $tempfile );
    return;
  }
  
  // If all went well, set the featured image
  set_post_thumbnail( $post_id, $attachment_id );
}

add_action( 'save_post', 'wpse_211802_featured_image_from_shortcode_url' );

You may need to require_once() additional files in order for the above to function. If you do something similar, I’d recommend expanding upon the error handling and URL validation.

Still though – far easier to just set the featured image with the button on the post editor UI.