There are a lot of ways of getting user content in WordPress. Plugins are ideal for creating that content and provide that functionality. Themes are better at presenting that content.

One of the many things we often see in theme reviews is what are known as pseudo post types. I love Justin’s post in regards to content and post types so do give a read to understand a little more. The following paragraphs will focus more on code and methods of not only getting content from the WordPress database but also on how to make use of that data for use in the customizer and on the front-end of a site.

The content

First we need to think about what we want to show on the page, right? For this example will focus on a single post. What’s super cool is that this can be applied to custom post types as well and not just the standard post. The function we will use for this is get_post_meta() and the developer documentation is a good starting point: https://developer.wordpress.org/reference/functions/get_post_meta/

What matters here is the returning value from that function:

(mixed) Will be an array if $single is false. Will be value of meta data field if $single is true.

That’s great, but what does it mean, right? Let’s say what we add a custom field to a post. Perhaps a subtitle. Depending on what you set your custom field to, you would use:

if ( $subtitle = get_post_meta( get_the_ID(), 'subtitle', true ) ) {
	printf( '<span class="subtitle">%s</span>', $subtitle );
}

That’s much easier than creating a custom meta box that would be lost when the theme is changed, right? I know you may be asking, but what about other post types? Well, we can use get_metadata() for that. The developer docs help here as well. All we do is change a small thing and we can use it in an archive style.

if ( $subtitle = get_metadata( get_post_type(), get_the_ID(), 'subtitle', true ) ) {
	printf( '<span class="subtitle">%s</span>', $subtitle );
}

Great!

Other types

So, with that you have a basic starting point of core’s built-in capabilities. Metadata is super useful and easy to get. Best part is that if that value doesn’t exist, we don’t output anything! No extra markup or code to worry about.

Now, there are tons of plugins out there that create all sorts of data and content for users. Let’s take a quick look at one that is pretty popular: testimonials.

What’s super cool is that by using a few function_exists() we can check if a plugin is active or not. The registration method may differ from plugin to plugin but the basic thing is to use the right function to check. There are a few functions we can use:

  • function_exists()
  • method_exists()
  • class_exists()
  • interface_exists()

That’s super cool and even core has one that can be useful as well:

  • post_type_exists()

Phenomenal if you want to check for a testimonial post type, or a portfolio, or a form, event, you name it. For this example I’ll use the Easy Testimonials plugin to test.

if ( class_exists( 'easyTestimonials' ) && post_type_exists( 'testimonial' ) ) {
	$testimonials = new WP_Query( array( 'post_type' => 'testimonial', 'posts_per_page' => 3 ) );
	if ( $testimonials->have_posts() ) {
		while ( $testimonials->have_posts() ) {
			$testimonials->the_post();
			the_title( '<h3 class="testimonial-title">', '</h3>' );
		}
	}
	wp_reset_postdata();
}

Super quick example. You can see what it is doing at the beginning. We are checking to see if the class easyTestimonials exists as well as the post type. If the functions return true then we can run our query. What’s nice is that now we are in control of how to present that content however we desire.

But what about the customizer?

I mentioned this a little earlier and we can use this as callback for a control. Yes, conditionally show a control based on what is active and available. Super awesome, right?

In this example we will use the custom fields suite plugin for a page customizer control. This is going to be a unique example in that we are going to check if the plugin is active and if a field is populated on a page. Now, it may not be the best example but it will give you an idea of how it works.

add_action( 'customize_register', function( $wp_customize ) {
	$wp_customize->add_setting( 'hero-location',
		array(
			'default' => 'above',
			'sanitize_callback' => 'jmc_sani_location'
		) );
	$wp_customize->add_control( 'hero-location-control',
		array(
			'settings'        => 'hero-location',
			'label'           => __( 'Hero Image Location', 'text-domain' ),
			'description'     => __( 'Where will the hero image show? Above or below the secondary menu location', 'text-domain'),
			'section'         => 'layout',
			'type'            => 'select',
			'active_callback' => 'jmc_check_field',
			'choices' => array(
				'above' => __( 'Above', 'text-domain' ),
				'below' => __( 'Below', 'text-domain' )
			)
		) );
}, 10 );
function jmc_check_field(){
	// bail if it's not a page
	if ( !is_page() ) {
		return false;
	}

	// check for the function and value
	if ( function_exists( 'CFS' ) && CFS()->get( 'hero-image' ) ) {
		return true;
	}

	// if nothing we won't show the control
	return false;
}

Let’s break it down a little bit. The part that really matters is:

'active_callback' => 'jmc_check_field'

This callback is what will handle our logic. This ultimately determines if that control will show or not. We want to begin with the assumption that it will not show so we always return false at the end. What’s super cool is that this can be done with sections as well. This next example is nice because we check for the available post types and can use a new WP_Query on the theme to get the content.

add_action( 'customize_register', 'jmc_customize' );
function jmc_customize( $wp_customize ) {
	// get list of post types
	$post_types = get_post_types( array( 'public' => true, '_builtin' => false ), 'object' );

	if ( ! empty( $post_types ) ) {
		$types = array();
		foreach( $post_types as $type ) {
			$types[$type->name] = $type->label;
		}

		$wp_customize->add_section( 'section-one',
			array(
				'title'           => __( 'Front Page sections', 'text-domain' ),
				'description'     => __( 'Choose what else shows on the front', 'text-domain' ),
				'active_callback' => 'jmc_haz_post_types'
			) );

		$wp_customize->add_setting( 'section-one-type',
			array(
				'default'           => '',
				'sanitize_callback' => 'jmc_post_validate',
				)
		);

		$wp_customize->add_control( 'section-one-control',
			array(
				'label'       => __( 'The post type for section one', 'text-domain' ),
				'description' => __( 'Choose what post type you would like to show', 'text-domain' ),
				'settings'    => 'section-one-type',
				'type'        => 'select',
				'choices'     => $types,
				'section'     => 'section-one'
			)
		);
	}
}

function jmc_haz_post_types() {
	$types = get_post_types( array( 'public' => true, '_builtin' => false ), 'object' )
	return empty( $types );
}

function jmc_post_validate( $value, $setting ){
	$post_types = get_post_types( array( 'public' => true, '_builtin' => false ), 'object' );
	if ( array_key_exists( $value, $post_types ) ){
		return $value;
	} else {
		return $setting->default;
	}
}

Then on the front-end we could do something like:

if ( ! get_theme_mod( 'section-one-type' ) === false ) {
	$type = new WP_Query(
	array( 'post_type' => get_theme_mod( 'section-one-type' ),
		'posts_per_page' => 3
		)
	);

	if ( $type->have_posts() ):
		while( $type->have_posts() ): $type->the_post();
		the_title( '<h3>', '</h3>' );
		endwhile;
	endif;
	wp_reset_postdata();
}

The thing to remember is that content should remain when the theme is switched. There should be no reason for a user to have to input the same thing over and over again on every theme switch – after all they are after the design and not the content of the theme. A good practice to keep in mind when creating your options is:

Do I give them decision A or decision B?