Tag Archives: Custom Post Type

Prevent WordPress from automatically purging trash from custom post type

Preface

WordPress has a constant called EMPTY_TRASH_DAYS that it automatically sets to 30. What that means is that WordPress will automatically delete all posts, pages, custom post types, etc, that have been in the trash for 30 or more days.

1
2
3
4
5
6
7
8
// wp-includes/default-constants.php
 
function wp_functionality_constants( ) {
    ...
    if ( !defined( 'EMPTY_TRASH_DAYS' ) )
        define( 'EMPTY_TRASH_DAYS', 30 );
    ...
}
// wp-includes/default-constants.php

function wp_functionality_constants( ) {
	...
	if ( !defined( 'EMPTY_TRASH_DAYS' ) )
		define( 'EMPTY_TRASH_DAYS', 30 );
	...
}

Whenever you trash a post, WordPress creates a meta field called _wp_trash_meta_time containing the time it was trashed.

1
2
3
4
5
6
7
// wp-includes/post.php
 
function wp_trash_post($post_id = 0) {
    ...
    add_post_meta($post_id,'_wp_trash_meta_time', time());
    ...
}
// wp-includes/post.php

function wp_trash_post($post_id = 0) {
	...
	add_post_meta($post_id,'_wp_trash_meta_time', time());
	...
}

WordPress has a daily event scheduled…

1
2
3
4
// wp-admin/admin.php
 
if ( !wp_next_scheduled('wp_scheduled_delete') && !defined('WP_INSTALLING') )
    wp_schedule_event(time(), 'daily', 'wp_scheduled_delete');
// wp-admin/admin.php

if ( !wp_next_scheduled('wp_scheduled_delete') && !defined('WP_INSTALLING') )
	wp_schedule_event(time(), 'daily', 'wp_scheduled_delete');

…that will run a function called wp_scheduled_delete to permanently delete all expired posts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// wp-includes/functions.php
 
