18

I know there are plugins that fetch images from remote URL and store locally. I just want to know whether it is possible not to store image in Media Library yet use it as Featured Image?

  • You could do it with a custom field where you store the external image URL. It may be difficult to make it work every time the_post_thumnail() is present (or similar function) or to make it works with different image sizes defined by the theme or plugin. – cybmeta Aug 20 '14 at 8:21
  • 1
    You can use this plugin to set external image url as your featured image: wordpress.org/plugins/wp-remote-thumbnail – Advanced SEO Aug 20 '14 at 8:59
  • Does it store image locally or call remotely? – Volatil3 Aug 20 '14 at 10:22
  • @Volatil3 I haven't tested but reading the plugin description I'd say it calls it remotely. – Andy Macaulay-Brook May 24 '16 at 7:48
34

Yes, it's possible and pretty easy.

This is the workflow I suggest:

  1. Put somewhere a UI to insert the URL of the featured image. Probably best choice is to use 'admin_post_thumbnail_html' filter hook
  2. Use 'save_post' action hook to save the URL (after security and validation routine) in a custom post meta
  3. Use 'post_thumbnail_html' filter hook to output proper <img> markup, overriding default, if the post for which the featured image is required has the post meta with the external featured image

To work, this workflow needs the featured image to be shown in the template using get_the_post_thumnbail() or the_post_thumbnail() functions.

Moreover, we must be sure that '_thumbnail_id' meta value has a non-empty value when we set the meta for the external URL, otherwise has_post_thumbnail() will return false for posts that only have an external featured image.

In fact, it is possible that a post has both a standard local featured image and one set via our workflow, and in this case the external will be used.

To implement our workflow we need a function to validate the URL used as an external featured image, because we have to be sure it is a valid image URL.

There are different ways to do this task; here I use a very simple way that only looks at the URL, without downloading the image. This works only for static image URLs and does not verify that the image actually exists, but it's fast. Modify it to something more advanced if you need to (here is some help).

function url_is_image( $url ) {
    if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
        return FALSE;
    }
    $ext = array( 'jpeg', 'jpg', 'gif', 'png' );
    $info = (array) pathinfo( parse_url( $url, PHP_URL_PATH ) );
    return isset( $info['extension'] )
        && in_array( strtolower( $info['extension'] ), $ext, TRUE );
}

Pretty easy. Now let's add the 3 hooks described in the workflow above:

add_filter( 'admin_post_thumbnail_html', 'thumbnail_url_field' );

add_action( 'save_post', 'thumbnail_url_field_save', 10, 2 );

add_filter( 'post_thumbnail_html', 'thumbnail_external_replace', 10, PHP_INT_MAX );

and the related functions. First the one that outputs the field in the admin:

function thumbnail_url_field( $html ) {
    global $post;
    $value = get_post_meta( $post->ID, '_thumbnail_ext_url', TRUE ) ? : "";
    $nonce = wp_create_nonce( 'thumbnail_ext_url_' . $post->ID . get_current_blog_id() );
    $html .= '<input type="hidden" name="thumbnail_ext_url_nonce" value="' 
        . esc_attr( $nonce ) . '">';
    $html .= '<div><p>' . __('Or', 'txtdomain') . '</p>';
    $html .= '<p>' . __( 'Enter the url for external image', 'txtdomain' ) . '</p>';
    $html .= '<p><input type="url" name="thumbnail_ext_url" value="' . $value . '"></p>';
    if ( ! empty($value) && url_is_image( $value ) ) {
        $html .= '<p><img style="max-width:150px;height:auto;" src="' 
            . esc_url($value) . '"></p>';
        $html .= '<p>' . __( 'Leave url blank to remove.', 'txtdomain' ) . '</p>';
    }
    $html .= '</div>';
    return $html;
}

Note that I've used 'txtdomain' as the text domain, but you should use a proper, registered text domain.

