Last updated September 14th, 2024 at 05:12 pm
One of the questions that comes up a lot in WordPress discussion forums is how to set up a member directory. Often the question is posed as “What’s the best plugin for creating a member directory?”
Last October I published a blog post that summarized how I created a custom member directory for a WordPress membership site. When I shared links to that article in WordPress-related Facebook groups, a common response was, “How about some details, man?”
Hence this blog post.
As I explained in that earlier post, my solution was not to use any of the third-party plugins I’ve used in the past for directories (GeoDirectory, Directories Pro, Ultimate Member). Instead, I tapped into the WordPress user metadata infrastructure.
Two Memberships, Two Directories
The SEBA website has two directories, each corresponding to one of the two memberships: Personal and Business.
Here’s a short (1’30”) video showing what the two directories look like on the public facing website:
Here’s a screen shot of the plan comparison page (and you can see it live here):

Links to the Live SEBA Directories
As you read through this article, it might help to occasionally visit each directory on the SEBA website:
We need custom fields for each membership
Out of the box, WordPress has some built-in custom fields associated with each user.
Examples are first name, last name, nickname, description (which is what a user can enter into the Biographical Info box in their profile), and these key/value pairs are stored in the usermeta table.
For both memberships, I’ve added custom fields that are not built in, and I’m using Advanced Custom Fields Pro (“ACF”) for creating the field groups that map custom fields to each directory.
About ACF Field Groups and Location Rules
If you’re not familiar with the ACF plugin, here’s a very basic explanation of how custom fields work in ACF.
In ACF, you add custom fields to what’s called a field group. The reason you need to organize custom fields into field groups is so that WordPress knows which fields it should add to which admin screens (official documentation here).
Here’s an example.
Let’s suppose for some reason that you wanted to allow blog authors to indicate what they had for breakfast on the morning they published each post. (Presumably, you would modify the post template to include that information somewhere in the post.)
Using ACF, you would create a field group; call it something like “Extra Blog Post Fields”; add the custom field “breakfast” to this field group; and then associate this field group with the “post” post type. The way you perform that association is by defining an ACF location rule (post_type = post).

I also introduced custom user roles, and here’s why.
I knew I was going to need to create two different forms — one for Personal members to add and edit their profile information and another for Business members to add and edit their profile information.
Before setting up my two memberships in MemberPress, I created two new custom user roles that I also named “Personal” and “Business”. Then, when creating the memberships, I assigned members in each membership to its respective User Role. This way, every person with an active “Personal” membership had the role of “Personal”, and every person with an active “Business” membership had the role of “Business”.
Now I could write an ACF location rule that would ensure that the custom fields for a Personal membership would show up on the form for the Personal membership profile (and that the custom fields for a Business membership would show up on the form for the Business membership profile).
Personal Membership Custom Fields
Let’s deal with the Personal membership first. In some ways it’s the easier of the two to understand.
That ACF location rule for the Personal member profile field group is:
- User Form is equal to Add/Edit and
- User Role is equal to Personal
These are the custom fields for the Personal Membership
Here are all the custom fields mapped to the Personal membership. Highlighted fields below are MemberPress custom fields (either basic or custom). All but one of the lines not highlighted (description) were added as ACF custom fields.
directory_options first_name last_name mepr_telephone preferred_email mepr-address-one mepr-address-two mepr-address-city mepr-address-state mepr-address-zip mepr-address-country description mepr_instruments_played personal_profile_image photo_gallery
Here are explanations for the non-highlighted fields:
- directory_options
- This is an ACF group field consisting of six True/False fields for users to configure their preferences with respect to what personal information will be displayed on the public-facing directory.
- Include me in Directory?
- Show my Account Email?
- Show my Preferred Email?
- Show my Telephone Number?
- Show my Street Address?
- Show my City?
By default, all of the above options except Show my City are set to false. We don’t want members’ personal information displaying on the front end just because they didn’t expressly configure their options.
- preferred_email
- Primary email is required of all WordPress users. I added a field for preferred email for members who don’t want to be contacted using their WordPress email of record.
- description
- This is the default WordPress custom field that will be populated when a user updates his or her profile by adding content to the Biographical Info box.
- personal_profile_image
- ACF image field type; the user’s profile image.
- photo_gallery
- ACF Gallery field type, allowing users to create photo galleries.
Business Membership Custom Fields
The ACF location rule for the Business membership is identical to the location rule for the Personal memberhsip, except for the User Role:
- User Form is equal to Add/Edit
- User Role is equal to Business
These custom fields were all created with Advanced Custom Fields. Most are probably self-explanatory. The ones that might not be (highlighted) are explained below.
business_name business_summary business_type business_address about_my_business websites business_profile_image photo_gallery the_team embed
- business_type
- This is an ACF radio field. At this writing, the choices are Band, Promoter, Instructor, Retail, or Other. If I had it to do over again, I might make these terms into a custom taxonomy.
- websites
- This is an ACF repeater field, allowing users to enter the web URL of a website (e.g., his/her Facebook page or independent website) and a label for that website.
- the_team
- An ACF repeater field, allowing users to list members of their team (band members, employees, etc.), including name, email telephone, and brief note (e.g., banjo player; Vice President; Marketing Manager)
- embed
- ACF oEmbed field for adding videos
Creating the Forms for Users to Update their Profiles
Are you still with me? Good!
The process for creating the form for updating profiles is the same for Personal members and Business members. In both cases, I’m beholden to the people at UserInsights, where I got the needed code.
Here are the steps.
Code to create shortcode that will display the ACF front-end form.
Note that on Line 14, the script calls the acf_form() function, which displays a form to add or edit a post.
function seba_member_profile_front_end_edit_shortcode( $atts ) {
$a = shortcode_atts( array(
'field_group' => ''
), $atts );
$uid = get_current_user_id();
if ( ! empty ( $a['field_group'] ) &&; ! empty ( $uid ) ) {
$options = array(
'post_id' => 'user_'.$uid,
'field_groups' => array( intval( $a['field_group'] ) ),
'return' => add_query_arg( 'updated', 'true', get_permalink() )
);
ob_start();
acf_form( $options );
$form = ob_get_contents();
ob_end_clean();
}
return $form;
}add_shortcode( 'seba_member_profile', 'seba_member_profile_front_end_edit_shortcode' );
field_group parameter that determines which form is loaded.Pages for Displaying the Forms
Screen capture of the Personal Profile form

