5

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;
}
2

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.

  • Sorry for the late response. I tried to modify this to my needs but it only seems to affect the first field, and doesn't seem to handle serialized fields correctly. I will work on a fiddle to share. – Shawn Oct 27 '14 at 23:24
  • I have added my current code to the question which was based off your answer. I have been working on this for a week and added and removed pieces trying to get it to work. I feel like it has to be very close, as per my comments in my code. If you could look at it that would be awesome! – Shawn Oct 30 '14 at 17:26

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.