How to Remove All Images From WordPress Posts

The latest iteration of TIEtools includes a new option that is extremely useful in news feed management: image expiry.

As anyone who runs a sizable WordPress site knows, a few thousand posts don’t take up much server space… but a few thousand images do. And while it’s nice to have images on the latest updates on your site’s front page, they’re not so important once the news is old.

Previous versions of TIEtools allowed you to expire entire posts, of course, but what if you want to keep the information and just strip out all those space-hogging pictures? Time for some clever code!

Since we’re going to strip all the images out of the content permanently, it’s a lot easier than removing just the first image in each post for display purposes. It’s a two-stage process, because we have to remove images with captions as well as those without.

Captioned image shortcodes are thankfully quite easy to spot before they are converted into HTML. They look like this:

[caption] image details [/caption]

The beauty of this is that we can just hack out everything between the shortcode markers (inclusive) and we’ve taken care of both the caption and the image in one hit.

Let’s start with the SQL query to find images that are attached to existing posts:

SELECT * FROM $wpdb->posts
WHERE $wpdb->posts.post_type = 'attachment'
AND $wpdb->posts.post_mime_type LIKE 'image%'
AND $wpdb->posts.post_parent IN (
	SELECT ID FROM $wpdb->posts
	WHERE $wpdb->posts.post_type = 'post')

If you want to further qualify the check, such as finding content over 90 days old or something, you can add criteria into the sub-query, before that last parenthesis. Don’t put them in the main SELECT or you’ll apply the criteria to the images, not their parent posts!

So that’s our query, which we’ll call $imagesquery. We run that in our php function in the normal way, then manipulate the results. Of course, removing the images from the post does not affect the attachment records: we need to handle those as well as the actual files (if required).

In this code snippet, I’ve used another variable called $trash_images. This is a switch which determines whether the images are to be deleted (switch is ‘on’) or just detached from their parent posts and left in the media library (switch is not ‘on’). You can either set the value manually or include it on an options page in the usual way:

$imagesquery = "SELECT * FROM $wpdb->posts
		WHERE $wpdb->posts.post_type = 'attachment'
		AND $wpdb->posts.post_mime_type LIKE 'image%'
		AND $wpdb->posts.post_parent IN (
			SELECT ID FROM $wpdb->posts
			WHERE $wpdb->posts.post_type = 'post')"
$result = $wpdb->get_results($imagesquery);
foreach ($result as $post) {
	setup_postdata($post);  
	$postid = $post->ID;
	$parentid = $post->post_parent;
	function_remove_images($parentid);
	if ($trash_images == 'on') {
		wp_delete_attachment($postid);
	}
	else {
		function_unattach_images($postid);
	}
}

Now we need the three functions to handle all the manipulation: one to remove images and two to handle the attachments, either deleting them or just detaching them from their parent posts.

Let’s deal with function_remove_images first. This function takes the image’s parent post ID as a parameter and strips all the image code out of the post content. As usual, I’ve gone direct to the database for the content.

Note that you will have to replace the “!” with brackets, as WordPress won’t let me display this code with “[caption” and “caption]” in it.

function function_remove_images($post_id) {
	global $wpdb;
	$contentquery = "SELECT post_content
			 FROM $wpdb->posts
			 WHERE $wpdb->posts.ID = $post_id";
	$original_content = $wpdb->get_var($contentquery);
	$captions_stripped = preg_replace('#'.preg_quote("!caption").'.*'.preg_quote("caption!").'#si', '', $original_content);
	$images_stripped = preg_replace('/]+./','', $captions_stripped);
	$new_content = esc_sql($images_stripped);
	$contentquery = "UPDATE $wpdb->posts
			 SET post_content = '$new_content'
			 WHERE $wpdb->posts.ID = $post_id";
	$update_content = $wpdb->query($contentquery);
}

The important parts of the code are the preg_replace lines. The first one takes care of any images with captions: it sets comment mode and looks for the opening and closing shortcode markers. Everything between and including them is replaced by an empty string.

The second preg_replace does a similar thing, using <img as the starting mark and replacing everything from there to the closing > with an empty string.

Note that it is better to run the caption check first. Since it also wipes out the image details between the caption shortcode markers, it potentially reduces the amount of work the second check does.

And don’t miss the esc_sql function in there: without it, the code won’t run because the final SQL query will be all messed up!

All that remains now is handling the attachments. WordPress provides a delete function if you want to physically remove the images from the server: just call wp_delete_attachment with the attachment’s post ID (which we picked up in our initial query). Easy.

If you want to keep the images in the media library and allow the admin to handle them manually, you will need another function. This one simply sets the post_parent field to zero for each attachment post, effectively detaching it and shifting it into the “unattached” category in the library:

function function_unattach_images($post_id) {
	global $wpdb;
	
	$unquery = "UPDATE $wpdb->posts
		    SET post_parent = 0
		    WHERE $wpdb->posts.ID = $post_id";
	
	$unattach_images = $wpdb->query($unquery);
}

And that’s it. Run the first function on your wp_cron schedule or on demand to strip images out of all your content, or add qualifying criteria to limit what it affects.

The final code used in TIEtools is very much like this, with the addition of expiry criteria such as post age and category inclusion or exclusion.

Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *