Simple Member Listing Custom Post Type

Overview

The customer needed to display member logos on the site, but wanted the logos to be listed dynamically. I searched a bit for a plugin to  do this for me (them) as I believe in using something available versus a custom build.

COVESA - Current Members

Completed Members Functionality

Unfortunately, their requirements led me to the conclusion that it was best to build the functionality myself.

The narrative below provides some insight into my thoughts and the code I created to make this a reality.

Code Snippet for Functionality

Instead of building a plugin, which is a lot harder for the customer or a follow-on developer to maintain, I simply added the code to Code Snippets Pro.  I typically use the free version of this plugin as I normally only need to add PHP code (and not JavaScript or CSS). I’ve also begun using this for code snippets more than the child theme (and yes, I have a child them–everyone should) because it simply is more convenient to add the code and deactivate it.

This code contains everything needed for the functionality and layout seen above.  It has the MySQL query, Styles, JavaScript, and HTML.

Click to See Custom PHP Code

// Register the shortcode
function current_members_shortcode() {
  // Your HTML content
global $post, $wpdb;
	//$fjoined = "pm3.meta_value";
	$fjoined = "substring(pm3.meta_value,1,4)";
	if (isset($_REQUEST['viewBy'])) {
    $viewBy = $_REQUEST['viewBy'];
    // Now you can use $viewBy to filter or sort your data accordingly
} else {
    $viewBy = 'default_value'; // Set a default value if 'viewBy' is not set
    // Or handle the scenario when 'viewBy' is not provided
}
switch ($viewBy) {
	case 'level':
    $qsort = "levelorder";
    break;
	case 'date':
    $qsort = "pm3.meta_value";
	$fjoined = "substring(pm3.meta_value,1,4)";
    break;
    default:
    $qsort = "industryorder";
}
// The SQL query
$sql = "
SELECT 
    p.ID, 
    p.post_title AS Member, 
    case
    when pm1.meta_value = 'OEM' then 'OEMS'
    when pm1.meta_value = 'Tier 1' then 'First Tiers'
    when pm1.meta_value = 'Other' then 'Others'
    when pm1.meta_value = 'Silicon or Semiconductor' then 'Silicon or Semiconductors'
    else pm1.meta_value end AS industry,
    case
    when pm1.meta_value = 'OEM' then 1
    when pm1.meta_value = 'Tier 1' then 2
    when pm1.meta_value = 'OSV, Middleware, Hardware & Services Suppliers' then 3
    when pm1.meta_value = 'Silicon or Semiconductor' then 4
    when pm1.meta_value = 'Other' then 5
    else 0 end AS industryorder,
    case
    when pm2.meta_value = 'Charter' then 'Founding Charter and Charter'
    else pm2.meta_value end AS level,
    case
    when pm2.meta_value = 'Charter' then 1
    when pm2.meta_value = 'Core' then 2
    when pm2.meta_value = 'Associate' then 3
    when pm2.meta_value = 'Start-up Associate' then 4
    when pm2.meta_value = 'Start-up Plus Associate' then 5
    else 0 end AS levelorder,
    substring(pm3.meta_value,1,4) AS joined,
    pm4.meta_value AS url,
    wp.guid AS logo
FROM 
    wp_posts AS p
LEFT JOIN 
    wp_postmeta AS pm1 ON p.ID = pm1.post_id AND pm1.meta_key = 'industry'
LEFT JOIN 
    wp_postmeta AS pm2 ON p.ID = pm2.post_id AND pm2.meta_key = 'level'
LEFT JOIN 
    wp_postmeta AS pm3 ON p.ID = pm3.post_id AND pm3.meta_key = 'joined'
LEFT JOIN 
    wp_postmeta AS pm4 ON p.ID = pm4.post_id AND pm4.meta_key = 'url'
LEFT JOIN 
    wp_postmeta AS pm5 ON p.ID = pm5.post_id AND pm5.meta_key = 'logo'
LEFT JOIN 
    wp_posts AS wp ON wp.ID = pm5.meta_value
WHERE 
    p.post_status = 'publish' 
    AND p.post_type = 'member'
    and wp.guid is not null
ORDER BY 
    {$qsort}, Member;
";

	// Execute the query
$results = $wpdb->get_results($sql, OBJECT);
//$results = $wpdb->get_results($sql, ARRAY_A);
//print_r($results); exit;
if (!empty($results)) {
// Variable to keep track of the current category
    $currentCategory = null;
    $colCount = 0;
	$rows = null;
	$level_selected = $date_selected  = $industry_selected  = "";
    foreach ($results as $member) {
      switch ($viewBy) {
	    case 'level':
          $hvalue = $member->level;
		  $level_selected = "selected";
        break;
	    case 'date':
          $hvalue = $member->joined;
		  $date_selected = "selected";
        break;
        default:
          $hvalue = $member->industry;
		  $industry_selected = "selected";
    }
    // Check if the category has changed or if column count has reached 8
    if ($currentCategory !== $hvalue || $colCount >= 8) {
        // If not the first iteration, close the previous row
        if ($currentCategory !== null) {
            $rows .= '</div>'; // Close the previous row
        }
		if ($currentCategory !== $hvalue) {
		  if ($currentCategory !== null) {
			$rows .=  '</div>';
		  }
		  //$heading = (($_REQUEST['viewBy'])=="date") ? substr : $hvalue;
		  $rows .= '<div><h4 class="heading">'. $hvalue.'</h4></div>';
		  $rows .=  '<div style="background-color:white; padding: 20px; margin-bottom: 50px; border-radius: 10px;">';
		}

        // Start a new row and reset column count
        $rows .=  '<div class="row">';
        $colCount = 0;
    }

    // Display the logo image
    $rows .=  '<div class="image-container">';
    $rows .=  '<a href="'.$member->url.'"><img src="' . htmlspecialchars($member->logo) . '" alt="'.$member->Member.'" title="'.$member->Member.', joined in '.$member->joined.'"></a>';

		$rows .=  '</div>';

    // Update the current category and increment column count
    $currentCategory = $hvalue;
    $colCount++;

    // If this is the last item, close the div
    if (end($results) === $member && $colCount <= 8) {
        $rows .=  '</div>'; // Close the last row if needed
    }

		}
// Check if the last row was closed in the loop
if ($colCount > 0) {
    $rows .=  '</div>'; // Ensure the row is closed
}	

	/*
        echo "Member ID: " . $member->ID . "<br>";
        echo "Member Name: " . $member->Member . "<br>";
        echo "Industry: " . $member->industry . "<br>";
        echo "Website URL: " . $member->url . "<br>";
        echo "Logo URL: " . $member->logo . "<br><br>";
		*/
		
    //
    
} else {
    echo "No members found.";
}
$output = <<<ID
<style>
  .container {
    width: 100%;
    margin: auto;
    padding: 20px;
    box-sizing: border-box;
  }

  .heading {
    text-align: center;
	font-size: 29px;
	color: #0C71C3!important;
	font-weight: 800;
    margin-bottom: 20px;
  }

  .row {
    display: flex;
    flex-wrap: wrap;
    gap: 5%;
    align-items: center;
	justify-content: center;
  }

  .image-container {
    width: 8%; /* Fixed width for the container */
    height: 100px; /* Fixed height for the container */
    display: flex;
    align-items: center; /* Centers the image vertically */
    justify-content: center; /* Centers the image horizontally */
    overflow: hidden; /* Ensures no part of the image spills out of the container */
  }
  
  .image-container a {
    display: flex; /* Ensures the anchor tag also uses flexbox */
    align-items: center; /* Centers the content vertically */
    justify-content: center; /* Centers the content horizontally */
    width: 100%; /* Full width of the container */
    height: 100%; /* Full height of the container */
    text-decoration: none; /* Optional: Removes underline from links */
  }

  .image-container img {
    max-width: 100%; /* Image can be as wide as the container but no wider */
    max-height: 100%; /* Image can be as tall as the container but no taller */
    height: auto; /* Maintain aspect ratio for the height */
    width: auto; /* Maintain aspect ratio for the width */
  }

  /* Responsive adjustments */
  @media (max-width: 980px) {
    .row {
      gap: 5%;
	}
    .image-container {
     // width: 5%;
      height: 90px;
    }
	.heading {
	  font-size: 26px;
    }
	.view-by-text {
      font-size: 17px;
    }
  }

  @media (max-width: 768px) {
    .row {
      gap: 8%;
	}
    .image-container {
      width: 15%;
      height: 80px;
    }
    .image-container {
     // width: 120px;
     // height: 90px;
    }
    .heading {
	  font-size: 24px;
    }
    .view-by-text {
      font-size: 16px;
    }
  }

  @media (max-width: 480px) {
    .image-container {
   //   width: 100px;
  //    height: 75px;
    }
  }

.form-container {
    display: flex;
    flex-direction: column; /* Stack the children vertically */
    align-items: center; /* Center children horizontally */
    justify-content: center; /* Center children vertically */
    text-align: center; /* Ensures text is centered */
}

.view-by-text {
    margin-bottom: 20px; /* Adjust the space between the text and the form as needed */
    font-family: 'Open Sans', Arial, sans-serif;
    font-size: 18px;
	font-weight: 600;
    color: #1d1d1d; /* Adjust the color as needed */
}

#viewByForm {
    display: flex;
    align-items: center;
    background: #FFFFFF; /* Example background color */
    box-shadow: 0px 3px 6px #00000029; /* Example shadow */
    border-radius: 5px; /* Example border radius */
    padding: 8px 16px; /* Example padding */
	width: 300px;
	margin: auto;
}

#viewBy {
    appearance: none; /* Removes default styling of select box */
    -webkit-appearance: none; /* Also for Safari */
    -moz-appearance: none; /* Also for Firefox */
    background: url('https://covesa.global/wp-content/uploads/2024/04/down-arrow.png') no-repeat right; /* Add your dropdown arrow image */
    background-size: 12px; /* Example size of the arrow image */
    width: 100%; /* Adjust width as needed */
}

</style>
<script>
document.getElementById('viewBy').addEventListener('change', function() {
    this.form.submit();
});
</script>
<div class="form-container">
<div class="view-by-text">View By</div>
<form id="viewByForm" action="" method="POST">
    <select id="viewBy" name="viewBy" onchange="this.form.submit()" style="border: 0px solid #CCCCCC; border-radius: 20px; padding: 8px 16px; outline: none; font-family: 'Open Sans', Arial, sans-serif; font-size: 18px; cursor: pointer;">
        <option value="industry" {$industry_selected}>Industry</option>
        <option value="level" {$level_selected}>Member Level</option>
     <!--   <option value="date" {$date_selected}>Join Date</option> -->
    </select>
</form>
</div>
<div class="container">

{$rows}

</div>
ID;
return $output;
}

add_shortcode('current-members', 'current_members_shortcode');