Here’s a section detail of that form:

Screen capture of the Business Profile form

Here is a section detail of that form:

Content of Personal Profile Form Page
What follows is the complete content for the Personal Profile Form page — consisting almost entirely of MemberPress shortcodes and the shortcode I created above for generating the front-end form.
The field_group parameter on line 8 is what tells WordPress which form to load. (“1621” is the post ID for the Personal profile field group.)
<div class="your-account-meta"> Welcome, <strong>[mepr-account-info field="full_name"]</strong>! Your account email is <strong>[mepr-account-info field="user_email"]</strong> Your active subscription(s): [mepr-list-subscriptions status="active"] </div> Use this form to edit your Personal Profile Page [seba_profile_edit field_group="1621"]
The numbered items in the screen capture below correspond to the numbered lines in the code snippet above.

Content of Business Profile Form Page
The field_group parameter on line 5 is what tells WordPress which form to load. (“1585” is the post ID for the Business profile field group.)
Welcome, <strong>[mepr-account-info field="full_name"]</strong>! Your active subscription(s): [mepr-list-subscriptions status="active"] Use this form to edit your Business Profile Page [seba_profile field_group="1585"]
The numbered items in the screen capture below correspond to the numbered lines in the code snippet above.

Archive Pages
The archive pages for both the Personal Memberships and the Business Memberships are also generated by simple shortcodes (keep reading for the details).
Also, both scripts call the same function — seba_get_members() — for returning an array of all active members:
function seba_get_members($roles) {
if ( isset($_GET['type']) && !empty($_GET['type']) ) {
/* If the query string for biz type was passed; This won't apply to personal memberships */
$args = array(
'meta_query' => array(
'key' => 'business_type',
'value' => $_GET['type'],
'compare' => '=',
),
'role__in' => array( $roles ),
'orderby' => 'meta_value',
'meta_key' => 'last_name',
'order' => 'ASC',
);
} else {
$args = array(
'role__in' => array( $roles ),
'orderby' => 'meta_value',
'meta_key' => 'last_name',
'order' => 'ASC',
);
}
$users = get_users($args);
return $users;
}
The shortcode scripts (below) pass the $roles variable to the above script.
Line 2: Users can filter the Business Members list by type (Band, Instructor, Promoter, Other); this line checks for a query string in the form of type=some_value and performs the appropriate filter (lines 5-8).
Creating the Archive Page for Personal Memberships
First, let’s see what this archive page looks like:
Screen Shot of Personal Members Archive Page

