3

On a WordPress website for a client, he's going to be posting articles with quite a lot of images in. When the post is saved/published, we need to be able to download/save these images to the server, and then instead of showing the original URLs, show the URLs of the image as in the uploads directory.

I've chosen to do this by writing a function in functions.php and then adding it as a filter. Here is the code I've written so far:

function getpostimgs() {
  global $post; 
  $postContent = $post->post_content;

  preg_match_all( '/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $postContent, $matches );
  // match all images in post, add to array and then
  // get first array (the one with the image URLs in)
  $imgURLs = $matches[1];

  $upload_dir = wp_upload_dir(); // Set upload folder

  foreach ( $imgURLs as $imgURL ) {
    $image_data = file_get_contents( $imgURL ); // Get image data
    $filename = basename( $imgURL ); // Create image file name
    // check upload file exists and the permissions on it are correct
    if( wp_mkdir_p( $upload_dir['path'] ) ) {
      $file = $upload_dir['path'] . '/' . $filename . "-" . $post->ID . ".jpg";
    } else {
      $file = $upload_dir['basedir'] . '/' . $filename . "-" . $post->ID . ".jpg";
    }
    // save file to server, with the filename of the image and then the post ID.
    file_put_contents( $file, $image_data ); // save the file to the server
    // find the occurence of each URL (within the post content)
    // that was in the array and replace them with the file link        
    preg_replace( "*" . $imgURL . "*", $file, $post->post_content );
  }
}

add_action('content_save_pre', 'getpostimgs');

I've written some comments so hopefully the lines are explained enough for you to understand what's going on.

The problem is, when it's saved (using content_save_pre, so that it's filtered before being saved to the database), it simply wipes all the content. The files are being saved and I know the value of $matches[1] is correct (i.e the right image links are there), as I've checked it with var_dump().

So there's something wrong with the preg_replace. Even if take out all the file stuff and just leave the preg_replace with a simple replacement string (e.g "Hello world"), it still doesn't work (still just wipes all the post content). Does anyone know why?

Thanks for helping out, hopefully I've been clear enough, happy to provide any more info or make it clearer :)

  • Well phrased question +1. Have you tried an exit at the end? Not sure if there isn't a redirect happening. Also take a look at your "Network" tab in the developer tools as this might be an AJAX call/request. Also preg_replace() has a return value: Look at the docs. – kaiser Apr 1 '14 at 0:23
  • content_save_pre is a filter. return result of preg_replace() which does not modify its argument. – user27457 Apr 1 '14 at 2:26
  • hey guys, thanks for the input. I've asked this same question with some different code on Stack Overflow (stackoverflow.com/questions/22792742/…), as I believe it's now more a PHP question than a Wordpress question. My only thing is, I've read that content_save_pre` is depreciated as of WP 3.8 (adambrown.info/p/wp_hooks/hook/content_save_pre). I've posted this on a separate question, wordpress.stackexchange.com/questions/139935/…. Thanks guys! – Tom Oakley Apr 1 '14 at 20:46
4

I have a plugin that does this manually in batch processes via ajax and I've been getting a lot of request for a way to make it automated.

This is the function that loads the post, downloads the images into the uploads directory and attaches them to the post. Then it does a search and replace for the old img urls and replaces them with the new ones. Attach this to all the publish actions and you should be good to go.

/**
 * Extracts all images in content adds to media library
 * if external and updates content with new url
 *
 * @param object $post The post object
 *
 */
function prefix_extract_external_images( $post ) {
  if ( ! is_object( $post ) ) {
    $post = get_post( $post );
  }
  $html = $post->post_content;
  $path = wp_upload_dir();
  $path = $path['baseurl'];

  if ( stripos( $html, '<img' ) !== false ) {

    $regex = '#<\s*img [^\>]*src\s*=\s*(["\'])(.*?)\1#im';
    preg_match_all( $regex, $html, $matches );

    if ( is_array( $matches ) && ! empty( $matches ) ) {
      $new = array();
      $old = array();
      foreach ( $matches[2] as $img ) {

        // Compare image source against upload directory
        // to prevent adding same attachment multiple times
        if ( stripos( $img, $path ) !== false ) {
          continue;
        }

        $tmp = download_url( $img );

        preg_match(
          '/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $img, $matches
        );
        $file_array['name'] = basename( $matches[0] );
        $file_array['tmp_name'] = $tmp;
        // If error storing temporarily, unlink
        if ( is_wp_error( $tmp ) ) {
          @unlink( $file_array['tmp_name'] );
          $file_array['tmp_name'] = '';
          continue;
        }

        $id = media_handle_sideload( $file_array, $post->ID );

        if ( ! is_wp_error( $id ) ) {
          $url = wp_get_attachment_url( $id );
          array_push( $new, $url );
          array_push( $old, $img );
        }
      } // end foreach
      if ( ! empty( $new ) ) {
        $content = str_ireplace( $old, $new, $html );
        $post_args = array( 'ID' => $post->ID, 'post_content' => $content, );
        if ( ! empty( $content ) ) {
          $post_id = wp_update_post( $post_args );
        }       
      }
    } // end if ( is_array( $matches ) && ! empty( $matches ) )
  } // end if ( stripos( $html, '<img' ) !== false )
  return $post;
} // end function

$action_arrays = array( 'new_to_publish', 'pending_to_publish', 'draft_to_publish' );

foreach ( $action_array as $action ) {
  add_action( $action, 'prefix_extract_external_images' );
}

Bonus: Set the first image found as the featured image.

Add this right before the return $post in the function above.

$atts = get_first_attachment( $post->ID );

foreach ( $atts as $a ) {
  $img = set_post_thumbnail( $post->ID, $a['ID'] );
}

This function will also be needed by the above code.

/**
 * Queries for attached images
 * @param int $post_id The post id to check if attachments exist
 * @return array|bool The 1st attached on success false if no attachments
 */
 function get_first_attachment( $post_id ) {
   return get_children( array (
     'post_parent'    => $post_id,
     'post_type'      => 'attachment',
     'post_mime_type' => 'image',
     'posts_per_page'  => (int)1
   ), ARRAY_A );
 }
  • +1. Just a thing, why don't use save_post to trigger the function: 1 instead of 3, and also if images are added during un update or if post is saved as 'private' those actions are not fired, save_post does. Also I'd used pathinfo($img, PATHINFO_FILENAME) to set $file_array['name'] instead of preg_match + basename, but this is a trifle. – gmazzap Apr 1 '14 at 7:09
  • hey this is great thanks :) for now I'm going to use my own function but will definitely use some of your ideas to fix my code. Thanks for the input! :) – Tom Oakley Apr 1 '14 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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