Is there a way to filter all custom fields on a post?
Lets assume for very basic simplicity I have:
standard fields of:
custom fields:
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;
}
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:
- launch a function that run on that filter, and manually retreive the meta data
- after retrieveing, trigger a custom filter to be able to filter the just retrieved data
- store the so filtered value in a static variable, to avoid run again db query on subsequent calls
- 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.