Make WordPress Core

Changeset 32648


Ignore:
Timestamp:
05/29/2015 01:09:26 PM (9 years ago)
Author:
boonebgorges
Message:

When parsing what appears to be a date archive request, check for a post with a clashing permalink before resolving to the archive.

A URL like example.com/2015/05/15/ generally resolves to the May 15, 2015 date
archive. But in certain cases, it could also be the permalink of a post with
the slug '2015'. When a conflict of this sort is detected, resolve to the post
instead of the archive.

URL conflicts of this sort should no longer occur for new posts; see [32647].

Props valendesigns, boonebgorges, Denis-de-Bernardy.
Fixes #5305.

Location:
trunk
Files:
1 added
2 edited

Legend:

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

    r32551 r32648  
    308308            }
    309309        }
     310
     311        // Resolve conflicts between posts with numeric slugs and date archive queries.
     312        $this->query_vars = wp_resolve_numeric_slug_conflicts( $this->query_vars );
    310313
    311314        foreach ( (array) $this->private_query_vars as $var) {
  • trunk/src/wp-includes/rewrite.php

    r32622 r32648  
    282282    }
    283283    return $base;
     284}
     285
     286
     287/**
     288 * Resolve numeric slugs that collide with date permalinks.
     289 *
     290 * Permalinks of posts with numeric slugs can sometimes look to `WP_Query::parse_query()` like a date archive,
     291 * as when your permalink structure is `/%year%/%postname%/` and a post with post_name '05' has the URL
     292 * `/2015/05/`. This function detects conflicts of this type and resolves them in favor of the post permalink.
     293 *
     294 * Note that, since 4.3.0, `wp_unique_post_slug()` prevents the creation of post slugs that would result in a date
     295 * archive conflict. The resolution performed in this function is primarily for legacy content, as well as cases when
     296 * the admin has changed the site's permalink structure in a way that introduces URL conflicts.
     297 *
     298 * @since 4.3.0
     299 *
     300 * @param array $query_vars Query variables for setting up the loop, as determined in `WP::parse_request()`.
     301 * @return array Returns the original array of query vars, with date/post conflicts resolved.
     302 */
     303function wp_resolve_numeric_slug_conflicts( $query_vars = array() ) {
     304    if ( ! isset( $query_vars['year'] ) && ! isset( $query_vars['monthnum'] ) && ! isset( $query_vars['day'] ) ) {
     305        return $query_vars;
     306    }
     307
     308    // Identify the 'postname' position in the permastruct array.
     309    $permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
     310    $postname_index = array_search( '%postname%', $permastructs );
     311
     312    if ( false === $postname_index ) {
     313        return $query_vars;
     314    }
     315
     316    /*
     317     * A numeric slug could be confused with a year, month, or day, depending on position. To account for
     318     * the possibility of post pagination (eg 2015/2 for the second page of a post called '2015'), our
     319     * `is_*` checks are generous: check for year-slug clashes when `is_year` *or* `is_month`, and check
     320     * for month-slug clashes when `is_month` *or* `is_day`.
     321     */
     322    $compare = '';
     323    if ( 0 === $postname_index && ( isset( $query_vars['year'] ) || isset( $query_vars['monthnum'] ) ) ) {
     324        $compare = 'year';
     325    } elseif ( '%year%' === $permastructs[ $postname_index - 1 ] && ( isset( $query_vars['monthnum'] ) || isset( $query_vars['day'] ) ) ) {
     326        $compare = 'monthnum';
     327    } elseif ( '%monthnum%' === $permastructs[ $postname_index - 1 ] && isset( $query_vars['day'] ) ) {
     328        $compare = 'day';
     329    }
     330
     331    if ( ! $compare ) {
     332        return $query_vars;
     333    }
     334
     335    // This is the potentially clashing slug.
     336    $value = $query_vars[ $compare ];
     337
     338    $post = get_page_by_path( $value, OBJECT, 'post' );
     339    if ( ! ( $post instanceof WP_Post ) ) {
     340        return $query_vars;
     341    }
     342
     343    // If the date of the post doesn't match the date specified in the URL, resolve to the date archive.
     344    if ( preg_match( '/^([0-9]{4})\-([0-9]{2})/', $post->post_date, $matches ) && isset( $query_vars['year'] ) && ( 'monthnum' === $compare || 'day' === $compare ) ) {
     345        // $matches[1] is the year the post was published.
     346        if ( intval( $query_vars['year'] ) !== intval( $matches[1] ) ) {
     347            return $query_vars;
     348        }
     349
     350        // $matches[2] is the month the post was published.
     351        if ( 'day' === $compare && isset( $query_vars['monthnum'] ) && intval( $query_vars['monthnum'] ) !== intval( $matches[2] ) ) {
     352            return $query_vars;
     353        }
     354    }
     355
     356    /*
     357     * If the located post contains nextpage pagination, then the URL chunk following postname may be
     358     * intended as the page number. Verify that it's a valid page before resolving to it.
     359     */
     360    $maybe_page = '';
     361    if ( 'year' === $compare && isset( $query_vars['monthnum'] ) ) {
     362        $maybe_page = $query_vars['monthnum'];
     363    } elseif ( 'monthnum' === $compare && isset( $query_vars['day'] ) ) {
     364        $maybe_page = $query_vars['day'];
     365    }
     366
     367    $post_page_count = substr_count( $post->post_content, '<!--nextpage-->' ) + 1;
     368
     369    // If the post doesn't have multiple pages, but a 'page' candidate is found, resolve to the date archive.
     370    if ( 1 === $post_page_count && $maybe_page ) {
     371        return $query_vars;
     372    }
     373
     374    // If the post has multiple pages and the 'page' number isn't valid, resolve to the date archive.
     375    if ( $post_page_count > 1 && $maybe_page > $post_page_count ) {
     376        return $query_vars;
     377    }
     378
     379    // If we've gotten to this point, we have a slug/date clash. First, adjust for nextpage.
     380    if ( '' !== $maybe_page ) {
     381        $query_vars['page'] = intval( $maybe_page );
     382    }
     383
     384    // Next, unset autodetected date-related query vars.
     385    unset( $query_vars['year'] );
     386    unset( $query_vars['monthnum'] );
     387    unset( $query_vars['day'] );
     388
     389    // Then, set the identified post.
     390    $query_vars['name'] = $post->post_name;
     391
     392    // Finally, return the modified query vars.
     393    return $query_vars;
    284394}
    285395
     
    401511                }
    402512            }
     513
     514            // Resolve conflicts between posts with numeric slugs and date archive queries.
     515            $query = wp_resolve_numeric_slug_conflicts( $query );
    403516
    404517            // Do the query
Note: See TracChangeset for help on using the changeset viewer.