function wp_scheduled_delete() {
    global $wpdb;
 
    $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
 
    $posts_to_delete = $wpdb->get_results($wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < '%d'", $delete_timestamp), ARRAY_A);
 
    foreach ( (array) $posts_to_delete as $post ) {
        $post_id = (int) $post['post_id'];
        if ( !$post_id )
            continue;
 
        $del_post = get_post($post_id);
 
        if ( !$del_post || 'trash' != $del_post->post_status ) {
            delete_post_meta($post_id, '_wp_trash_meta_status');
            delete_post_meta($post_id, '_wp_trash_meta_time');
        } else {
            wp_delete_post($post_id);
        }
    }
    ...
}
// wp-includes/functions.php

function wp_scheduled_delete() {
	global $wpdb;

	$delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );

	$posts_to_delete = $wpdb->get_results($wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_trash_meta_time' AND meta_value < '%d'", $delete_timestamp), ARRAY_A);

	foreach ( (array) $posts_to_delete as $post ) {
		$post_id = (int) $post['post_id'];
		if ( !$post_id )
			continue;

		$del_post = get_post($post_id);

		if ( !$del_post || 'trash' != $del_post->post_status ) {
			delete_post_meta($post_id, '_wp_trash_meta_status');
			delete_post_meta($post_id, '_wp_trash_meta_time');
		} else {
			wp_delete_post($post_id);
		}
	}
	...
}

Continue reading

Remove post type slug in custom post type URL and move subpages to website root in WordPress

I recently had a need to rewrite the URLs of all parent and child pages in a custom post type so that they appeared to live at the website root, but in reality, continued to live in a custom post type within their hierarchy.

Preface

The situation:

  1. I have a page called Services that lives at domain.com/services.
  2. I have a custom post type called Services.
  3. I have Services post called Programming that lives at domain.com/services/programming.
  4. I have Services post called Web Development, that is a child of Programming, that lives at domain.com/services/programming/web-development.

The goal:

  1. The Services page should remain where it is i.e. domain.com/services.
  2. The Programming post should appear to live at the website root i.e. domain.com/programming.
  3. The Web Development service should also live at the website root i.e. domain.com/web-development.

The reason:

  1. Shorter URLs.
  2. Keep all website pages together.
  3. Keep all services together.
  4. Maintain hierarchy of services.

Continue reading

Prevent autosave on a custom post type in WordPress

I have a custom post type that doesn’t use the WordPress editor or its contents externally, but every time a post is saved or programmatically created, I do write contents to the post_content field so I can search for certain data in the WordPress back-end.

The problem is that every time a user goes to edit one of those posts, the autosave will overwrite my post_content data.

The following piece of code removes the autosave script when the user is editing a post of type movie.

1
2
3
4
5
6
7
8
9
add_action('admin_enqueue_scripts', 'my_admin_enqueue_scripts');
 
function my_admin_enqueue_scripts() {
  switch(get_post_type()) {
    case 'movie':
      wp_dequeue_script('autosave');
      break;
  }
}
add_action('admin_enqueue_scripts', 'my_admin_enqueue_scripts');

function my_admin_enqueue_scripts() {
  switch(get_post_type()) {
    case 'movie':
      wp_dequeue_script('autosave');
      break;
  }
}

WordPress get_post_type() doesn’t work when restoring a trashed post via undo

I have a custom post type that uses the following hook:

1
2
3
4
add_action(
    'untrash_post',
    array($this, 'custom_restore_function')
);
add_action(
	'untrash_post',
	array($this, 'custom_restore_function')
);

And the custom function looks as follows:

1
2
3
4
5
function custom_restore_function() {
    if(get_post_type() == 'custom_post_type') {
        $this->set_custom_post_type_display_status(1);
    }
}
function custom_restore_function() {
	if(get_post_type() == 'custom_post_type') {
		$this->set_custom_post_type_display_status(1);
	}
}

I have a very similar setup for custom_save_function, custom_trash_function, and custom_delete_function, and in all instances, get_post_type() works like a charm.

When you use the Trash action link to move a post to the trash, the link looks something like this:

post.php?post=118&action=trash&_wpnonce=910f2d7f30

And if you choose to restore that post from the trash screen via the Restore action link, it looks very similar:

post.php?post=118&action=untrash&_wpnonce=ddb4c9c68b

The problem is that when you trash a post and then decide to restore it again via the Undo link in the confirmation message, the custom_restore_function will fail.

The Undo link looks something like this:

edit.php?post_type=custom_post_type&doaction=undo&action=untrash&
ids=118&_wpnonce=ea0d2a97d2

And as you can see, the format is a bit different.

After doing more research and taking a closer look inside post.php to see what get_post_type() is actually doing:

1
2
3
4
5
6
7
8
9
10
function get_post_type($the_post = false) {
    global $post;
    if(false === $the_post)
        $the_post = $post;
    elseif(is_numeric($the_post))
        $the_post = get_post($the_post);
    if(is_object($the_post))
        return $the_post->post_type;
    return false;
}
function get_post_type($the_post = false) {
	global $post;
	if(false === $the_post)
		$the_post = $post;
	elseif(is_numeric($the_post))
		$the_post = get_post($the_post);
	if(is_object($the_post))
		return $the_post->post_type;
	return false;
}

It appears that $post doesn’t contain the post when using the Undo action link. I would be interested in knowing why?

Nevertheless, the easiest way to solve this problem was to modify the if statement to check for the post_type query string, which fortunately is available inside the custom_restore_function:

1
2
3
4
5
6
function custom_restore_function() {
    if(get_post_type() == 'custom_post_type'
    || $_GET['post_type'] == 'custom_post_type') {
        $this->set_custom_post_type_display_status(1);
    }
}
function custom_restore_function() {
	if(get_post_type() == 'custom_post_type'
	|| $_GET['post_type'] == 'custom_post_type') {
		$this->set_custom_post_type_display_status(1);
	}
}

WordPress hooks for saving, trashing, restoring, and deleting custom fields in a custom post type

This took me quite a while to figure out, but I finally determined what the proper hooks for managing custom fields in a custom post type in WordPress are.

In my scenario, I have a custom post type that uses the add_meta_box() function to add a few custom fields. As a matter of fact, those are the only fields available… the title, body, etc, have all been removed. Furthermore, I save those custom fields in a new table, because I need to perform additional actions outside of WordPress with that data, and storing them separately will make that a lot easier.

WordPress allows you to perform four actions with any post type, which meant I needed four hooks in order to keep the data in my table in sync with the status of the post. Those actions are: saving, moving something to the trash, restoring it from the trash, and permanently deleting.

It seems that WordPress is a bit inconsistent in the naming convention of those hooks, which is why it took some trial and error, but after spending some time with it, I’ve tested and confirmed the following four hooks to be the ones you need:

Saving

add_action('save_post', 'custom_save_function');

Trashing

add_action('wp_trash_post', 'custom_trash_function');

Restoring

add_action('untrash_post', 'custom_restore_function');

Deleting

add_action('delete_post', 'custom_delete_function');

Hooks that didn’t act the way I expected were: trash_post, wp_untrash_post, trash_custom-post-type, and untrash_custom-post-type. For instance, trash_post only worked with built-in post types, such as pages and posts, but save_post works even with custom post types.

Hopefully this two minute blog post will save someone some time, because those four hooks are pretty much necessary if you’re working with custom fields (unless you disable trash entirely and just permanently delete).