This is how the output looks when empty:

External URL for featured image: the field

And this is how it looks after having added an image URL and saved/updated the post:

External URL for featured image: the field after filling up and saved

So, now our admin UI is done, let's write the saving routine:

function thumbnail_url_field_save( $pid, $post ) {
    $cap = $post->post_type === 'page' ? 'edit_page' : 'edit_post';
    if (
        ! current_user_can( $cap, $pid )
        || ! post_type_supports( $post->post_type, 'thumbnail' )
        || defined( 'DOING_AUTOSAVE' )
    ) {
        return;
    }
    $action = 'thumbnail_ext_url_' . $pid . get_current_blog_id();
    $nonce = filter_input( INPUT_POST, 'thumbnail_ext_url_nonce', FILTER_SANITIZE_STRING );
    $url = filter_input( INPUT_POST,  'thumbnail_ext_url', FILTER_VALIDATE_URL );
    if (
        empty( $nonce )
        || ! wp_verify_nonce( $nonce, $action )
        || ( ! empty( $url ) && ! url_is_image( $url ) )
    ) {
        return;
    }
    if ( ! empty( $url ) ) {
        update_post_meta( $pid, '_thumbnail_ext_url', esc_url($url) );
        if ( ! get_post_meta( $pid, '_thumbnail_id', TRUE ) ) {
            update_post_meta( $pid, '_thumbnail_id', 'by_url' );
        }
    } elseif ( get_post_meta( $pid, '_thumbnail_ext_url', TRUE ) ) {
        delete_post_meta( $pid, '_thumbnail_ext_url' );
        if ( get_post_meta( $pid, '_thumbnail_id', TRUE ) === 'by_url' ) {
            delete_post_meta( $pid, '_thumbnail_id' );
        }
    }
}

The function, after some security checks, looks at the URL posted and if it's fine, saves it in '_thumbnail_ext_url' post meta. If the URL is empty and the meta was saved, it is deleted, allowing to remove the meta simply by emptying the external URL field.

Last thing to do is output the featured image markup when our external image URL is set in meta:

function thumbnail_external_replace( $html, $post_id ) {
    $url =  get_post_meta( $post_id, '_thumbnail_ext_url', TRUE );
    if ( empty( $url ) || ! url_is_image( $url ) ) {
        return $html;
    }
    $alt = get_post_field( 'post_title', $post_id ) . ' ' .  __( 'thumbnail', 'txtdomain' );
    $attr = array( 'alt' => $alt );
    $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, NULL );
    $attr = array_map( 'esc_attr', $attr );
    $html = sprintf( '<img src="%s"', esc_url($url) );
    foreach ( $attr as $name => $value ) {
        $html .= " $name=" . '"' . $value . '"';
    }
    $html .= ' />';
    return $html;
}

We are done.

What's left to do

In the featured image output I've not used width or height properties, nor classes that WordPress usually adds, like 'attachment-$size'. That's because sniffing the size of an image requires extra work that will slow down page loading, especially if you have more than one featured image in the page.

If you need those attributes, you can use my code adding a callback to wp_get_attachment_image_attributes' filter (it's a standard WordPress hook) or maybe you can alter my code to sniff image size and output related attributes and classes.

Plugin Gist

All the code posted here, with the exception of adding a proper text domain initialization, is available as a full-working plugin in a Gist here. The code there uses a namespace, so it requires PHP 5.3+.

Notes

Of course, you should be sure that you have license and authorization to use and hotlink images in your site from external ones.

  • where i am to put this code – Ankit Agrawal Dec 5 '18 at 10:14
  • can you please explain which code, on which page we have to write. I am a newbie in PHP/Wordpress so please explain step by step. Thanks – Ankit Agrawal Dec 10 '18 at 13:09
  • @AnkitAgrawal look here – gmazzap Dec 10 '18 at 15:03

protected by fuxia Jan 13 '15 at 15:20

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.