Skip to content

Query: Match page and post paths that begin with a slash - #12614

Open
thisismyurl wants to merge 1 commit into
WordPress:trunkfrom
thisismyurl:fix/65671-is-page-leading-slash-path
Open

Query: Match page and post paths that begin with a slash#12614
thisismyurl wants to merge 1 commit into
WordPress:trunkfrom
thisismyurl:fix/65671-is-page-leading-slash-path

Conversation

@thisismyurl

Copy link
Copy Markdown

WP_Query::is_page() and WP_Query::is_single() document their parameter as accepting a path, and guard the path branch with:

if ( ! strpos( $pagepath, '/' ) ) {
    continue;
}

strpos() returns 0 when the slash is the first character, so ! strpos() is true for a path like /foo/bar and the path is skipped without ever being checked. get_page_by_path(), which does the actual lookup, trims leading and trailing slashes, so that is input it already accepts. The result is that is_page( '/foo/bar' ) returns false on the very page it names, silently.

This switches both guards to str_contains(), which is polyfilled in core for PHP < 8.0 and already used elsewhere in class-wp-query.php.

Behaviour is identical for every other input. The affected set is any path whose first character is a slash, which is worth stating plainly because it is broader than the nested case that prompted the ticket: a single-segment path like /about also starts matching, since get_page_by_path() trims it to about and resolves it correctly.

Input Before After
about skipped skipped
foo/bar checked checked
foo/bar/ checked checked
/foo/bar skipped checked
/about skipped checked

The degenerate inputs / and // now reach get_page_by_path(), which trims them to an empty string and finds nothing. The result is still false, at the cost of one query that previously short-circuited. I mention it for completeness rather than because it changes anything observable.

Two regression tests sit beside the existing test_is_page_with_parent() and test_is_single_with_parent() and reuse their fixtures, one per changed line, each asserting both the slashless and leading-slash forms. Both fail on trunk and pass with the change.

One disclosure on verification: this box has no Docker/wp-env, so I could not run the full PHPUnit suite locally. I confirmed the behaviour difference with a standalone harness across the inputs in the table above, checked that get_page_by_path() trims the leading slash and that nothing upstream normalises it first, and ran php -l plus PHPCS (WordPress-Core, 0 errors on both changed files, no new warnings on the changed lines). I would appreciate a CI run confirming the tests.

Trac ticket: https://core.trac.wordpress.org/ticket/65671

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Helping locate the faulty guard and cross-check it against get_page_by_path() and the documented parameter. I reviewed the change and take responsibility for the final code.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

WP_Query::is_page() and WP_Query::is_single() guard their path check with
! strpos( $path, '/' ). strpos() returns 0 when the slash is the first
character, so ! strpos() evaluates to true for a path such as '/foo/bar'
and the path is skipped without ever being checked.

get_page_by_path(), which performs the actual lookup, trims leading and
trailing slashes, so a leading-slash path is input it already accepts, and
the parameter is documented as accepting a path. The result is that
is_page( '/foo/bar' ) returns false on the very page it names.

Use str_contains() instead. Behaviour is identical for every other input;
only a path whose first character is a slash changes, from silently skipped
to correctly checked.

Props thisismyurl.
See #65671.
Copilot AI review requested due to automatic review settings July 20, 2026 13:54
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props thisismyurl.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a bug in WP_Query::is_page() and WP_Query::is_single() where paths beginning with / (e.g. '/foo/bar') were incorrectly skipped due to ! strpos(...) treating a 0 return value as falsy. The change uses str_contains() (already used elsewhere in class-wp-query.php and polyfilled in core) so leading-slash paths are evaluated correctly, and adds regression tests for both conditionals.

Changes:

  • Replace ! strpos( $path, '/' ) guards with ! str_contains( $path, '/' ) in WP_Query::is_page() and WP_Query::is_single().
  • Add PHPUnit regression tests confirming leading-slash paths (and the existing slashless variants) match as expected for hierarchical posts and pages.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/wp-includes/class-wp-query.php Fixes conditional path handling by switching to str_contains() so leading-slash paths are no longer skipped.
tests/phpunit/tests/query/conditionals.php Adds regression tests ensuring is_single() / is_page() match hierarchical paths with and without a leading slash.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants