1

I'm new to Wordpress plugin development and I'm trying to create a list of checkboxes (from a WP Query) to associate as custom fields for a specific post type. Nothing wrong with the display function, but when I'm trying to save I get the error

Invalid argument supplied for foreach().

How can I check what the save function sees in my global array $loop_brands? Someone can helps me to solve and understand?

<?php

function brands_display_meta_box( $post ) { 

    global $loop_brands;
    $loop_brands = array();

    wp_nonce_field( plugin_basename( __FILE__ ), 'brands-nonce-field' );

    $args = array(
        'post_type' => 'page',
        'post_parent' => 7,
        'orderby' => 'title',
        'order' => 'ASC'
    );

    $query_brands = new WP_Query($args);

    while ($query_brands->have_posts()) : $query_brands->the_post(); 

        $id_brand = get_the_ID();
        $brand = get_post($id_brand, ARRAY_A);
        $slug_brand = $brand['post_name'];
        $titolo_brand = $brand['post_title'];

        $loop_brands[] = $slug_brand;

?>

        <p>
            <input type="checkbox" id="<?php echo $slug_brand; ?>" name="<?php echo $slug_brand; ?>" value="yes" <?php checked( get_post_meta($post->ID, $slug_brand, true ), 'yes' ); ?>>
            <label for="<?php echo $slug_brand; ?>"><?php echo $titolo_brand; ?></label>

        </p>

<?php endwhile;

}

function brands_add_meta_box() {

    add_meta_box(
        'brands-meta-box',
        'brands Meta Box',
        'brands_display_meta_box',
        'stores',
        'side',
        'high'
    );

}

add_action( 'add_meta_boxes', 'brands_add_meta_box' );

function brands_user_can_save( $post_id, $nonce ) {

    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ $nonce ] ) && wp_verify_nonce( $_POST[ $nonce ], plugin_basename( __FILE__ ) ) );

    return ! ( $is_autosave || $is_revision ) && $is_valid_nonce;

}

function brands_save_meta_box( $post_id ) {

    global $loop_brands;

    foreach ( $loop_brands as $brand ) {

        if ( brands_user_can_save( $post_id, $brand ) ) {

            if ( isset( $_POST[ $brand ] ) ) {

                update_post_meta( $post_id, $brand, $_POST[ $brand ]);

            } else {

                delete_post_meta( $post_id, $brand);

            }

        }

    }

}

add_action( 'save_post', 'brands_save_meta_box');
1

My advise would be to extract the query portion of your code:

function get_brands() {
  $loop_brands = array();

  wp_nonce_field( plugin_basename( __FILE__ ), 'brands-nonce-field' );

  $args = array(
      'post_type' => 'page',
      'post_parent' => 7,
      'orderby' => 'title',
      'order' => 'ASC'
  );

  $query_brands = new WP_Query($args);
  return $query_brands;
}

Then use it like this:

function brands_save_meta_box( $post_id ) {
    $loop_brands = get_brands();
    $loop_brands = wp_list_pluck($loop_brands->posts,'post_name');
    foreach ( $loop_brands as $brand ) {
        if ( brands_user_can_save( $post_id, $brand ) ) {
            if ( isset( $_POST[ $brand ] ) ) {
                update_post_meta( $post_id, $brand, $_POST[ $brand ]);
            } else {
                delete_post_meta( $post_id, $brand);
            }
        }
    }
}

That would be the first step. Unless I made a mistake the code should work exactly as before, but without the worry and mess of the global.

You can then move to a more complicated solution involving a bit of "caching" if you will:

function brands_query() {
  $loop_brands = array();

  wp_nonce_field( plugin_basename( __FILE__ ), 'brands-nonce-field' );

  $args = array(
      'post_type' => 'page',
      'post_parent' => 7,
      'orderby' => 'title',
      'order' => 'ASC'
  );

  $query_brands = new WP_Query($args);
  return $query_brands;
}

