Repository: wp-graphql/wp-graphql (language: php)

Issue:
URI Query - Receiving Post for Incorrect Slug Path
### Description

Is there a way to query by URI?
I have attempted this, but I encountered an issue in my React frontend. When I use $slug with /test/, I receive the post as expected. The same happens when I use /news/test/.
However, I only want to receive the post for /news/test/.

### Steps to reproduce

1. Set backend permalink to `/news/%postname%/`
2. Try query
```
query GetPage($slug: String!) {
  nodeByUri(uri: $slug) {
    __typename
    ... on Page {
      id
      title
      slug
    }
    ... on Post {
      id
      title
      slug
    }
    uri
  }
}
```
First 
```
{
  "slug": "test"
}
```

Second

```
{
  "slug": "/news/test"
}
```

### Additional context

![grafik](https://github.com/user-attachments/assets/067f1e10-69a8-4e49-8717-d6b134c8e372)
![grafik](https://github.com/user-attachments/assets/35439f64-2afb-4da3-abd2-50748cd4c672)
![grafik](https://github.com/user-attachments/assets/b9fb519d-cc8b-4566-981f-48af9d1722c3)


### WPGraphQL Version

4.3.1 

### WordPress Version

6.6.2.

### PHP Version

8.2.x

### Additional environment details

Im at the moment testing it local.

### Please confirm that you have searched existing issues in the repo.

- [X] Yes

### Please confirm that you have disabled ALL plugins except for WPGraphQL.

- [X] Yes
- [ ] My issue is with compatibility with a specific WordPress plugin, and I have listed all my installed plugins (and version info) above.


Discussion / hints:
@jasonbahl - Could you please help me? Perhaps I made a mistake. 😄 
@Messa1 WPGraphQL should be resolving uri's the same way that core WordPress would if you visited the uri in a non-headless theme. 

What does WordPress do if you visit `/news/test` and `/test` in the browser?

Does it also resolve to `/news/test` in both cases?
@Messa1 ok, I was actually able to reproduce this and indeed WPGraphQL's behavior does NOT match core WordPress's behavior in a theme. 

I set my permalinks the same as you: 

![CleanShot 2024-12-04 at 09 21 47](https://github.com/user-attachments/assets/b51577df-08cc-4ec1-a327-dc6dc9bf1e29)

I then published a post with the slug "test"

![CleanShot 2024-12-04 at 09 22 21](https://github.com/user-attachments/assets/f6961174-9b76-4dd7-b3ff-1c07a5c994a7)

If I visit the `/news/test/` uri in the browser I get the expected post:

![CleanShot 2024-12-04 at 09 22 45](https://github.com/user-attachments/assets/4c7fd158-2d27-4892-948d-cadc86d1db47)

However, if I visit the `/test/` uri, I get a 404. 

![CleanShot 2024-12-04 at 09 23 01](https://github.com/user-attachments/assets/5a2d9542-e527-4a88-ab21-e47abb235119)


BUT, if I query `nodeByUri( uri: "/test/" ) { ... }`, I get the `/news/test` node instead of a `null`. 

![CleanShot 2024-12-04 at 09 27 55](https://github.com/user-attachments/assets/9224560a-e283-42f7-8a96-fadf2db5e354)

So, this is confirmation that the `nodeByUri` behavior is indeed different than core's resolution of a uri in the browser in a traditional theme setup. 

----

Until we have a fix for this, one suggestion might be to compare the `$uri` with the `node.uri` response, and redirect to the resolved node uri in the client. 

i.e. if you query for `/test` and a node is returned with uri of `/news/test` you could redirect to that uri. 

@jasonbahl Yeah - I got the same behavour ob my system.
![Bildschirmfoto 2024-12-04 um 17 39 00](https://github.com/user-attachments/assets/3f1efa52-472d-46b3-a438-6567e19f78a9)
![Bildschirmfoto 2024-12-04 um 17 39 38](https://github.com/user-attachments/assets/aa4ebefc-9f37-45fd-8aa8-0cfda15f3802)

I could use a redirect, but I suspect the issue might be on my side.
Do you think using a redirect could have any negative effects? Perhaps on SEO or user behavior with direct redirects? 

> Do you think using a redirect could have any negative effects? Perhaps on SEO or user behavior with direct redirects?

Unlikely. Redirects are pretty common. WordPress has plugins like yoast, redirection, etc that all allow for redirects to occur after a request to WordPress has been made. 


@jasonbahl I will use it as a temporary solution. 😄 Hope you got time to find a fix. If you need someone to test something just write.

Fix patch:
diff --git a/src/Data/NodeResolver.php b/src/Data/NodeResolver.php
index 7e0d45ca0..9eeebb0cf 100644
--- a/src/Data/NodeResolver.php
+++ b/src/Data/NodeResolver.php
@@ -21,6 +21,11 @@ class NodeResolver {
 	 */
 	protected $context;
 
+	/**
+	 * @var string
+	 */
+	protected $route;
+
 	/**
 	 * NodeResolver constructor.
 	 *
@@ -31,7 +36,8 @@ class NodeResolver {
 	public function __construct( AppContext $context ) {
 		global $wp;
 		$this->wp               = $wp;
-		$this->wp->matched_rule = Router::$route . '/?$';
+		$this->route            = Router::$route . '/?$';
+		$this->wp->matched_rule = $this->route;
 		$this->context          = $context;
 	}
 
@@ -51,22 +57,6 @@ public function validate_post( WP_Post $post ) {
 			return null;
 		}
 
-		/**
-		 * Disabling the following code for now, since add_rewrite_uri() would cause a request to direct to a different valid permalink.
-		 */
-		/* phpcs:disable
-		if ( ! isset( $this->wp->query_vars['uri'] ) ) {
-			return $post;
-		}
-		$permalink    = get_permalink( $post );
-		$parsed_path  = $permalink ? wp_parse_url( $permalink, PHP_URL_PATH ) : null;
-		$trimmed_path = $parsed_path ? rtrim( ltrim( $parsed_path, '/' ), '/' ) : null;
-		$uri_path     = rtrim( ltrim( $this->wp->query_vars['uri'], '/' ), '/' );
-		if ( $trimmed_path !== $uri_path ) {
-			return null;
-		}
-		phpcs:enable */
-
 		if ( empty( $this->wp->query_vars['uri'] ) ) {
 			return $post;
 		}
@@ -222,6 +212,7 @@ public function resolve_uri( string $uri, $extra_query_vars = '' ) {
 
 		// Resolve Post Objects.
 		if ( $queried_object instanceof WP_Post ) {
+
 			// If Page for Posts is set, we need to return the Page archive, not the page.
 			if ( $query->is_posts_page ) {
 				// If were intentionally querying for a something other than a ContentType, we need to return null instead of the archive.
@@ -243,6 +234,10 @@ public function resolve_uri( string $uri, $extra_query_vars = '' ) {
 				return null;
 			}
 
+			if ( empty( $extra_query_vars ) && isset( $this->wp->query_vars['error'] ) && '404' === $this->wp->query_vars['error'] ) {
+				return null;
+			}
+
 			$post_id = $queried_object->ID;
 
 			$as_preview = false;
@@ -485,7 +480,7 @@ public function parse_request( string $uri, $extra_query_vars = '' ) {
 				}
 			}
 
-			if ( ! empty( $this->wp->matched_rule ) ) {
+			if ( ! empty( $this->wp->matched_rule ) && $this->wp->matched_rule !== $this->route ) {
 				// Trim the query of everything up to the '?'.
 				$query = preg_replace( '!^.+\?!', '', $query );
 


Test patch:
diff --git a/tests/wpunit/NodeByUriTest.php b/tests/wpunit/NodeByUriTest.php
index faddf5483..700daa0b7 100644
--- a/tests/wpunit/NodeByUriTest.php
+++ b/tests/wpunit/NodeByUriTest.php
@@ -2508,4 +2508,74 @@ public function testQueryPostsOfMultiplePostTypesBySameSlug() {
 			]
 		);
 	}
+
+	/**
+	 * @see: https://github.com/wp-graphql/wp-graphql/issues/3240
+	 * @return void
+	 */
+	public function testQueryPostWithCustomPermalinkStructure() {
+
+		$post_id = $this->factory()->post->create([
+			'post_type'   => 'post',
+			'post_status' => 'publish',
+			'post_name'   => 'test-post',
+		]);
+
+		$this->set_permalink_structure( '/news/%postname%/' );
+
+		$query = '
+		query GetNodeByUri($uri: String!) {
+		  nodeByUri(uri: $uri) {
+		    __typename
+		    ... on Page {
+		      id
+		      title
+		      slug
+		    }
+		    ... on Post {
+		      id
+		      title
+		      slug
+		    }
+		    uri
+		  }
+		}
+		';
+
+		$actual = $this->graphql(
+			[
+				'query'     => $query,
+				'variables' => [
+					'uri' => '/news/test-post',
+				],
+			]
+		);
+
+		$this->assertArrayNotHasKey( 'errors', $actual );
+		$this->assertQuerySuccessful( $actual, [
+			$this->expectedField( 'nodeByUri.__typename', 'Post' ),
+			$this->expectedField( 'nodeByUri.slug', 'test-post' ),
+		] );
+
+		// query again with just the slug instead of full uri
+		$query2 = $this->graphql(
+			[
+				'query'     => $query,
+				'variables' => [
+					'uri' => 'test-post',
+				],
+			]
+		);
+
+		// The query should succeed, but should return null as no post should have been found.
+		// WordPress will 404 here as there is no post at `/test` due to the permalink configuration,
+		// so we should return null
+		$this->assertArrayNotHasKey( 'errors', $query2 );
+		$this->assertQuerySuccessful( $query2, [
+			$this->expectedField( 'nodeByUri', self::IS_NULL ),
+		] );
+
+		wp_delete_post( $post_id, true );
+
+	}
 }