Personal Members Archive Page Contents
And here are the contents of that Personal Membership archive page (/personal-profiles/):
Here is a listing of all SEBA Personal Members who have elected to be included in the member directory: [seba_pers_members_show]
Script for Generating Personal Members Archive Page
Below is the script that generates the Personal Member Directory archive page via a shortcode:
- Line 3: Passes the ‘personal’ role to the
seba_get_membersfunction. - Line 7: This is used on line 55 in the hyperlink to the single profile for a member.
- Line 9: Calls the
seba_get_membersfunction. - Line 14: Starting here, the script checks whether the user has opted to have his/her profile public and then defines variables accordingly.
function seba_pers_profile_display_all_shortcode($atts) {
$atts = shortcode_atts( array(
'roles' => 'personal',
), $atts, 'seba_pers_members_show' );
$roles = $atts['roles'];
$single_page = '/personal-profile/';
$users = seba_get_members($roles);
$output = '<div class="all-profiles-wrap personal">';
foreach ( $users as $user ) {
$user_id = $member_id = $user->ID; // for compat
/* Does the member want to be displayed in the directory? 2023-03-23 19:10:42 */
$include_in_directory = get_user_meta($member_id, 'directory_options_include_in_directory', true);
$show_city = get_user_meta($member_id, 'directory_options_show_city', true);
if ( ! $include_in_directory ) continue;
$account_email = $this_member->user_email;
$telephone = get_user_meta($member_id, 'mepr_telephone', true);
$preferred_email = get_user_meta($member_id, 'preferred_email', true);
$bio = get_user_meta($member_id, 'description', true);
$instruments = get_user_meta($member_id, 'mepr_instruments_played', true);
$address_1 = get_user_meta($member_id, 'mepr-address-one', true);
$address_2 = get_user_meta($member_id, 'mepr-address-two', true);
$city = get_user_meta($member_id, 'mepr-address-city', true);
$state = get_user_meta($member_id, 'mepr-address-state', true);
$zip = get_user_meta($member_id, 'mepr-address-zip', true);
$country = get_user_meta($member_id, 'mepr-address-country', true);
/* Profile Image2023-10-02 18:10:34 */
$profile_image = get_user_meta($member_id, 'personal_profile_image', true);
$profile_image_size ='thumbnail';
if ( $profile_image ) {
$profile_image_render = wp_get_attachment_image($profile_image, $profile_image_size);
} else {
$profile_image_render = '<img src="/wp-content/uploads/profile-anonymous_648x648-150x150.png">';
}
/* 2023-10-02 19:13:35, */
$bio_character_count = strlen($bio);
$average_characters_per_word = 6.47;
$bio_word_count = str_word_count($bio);
$bio_cos = ( $bio_word_count ) ? $bio_word_count . ' words': 'None' ;
$output .= '<div class="profile-wrap archive">';
/* Thumb image */
$output.= '<div class="member-profile-image">';
$output .= $profile_image_render;
$output .= '</div>';
$output.= '<div class="member-name">';
$output .= '<a href="' . $single_page . '?member_id=' . $user_id . '">' . $user->display_name . '</a>';
$output .= '</div>';
/* Location: Show only if she opted to show this 2023-05-02 17:31:54 */
if ( $show_city ) {
$output.= '<div class="member-city-state">';
$output.= $city . ', ' . $state;
$output .= '</div>';
}
/* Instruments */
$output .= '<div class="instruments-played">';
$output .= '<span class="prompt">Instruments Played: </span>';
$output .= $instruments;
$output .= '</div>';
/* Bio */
$output .= '<div class="bio">';
$output .= 'Bio Word Count: ' . $bio_cos;
$output .= '</div>';
$output .= '</div>';
}
$output .= '</div>';
return $output;
}
add_shortcode('seba_pers_members_show', 'seba_pers_profile_display_all_shortcode');
get_user_meta() calls: You may note I use that native WordPress function for custom fields and do NOT use the corresponding ACF functions. Bill Erickson convinced me to do this in his 2014 blog post, Remove frontend dependency from ACF.Creating the Archive Page for Business Memberships
First, let’s see what the Business Members archive page looks like:
Screen Shot of Business Members Archive Page