function get_brands() {
  $brands = get_option('loop_brands');
  if (empty($brands)) {
    $brands = brands_query();
    update_option('loop_brands',$brands);
  }
  return $brands;
}

And if I am reading it right, you really only need to refresh loop_brands on page save. Something like this:

function save_brands($post_id) {
  $brands = brands_query();
  update_option('loop_brands',$brands);
}
add_action('save_post_page','save_brands');
  • I'm sorry but the problem of saving was an issue in the if statement 'brands_user_can_save' where the 'nonce' was incorrect (I should check better before posting), by the way your code is really clean and useful. The tip to create a loop function is good, I've implemented it and works fine, so many thanks :) – Alessio May 26 '14 at 6:40
  • @Alessio : Programming jargon is saturated with acronyms, most of which I find pretty annoying, but DRY is good. – s_ha_dum May 26 '14 at 13:55
1

Basically, your $loop_brands isn't carried over to the save function. So you are running foreach on an empty/false variable, hence the error. I would suggest saving the $loop_brands as a transient or an option. Here I've tried a transient. I don't think you need a way to clear the transient because every time the metabox is loaded the transient is refreshed, therefore it should be up to date when the save routine runs. I haven't tested this, so your mileage may vary.

function brands_display_meta_box( $post ) { 

    wp_nonce_field( plugin_basename( __FILE__ ), 'brands-nonce-field' );

    $args = array(
        'post_type' => 'page',
        'post_parent' => 7,
        'orderby' => 'title',
        'order' => 'ASC'
    );

    $query_brands = new WP_Query($args);

    while ($query_brands->have_posts()) : $query_brands->the_post(); 

        $id_brand = get_the_ID();
        $brand = get_post($id_brand, ARRAY_A);
        $slug_brand = $brand['post_name'];
        $titolo_brand = $brand['post_title'];

        $loop_brands[] = $slug_brand;

?>

        <p>
            <input type="checkbox" id="<?php echo $slug_brand; ?>" name="<?php echo $slug_brand; ?>" value="yes" <?php checked( get_post_meta($post->ID, $slug_brand, true ), 'yes' ); ?>>
            <label for="<?php echo $slug_brand; ?>"><?php echo $titolo_brand; ?></label>

        </p>

<?php endwhile;

    set_transient( 'loop_brands', $loop_brands );

}

function brands_add_meta_box() {

    add_meta_box(
        'brands-meta-box',
        'brands Meta Box',
        'brands_display_meta_box',
        'stores',
        'side',
        'high'
    );

}

add_action( 'add_meta_boxes', 'brands_add_meta_box' );

function brands_user_can_save( $post_id, $nonce ) {

    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ $nonce ] ) && wp_verify_nonce( $_POST[ $nonce ], plugin_basename( __FILE__ ) ) );

    return ! ( $is_autosave || $is_revision ) && $is_valid_nonce;

}

function brands_save_meta_box( $post_id ) {

    $loop_brands = get_transient( 'loop_brands' );

    if( $loop_brands ):

    foreach ( $loop_brands as $brand ) {

        if ( brands_user_can_save( $post_id, $brand ) ) {

            if ( isset( $_POST[ $brand ] ) ) {

                update_post_meta( $post_id, $brand, $_POST[ $brand ]);

            } else {

                delete_post_meta( $post_id, $brand);

            }

        }

    }
    endif;

}

add_action( 'save_post', 'brands_save_meta_box');
  • Now the error is gone and now I know the transient API thanks to you, but my code doesn't save the check/uncheck clicks. Probably I had to look in the save function to find the issue. Is there a way to print_r that $loop_brands inside the 'brands_save_meta_box'? Just to check if the data is passed correctly? – Alessio May 25 '14 at 20:15
  • You save routine must be borked. Why not merge the 2 functions? And you can die(var_dump($something)); to kill the process and dump the variable. – helgatheviking May 26 '14 at 1:36
  • Thanks Helga die(var_dump($something));helps me a lot :) – Alessio May 26 '14 at 9:30

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.