I have a need to include user profiles, along with posts & pages, in front-end searches.
I've searched for solutions to this problem (both here on WSE and elsewhere on the web) and haven't found must help. The closest I found, as far as my requirement, are Include Author profile in search results and Extend WordPress search to include user search, neither of which have reasonable answers.
To be more clear about the requirement: the site has posts, pages and (of course) users. When an end-user performs a search on the front-end, I need the search results to contain posts and pages whose titles or post_content contains the search terms (as is normal for front-end searches) plus the profiles of users that match the search terms (see below about what is meant by a user matching the search terms).
The important point is that I'm not talking about searching for posts that have been authored by a given user!
Rationale behind requirement
To help understand why I have this requirement, it might help to know that a part of the site lists all user profiles and an end-user can click on the name of any user to see that user's profile. On the user profile, various usermeta
's are displayed. From the end-user's perspective, there is no difference between the user profiles and posts/pages they can reach on the front-end.
Suppose the user profile for a given user displays the string "foo" (i.e., one of the displayed usermeta
's for that user contains "foo"). When an end-user searches for "foo" then they would expect that user's profile to show up in the search results.
Provisional solution
The following is the solution that I've come up with. This solution works but I feel there's got to be a better/easier/less-brittle way to accomplish this:
- register a non-public custom post type (say, 'my_user_profile')
- hook into
user_register
. insert a post of type 'my_user_profile' and add apostmeta
(say 'my_user_id') whose value is the ID of the newly registered user - hook into
the_posts
. Whenis_search()
is true, then do aget_users()
that searches varioususermeta
for the search terms in the end-user's search; then do aget_posts()
for posts of type 'my_user_profile' withpostmeta
'my_user_id' "IN" the user IDs found in theget_users()
; and then return the merge of the posts found by the original search with the posts of type 'my_user_profile' found by myget_posts()
. - hook into
post_type_link
(which is called byget_permalink()
). When the$post->post_type
is 'my_user_profile', returnget_author_posts_url()
on the user whose ID is in the 'my_user_id'postmeta
of$post
. This is so that the theme'ssearch.php
doesn't have to contain code with specific knowledge of how the above steps have "augmented" the search results - hook into
get_the_excerpt
. When$post->post_type
is 'my_user_profile', then return the value of a specificusermeta
(say 'my_excerpt') for the user whose ID is in the 'my_user_id'postmeta
of$post
. This is so that the theme'ssearch.php
doesn't have to contain code with specific knowledge of how the above steps have "augmented" the search results
Code for provisional solution
[note: code for step #3 edited to correct a bug I introduced when transcribing (and sanitizing) my working code]
Here's the code for my provisional solution:
// step #1
add_action( 'init', 'wpse_register_post_type' );
function wpse_register_post_type() {
$args = array(
'public' => false,
'show_ui' => false,
'supports' => array(
'title',
'author',
),
);
register_post_type( 'my_user_profile', $args );
}
// step #2
add_action( 'user_register', 'wpse_add_user_profile_post' );
function wpse_add_user_profile_post( $my_user_id ) {
$user = get_user_by( 'ID', $my_user_id );
$args = array(
'post_type' => 'my_user_profile',
// so that I can find them easier when manually looking thru the wp_posts table
'post_title' => $user->display_name,
'post_status' => 'publish',
);
$post_id = wp_insert_post( $args );
if ( ! is_wp_error( $post_id ) ) {
update_post_meta( $post_id, 'my_user_id', $my_user_id );
}
return;
}
// step #3
add_filter( 'the_posts', array( $this, 'wpse_user_search' ), 10, 2 );
function wpse_user_search( $posts, $query ) {
if ( ! is_search() ) {
return $posts;
}
$search_terms = explode( ' ', $query->get( 's' ) );
$user_meta_keys = array( /* my usermeta keys */ );
$args = array(
'fields' => 'ID',
'meta_query' => array( 'relation' => 'OR' ),
);
// build the meta_query
foreach ( $user_meta_keys as $meta_key ) {
foreach ( $search_terms as $search_term ) {
$args['meta_query'][] = array(
'key' => $meta_key,
'value' => $search_term,
'compare' => 'LIKE',
);
}
}
$users = get_users( $args );
// get the my_user_profile posts associated with $users
$args = array(
'post_type' => 'my_user_profile',
'meta_query' => array(
array(
'key' => 'my_user_id',
'value' => $users,
'compare' => 'IN',
),
)
);
// make sure we don't call ourselves in the get_posts() below
remove_filter( 'the_posts', array( $this, 'user_search' ) );
$user_posts = get_posts( $args );
add_filter( 'the_posts', array( $this, 'user_search' ), 10, 2 );
$posts = array_merge( $posts, $user_posts );
return $posts;
}
// step 4
add_filter( 'post_type_link', array( $this, 'wpse_user_profile_permalink' ), 10, 2 );
function wpse_user_profile_permalink( $post_link, $post ) {
if ( 'my_user_profile' !== $post->post_type ) {
return $post_link;
}
// rely on WP_Post::__get() magic method to get the postmeta
return get_author_posts_url( $post->my_user_id );
}
// step 5
add_filter( 'get_the_excerpt', array( $this, 'wpse_user_profile_excerpt' ), 10, 2 );
function wpse_user_profile_excerpt( $excerpt, $post ) {
if ( 'my_user_profile' !== $post->post_type ) {
return $excerpt;
}
// rely on WP_Post::__get() magic method to get the postmeta
$user = get_user_by( 'ID', $post->my_user_id );
// rely on WP_User::__get() magic method to get the usermeta
return $user->my_excerpt;
}
Like I said, the above works but I can't help but think that there's an easier way that I just haven't thought of.
Alternate (rejected) solution
One alternative I thought of, but rejected because it seems more complex/brittle than the above solution, is:
- same as #1 above
- same as #2 above
- hook into
personal_options_update
. For eachusermeta
that I'm already storing for a user, add them aspostmeta
to the post of type 'my_user_profile' associated with the given user - hook into
posts_join
andposts_where
to search the variouspostmeta
added in step #3 - same as #4 above
- same as #5 above
Does anyone have a simpler/less-brittle solution?