When developing a custom WordPress theme for a client we came across an annoying quirk in the WordPress editor user interface.
I had added a custom post type to their theme to allow the client to create a database of service providers. However, once their database was populated, we discovered that the “link to existing content” list in the “Insert/edit link” popup from the editor window was cluttered with all these “provider” entries which made it difficult to locate posts and pages that the client wanted to link to in their posts.
UPDATE: This filter has now been included in WordPress v3.7 – this hack is no longer required to make the filter work! You can skip this bit and go directly to the part below about implementing the filter.
After some extensive searching online, I found that there was no existing way to hook into the functions which generated this list – it would require hacking the core WordPress files to make these changes.
There is a WordPress Trac ticket already in place with a suggested fix, which I hope might make it into v3.6. Until then, I implemented the suggested patch to my core WordPress files:
public static function wp_link_query( $args = array() ) { $pts = get_post_types( array( 'public' => true ), 'objects' ); $pt_names = array_keys( $pts ); $query = array( 'post_type' => $pt_names, 'suppress_filters' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'post_status' => 'publish', 'order' => 'DESC', 'orderby' => 'post_date', 'posts_per_page' => 20, ); $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1; if ( isset( $args['s'] ) ) $query['s'] = $args['s']; $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0; $query = apply_filters( 'wp_link_query_args', $query ); // patch: add new filter // Do main query. $get_posts = new WP_Query; $posts = $get_posts->query( $query ); // Check if any posts were found. if ( ! $get_posts->post_count ) return false; // Build results. $results = array(); foreach ( $posts as $post ) { if ( 'post' == $post->post_type ) $info = mysql2date( __( 'Y/m/d' ), $post->post_date ); else $info = $pts[ $post->post_type ]->labels->singular_name; $results[] = array( 'ID' => $post->ID, 'title' => trim( esc_html( strip_tags( get_the_title( $post ) ) ) ), 'permalink' => get_permalink( $post->ID ), 'info' => $info, ); } return apply_filters( 'wp_link_query', $results, $query ); // patch: add new filter }
Warning! only make modifications to your core WordPress files if you know what you are doing! Otherwise you would be wise to wait until v3.6 or so when it is hoped this patch will be part of the core release.
UPDATE: the filter provided in v3.7 is identical to the hack I previously made above, so the following code will still work as expected:
To take advantage of these new hooks is quite straight forward. The first hook wp_link_query_args
allows you to modify the parameters sent to the WP_Query object so you can customise the query itself. The second hook wp_link_query
allows you to modify the resulting array.
In our case, all we want to do is prevent the query from including our custom ‘provider’ post type. If we notice right at the start of the function, it gathers a list of all public post types and adds a list of these to the query parameters:
$pts = get_post_types( array( 'public' => true ), 'objects' ); $pt_names = array_keys( $pts ); $query = array( 'post_type' => $pt_names, 'suppress_filters' => true, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'post_status' => 'publish', 'order' => 'DESC', 'orderby' => 'post_date', 'posts_per_page' => 20, );
This $query
array is sent to our new hook, so we can alter the values of the array to get the query to return what we want.
$query['post_type']
is an array of post type names, which in our case is ('post', 'page', 'attachment', 'provider')
. We simply want to remove provider from this list so the query doesn’t return posts of this type.
I did it by adding the following code to my functions.php
file:
add_filter('wp_link_query_args', 'provider_wp_link_query_args'); function provider_wp_link_query_args($query) { $pt_new = array(); foreach ($query['post_type'] as $pt) { if ($pt == 'provider') continue; // don't add our provider type to the list $pt_new[] = $pt; // add everything else back to our list } $query['post_type'] = $pt_new; // change the query to use our new array of post types return $query; // don't forget to return the $query array }
Basically, I declared a new empty array $pt_new
, then looped through all post types found by the get_post_types
function, adding each to my new array but skipping the ‘provider’ type. Doing it this way makes it easy to extend the functionality in the future to skip other custom post types too.
Indeed, if I had a second custom post type called ‘supplier’, I could hide both of them from the link to existing content list by using the following code instead:
add_filter('wp_link_query_args', 'custom_wp_link_query_args'); function custom_wp_link_query_args($query) { $pt_new = array(); $exclude_types = array('provider', 'supplier'); // our list of custom post types to exclude from the query foreach ($query['post_type'] as $pt) { if (in_array($pt, $exclude_types)) continue; // skip anything found in our exclude list $pt_new[] = $pt; // add anything else back to the list } $query['post_type'] = $pt_new; // replace the list with our new one return $query; // don't forget to return the $query array }
Some StackOverflow/StackExchange entries on this topic:
Kris Doyle says
Thanks so much for taking the time to post this, it’s made my client’s life a LOT easier. Obviously I’m hoping this gets released with 3.6 but I think it’s “reasonably” acceptable to modify the core if we’re just adding a valid hook. Just need to keep it in mind during the upgrade.
Simon Hampel says
Just as an update, this filter has now been included in v3.7