0

How would I restrict an author in scheduling their post X (let's say 8) days ahead of the current date?

2

It's possible to add a custom dropdown box to the submit meta-box:

restriction

where the user can only select X days into the future.

If the date is too far, we set the post date to the current date.

Here's how it looks like in action when I add a new post, change the date and publish it:

select

and we see here the scheduling.

Demo Plugin

Here's one idea, that you can hopefully test further and adjust to your needs.

The configuration is of this form:

class Config
{
    const CPT       = 'post';
    const INTERVAL  = 8;
}

where the CPT is the custom post type to target and INTERVAL is the maximum allowed days into the future.

We use three classes:

  • Main for the plugin setup.
  • Dropdown that takes care of the custom dropdown box.
  • Restrictions to handle the restrictions when the post data is saved.

Here's the plugin:

<?php
/**
 * Plugin Name: Publish Date Restrictions
 * Plugin URI:  http://wordpress.stackexchange.com/a/205252/26350
 * Author:      Birgir Erlendsson (birgire)
 * Version:     0.0.2
 */

namespace wpse\birgire;

add_action( 'admin_init', function()
{
    if( class_exists( 'wpse\birgire\Main' ) )
    {
        $main = new Main;
        $main->init( 
            new Dropdown, 
            new Restrictions 
        );
    }
} );

class Config
{
    const CPT       = 'post';
    const INTERVAL  = 8;
}

interface MainInterface 
{
    public function init( DropdownInterface $dropdown, RestrictionsInterface $restrictions );
    public function wp_insert_post_data( Array $data, Array $arr );
    public function do_meta_boxes();
    public function modified_meta_box( \WP_Post $post, Array $args );
}

interface DropdownInterface
{
    public function merge( \WP_Post $post, $html = '' );
}

interface RestrictionsInterface
{
    public function post_data( Array $data, Array $arr );

}

class Dropdown implements DropdownInterface
{       
    private function get_options( \WP_Post $post )
    {
        // current post's Y-m-d time
        $post_ymd = ( new \DateTime( $post->post_date ) )->format( 'Y-m-d' );

        // Current local timestamp
        $now = current_time( 'timestamp' );

        // Construct custom +8days "Y-m-d" dropdown options
        $options = '';
        for( $i = 0; $i < (int) Config::INTERVAL; $i++ )
        {
            // Next YMD according to the local time
            $ymd = ( new \DateTime )
                ->setTimestamp( $now )
                ->modify( sprintf( '+%dday', $i ) )
                ->format( 'Y-m-d' );

            // Label
            switch ( $i ) 
            {
                case 0:
                    $label = __( 'Today' );
                    break;
                case 1:
                    $label = __( 'Tomorrow' );
                    break;
                default:
                    $label = $ymd;
            }

            // Options
            $options .= sprintf( 
                '<option value="%s"%s>%s</option>', 
                $ymd, 
                selected( $ymd, $post_ymd, 0 ),
                $label 
            );

        } // end of for loop

        return $options;
    }

    private function get_style()
    {
        // Hide the current "Y, m, d" inputs 
        // Note: We can't remove it with preg_replace, because of Javascript checks
        return '
            <style>
                .timestamp-wrap label:nth-child(5),
                .timestamp-wrap label:nth-child(2),
                .timestamp-wrap label:nth-child(3),
                .timestamp-wrap label:nth-child(4) {
                    display:none !important;
                }
            </style>
        ';
    }

    private function get_html( \WP_Post $post )
    {
        // Construct custom +8days "Y-m-d" dropdown 
        return sprintf(
            '<label><span class="screen-reader-text">YMD</span>
                <select id="wpse_ymd" name="wpse_ymd">
                    <option value="">Select</option> %s     
                </select>
            </label>
            %s',
            $this->get_options( $post ),
            $this->get_style()
        );  
    }   

    public function merge( \WP_Post $post, $html = '' )
    {
        // Create a +8days "Y-m-d" dropdown 
        $dropdown = $this->get_html( $post );

        // Inject into the metabox HTML
        $from = '<label><span class="screen-reader-text">Month</span>';
        $to  = $dropdown . $from;
        return str_replace( $from, $to, $html );
    }
}


class Restrictions implements RestrictionsInterface
{
    public function post_data( Array $data, Array $arr )
    {
        // Target the corresponding post type when the restricted 'wpse_ymd' is posted
        if(     
            ! empty( $data['post_type'] ) 
            && Config::CPT !== $data['post_type'] 
        )
            return $data;   

        // input date is valid
        $valid = false;

        // Current local datetime object
        $local = ( new \DateTime )->setTimestamp( current_time( 'timestamp' ) );

        // Process the selected custom dropdown date     
        if( ! empty( $arr['wpse_ymd'] ) ) 
        {
            // Create datetime objects
            $input_date_obj = ( new \DateTime )->createFromFormat( 'Y-m-d', $arr['wpse_ymd']   );   
            $post_date_obj  = ( new \DateTime )->createFromFormat( 'Y-m-d H:i:s', $data['post_date'] );

            // If valid date and not too far into the future!
            if(    $this->is_valid_date( $input_date_obj )
                && (int) Config::INTERVAL  >= $this->signed_diff_in_days( $local, $input_date_obj )
            ) {
                // Create mysql date strings
                $new_post_date_obj = $post_date_obj->setDate( 
                    $input_date_obj->format( 'Y' ), 
                    $input_date_obj->format( 'm' ), 
                    $input_date_obj->format( 'd' )
                );

                // Override current post_date and post_date_gmt 
                $data['post_date']      = $new_post_date_obj->format( 'Y-m-d H:i:s' );
                $data['post_date_gmt']  = $new_post_date_obj->setTimezone( new \DateTimeZone( 'GMT' ) )->format( 'Y-m-d H:i:s' );

                // Set status to 'future' if > now
                if( $new_post_date_obj->format( 'timestamp' ) > $now )
                    $data['post_status'] = 'future';

                $valid = true;
            }
        }

        // Set the post date to the current time, 
        // if user selected a date too far into the future
        if( ! $valid )
        {
            $data['post_date']      = $local->format( 'Y-m-d H:i:s' );
            $data['post_date_gmt']  = $local->setTimezone( new \DateTimeZone( 'GMT' ) )->format( 'Y-m-d H:i:s' );           
        }

        return $data;
    }

    private function is_valid_date( \DateTime $obj )
    {
        return 
               is_a( $obj, '\DateTime' ) 
            && 0 === $obj->getLastErrors()['error_count']
            && 0 === $obj->getLastErrors()['warning_count'];
    }

    private function signed_diff_in_days( \DateTime $dt1, \DateTime $dt2 )
    {
        $dt = $dt1->diff( $dt2 );

        // Calculate the number of days (both positive or negativve) 
        // See more here http://stackoverflow.com/a/22967760/2078474
        return $dt->days * ( $dt->invert ? -1 : 1 );        
    }

}

class Main implements MainInterface
{
    private $dropdown;
    private $restrictions;

    public function init( DropdownInterface $dropdown, RestrictionsInterface $restrictions  )
    {
        $this->restrictions  = $restrictions;
        $this->dropdown      = $dropdown;

        add_action( 'do_meta_boxes',        [ $this, 'do_meta_boxes' ] );       
        add_filter( 'wp_insert_post_data',  [ $this, 'wp_insert_post_data'], 10, 2 );
    }

    public function wp_insert_post_data( Array $data, Array $arr )
    {
        return $this->restrictions->post_data( $data, $arr );
    }

    public function do_meta_boxes()
    {
        // Remove submitdiv meta-box
        remove_meta_box( 
            'submitdiv', 
            sanitize_key( Config::CPT ), 
            'side' 
        );

        // Add it again, modified
        add_meta_box( 
            'submitdiv', 
            __( 'Publish' ), 
            [ $this, 'modified_meta_box' ], 
            sanitize_key( Config::CPT ), 
            'side', 
            'high' 
        );
    }

    public function modified_meta_box( \WP_Post $post, Array $args )
    {
        // Inject our custom +8days "Y-m-d" dropdown
        echo $this->dropdown->merge( 
            $post,
            $this->get_meta_box( $post, $args )  // Grab the current submit box
        );      
    }

    private function get_meta_box( \WP_Post $post, Array $args )
    {
        ob_start();
        post_submit_meta_box( $post, $args = [] );
        return ob_get_clean();      
    }


} // end class
  • Quite an interesting approach ;-) – Pieter Goosen Oct 12 '15 at 9:50
  • 1
    I guess there're many ways to do this and better. It get's rather bloated using the DateTime class, but I decided to use it as an excercise ;-) @PieterGoosen – birgire Oct 12 '15 at 9:54
  • I guess there're many ways to do this and better. Might be yes, but this is still a nice interesting approach, specially the dropdown box. ;-) – Pieter Goosen Oct 12 '15 at 9:58
  • Plugin could not be activated because it triggered a fatal error. Parse error: syntax error, unexpected T_STRING in /home4/abc/public_html/xyz/wp-content/plugins/limit-publish-date.php on line 9 – Pete Oct 14 '15 at 11:01
  • 1
    Upgraded to php 5.6 – Pete Oct 14 '15 at 11:06

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.