0

I have a class used for a plugin's admin form, using the Settings API. It has a lot of fields, and some of those fields depend on already existing values in wp_options.

The existing options determine how often to render a field (in this case, it's a MailChimp API integration that renders a field for each list in the API, which is several layers down the stored API data).

In the admin interface, I have nine fields that get displayed. This is as it should be. But only the first three will save any data. I've tried changing the field types around on those first three and they always work, and I've also tried changing the types around on the last six, and they never work.

One other thing I tried was hardcoding the full ID values of the last six fields outside the big nested foreach. The fields save their data then.

Here's the code:

add_action( 'admin_init', array( $this, 'admin_settings_form' ) );

/**
* Register items for the settings api
* @return void
*
*/
public function admin_settings_form() {

    $get_data = filter_input_array( INPUT_GET, FILTER_SANITIZE_STRING );
    $page     = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'minnpost_mailchimp_settings';
    $section  = isset( $get_data['tab'] ) ? sanitize_key( $get_data['tab'] ) : 'minnpost_mailchimp_settings';

    $this->form_settings( 'form_settings', 'form_settings' );

}

private function form_settings( $page, $section ) {
    $form_sections = $this->setup_form_sections();
    $settings      = array();
    if ( ! empty( $form_sections ) ) {
        foreach ( $form_sections as $key => $value ) {
            $section = $key;
            // translators: 1 is the name of the shortcode
            $title = sprintf( 'Shortcode: [%1$s]',
                esc_attr( strtolower( $value ) )
            );

            $page = $section;
            add_settings_section( $section, $title, null, $page );

            $settings[ $section . '_resource_type' ] = array(
                'title'    => __( 'Resource type'),
                'callback' => 'display_select',
                'page'     => $page,
                'section'  => $section,
                'args'     => array(
                    'type'     => 'select',
                    'items'    => $this->get_resource_types(),
                ),
            );
            $resource_type                           = get_option( $this->option_prefix . 'newsletter_form_resource_type', '' );
            $settings[ $section . '_resource_id' ]   = array(
                'title'    => __( 'Resource name' ),
                'callback' => 'display_select',
                'page'     => $page,
                'section'  => $section,
                'args'     => array(
                    'type'     => 'select',
                    'items'    => $this->get_resource_ids( $resource_type ),
                ),
            );
            $resource_id                             = get_option( $this->option_prefix . 'newsletter_form_resource_id', '' );

            if ( 'lists' === $resource_type ) {
                $settings[ $section . '_' . $resource_type . '_default_member_status' ] = array(
                    'title'    => __( 'Default member status' ),
                    'callback' => 'display_select',
                    'page'     => $page,
                    'section'  => $section,
                    'args'     => array(
                        'type'     => 'select',
                        'items'    => $this->get_member_statuses(),
                    ),
                );
            }

            $item_keys = array();

            $subresource_types = get_option( $this->parent_option_prefix . 'subresource_types_' . $resource_type, array() );

            if ( ! empty( $subresource_types[ $resource_type ] ) ) {
                $subresource_types = $subresource_types[ $resource_type ];

                foreach ( $subresource_types as $subresource_type ) {

                    $items = get_option( $this->parent_option_prefix . 'subresources_' . $resource_id . '_' . $subresource_type, array() );

                    if ( ! empty( $items[ $resource_type ][ $resource_id ][ $subresource_type ] ) ) {

                        $subresources = $items[ $resource_type ][ $resource_id ][ $subresource_type ];

                        $methods = get_option( $this->parent_option_prefix . 'subresource_methods', array() );

                        if ( ! empty( $methods[ $resource_type ][ $resource_id ][ $subresource_type ] ) ) {

                            $methods = $methods[ $resource_type ][ $resource_id ][ $subresource_type ];

                            foreach ( $subresources as $subresource ) {

                                foreach ( $methods as $method ) {

                                    $test_all_items = $this->get_all_items( $resource_type, $resource_id, $subresource_type, $subresource, $method );

                                    if ( ! empty( $test_all_items ) ) {

                                        foreach ( $test_all_items as $test_item ) {

                                            $settings[ $section . '_' . $subresource_type . '_' . $subresource . '_' . $method . '_' . $test_item['id'] . '_title' ] = array(
                                                'title'    => __( 'Title' ),
                                                'callback' => 'display_input_field',
                                                'page'     => $page,
                                                'section'  => $section,
                                                'args'     => array(
                                                    'type'     => 'text',
                                                ),
                                            );

                                        } // End foreach().

                                    }

                                } // End foreach().

                            } // End foreach().

                        } // End if().

                    } // End if().

                } // End foreach().

            }  // End if().

        } // End foreach().

    } // End if().

    foreach ( $settings as $key => $attributes ) {

        $id       = $this->option_prefix . $key;
        $name     = $this->option_prefix . $key;
        $title    = $attributes['title'];
        $callback = $attributes['callback'];
        $page     = $attributes['page'];
        $section  = $attributes['section'];
        $args     = array_merge(
            $attributes['args'],
            array(
                'title'     => $title,
                'id'        => $id,
                'label_for' => $id,
                'name'      => $name,
                'class'     => $class,
            )
        );

        add_settings_field( $id, $title, $callback, $page, $section, $args );
        register_setting( $section, $id );

    }  // End foreach().
}

/**
* Default display for <input> fields
*
* @param array $args
*/
public function display_input_field( $args ) {
    $type    = $args['type'];
    $id      = $args['label_for'];
    $name    = $args['name'];
    $checked = '';

    $class = 'regular-text';

    $value = esc_attr( get_option( $id, '' ) );
    if ( 'checkbox' === $type ) {
        $value = filter_var( get_option( $id, false ), FILTER_VALIDATE_BOOLEAN );
        if ( true === $value ) {
            $checked = 'checked ';
        }
        $value = 1;
    }
    if ( '' === $value && isset( $args['default'] ) && '' !== $args['default'] ) {
        $value = $args['default'];
    }

    echo sprintf( '<input type="%1$s" value="%2$s" name="%3$s" id="%4$s" class="%5$s"%6$s>',
        esc_attr( $type ),
        esc_attr( $value ),
        esc_attr( $name ),
        esc_attr( $id ),
        sanitize_html_class( $class, esc_html( ' code' ) ),
        esc_html( $checked )
    );
}

/**
* Display for a dropdown
*
* @param array $args
*/
public function display_select( $args ) {
    $type = $args['type'];
    $id   = $args['label_for'];
    $name = $args['name'];
    $desc = $args['desc'];
    if ( ! isset( $args['constant'] ) || ! defined( $args['constant'] ) ) {
        $current_value = get_option( $name );

        echo sprintf( '<div class="select"><select id="%1$s" name="%2$s"><option value="">- Select one -</option>',
            esc_attr( $id ),
            esc_attr( $name )
        );

        foreach ( $args['items'] as $key => $value ) {
            $text     = $value['text'];
            $value    = $value['value'];
            $selected = '';
            if ( $key === $current_value || $value === $current_value ) {
                $selected = ' selected';
            }

            echo sprintf( '<option value="%1$s"%2$s>%3$s</option>',
                esc_attr( $value ),
                esc_attr( $selected ),
                esc_html( $text )
            );

        }
        echo '</select>';
        if ( '' !== $desc ) {
            echo sprintf( '<p class="description">%1$s</p>',
                esc_html( $desc )
            );
        }
        echo '</div>';
    } else {
        echo sprintf( '<p><code>%1$s</code></p>',
            esc_html__( 'Defined in wp-config.php', 'minnpost-form-processor-mailchimp' )
        );
    }
}

I'm able to run an error log where the register_setting and add_settings_field methods are called, and all the values match what they should. In this case, section displays as "newsletter_form". The HTML template (which is added earlier via add_options_page) looks like this:

<div id="main">
    <form method="post" action="options.php">
        <?php
        settings_fields( 'newsletter_form' ) . do_settings_sections( 'newsletter_form' );
        ?>
        <?php submit_button( __( 'Save settings', 'minnpost-form-processor-settings' ) ); ?>
    </form>
</div>

I'm having trouble figuring out why the nested options don't save. I went into wp-includes/option.php to try some debugging.

I ran error_log( 'post is ' . print_r( $_POST, true ) ); inside there and it did have the missing data. Then I went to the foreach ( $options as $option ) { line and ran error_log( 'option is ' . $option ); and it did not have the missing data.

So something is keeping my missing fields from being added to the $options array.

What can I try next on this?

6
  • I tried going through this, but there are a lot of unknowns in your code. The only thing that stands out is $settings[ $section . '_' . $subresource_type . '_' . $subresource . '_' . $method . '_' . $test_item['id'] . '_title' ] which is quite long. Apr 5 '19 at 14:31
  • @MikeNGarrett it is very long. I thought maybe WP had a max length, but it does work when I hardcode the value that comes out of those variables outside the foreach. So it's like the nested foreach breaks it? I don't know how to wrap my head around that. Apr 5 '19 at 14:35
  • That was something I definitely didn't unpack. It's a lot. It's definitely better to process some of this without looping, if you can. Array functions like array_map are your friends. Apr 5 '19 at 14:50
  • @MikeNGarrett maybe beyond the scope here, but do you have a good example of how to use array_map in this context? Specifically where you're loading a variable in each level that the lower levels depend on? Apr 5 '19 at 17:54
  • 2
    Probably need to start another question on SO for that one. :-) Apr 5 '19 at 18:36
0

I figured it out. It's not actually in the code above at all, but in one of the lower methods I use to generate the array keys for $settings.

The problem was that in one of those other methods, I had the following code:

if ( ! isset( $_GET['page'] ) || $this->slug !== $_GET['page'] ) {
    return $options;
}

I often use this to avoid making unnecessary API calls when users are doing other things inside the admin. But in this case, it was also preventing the plugin from getting the settings key names for those fields when the data was posted to Core's options.php, since of course options.php does not have the $_GET['page'] value expected by this plugin.

I still need to do some investigating to make sure I only call these things when needed, but at least for this question that is the problem, and is thus solved.

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.