Add Settings to Custom Post Type

I have a custom post type called Portfolio. It is associated with three custom taxonomies. This is all working fine.

For the archive page, however, I need to add a few custom settings. Due to a limitation in the project, I can’t write a plugin—all changes have to be done in the theme.

I’ve got the settings sub menu page appearing (easy enough) following this guide, and the settings appear on the page without issue. The problem now is that they’re not saving.

I’ve only added one setting (header_text) until I can figure out the saving problem.

I imagine that $option_group is probably the problem.

If I var_dump($_POST) I get:

array (size=6)
  'option_page' => string 'edit.php?post_type=rushhour_projects&page=projects_archive' (length=58)
  'action' => string 'update' (length=6)
  '_wpnonce' => string '23c70a3029' (length=10)
  '_wp_http_referer' => string '/wp-admin/edit.php?post_type=rushhour_projects&page=projects_archive' (length=68)
  'rushhour_projects_archive' => 
    array (size=1)
      'header_text' => string 'asdf' (length=4)
  'submit' => string 'Save Changes' (length=12)

Here’s the custom post type registration:

if ( ! function_exists('rushhour_post_type_projects') ) :

// Add Portfolio Projects to WordPress
add_action( 'init', 'rushhour_post_type_projects', 0 );

// Register Portfolio Projects Custom Post Type
function rushhour_post_type_projects()
{
    $labels = array(
        'name'                  => _x( 'Portfolio', 'Post Type General Name', 'rushhour' ),
        'singular_name'         => _x( 'Project', 'Post Type Singular Name', 'rushhour' ),
        'menu_name'             => __( 'Portfolio Projects', 'rushhour' ),
        'name_admin_bar'        => __( 'Portfolio Project', 'rushhour' ),
        'archives'              => __( 'Portfolio Archives', 'rushhour' ),
        'parent_item_colon'     => __( 'Parent Project:', 'rushhour' ),
        'all_items'             => __( 'All Projects', 'rushhour' ),
        'add_new_item'          => __( 'Add New Project', 'rushhour' ),
        'add_new'               => __( 'Add New', 'rushhour' ),
        'new_item'              => __( 'New Project', 'rushhour' ),
        'edit_item'             => __( 'Edit Project', 'rushhour' ),
        'update_item'           => __( 'Update Project', 'rushhour' ),
        'view_item'             => __( 'View Project', 'rushhour' ),
        'search_items'          => __( 'Search Projects', 'rushhour' ),
        'not_found'             => __( 'Not found', 'rushhour' ),
        'not_found_in_trash'    => __( 'Not found in Trash', 'rushhour' ),
        'featured_image'        => __( 'Featured Image', 'rushhour' ),
        'set_featured_image'    => __( 'Set featured image', 'rushhour' ),
        'remove_featured_image' => __( 'Remove featured image', 'rushhour' ),
        'use_featured_image'    => __( 'Use as featured image', 'rushhour' ),
        'insert_into_item'      => __( 'Insert into project', 'rushhour' ),
        'uploaded_to_this_item' => __( 'Uploaded to this project', 'rushhour' ),
        'items_list'            => __( 'Projects list', 'rushhour' ),
        'items_list_navigation' => __( 'Projects list navigation', 'rushhour' ),
        'filter_items_list'     => __( 'Filter projects list', 'rushhour' ),
        );
    $rewrite = array(
        'slug'                  => 'portfolio',
        'with_front'            => true,
        'pages'                 => true,
        'feeds'                 => true,
        );
    $args = array(
        'label'                 => __( 'Project', 'rushhour' ),
        'description'           => __( 'Portfolio projects for Global VDC.', 'rushhour' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', ),
        'taxonomies'            => array( 'rushhour_clients', 'rushhour_locations', 'rushhour_project_type' ),
        'hierarchical'          => false,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'menu_icon'             => 'dashicons-portfolio',
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => true,
        'can_export'            => true,
        'has_archive'           => 'portfolio',
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'rewrite'               => $rewrite,
        'capability_type'       => 'page',
        );
    register_post_type( 'rushhour_projects', $args );
}
endif;

Then I’ve got a function for setting up the admin sub menu page.

if ( ! function_exists('rushhour_projects_admin_page') ) :

add_action( 'admin_menu' , 'rushhour_projects_admin_page' );

/**
 * Generate sub menu page for settings
 *
 * @uses rushhour_projects_options_display()
 */
function rushhour_projects_admin_page()
{
    add_submenu_page(
        'edit.php?post_type=rushhour_projects',
        __('Portfolio Projects Options', 'rushhour'),
        __('Portfolio Options', 'rushhour'),
        'manage_options',
        'projects_archive',
        'rushhour_projects_options_display');
}
endif;

So the above two items work without trouble.

The problem, I think, is somewhere in the functions below for registering and saving the settings:

if ( ! function_exists('rushhour_projects_options_display') ) :
/**
 * Display the form on the Rush Hour Projects Settings sub menu page.
 *
 * Used by 'rushhour_projects_admin_page'.
 */
function rushhour_projects_options_display()
{
    // Create a header in the default WordPress 'wrap' container
    echo '<div class="wrap">';

    settings_errors();

    echo '<form method="post" action="">';

    var_dump( get_option('rushhour_projects_archive') );

    settings_fields( 'edit.php?post_type=rushhour_projects&page=projects_archive' );

    do_settings_sections( 'edit.php?post_type=rushhour_projects&page=projects_archive' );

    submit_button();

    echo '</form></div><!-- .wrap -->';
}
endif;

add_action( 'admin_init', 'rushhour_projects_settings' );

/**
 * Register settings and add settings sections and fields to the admin page.
 */
function rushhour_projects_settings()
{
    if ( false == get_option( 'rushhour_projects_archive' ) )
        add_option( 'rushhour_projects_archive' );

    add_settings_section(
        'projects_archive_header', // Section $id
        __('Portfolio Project Archive Page Settings', 'rushhour'),
        'rushhour_project_settings_section_title', // Callback
        'edit.php?post_type=rushhour_projects&page=projects_archive' // Settings Page Slug
        );

    add_settings_field(
        'header_text',          // Field $id
        __('Header Text', 'rushhour'),          // Setting $title
        'projects_archive_header_text_callback',
        'edit.php?post_type=rushhour_projects&page=projects_archive',   // Settings Page Slug
        'projects_archive_header',          // Section $id
        array('Text to display in the archive header.')
        );

    register_setting(
        'edit.php?post_type=rushhour_projects&page=projects_archive', // $option_group
        'rushhour_projects_archive',  // $option_name
        'rushhour_projects_archive_save_options'
        );
}

/**
 * Callback for settings section.
 *
 * Commented out until settings are working.
 * 
 * @param  array $args Gets the $id, $title and $callback.
 */
function rushhour_project_settings_section_title( $args ) {
    // printf( '<h2>%s</h2>', apply_filters( 'the_title', $args['title'] ) );
}

/**
 * Settings fields callbacks.
 */
function projects_archive_header_text_callback($args)
{
    $options = get_option('rushhour_projects_archive');

    printf( '<input class="widefat" id="header_text" name="rushhour_projects_archive[header_text]" type="textarea" value="%1$s" />',
        $options );
}

/**
 * Save options.
 */
function rushhour_projects_archive_save_options()
{
    if ( isset( $_POST['rushhour_projects_archive[header_text]'] ) )
    {
        update_option( 'rushhour_projects_archive', $_POST['rushhour_projects_archive[header_text]'] );
    }
}

2 Answers
2

Ok, so I got annoyed with it not working and decided to just rewrite it. I’m not sure what the solution was, but I suspect two things: the wrong endpoint for the form (should be options.php) and the wrong $option_group and $option_name (they were probably not matched correctly).

For posterity, I’ll leave my rewrite here and hopefully it helps others. A few differences between this and the previous version.

  1. This is now in it’s own file so you won’t see the custom post type registered.
  2. I used an object for the page per example 2 from the WordPress codex.
  3. I added a second option for a media uploader using this tutorial, which I updated to use wp_localize_script() to inject a JSON object to get some PHP data.

    public function __construct()
    {
        add_action( 'admin_menu', array( $this, 'add_submenu_page_to_post_type' ) );
        add_action( 'admin_init', array( $this, 'sub_menu_page_init' ) );
        add_action( 'admin_init', array( $this, 'media_selector_scripts' ) );
    }
    
    /**
     * Add sub menu page to the custom post type
     */
    public function add_submenu_page_to_post_type()
    {
        add_submenu_page(
            'edit.php?post_type=rushhour_projects',
            __('Portfolio Projects Options', 'rushhour'),
            __('Portfolio Options', 'rushhour'),
            'manage_options',
            'projects_archive',
            array($this, 'rushhour_projects_options_display'));
    }
    
    /**
     * Options page callback
     */
    public function rushhour_projects_options_display()
    {
        $this->options = get_option( 'rushhour_projects_archive' );
    
        wp_enqueue_media();
    
        echo '<div class="wrap">';
    
        printf( '<h1>%s</h1>', __('Portfolio Options', 'rushhour' ) ); 
    
        echo '<form method="post" action="options.php">';
    
        settings_fields( 'projects_archive' );
    
        do_settings_sections( 'projects-archive-page' );
    
        submit_button();
    
        echo '</form></div>';
    }
    
    /**
     * Register and add settings
     */
    public function sub_menu_page_init()
    {
        register_setting(
            'projects_archive', // Option group
            'rushhour_projects_archive', // Option name
            array( $this, 'sanitize' ) // Sanitize
            );
    
        add_settings_section(
            'header_settings_section', // ID
            __('Header Settings', 'rushhour'), // Title
            array( $this, 'print_section_info' ), // Callback
            'projects-archive-page' // Page
            );
    
        add_settings_field(
            'archive_description', // ID
            __('Archive Description', 'rushhour'), // Title
            array( $this, 'archive_description_callback' ), // Callback
            'projects-archive-page', // Page
            'header_settings_section' // Section
            );
    
        add_settings_field(
            'image_attachment_id',
            __('Header Background Image', 'rushhour'),
            array( $this, 'header_bg_image_callback' ),
            'projects-archive-page',
            'header_settings_section'
            );
    }
    
    /**
     * Sanitize each setting field as needed
     *
     * @param array $input Contains all settings fields as array keys
     */
    public function sanitize( $input )
    {
        $new_input = array();
    
        if( isset( $input['archive_description'] ) )
            $new_input['archive_description'] = sanitize_text_field( $input['archive_description'] );
    
        if( isset( $input['image_attachment_id'] ) )
            $new_input['image_attachment_id'] = absint( $input['image_attachment_id'] );
    
        return $new_input;
    }
    
    /**
     * Print the Section text
     */
    public function print_section_info()
    {
        print 'Select options for the archive page header.';
    }
    
    /**
     * Get the settings option array and print one of its values
     */
    public function archive_description_callback()
    {
        printf(
            '<input type="text" id="archive_description" name="rushhour_projects_archive[archive_description]" value="https://wordpress.stackexchange.com/questions/235088/%s" />',
            isset( $this->options['archive_description'] ) ? esc_attr( $this->options['archive_description']) : ''
            );
    }
    
    /**
     * Get the settings option array and print one of its values
     */
    public function header_bg_image_callback()
    {
        $attachment_id = $this->options['image_attachment_id'];
    
        // Image Preview
        printf('<div class="image-preview-wrapper"><img id="image-preview" src="https://wordpress.stackexchange.com/questions/235088/%s" ></div>', wp_get_attachment_url( $attachment_id ) );
    
        // Image Upload Button
        printf( '<input id="upload_image_button" type="button" class="button" value="https://wordpress.stackexchange.com/questions/235088/%s" />',
            __( 'Upload image', 'rushhour' ) );
    
        // Hidden field containing the value of the image attachment id
        printf( '<input type="hidden" name="rushhour_projects_archive[image_attachment_id]" id="image_attachment_id" value="https://wordpress.stackexchange.com/questions/235088/%s">',
            $attachment_id );
    }
    
    public function media_selector_scripts()
    {
        $my_saved_attachment_post_id = get_option( 'media_selector_attachment_id', 0 );
    
        wp_register_script( 'sub_menu_media_selector_scripts', get_template_directory_uri() . '/admin/js/media-selector.js', array('jquery'), false, true );
    
        $selector_data = array(
            'attachment_id' => get_option( 'media_selector_attachment_id', 0 )
            );
    
        wp_localize_script( 'sub_menu_media_selector_scripts', 'selector_data', $selector_data );
    
        wp_enqueue_script( 'sub_menu_media_selector_scripts' );
    }
    

    }

Then simply call the object on if is_admin() is true:

if ( is_admin() )
     $my_settings_page = new RushHourProjectArchivesAdminPage();

Leave a Comment