Add field to posts (checkbox) that only one post can have

Hopefully I’ve understood your question correctly, and I hope the code below helps.

First you must add a metabox to hold the option that you wish to add to each Post, and the my_add_sticky_metabox() function will do this, in conjunction with the add_meta_boxes action hook.

At this stage you are not actually printing anything, but rather telling WordPress to print using your declared callback (my_output_sticky_metabox()) whenever the Post edit screen is displayed.

Using the my_output_sticky_metabox() function the metabox content is printed, including a check as to whether or not the box should be checked and/or disabled.

Finally the correct value is saved using the my_save_sticky_metabox() function in conjunction with the save_post action hook.

add_action('add_meta_boxes', 'my_add_sticky_metabox');
function my_add_sticky_metabox(){

    add_meta_box(
        'my_sticky_post_metabox',
        __('Sticky Post', 'my_text_domain'),
        'my_output_sticky_metabox',
        'post'
    );
    
}

function my_output_sticky_metabox($post){
    
    /** Grab the current 'my_sticky_post' option value */
    $sp = intval(get_option('my_sticky_post'));
    
    /** Check to see if the 'my_sticky_post' option should be disabled or checked for the current Post */
    $checked = checked($sp, $post->ID, false);
    if($sp > 0) :
        $disabled = (!disabled($sp, $post->ID, false)) ? 'disabled="true"' : '';
    else :
        $disabled = '';
    endif;
    
    /** Add a nonce field */
    wp_nonce_field('my_sticky_post_metabox', 'my_sticky_post_metabox_nonce');
    
    /** Add a hidden field to check against in case it is unchecked before save */
    $value = ($checked) ? '1' : '0';
    echo '<input type="hidden" name="was_checked" value="' . $value . '" />';
    
    /** Output the checkbox and label */
    echo '<label for="my_sticky_post">';
    echo '<input type="checkbox" id="my_sticky_post" name="my_sticky_post" value="' . $post->ID . '" ' . $checked . $disabled . ' />';
    echo 'Make this the sticky post?</label>';
    
    /** Let the user know which Post is currently sticky */
    switch($sp) :
    
        case 0:
            $message="There is currently no Sticky Post.";
            break;
        case $post->ID:
            $message="This Post is the Sticky Post.";
            break;
        default:
            $message="<a href="" . get_edit_post_link($sp) . '" title="' . the_title_attribute('before=Edit post \'&after=\'&echo=0') . '">' . get_the_title($sp) . '</a> is the current Sticky Post';
            $message.= '<br />You must remove the sticky status from that post before you can make this one sticky.';
    
    endswitch;
    echo '<p><em>' . $message .'</em></p>';
    
}

add_action('save_post', 'my_save_sticky_metabox');
function my_save_sticky_metabox($post_id){

    /*
     * We need to verify this came from our screen and with proper authorization,
     * because the save_post action can be triggered at other times.
     */
    
    /** Ensure that a nonce is set */
    if(!isset($_POST['my_sticky_post_metabox_nonce'])) :
        return;
    endif;
    
    /** Ensure that the nonce is valid */
    if(!wp_verify_nonce( $_POST['my_sticky_post_metabox_nonce'], 'my_sticky_post_metabox')) :
        return;
    endif;
    
    /** Ensure that an AUTOSAVE is not taking place */
    if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) :
        return;
    endif;
    
    /** Ensure that the user has permission to update this option */
    if(!current_user_can('edit_post', $post_id)) :
        return;
    endif;
    
    /**
     * Everything is valid, now the option can be updated
     */
    
    /** Check to see if the 'my_sticky_post' option was checked */
    if(isset($_POST['my_sticky_post'])) : // It was...
    
        update_option('my_sticky_post', $_POST['my_sticky_post']);  // Update the option
        
    else : // It was not...
    
        /** Check to see if the option was checked prior to the options being updated */
        if($_POST['was_checked'] != 0) : // It was...
            
            update_option('my_sticky_post', -1);    // Set the option to '-1'
            
        endif;
    
    endif;
    
}

Now when you want to grab your custom sticky post you use this query –

$args = array(
    'p' => get_option('my_sticky_post')
);
$stickyPost = new WP_Query($args);

if($stickyPost->have_posts()) : while($stickyPost->have_posts()) : $stickyPost->the_post();

        { Your code goes here }
        
    endwhile;
endif;
wp_reset_postdata();

There is plenty of reading material I’d encourage you to take a look at here, hopefully it’ll answer any questions you may have about the code above –

Edit

You mention that if no sticky post is set, you have 4 posts output on index.php (which is your standard number).

I believe that is because if the p parameter is empty it is ignored, thus you need to check the value of the my_sticky_post option.

Make this change to the $args array (and note the additional line above) –

$sticky_id = get_option('my_sticky_post');
$args = array(
    'p' => ($sticky_id) ? $sticky_id : '-1'
);

Leave a Comment