Creating the Post Type

To build the custom post type, I turned to the ACF (Advanced Custom Fields) plugin.  This plugin is easy to use leading you to create the post type, then the filed groups, and finally the fields.

I’ve been working in WordPress for some time now, but in my early days with the platform I fought the way WordPress did things as I have a standard PHP/MySQL background. Instead of developing using custom post types, I build plugins that created my own interface with the WP backend and many times I also editied the theme templates directly.

I still encounter customer sites where a previous developer solved the customer’s needs the way I used to.  No more, I’ve been converted.  I only create unique solutions when there is not a plugin already available that does at least 90% of the work. Of course, I’ve also found it a challenge to convince the customer to accept the 90% functionality!

View PHP Code for Members post type

add_action( 'acf/include_fields', function() {
	if ( ! function_exists( 'acf_add_local_field_group' ) ) {
		return;
	}

	acf_add_local_field_group( array(
	'key' => 'group_660c6f20cb760',
	'title' => 'Member Fields',
	'fields' => array(
		array(
			'key' => 'field_660c6f20c3181',
			'label' => 'Member Name',
			'name' => 'member_name',
			'aria-label' => '',
			'type' => 'text',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'default_value' => '',
			'maxlength' => '',
			'placeholder' => '',
			'prepend' => '',
			'append' => '',
		),
		array(
			'key' => 'field_660c6f72664d4',
			'label' => 'Logo',
			'name' => 'logo',
			'aria-label' => '',
			'type' => 'image',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'return_format' => 'array',
			'library' => 'all',
			'min_width' => '',
			'min_height' => '',
			'min_size' => '',
			'max_width' => '',
			'max_height' => '',
			'max_size' => '',
			'mime_types' => '',
			'preview_size' => 'medium',
		),
		array(
			'key' => 'field_660c70187f0cb',
			'label' => 'Level',
			'name' => 'level',
			'aria-label' => '',
			'type' => 'select',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'choices' => array(
				'Associate' => 'Associate',
				'Charter' => 'Charter',
				'Core' => 'Core',
				'Core 2022' => 'Core 2022',
				'Start-up Associate' => 'Start-up Associate',
				'Start-up Plus Associate' => 'Start-up Plus Associate',
			),
			'default_value' => 'Associate',
			'return_format' => 'value',
			'multiple' => 0,
			'allow_null' => 0,
			'ui' => 0,
			'ajax' => 0,
			'placeholder' => '',
		),
		array(
			'key' => 'field_660c7a2c5352f',
			'label' => 'Industry',
			'name' => 'industry',
			'aria-label' => '',
			'type' => 'select',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'choices' => array(
				'OEM' => 'OEM',
				'OSV, Middleware, Hardware & Services Suppliers' => 'OSV, Middleware, Hardware & Services Suppliers',
				'Silicon or Semiconductor' => 'Silicon or Semiconductor',
				'Tier 1' => 'Tier 1',
				'Other' => 'Other',
			),
			'default_value' => 'OSV, Middleware, Hardware & Services Suppliers',
			'return_format' => 'value',
			'multiple' => 0,
			'allow_null' => 0,
			'ui' => 0,
			'ajax' => 0,
			'placeholder' => '',
		),
		array(
			'key' => 'field_660c7a79c27ea',
			'label' => 'Joined',
			'name' => 'joined',
			'aria-label' => '',
			'type' => 'date_picker',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'display_format' => 'm/d/Y',
			'return_format' => 'd/m/Y',
			'first_day' => 0,
		),
		array(
			'key' => 'field_660f80ec3aad5',
			'label' => 'URL',
			'name' => 'url',
			'aria-label' => '',
			'type' => 'url',
			'instructions' => '',
			'required' => 0,
			'conditional_logic' => 0,
			'wrapper' => array(
				'width' => '',
				'class' => '',
				'id' => '',
			),
			'default_value' => '',
			'placeholder' => '',
		),
	),
	'location' => array(
		array(
			array(
				'param' => 'post_type',
				'operator' => '==',
				'value' => 'member',
			),
		),
	),
	'menu_order' => 0,
	'position' => 'normal',
	'style' => 'default',
	'label_placement' => 'top',
	'instruction_placement' => 'label',
	'hide_on_screen' => '',
	'active' => true,
	'description' => '',
	'show_in_rest' => 0,
) );
} );

