Reputation: 1900
I have a meta box associated with a custom post type. The meta box allows the user to select multiple options from a dropdown and then saves it in the form of the serialized array into the database. If the user selects only one option the meta_value is stored as an integer but if a user selects multiple options then it is saved in the form of a serialized array.
Below are the functions that display and save my meta box
function av_add_meta_box() {
$post_types = array( 'advertisements' );
foreach ( $post_types as $post_type ) {
add_meta_box(
'avd_screen_meta_box', // Unique ID of meta box
'Set Screen', // Title of meta box
'av_display_meta_box', // Callback function
$post_type // Post type
);
}
}
add_action( 'add_meta_boxes', 'av_add_meta_box' );
// display meta box
function av_display_meta_box( $post ) {
$value = get_post_meta( $post->ID, '_av_meta_key', true );
if ( isset( $values['av-meta-box'] ) ) {
$value = esc_attr( $values['ued-av-box'][0] );
}
wp_nonce_field( basename( __FILE__ ), 'av_meta_box_nonce' );
//echo "<pre>"; print_r($value) ; echo "</pre>";
?>
<select id="av-meta-box" type="text" name="av-meta-box[]" multiple="multiple">
<option value="">Select Screen</option>
<?php $screens = json_decode(apply_filters("av_load_screens", "all"));
foreach ($screens as $screen) {
$location_name = json_decode(apply_filters("av_load_locations", $screen->location));
$selected = "";
if(gettype($value) == "array"){
$selected = (in_array($screen->id, $value))?"selected":"";
} else{
$selected = ($screen->id == $value)?"selected":"";
} ?>
<option <?php echo $selected; ?> value="<?php echo $screen->id; ?>"><?php echo $screen->screen_name." - ".$location_name[0]->location_name." (".$location_name[0]->pin.") "; ?></option><?php
} ?>
</select>
<?php
}
// save meta box
function av_save_meta_box( $post_id ) {
$is_autosave = wp_is_post_autosave( $post_id );
$is_revision = wp_is_post_revision( $post_id );
$is_valid_nonce = false;
if ( isset( $_POST[ 'av_meta_box_nonce' ] ) ) {
if ( wp_verify_nonce( $_POST[ 'av_meta_box_nonce' ], basename( __FILE__ ) ) ) {
$is_valid_nonce = true;
}
}
if ( $is_autosave || $is_revision || !$is_valid_nonce ) return;
if ( array_key_exists( 'av-meta-box', $_POST ) ) {
update_post_meta(
$post_id, // Post ID
'_av_meta_key', // Meta key
array_map( 'strip_tags', $_POST[ 'av-meta-box' ])// Meta value
);
}
}
add_action( 'save_post', 'av_save_meta_box' );
And Below are the arguments what I'm passing to my WP_Query
$args = array(
'post_type' => 'advertisements',
'meta_query' => array (
'key' => '_av_meta_key',
'value' => 6,
'compare' => 'IN'
)
);
The above WP_Query returns all post with having meta value 6
(integer). But what if the meta_value
is stored in a serialized array like a:3:{i:0;s:1:"6";i:1;s:1:"7";i:2;s:1:"8";}
In my case, the post_id:20, meta_key:_av_meta_key and meta_value:a:3:{i:0;s:1:"6";i:1;s:1:"7";i:2;s:1:"8";}
I want my WP_Query to return the post with id 20 either the meta_value is 6 or 7 or 8.
Upvotes: 0
Views: 2128
Reputation: 821
I'd suggest saving the (multiple) values into multiple rows in the database to avoid the problem from the beginning. WordPress supports saving multiple values with the same key for a custom field.
You can rewrite the code that saves value to:
if ( array_key_exists( 'av-meta-box', $_POST ) ) {
delete_post_meta( $post_id, '_av_meta_key' );
$values = array_map( 'absint', $_POST[ 'av-meta-box' ] );
foreach ( $values as $value ) {
add_post_meta( $post_id, '_av_meta_key', $value, false );
}
}
This way, you can write your query without worrying about serialize data. And getting values from the database is simple as:
$values = get_post_meta( $post_id, '_av_meta_key', false );
PS: I'd suggest using a custom fields plugin to avoid writing repetitive code. It also helps you to solve this problem as I suggested above quickly.
Upvotes: 1
Reputation: 728
It is difficult to get the correct result using WP_Query with serialised data. You can try using the LIKE
keyword instead of IN
.
Alternatively you can use a custom $wpdb
query to get what you are looking for.
global $wpdb;
$posts = $wpdb->get_results(
$wpdb->prepare(
"
SELECT *
FROM $wpdb->postmeta
LEFT JOIN $wpdb->posts as post
ON post.id = post_id
WHERE post.post_type = %s
AND meta_key = %s
AND meta_value LIKE %s
",
'advertisements',
'_av_meta_key',
':"6";'
)
);
// Check if anything was found
if( $posts !== NULL ) {
foreach($posts as $post_info) {
// $post_info contains all post info as object
echo $post_info->post_title;
}
}
You can change the part where it is :"6";
to the value you are looking for. It would make the search better if you wrap the number in :"{number}";"
as this is how it is serialised in your example which would make it more of a exact match
function av_add_meta_box() {
$post_types = array( 'advertisements' );
foreach ( $post_types as $post_type ) {
add_meta_box(
'avd_screen_meta_box', // Unique ID of meta box
'Set Screen', // Title of meta box
'av_display_meta_box', // Callback function
$post_type // Post type
);
}
}
add_action( 'add_meta_boxes', 'av_add_meta_box' );
// display meta box
function av_display_meta_box( $post ) {
$value = get_post_meta( $post->ID, '_av_meta_key', true );
if ( isset( $values['av-meta-box'] ) ) {
$value = esc_attr( $values['ued-av-box'][0] );
}
wp_nonce_field( basename( __FILE__ ), 'av_meta_box_nonce' );
//echo "<pre>"; print_r($value) ; echo "</pre>";
?>
<select id="av-meta-box" type="text" name="av-meta-box[]" multiple="multiple">
<option value="">Select Screen</option>
<?php $screens = json_decode(apply_filters("av_load_screens", "all"));
foreach ($screens as $screen) {
$location_name = json_decode(apply_filters("av_load_locations", $screen->location));
$selected = "";
if(gettype($value) == "array"){
$selected = (in_array($screen->id, $value))?"selected":"";
} else{
$selected = ($screen->id == $value)?"selected":"";
} ?>
<option <?php echo $selected; ?> value="<?php echo $screen->id; ?>"><?php echo $screen->screen_name." - ".$location_name[0]->location_name." (".$location_name[0]->pin.") "; ?></option><?php
} ?>
</select>
<?php
}
// save meta box
function av_save_meta_box( $post_id ) {
$is_autosave = wp_is_post_autosave( $post_id );
$is_revision = wp_is_post_revision( $post_id );
$screens = json_decode(apply_filters("av_load_screens", "all"));
$is_valid_nonce = false;
if ( isset( $_POST[ 'av_meta_box_nonce' ] ) ) {
if ( wp_verify_nonce( $_POST[ 'av_meta_box_nonce' ], basename( __FILE__ ) ) ) {
$is_valid_nonce = true;
}
}
if ( $is_autosave || $is_revision || !$is_valid_nonce ) return;
if ( array_key_exists( 'av-meta-box', $_POST ) ) {
$selected_screens = array_map( 'absint', $_POST[ 'av-meta-box' ] );
foreach($screens as $screen) {
update_post_meta(
$post_id,
'_av_meta_key_' . $screen->id,
in_array( $screen->id, $selected_screens ) ? 'yes' : 'no'
);
}
}
}
add_action( 'save_post', 'av_save_meta_box' );
Then when you want to use WP_Query
you can build an array to search for all keys and set the relation to OR
.
$meta_query = [
'relation' => 'OR',
];
$screens = json_decode(apply_filters("av_load_screens", "all"));
foreach($screens as $screen) {
$meta_query[] = [
'key' => '_av_meta_key_' . $screen->id,
'value' => 'yes',
'compare' => '='
];
}
$args = array(
'post_type' => 'advertisements',
'meta_query' => $meta_query,
);
Upvotes: 1