Make WordPress Core


Ignore:
Timestamp:
10/16/2014 08:11:13 PM (11 years ago)
Author:
boonebgorges
Message:

Throw notices when invalid date values are passed to WP_Date_Query.

_doing_it_wrong() notices are now generated when passing out-of-range values
(month=13) or invalid dates (2014-02-29).

Includes unit tests.

Props ChriCo.
Fixes #25834.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/date.php

    r29923 r29925  
    66 * to filter their results by date columns, by generating `WHERE` subclauses to be attached
    77 * to the primary SQL query string.
     8 *
     9 * Attempting to filter by an invalid date value (eg month=13) will generate SQL that will
     10 * return no results. In these cases, a _doing_it_wrong() error notice is also thrown.
     11 * See {@link WP_Date_Query::validate_date_values()}.
    812 *
    913 * @link http://codex.wordpress.org/Function_Reference/WP_Query Codex page.
     
    202206        }
    203207
     208        // Validate the dates passed in the query.
     209        if ( $this->is_first_order_clause( $queries ) ) {
     210            $this->validate_date_values( $queries );
     211        }
     212
    204213        foreach ( $queries as $key => $q ) {
    205214            if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) {
     
    243252
    244253        return $this->compare;
     254    }
     255
     256    /**
     257     * Validates the given date_query values and triggers errors if something is not valid.
     258     *
     259     * Note that date queries with invalid date ranges are allowed to
     260     * continue (though of course no items will be found for impossible dates).
     261     * This method only generates debug notices for these cases.
     262     *
     263     * @since  4.1.0
     264     * @access public
     265     *
     266     * @param  array $date_query The date_query array.
     267     * @return bool  True if all values in the query are valid, false if one or more fail.
     268     */
     269    public function validate_date_values( $date_query = array() ) {
     270        if ( empty( $date_query ) ) {
     271            return false;
     272        }
     273
     274        $valid = true;
     275
     276        /*
     277         * Validate 'before' and 'after' up front, then let the
     278         * validation routine continue to be sure that all invalid
     279         * values generate errors too.
     280         */
     281        if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ){
     282            $valid = $this->validate_date_values( $date_query['before'] );
     283        }
     284
     285        if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ){
     286            $valid = $this->validate_date_values( $date_query['after'] );
     287        }
     288
     289        // Message template for the min-max-check.
     290        /* translators: Date query invalid date message: 1: invalid value, 2: type of value, 3: minimum valid value, 4: maximum valid value */
     291        $min_max_msg = __( 'Invalid value <code>%1$s</code> for <strong>%2$s</strong>. Excepted value should between <code>%3$d</code> and </code>%4$d</code>.' );
     292
     293        // Array containing all min-max checks.
     294        $min_max_checks = array();
     295
     296        // Days per year.
     297        if ( array_key_exists( 'year', $date_query ) ) {
     298            // If a year exists in the date query, we can use it to get the days.
     299            $max_days_of_year = date( 'z', mktime( 0, 0, 0, 12, 31, $date_query['year'] ) ) + 1;
     300        } else {
     301            // otherwise we use the max of 366 (leap-year)
     302            $max_days_of_year = 366;
     303        }
     304
     305        $min_max_checks['dayofyear'] = array(
     306            'min' => 1,
     307            'max' => $max_days_of_year
     308        );
     309
     310        // Days per week.
     311        $min_max_checks['dayofweek'] = array(
     312            'min' => 1,
     313            'max' => 7
     314        );
     315
     316        // Months per year.
     317        $min_max_checks['month'] = array(
     318            'min' => 1,
     319            'max' => 12
     320        );
     321
     322        // Weeks per year.
     323        if ( array_key_exists( 'year', $date_query ) ) {
     324            // If we have a specific year, use it to calculate number of weeks.
     325            $date = new DateTime();
     326            $date->setISODate( $date_query['year'], 53 );
     327            $week_count = $date->format( "W" ) === "53" ? 53 : 52;
     328
     329        } else {
     330            // Otherwise set the week-count to a maximum of 53.
     331            $week_count = 53;
     332        }
     333
     334        $min_max_checks['week'] = array(
     335            'min' => 1,
     336            'max' => $week_count
     337        );
     338
     339        // Days per month.
     340        $min_max_checks['day'] = array(
     341            'min' => 1,
     342            'max' => 31
     343        );
     344
     345        // Hours per day.
     346        $min_max_checks['hour'] = array(
     347            'min' => 1,
     348            'max' => 23
     349        );
     350
     351        // Minutes per hour.
     352        $min_max_checks['minute'] = array(
     353            'min' => 0,
     354            'max' => 59
     355        );
     356
     357        // Seconds per minute.
     358        $min_max_checks['second'] = array(
     359            'min' => 0,
     360            'max' => 59
     361        );
     362
     363        // Concatenate and throw a notice for each invalid value.
     364        foreach ( $min_max_checks as $key => $check ) {
     365            if ( ! array_key_exists( $key, $date_query ) ) {
     366                continue;
     367            }
     368
     369            $is_between = $date_query[ $key ] >= $check['min'] && $date_query[ $key ] <= $check['max'];
     370
     371            if ( ! $is_between ) {
     372                $error = sprintf(
     373                    $min_max_msg,
     374                    esc_html( $date_query[ $key ] ),
     375                    $key,
     376                    $check['min'],
     377                    $check['max']
     378                );
     379
     380                _doing_it_wrong( __CLASS__, $error, '4.1.0' );
     381
     382                $valid = false;
     383            }
     384        }
     385
     386        // If we already have invalid date messages, don't bother running through checkdate().
     387        if ( ! $valid ) {
     388            return $valid;
     389        }
     390
     391        $day_month_year_error_msg = '';
     392
     393        $day_exists   = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] );
     394        $month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] );
     395        $year_exists  = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] );
     396
     397        if ( $day_exists && $month_exists && $year_exists ) {
     398            // 1. Checking day, month, year combination.
     399            if ( ! checkdate( $date_query['month'], $date_query['day'], $date_query['year'] ) ) {
     400                /* translators: 1: year, 2: month, 3: day of month */
     401                $day_month_year_error_msg = sprintf(
     402                    __( 'The following values do not describe a valid date: year <code>%1$s</code>, month <code>%2$s</code>, day <code>%3$s</code>.' ),
     403                    esc_html( $date_query['year'] ),
     404                    esc_html( $date_query['month'] ),
     405                    esc_html( $date_query['day'] )
     406                );
     407
     408                $valid = false;
     409            }
     410
     411        } else if ( $day_exists && $month_exists ) {
     412            /*
     413             * 2. checking day, month combination
     414             * We use 2012 because, as a leap year, it's the most permissive.
     415             */
     416            if ( ! checkdate( $date_query['month'], $date_query['day'], 2012 ) ) {
     417                /* translators: 1: month, 2: day of month */
     418                $day_month_year_error_msg = sprintf(
     419                    __( 'The following values do not describe a valid date: month <code>%1$d</code>, day <code>%2$d</code>.' ),
     420                    esc_html( $date_query['month'] ),
     421                    esc_html( $date_query['day'] )
     422                );
     423
     424                $valid = false;
     425            }
     426        }
     427
     428        if ( ! empty( $day_month_year_error_msg ) ) {
     429            _doing_it_wrong( __CLASS__, $day_month_year_error_msg, '4.1.0' );
     430        }
     431
     432        return $valid;
    245433    }
    246434
Note: See TracChangeset for help on using the changeset viewer.