Business Members Archive Page Contents
And here are the contents of that Business Members archive page (/personal-profiles/):
<p>Listing of all SEBA Members with active Business Memberships</p> <div>Filter the directory by <strong>type</strong> of Business Membership:</div> [biz_types] [seba_members_show]
Script for Generating Business Members Archive Page
And here’s the actual script that generates the Business Member Directory archive page via a shortcode:
- Line 3: Passes the ‘business’ role to the
seba_get_membersfunction. - Line 6: This is used on line 27 as a link to the single profile page.
- Line 7: Calls the
seba_get_membersfunction. - Line 10-13, 16: Tests whether the user has an active business membership.
- Line 22: Starts generating the output.
function seba_shortcode_members_display_all($atts) {
$atts = shortcode_atts( array(
'roles' => 'business',
), $atts, 'seba_members_show' );
$roles = $atts['roles'];
$single_page = '/business-profile/';
$users = seba_get_members($roles);
$output = '<div class="all-profiles-wrap business">';
foreach ( $users as $user ) {
$user_id = $user->ID;
$business_name = get_user_meta($user_id, 'business_name', true);
$business_type = get_user_meta($user_id, 'business_type', true);
$business_summary = get_user_meta($user_id, 'business_summary', true);
/* If we don't have all 3 of the above, then it's not an active profile */
$profile_is_active = ( $business_name && $business_type && $business_summary);
$profile_class = ( $profile_is_active ) ? 'profile-active' : 'profile-inactive' ;
if ( ! $profile_is_active ) {
//continue;
}
$output .= '<div class="profile-wrap ' . $profile_class . ' business-type-' . $business_type . '">';
$business_name_cos = ($business_name) ? $business_name : $user->display_name;
$output.= '<div class="business-name">';
$output .= '<a href="' . $single_page . '?member_id=' . $user_id . '">' . $business_name_cos . '</a>';
$output .= '</div>';
$output.= '<div class="member-name">';
$output.= '<span class="prompt">Member:</span> ' . $user->display_name;
$output .= '</div>';
$business_type_cos = ($business_type) ? ucwords($business_type) : 'No Business Type provided' ;
$output .= '<div class="business-type">';
$output .= '<span class="prompt">Business Type:</span> ' . $business_type_cos;
$output .= '</div>';
$business_summary_cos = ($business_summary) ? $business_summary : 'No Business Summary provided' ;
$output .= '<div class="profile-summary">';
$output .= $business_summary_cos;
$output .= '</div>';
$output .= '</div>';
}
$output .= '</div>';
return $output;
}
add_shortcode('seba_members_show', 'seba_shortcode_members_display_all');
Single Pages
Finally we come to the single pages!
Unlike the archive pages that used shortcodes for rendering the content, I’m using the the_content filter for the single pages. Both scripts have a conditional statement that aborts the mission (and returns the existing content) if the slug of the current page is not equal to the slug of the page used for single profiles.
If anyone wants explanations of any of this code, let me know in the comments.
Creating the Code for the Single Personal Member Profile
function seba_pers_profile_display_single($content) {
if ( ! is_page( 'personal-profile' ) ) return $content;
if ( ! isset( $_GET['member_id'] ) ) {
$content = '<p>No Member ID defined..</p>';
return $content;
}
$member_id = $_GET['member_id'];
if ( ! $this_member = get_user_by('ID', $member_id) ) {
$content = '<p>That member (ID of ' . $member_id . ') was not found.</p>';
return $content;
}
/* Assemble the stuff now! */
/* Get the member's options */
$include_in_directory = get_user_meta($member_id, 'directory_options_include_in_directory', true);
/* Show nothing if s/he opted out of directory. */
if ( ! $include_in_directory ) {
$content = '<p>This member has elected not to be included in the Member Directory.</p>';
return $content;
}
$show_account_email = get_user_meta($member_id, 'directory_options_show_account_email', true);
$show_preferred_email = get_user_meta($member_id, 'directory_options_show_preferred_email', true);
$show_telephone = get_user_meta($member_id, 'directory_options_show_telephone', true);
$show_address = get_user_meta($member_id, 'directory_options_show_address', true);
$show_city = get_user_meta($member_id, 'directory_options_show_city', true);
$name = $this_member->display_name;
$account_email = $this_member->user_email;
$telephone = get_user_meta($member_id, 'mepr_telephone', true);
$preferred_email = get_user_meta($member_id, 'preferred_email', true);
$bio = get_user_meta($member_id, 'description', true);
$instruments = get_user_meta($member_id, 'mepr_instruments_played', true);
$address_1 = get_user_meta($member_id, 'mepr-address-one', true);
$address_2 = get_user_meta($member_id, 'mepr-address-two', true);
$city = get_user_meta($member_id, 'mepr-address-city', true);
$state = get_user_meta($member_id, 'mepr-address-state', true);
$zip = get_user_meta($member_id, 'mepr-address-zip', true);
$country = get_user_meta($member_id, 'mepr-address-country', true);
/* Hide stuff according to options 2023-03-23 18:40:23, Thu */
$hide_word = 'NA';
$blank_word = '';
$account_email = ( $show_account_email) ? $account_email : $hide_word ;
$telephone = ( $show_telephone) ? $telephone : $hide_word ;
$preferred_email = ( $show_preferred_email ) ? $preferred_email : $hide_word ;
$address_1 = ( $show_address ) ? $address_1 : $hide_word ;
$address_2 = ( $show_address ) ? $address_2 : $hide_word ;
$city = ( $show_city ) ? $city : $hide_word ;
$state = ( $show_city ) ? $state : $hide_word ;
$zip = ( $show_city ) ? $zip : $hide_word ;
$country = ( $show_city ) ? $country : $hide_word ;
$gallery_images = seba_profile_gallery_get($member_id); // Returns gallery IDs
$size = 'thumbnail';
$content .= '<h2 class="member-name section">'. $name . '</h2>';
$content .= '<div class="single-profile-wrap personal compact">';
$content .= '<div class="basics">';
/* Profile Image */
$profile_image = get_user_meta($member_id, 'personal_profile_image', true);
// $the_size ='thumbnail';
$the_size ='medium';
$content .= '<div class="profile-image personal">';
if ( $profile_image ) {
$content .= wp_get_attachment_image($profile_image, $the_size);
} else {
$content .= "<p>No Profile Image Provided</p>";
}
$content .= '</div>';
/* Contact Info */
$content .= '<div class="contact-info section">';
/* Account Email */
$content .= '<div class="account-email item">';
if ( $show_account_email ) {
$content .= '<span class="prompt">Account Email:</span> <a href="mailto:' . $account_email . '">' . $account_email . '</a>';
} else {
$content .= '<span class="prompt">Account Email:</span> ' . $account_email;
}
$content .= '</div>';
/* Preferred Email */
$content .= '<div class="preferred-email item">';
if ( $show_preferred_email ) {
$content .= '<span class="prompt">Preferred Email:</span> <a href="mailto:' . $preferred_email . '">' . $preferred_email . '</a>';
} else {
$content .= '<span class="prompt">Preferred Email:</span> ' . $preferred_email;
}
$content .= '</div>';
/* Telephone */
$content .= '<div class="telephone item">';
$content .= '<span class="prompt">Telephone:</span> ' . $telephone;
$content .= '</div>';
$content .= '</div>';
/* Address */
$content .= '<div class="address-wrap section">';
$content .= '<div class="prompt">Address:</div>';
if ( $show_address ) {
$content .= '<div class="address-1 item">' . $address_1 . '</div>';
$content .= '<div class="address-2 item">' . $address_2 . '</div>';
} else {
$content .= '<div>(Street Address hidden)</div>';
}
if ( $show_city ) {
$content .= '<div class="city-state-zip item">';
$content .= $city . ', ' . $state . ' ' . $zip;
$content .= '</div>';
$content .= '<div class="country item">' . $country . '</div>';
} else {
$content .= '<div>(City hidden)</div>';
}
$content .= '</div>';
/* Instruments */
$content .= '<div class="instruments-played section">';
if ( $instruments ) {
$content .= '<span class="prompt">Instruments I Play:</span><br />' . $instruments;
} else {
$content .= 'No Instruments provided';
}
$content .= '</div>';
$content .= '</div><!-- .basics -->';
/* Bio */
$content .= '<div class="pers-bio section">';
$content .= '<h3>About Me</h3>';
if ( $bio ) {
$content .= nl2br($bio);
} else {
$content .= 'No Bio provided';
}
$content .= '</div>';
/* Gallery */
if ( $gallery_images ) {
$content .= '<div class="gallery section">';
$content .= '<h3>' . $name . ' Gallery Images</h3>';
$content .= '<p>Click on thumbnail to enlarge</p>';
$content .= '<div class="gallery-images">';
foreach ( $gallery_images as $image_id ) {
$content .= '<div class="gallery-image">';
/* Here's the clickable thumb: */
$content .= '<a class="foobox" rel="gallery" href="' . wp_get_attachment_url($image_id) . '">' . wp_get_attachment_image($image_id, $size) . '</a>';
$content .= '</div>';
}
$content .= '</div>';
$content .= '</div>';
}
$content .= '</div><!-- .wrap -->';
return $content;
}
add_filter('the_content', 'seba_pers_profile_display_single');
Creating the Code for the Single Business Member Profile
function seba_business_member_display_single($content) {
if ( ! is_page( 'business-profile' ) ) return $content;
if ( ! isset( $_GET['member_id'] ) ) {
$content = '<p>No Member ID defined..</p>';
return $content;
}
$member_id = $_GET['member_id'];
if ( ! $this_member = get_user_by('ID', $member_id) ) {
$content = '<p>That member (ID of ' . $member_id . ') was not found.</p>';
return $content;
}
/* Assemble the stuff now! */
$member_name = $this_member->display_name;
$business_name = get_user_meta($member_id, 'business_name', true);
$business_summary = get_user_meta($member_id, 'business_summary', true);
$about_my_business = nl2br(get_user_meta($member_id, 'about_my_business', true));
$business_type = get_user_meta($member_id, 'business_type', true);
$business_type_label = ( !empty($business_type) ) ? ucwords($business_type) : 'Not Provided';
/* 2023-02-08 15:15:19: Get those callery images, if they exist */
$gallery_images = seba_profile_gallery_get($member_id); // Returns gallery IDs
$size = 'thumbnail';
/* Team Members, assemble */
$the_team = get_user_meta($member_id, 'the_team', true); // To get the count
$team_cos = ''; // The team display, init
if ( $the_team ) {
$team_cos .= '<div class="the-team section">';
$team_cos .= '<h3>' . $business_name . ': The Team</h3>';
$team_cos .= '<table class="team-listing">';
$team_cos .= '<thead>';
$team_cos .= '<tr>';
$team_cos .= '<th>Name</th>';
$team_cos .= '<th>Role</th>';
$team_cos .= '<th>Telephone</th>';
$team_cos .= '</tr>';
$team_cos .= '</thead>';
$team_cos .= '<tbody>';
for ( $i=0; $i < $the_team; $i++ ) {
/* Assemble */
$name = get_user_meta($member_id, 'the_team_' . $i . '_team_member_name', true);
$about = get_user_meta($member_id, 'the_team_' . $i . '_team_member_about', true);
$email = get_user_meta($member_id, 'the_team_' . $i . '_team_member_email', true);
$tele = get_user_meta($member_id, 'the_team_' . $i . '_team_member_telephone', true);
$tele_cos = ( !empty( $tele ) ) ? $tele : '';
$email_cos = (!empty($email) ) ? '<a href="mailto:' . $email . '">' . $email . '</a>' : '';
$team_cos .= '<tr>';
$team_cos .= '<td class="has-data-label name" data-label="Name">' . $name . '<br /><span class="has-data-label" data-label="Email">' . $email_cos . '</span></td>';
$team_cos .= '<td class="has-data-label role" data-label="Role">' . $about . '</td>';
$team_cos .= '<td class="has-data-label tele" data-label="Tele">' . $tele_cos . '</td>';
$team_cos .= '</tr>';
}
$team_cos .= '</tbody>';
$team_cos .= '</table>';
$team_cos .= '</div>';
}
/* Assemble Address */
$business_address = get_user_meta($member_id, 'business_address', true);
$embed = get_user_meta($member_id, 'embed', true);
/* Telephone - 2024-06-05 14:46:24 */
$telephone = get_user_meta($member_id, 'mepr_telephone', true);
$telephone = ( $telephone ) ? $telephone : 'NA'; // If blank
$content = ''; // Initialize
$content .= '<div class="single-profile-wrap business business-type-' . $business_type . '">';
$business_name_cos = ( $business_name ) ? $business_name : 'No Business Name Provided';
$content .= '<h2 class="business-name">'. $business_name_cos . '</h2>';
/* Business Type */
$content .= '<div class="business-type">';
$content .= '<span class="prompt">Business Type: </span>';
$content .= $business_type_label;
$content .= '</div>';
/* Business Profile Image */
$bpi = get_user_meta($member_id, 'business_profile_image', true);
$the_size ='full';
if ( $bpi ) {
$content .= '<div class="business-profile-image">';
$content .= wp_get_attachment_image($bpi, $the_size);
$content .= '</div>';
} else {
$content .= "<p>No Business Profile Image Provided)</p>";
}
/* Member Name */
$content .= '<div class="member-name section">';
$content .= '<h3>SEBA Member</h3>';
$content .= $member_name;
$content .= '</div>';
/* Mailing Address */
$content .= '<div class="mailing-address section">';
$content .= '<h3>Address/Telephone</h3>';
if( $business_address ) {
$content .= '<div class="business-address">' . nl2br($business_address) . '</div>';
} else {
$content .= '<div class="business-address none">No business address provided.</div>';
}
/* Now tele */
$content .= '<div class="business-tele">Telephone: ' . $telephone . '</div>';
$content .= '</div>';
/* Business Summary */
$content .= '<div class="business-summary section">';
$content .= '<h3>Business Summary</h3>';
if ( $business_summary ) {
$content .= $business_summary;
} else {
$content .= 'No business summary provided';
}
$content .= '</div>';
/* Gallery */
if ( $gallery_images ) {
$content .= '<div class="gallery section">';
$content .= '<h3>' . $business_name . ' Gallery Images</h3>';
$content .= '<p>Click on thumbnail to enlarge</p>';
$content .= '<div class="gallery-images">';
foreach ( $gallery_images as $image_id ) {
$content .= '<div class="gallery-image">';
/* Here's the clickable thumb: */
$content .= '<a class="foobox" rel="gallery" href="' . wp_get_attachment_url($image_id) . '">' . wp_get_attachment_image($image_id, $size) . '</a>';
$content .= '</div>';
}
$content .= '</div>';
$content .= '</div>';
}
/* About My Business */
$content .= '<div class="about-my-business section">';
$content .= '<h3>Full Business Profile</h3>';
if ( $about_my_business ) {
$content .= $about_my_business;
} else {
$content .= 'No business profile provided';
}
$content .= '</div>';
/* Websites */
$websites = get_user_meta($member_id, 'websites', true); // To get the count
if ( $websites ) {
$content .= '<div class="websites section">';
$content .= '<h3>Websites</h3>';
$content .= '<table>';
$content .= '<thead>';
$content .= '<tr>';
$content .= '<th>Website URL</th>';
$content .= '<th>Website Name</th>';
$content .= '</tr>';
$content .= '</thead>';
$content .= '<tbody>';
for ( $i=0; $i < $websites; $i++ ) {
/* Assemble */
$url = get_user_meta($member_id, 'websites_' . $i . '_website_url', true);
$name = get_user_meta($member_id, 'websites_' . $i . '_website_text', true);
$content .= '<tr>';
$content .= '<td><a target="_blank" href="' . $url . '">' . $url . '</a></td>';
$content .= '<td>' . $name . '</td>';
$content .= '</tr>';
}
$content .= '</tbody>';
$content .= '</table>';
$content .= '</div>';
}
/* Team table here */
$content .= $team_cos;
/* Embed */
if ( $embed ) {
$content .= '<div class="embed section">';
$content .= '<h3>Featured Video</h3>';
$content .= wp_oembed_get($embed);
$content .= '</div>';
}
$content .= '</div>';
return $content;
}
add_filter('the_content', 'seba_business_member_display_single');
Let’s Recap
Here’s my attempt at a concise recap of the various parts that went into my custom member directories.
- MemberPress
I installed and configured MemberPress for creating memberships and restricting access to content. - Custom Fields
Using Advanced Custom Fields Pro, I created a set of custom fields for each membership. The pro version was necessary because I used two pro features: Repeater and Gallery fields. - Custom User Roles
I created custom user roles (using the Members Role Editor plugin) so that I had a way to associate the custom fields to the appropriate front end forms. - Front End Forms
I created the front end forms using code “borrowed” from a colleague. - Archive Templates
Custom code for rendering the archive pages (using shortcodes) - Single Post Tempates
Custom code for rendering the single pages (using thethe_contentWordPress filter hook)
Let me know in the comments if you found this article helpful, have questions or comments, want to recommend other techniques or solutions, or just want to say hi.
Like what you see? Share with others and join my mailing list. No long-term commitment, unsubscribe any time.

Leave a Reply