Is there a way to filter all custom fields on a post?

Lets assume for very basic simplicity I have:

standard fields of:

  • title
  • post body

custom fields:

  • author
  • ISBN
  • quote

Lets assume on page load I want to append 123456 to the end of each custom fields value but that I want to do this with a filter. Which add_filter could accomplish this? A small blurb of code would be helpful.

Edit as per the current top answer:

After working the top answer I ran into an issue where it would only work on the first field. I then realized i needed to go through each element and this is what i have. What is weird is the data looks intact but the page does not display the new data. I have comments in my code below:

function my_post_meta_filters($null, $post_id, $key, $single){
    if(is_admin() || (substr($key, 0, 8) != '_author_' && substr($key, 0, 7) != '_quote_')){
        return $null;
    }

    static $filtered_values = NULL;

    if(is_null($filtered_values)){
        $cache = update_postmeta_cache(array($post_id));
        $values = $cache[$post_id];

        //must loop through all the fields or else only the first field is affected
        foreach($values AS $valkey => $value){                                   
            if(substr($key, 0, 8) == '_author_' || substr($key, 0, 7) == '_quote_'){
                $filtered[$valkey] = filtered($values[$valkey][0]);
                $filtered[$valkey] = maybe_serialize($filtered[$valkey]); //at this point the data is correct and even reserialized where expected
                $filtered_values[$valkey] = $filtered[$valkey];
            }
        }
        return $filtered_values;
    }
}
add_filter('get_post_metadata', 'my_post_meta_filters', 0, 4);

function filtered($it){
    if(!is_array($it) && !is_serialized($it)){
        $filtered = apply_filters('number_filter', $it); //adds numbers to the end
    } else {       
        //otherwise we ran into a serialized array so lets unserialize and run each part through our function
        $unserialized = maybe_unserialize($it);
        $filtered = array_map('filtered', $unserialized);
    }

    return $filtered;
}

1 Answer
1

Internally post meta are handled via object cache that is hardly filterable.

The only chance to filter post metadata is to use the 'get_post_metadata', but that filter is triggered when meta data are not available, so there is nothing to filter, it was intended to short-circuit the result more than to filter them.

So the solution I propose is:

  1. launch a function that run on that filter, and manually retreive the meta data
  2. after retrieveing, trigger a custom filter to be able to filter the just retrieved data
  3. store the so filtered value in a static variable, to avoid run again db query on subsequent calls
  4. finally add a callback to our custom hook (added at point #2) and filter data

So, first add the filter:

add_filter( 'get_post_metadata', 'my_post_meta_filters', 0, 4 );

Then write the hooking callback

function my_post_meta_filters( $null, $pid, $key, $single ) {
  if ( ! in_array( $key, array( 'author', 'ISBN', 'quote', '' ), TRUE ) || is_admin() ) {
    return $null;
  };
  static $filtered_values = NULL;
  if ( is_null( $filtered_values ) ) {
    $cache = update_meta_cache( 'post', array( $pid ) );
    $values = $cache[$pid];
    $raw = array(
      'author' => isset( $values['author'] ) ? $values['author'] : NULL,
      'ISBN'   => isset( $values['ISBN'] )   ? $values['ISBN']   : NULL,
      'quote'  => isset( $values['quote'] )  ? $values['quote']  : NULL,
    );
    // this is the filter you'll use to filter your values
    $filtered = (array) apply_filters( 'my_post_meta_values', $raw, $pid );
    foreach ( array( 'author', 'ISBN', 'quote' ) as $k ) {
      if ( isset( $filtered[$k] ) ) $values[$k] = $filtered[$k];
    }
    $filtered_values = $values;
  }
  if ( $key === '' )
     $filtered_values;
  if ( ! isset( $filtered_values[$key] ) )
     return;
  return $single
    ? maybe_unserialize( $filtered_values[$key][0] )
    : array_map( 'maybe_unserialize', $filtered_values[$key] );
}

Having this function in your code you’ll be able to filter your custom fields using the custom 'my_post_meta_values' filter.

Just an example:

add_filter( 'my_post_meta_values', function( $values, $post_id ) {

  // append '123456' to all values

  if ( is_array( $values['author'] ) ) {
    $values['author'][0] .= ' 123456';
  }
  if ( is_array( $values['ISBN'] ) ) {
    $values['ISBN'][0] .= ' 123456';
  }
  if ( is_array( $values['quote'] ) ) {
    $values['quote'][0] .= ' 123456';
  }

  return $values;

}, 10, 2 );

With this filter active, if you do:

echo get_post_meta( $post_id, 'author', TRUE );

and your “author” custom field is set to “Shawn” than the output is “Shawn 123456”.

Note that my_post_meta_filters is also compatible with get_post_custom with no additional effort.

Leave a Reply

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