0

A client of mine wants the ability to create small blocks of content (call them "pods") and (re)use them in various pages across the site. So far, this is achieved by creating a custom post type [fig. 1] and calling pages generated therein via shortcode [fig. 2] into the "container" page [fig. 3].

The problem is that Search only returns hits within those "pods", never the "container" page. That makes sense given the content isn't built within the "container" page, but I'd prefer search showed results for the "container" page ONLY. I've searched for solutions thinking my issue couldn't be new, but to no avail. Is this possible or am I barking up the wrong tree?

fig. 1 - Custom post type

function create_content_pod_post_type() {
  register_post_type( 'content_pod',
    array(
      'labels' => array(
          'name' => __( 'Content Pods' ),
          'singular_name' => __( 'Content Pod' ),
          'menu_name' => __( 'Content Pods' ),
          'name_admin_bar' => __( 'Pod' ),
      ),
      'public' => true,
      'publicly_queryable' => true,
      'exclude_from_search' => true,
      'has_archive' => true,
      'menu_position' => 20,
      'capability_type' => 'page'
    )
  );
}
add_action( 'init', 'create_content_pod_post_type' );

fig. 2 - Shortcode function for displaying "pod" content

function get_pod_content( $atts ) {
    $pod_id = $atts[ 'id' ];
    $args = array( 'page_id' => $pod_id, 'post_type' => 'content_pod' );
    $custom_query = new WP_Query( $args );

    if($custom_query->have_posts()) {
        while( $custom_query->have_posts() ) {
            $custom_query->the_post();
            echo '<div class="content-pod">' . get_the_content() . '</div>';
        }     
    }
    wp_reset_postdata();
}
add_shortcode( 'pod', 'get_pod_content' );

fig. 3 - Example shortcode macro in "container" page

[pod id="135"]
1

There is a little underknown field in posts database called post_content_filtered. It isn't used by core (as far as I remember) and sometimes plugins use it to store alternate representation of content (which is more or less what it is intended for).

So in for your use case my idea would be to store content with shortcodes rendered into that field and adjust search SQL to use it for your "containers" instead of main content field.

But really there are multiple ways to approach this, just first thing that came into my head, by no means staple way to do it.

  • I never knew of that field. Fantastic idea! I'll give it a go and report back with results. Thank you! – Gabe Oct 28 '14 at 22:01
-1

I wanted to follow up and mention that between Rarst's suggestion (WP now saves to post_content_filtered any time a new "container" page is created or updated) and Relevanssi's ability to include wp_posts columns in search results, I was able to achieve the desired results. Thank you so much, Rarst!

Edit: To kaiser's point, my answer does nothing to help anyone in a similar situation. So, to add clarity, here's how it was resolved.

First, to help illustrate how a page would come together in this scenario, here's a little visual aid (below). I should note that the site also employs a plugin that enables drag and drop page layout. The challenge this presents comes into play later.

The following filter was added to functions.php and is triggered when a page is created or updated. It first checks to see if the page is a "container" page by the presence of the "pod" shortcode*, then grabs the page-specific content and strips out shortcodes generated by the page layout plugin. Next, it makes a list of the "pod" shortcodes, extracts the content from each, and adds it to the page-specific content. Finally, it makes one last pass to remove any HTML, then writes all of the text to the post's post_content_filtered field.

function write_content_pod_content ( $data, $postarr ) {

  // If this is not an update or a pod container page, there's nothing to do
  if ( ! isset($postarr[ 'ID' ] ) || ! $postarr[ 'ID' ] || ! has_shortcodes( 'pod' ) ) {
      return $data;
  } else {

      $post_content = $postarr[ 'post_content' ]; // Get the container page's content
      $search_data = preg_replace("~(?:\[/?)[^/\]]+/?\]~s", '', $post_content);  // Strip shortcodes, keep shortcode content

      $pod_id_array = has_shortcodes( 'pod', true ); // Get a list of the pod content page IDs
      foreach( $pod_id_array as $pod_id ) {
          $search_data .= do_shortcode( '[pod id=' . $pod_id . ']' );
      }

      // Write pod content to post_content_filtered field
      $clean_data = wp_filter_nohtml_kses( $search_data ); // Remove all of the HTML, leaving only raw text
      $data[ 'post_content_filtered' ] = $clean_data;
      return $data;
  }
}

add_filter('wp_insert_post_data', 'write_content_pod_content', 999, 2);

Lastly, in order to make any of this useful, the post_content_filtered database column had to be included in search. Turns out this was the least difficult step in the process thanks to Relevanssi. On the plugin's settings page, there's a field for defining additional database columns to index. For example (note: this functionality is only available in the Premium version of the the plugin):

*Because the shortcodes are added to the page using the drag and drop interface, I found it necessary to create a function for finding shortcodes within a page and/or their ID. I'm certain there are better ways to do this, so please excuse the lack of optimization, etc.

function has_shortcodes( $shortcode = '', $return_ids = false ) {

    $post_to_check = get_post( get_the_ID() );

    //False because we have to search through the post content first
    $found = false;

    //If no short code was provided, return false
    if ( ! $shortcode ) {
        return $found;
    }

    //Check the post content for the short code
    $haystack = $post_to_check->post_content;
    $needle = '[' . $shortcode;
    $lastPos = 0;
    $positions = array();
    if ( stripos( $haystack, $needle ) !== false ) {

        //Return an array containing the shortcode IDs
        if ( $return_ids ) {
            $pattern = '/\[\b' . $shortcode. '\b(.*?)\]/';
            preg_match_all( $pattern, $haystack, $all_codes, PREG_PATTERN_ORDER );

            $found = array();
            for( $i = 0; $i < count( $all_codes[1]); $i++ ) {
                $id_string_pattern = '/id\=\"(.*?)\"/';
                preg_match( $id_string_pattern, $all_codes[1][$i], $id_string_matches, PREG_OFFSET_CAPTURE );
                $found[] = $id_string_matches[1][0];
            }

        //Return true when nothing more than confirmation of finding shortcodes is needed
        } else {
            $found = true;
        }
    }

    //Return our final results
    return $found;
}

Hope that helps!

  • In your current format, your answer will likely not help any later reader. Please rework your answer and provide more detail. – kaiser Nov 30 '14 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.