add_action( 'init', function() {
	register_post_type( 'member', array(
	'labels' => array(
		'name' => 'Members',
		'singular_name' => 'Member',
		'menu_name' => 'Members',
		'all_items' => 'All Members',
		'edit_item' => 'Edit Member',
		'view_item' => 'View Member',
		'view_items' => 'View Members',
		'add_new_item' => 'Add New Member',
		'add_new' => 'Add New Member',
		'new_item' => 'New Member',
		'parent_item_colon' => 'Parent Member:',
		'search_items' => 'Search Members',
		'not_found' => 'No members found',
		'not_found_in_trash' => 'No members found in Trash',
		'archives' => 'Member Archives',
		'attributes' => 'Member Attributes',
		'insert_into_item' => 'Insert into member',
		'uploaded_to_this_item' => 'Uploaded to this member',
		'filter_items_list' => 'Filter members list',
		'filter_by_date' => 'Filter members by date',
		'items_list_navigation' => 'Members list navigation',
		'items_list' => 'Members list',
		'item_published' => 'Member published.',
		'item_published_privately' => 'Member published privately.',
		'item_reverted_to_draft' => 'Member reverted to draft.',
		'item_scheduled' => 'Member scheduled.',
		'item_updated' => 'Member updated.',
		'item_link' => 'Member Link',
		'item_link_description' => 'A link to a member.',
	),
	'public' => true,
	'show_in_rest' => true,
	'menu_icon' => 'dashicons-admin-post',
	'supports' => array(
		0 => 'title',
		1 => 'editor',
		2 => 'thumbnail',
	),
	'taxonomies' => array(
		0 => 'category',
		1 => 'post_tag',
	),
	'delete_with_user' => false,
) );
} );

Assigning Editor Role Permissions

Once I completed the first phase of site development, I was asked by the customer to give access to their staff to edit the text, create new blog and project posts, and update the Members post.

Naturally, I doled out Editor role privileges to whoever asked. What I learned; however, was that the new custom post type I created did not naturally provide all the permissions needed for someone maintaining the Members posts.

That caused me to researchjust what was needed to make this happen.  The code snippet I added is provided below.

View PHP Code for Members post Editor Permissions

function add_member_caps_to_editor() {
    $role = get_role('editor');

    $role->add_cap('edit_member');
    $role->add_cap('read_member');
    $role->add_cap('delete_member');
    $role->add_cap('edit_members');
    $role->add_cap('edit_others_members');
    $role->add_cap('publish_members');
    $role->add_cap('read_private_members');
    $role->add_cap('delete_members');
    $role->add_cap('delete_private_members');
    $role->add_cap('delete_published_members');
    $role->add_cap('delete_others_members');
    $role->add_cap('edit_private_members');
    $role->add_cap('edit_published_members');
}
add_action('admin_init', 'add_member_caps_to_editor');

Related Links

Customer Website: COVESA Global

Site Designer: Colotera Creative

0 Comments

Submit a Comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Project Search

Project Categories

Follow Us

Feel free to follow us on social media for the latest news and more inspiration.

Related Content