Skip to content

Commit f997e86

Browse files
committed
Code Quality: Normalize object as type being supplied as comment, post, site, term, and user.
When constructing these objects, the specific instance type is redundantly used in a union with a generic `object`. A generic object can be passed directly from database row result. This also hardens `get_post()` to account for passing an object that lacks an `ID` property. Similarly, `sanitize_term()` is hardened to account for an object lacking a `term_id` property. Comprehensive unit tests are added for `get_post()` and `sanitize_term()`. Developed in #11096 Props westonruter, apermo. See #64238, #64225. git-svn-id: https://develop.svn.wordpress.org/trunk@61789 602fd350-edb4-49c9-b593-d223f7449a82
1 parent c74d7be commit f997e86

9 files changed

Lines changed: 425 additions & 10 deletions

File tree

src/wp-includes/class-wp-comment.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public static function get_instance( $id ) {
210210
*
211211
* @since 4.4.0
212212
*
213-
* @param WP_Comment $comment Comment object.
213+
* @param object $comment Comment object.
214214
*/
215215
public function __construct( $comment ) {
216216
foreach ( get_object_vars( $comment ) as $key => $value ) {

src/wp-includes/class-wp-post.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public static function get_instance( $post_id ) {
260260
*
261261
* @since 3.5.0
262262
*
263-
* @param WP_Post|object $post Post object.
263+
* @param object $post Post object.
264264
*/
265265
public function __construct( $post ) {
266266
foreach ( get_object_vars( $post ) as $key => $value ) {

src/wp-includes/class-wp-site.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public static function get_instance( $site_id ) {
190190
*
191191
* @since 4.5.0
192192
*
193-
* @param WP_Site|object $site A site object.
193+
* @param object $site A site object.
194194
*/
195195
public function __construct( $site ) {
196196
foreach ( get_object_vars( $site ) as $key => $value ) {

src/wp-includes/class-wp-term.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public static function get_instance( $term_id, $taxonomy = null ) {
192192
*
193193
* @since 4.4.0
194194
*
195-
* @param WP_Term|object $term Term object.
195+
* @param object $term Term object.
196196
*/
197197
public function __construct( $term ) {
198198
foreach ( get_object_vars( $term ) as $key => $value ) {

src/wp-includes/class-wp-user.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ class WP_User {
121121
*
122122
* @global wpdb $wpdb WordPress database abstraction object.
123123
*
124-
* @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB.
125-
* @param string $name Optional. User's username
126-
* @param int $site_id Optional Site ID, defaults to current site.
124+
* @param int|string|object $id User's ID, a WP_User object, or a user object from the DB.
125+
* @param string $name Optional. User's username
126+
* @param int $site_id Optional Site ID, defaults to current site.
127127
*/
128128
public function __construct( $id = 0, $name = '', $site_id = 0 ) {
129129
global $wpdb;

src/wp-includes/post.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,7 @@ function get_extended( $post ) {
11351135
*
11361136
* @global WP_Post $post Global post object.
11371137
*
1138-
* @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values
1138+
* @param int|object|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values
11391139
* return the current global post inside the loop. A numerically valid post ID that
11401140
* points to a non-existent post returns `null`. Defaults to global $post.
11411141
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
@@ -1159,8 +1159,10 @@ function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
11591159
$_post = new WP_Post( $_post );
11601160
} elseif ( 'raw' === $post->filter ) {
11611161
$_post = new WP_Post( $post );
1162-
} else {
1162+
} elseif ( isset( $post->ID ) ) {
11631163
$_post = WP_Post::get_instance( $post->ID );
1164+
} else {
1165+
$_post = null;
11641166
}
11651167
} else {
11661168
$_post = WP_Post::get_instance( $post );

src/wp-includes/taxonomy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ function sanitize_term( $term, $taxonomy, $context = 'display' ) {
17231723

17241724
$do_object = is_object( $term );
17251725

1726-
$term_id = $do_object ? $term->term_id : ( $term['term_id'] ?? 0 );
1726+
$term_id = $do_object ? ( $term->term_id ?? 0 ) : ( $term['term_id'] ?? 0 );
17271727

17281728
foreach ( (array) $fields as $field ) {
17291729
if ( $do_object ) {
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
<?php
2+
3+
/**
4+
* @group post
5+
* @group template
6+
*
7+
* @covers ::get_post
8+
*/
9+
class Tests_Post_GetPost extends WP_UnitTestCase {
10+
11+
private static int $post_id;
12+
13+
public static function set_up_before_class(): void {
14+
parent::set_up_before_class();
15+
$post_id = self::factory()->post->create();
16+
assert( is_int( $post_id ) );
17+
self::$post_id = $post_id;
18+
19+
global $wpdb;
20+
$wpdb->update(
21+
$wpdb->posts,
22+
array( 'post_title' => 'Test <script>console.log("Hello, World!")</script> Title' ),
23+
array( 'ID' => self::$post_id )
24+
);
25+
clean_post_cache( self::$post_id );
26+
}
27+
28+
public function tear_down(): void {
29+
$GLOBALS['post'] = null;
30+
parent::tear_down();
31+
}
32+
33+
/**
34+
* Tests that the global $post is returned.
35+
*
36+
* @ticket 64238
37+
*/
38+
public function test_get_post_global(): void {
39+
global $post;
40+
$post = $this->get_test_post_instance();
41+
$this->assertSame( $post, get_post() );
42+
$this->assertSame( $post, get_post( null ) );
43+
$this->assertSame( $post, get_post( 0 ) );
44+
$this->assertSame( $post, get_post( '0' ) ); // @phpstan-ignore argument.type (Testing another value that is empty.)
45+
$this->assertSame( $post, get_post( '' ) ); // @phpstan-ignore argument.type (Testing another value that is empty.)
46+
$this->assertSame( $post, get_post( false ) ); // @phpstan-ignore argument.type (Testing another value that is empty.)
47+
$this->assertSame( $post->to_array(), get_post( null, ARRAY_A ) );
48+
$this->assertSame( array_values( $post->to_array() ), get_post( null, ARRAY_N ) );
49+
}
50+
51+
/**
52+
* Tests inputs and outputs.
53+
*
54+
* @ticket 64238
55+
* @dataProvider data_provider_to_test_get_post
56+
*
57+
* @param callable(): mixed $input Input to get_post.
58+
* @param string $output The required return type.
59+
* @param string $filter Type of filter to apply.
60+
* @param callable(): (int|null) $expected_id Expected ID of the returned post, or null if expecting null.
61+
*/
62+
public function test_get_post( callable $input, string $output, string $filter, callable $expected_id ): void {
63+
$input_val = $input();
64+
$expected_id_val = $expected_id();
65+
66+
$post = get_post( $input_val, $output, $filter );
67+
68+
if ( null === $expected_id_val ) {
69+
$this->assertNull( $post );
70+
return;
71+
}
72+
73+
if ( ARRAY_A === $output ) {
74+
$this->assertIsArray( $post );
75+
$this->assertArrayHasKey( 'ID', $post );
76+
$this->assertSame( $expected_id_val, $post['ID'] );
77+
$this->assertArrayHasKey( 'filter', $post );
78+
$this->assertSame( $filter, $post['filter'] );
79+
} elseif ( ARRAY_N === $output ) {
80+
$this->assertIsArray( $post );
81+
$this->assertContains( $expected_id_val, $post );
82+
$this->assertContains( $filter, $post );
83+
} else {
84+
$this->assertInstanceOf( WP_Post::class, $post );
85+
86+
if ( 'raw' === $filter && $input_val instanceof WP_Post ) {
87+
$this->assertSame( $input_val, $post, 'Should return the same instance when input is a WP_Post and filter is raw.' );
88+
}
89+
90+
$this->assertSame( $expected_id_val, $post->ID );
91+
$this->assertSame( $filter, $post->filter );
92+
}
93+
}
94+
95+
/**
96+
* Tests that sanitize_post() is called as expected.
97+
*
98+
* @ticket 64238
99+
* @dataProvider data_provider_to_test_get_post_sanitization
100+
*
101+
* @param string $filter Type of filter to apply.
102+
* @param string $expected Expected sanitized post title.
103+
*/
104+
public function test_get_post_sanitization( string $filter, string $expected ): void {
105+
$post = get_post( self::$post_id, OBJECT, $filter );
106+
107+
$this->assertInstanceOf( WP_Post::class, $post );
108+
$this->assertSame( $expected, $post->post_title );
109+
$this->assertSame( $filter, $post->filter );
110+
}
111+
112+
/**
113+
* Data provider for test_get_post_sanitization.
114+
*
115+
* @return array<string, array{
116+
* filter: string,
117+
* expected: string,
118+
* }>
119+
*/
120+
public function data_provider_to_test_get_post_sanitization(): array {
121+
return array(
122+
'Raw filter' => array(
123+
'filter' => 'raw',
124+
'expected' => 'Test <script>console.log("Hello, World!")</script> Title',
125+
),
126+
'Edit filter' => array(
127+
'filter' => 'edit',
128+
'expected' => 'Test &lt;script&gt;console.log(&quot;Hello, World!&quot;)&lt;/script&gt; Title',
129+
),
130+
'Display filter' => array(
131+
'filter' => 'display',
132+
'expected' => 'Test <script>console.log("Hello, World!")</script> Title',
133+
),
134+
'Attribute filter' => array(
135+
'filter' => 'attribute',
136+
'expected' => 'Test &lt;script&gt;console.log(&quot;Hello, World!&quot;)&lt;/script&gt; Title',
137+
),
138+
'JS filter' => array(
139+
'filter' => 'js',
140+
'expected' => 'Test &lt;script&gt;console.log(&quot;Hello, World!&quot;)&lt;/script&gt; Title',
141+
),
142+
);
143+
}
144+
145+
/**
146+
* Data provider for test_get_post.
147+
*
148+
* @return array<string, array{
149+
* input: Closure(): mixed,
150+
* output: string,
151+
* filter: string,
152+
* expected_id: Closure(): (int|null),
153+
* }>
154+
*/
155+
public function data_provider_to_test_get_post(): array {
156+
return array(
157+
'Valid ID' => array(
158+
'input' => fn() => self::$post_id,
159+
'output' => OBJECT,
160+
'filter' => 'raw',
161+
'expected_id' => fn() => self::$post_id,
162+
),
163+
'WP_Post instance' => array(
164+
'input' => fn() => $this->get_test_post_instance(),
165+
'output' => OBJECT,
166+
'filter' => 'raw',
167+
'expected_id' => fn() => self::$post_id,
168+
),
169+
'Valid numeric string ID' => array(
170+
'input' => fn() => (string) self::$post_id,
171+
'output' => OBJECT,
172+
'filter' => 'raw',
173+
'expected_id' => fn() => self::$post_id,
174+
),
175+
'Object with raw filter' => array(
176+
'input' => fn() => (object) array(
177+
'ID' => self::$post_id,
178+
'filter' => 'raw',
179+
),
180+
'output' => OBJECT,
181+
'filter' => 'raw',
182+
'expected_id' => fn() => self::$post_id,
183+
),
184+
'Object with non-raw filter and ID' => array(
185+
'input' => fn() => (object) array(
186+
'ID' => self::$post_id,
187+
'filter' => 'edit',
188+
),
189+
'output' => OBJECT,
190+
'filter' => 'raw',
191+
'expected_id' => fn() => self::$post_id,
192+
),
193+
'Object with non-raw filter and NO ID' => array(
194+
'input' => fn() => (object) array( 'filter' => 'edit' ),
195+
'output' => OBJECT,
196+
'filter' => 'raw',
197+
'expected_id' => fn() => null,
198+
),
199+
'Invalid ID' => array(
200+
'input' => fn() => 9999999,
201+
'output' => OBJECT,
202+
'filter' => 'raw',
203+
'expected_id' => fn() => null,
204+
),
205+
'ARRAY_A output' => array(
206+
'input' => fn() => self::$post_id,
207+
'output' => ARRAY_A,
208+
'filter' => 'raw',
209+
'expected_id' => fn() => self::$post_id,
210+
),
211+
'ARRAY_N output' => array(
212+
'input' => fn() => self::$post_id,
213+
'output' => ARRAY_N,
214+
'filter' => 'raw',
215+
'expected_id' => fn() => self::$post_id,
216+
),
217+
'Display filter' => array(
218+
'input' => fn() => self::$post_id,
219+
'output' => OBJECT,
220+
'filter' => 'display',
221+
'expected_id' => fn() => self::$post_id,
222+
),
223+
'Empty input and no global post' => array(
224+
'input' => fn() => null,
225+
'output' => OBJECT,
226+
'filter' => 'raw',
227+
'expected_id' => fn() => null,
228+
),
229+
'0 input and no global post' => array(
230+
'input' => fn() => 0,
231+
'output' => OBJECT,
232+
'filter' => 'raw',
233+
'expected_id' => fn() => null,
234+
),
235+
'Non-numeric string' => array(
236+
'input' => fn() => 'not-a-post-id',
237+
'output' => OBJECT,
238+
'filter' => 'raw',
239+
'expected_id' => fn() => null,
240+
),
241+
'Boolean false' => array(
242+
'input' => fn() => false,
243+
'output' => OBJECT,
244+
'filter' => 'raw',
245+
'expected_id' => fn() => null,
246+
),
247+
'Object with invalid ID' => array(
248+
'input' => fn() => (object) array(
249+
'ID' => 9999999,
250+
'filter' => 'edit',
251+
),
252+
'output' => OBJECT,
253+
'filter' => 'raw',
254+
'expected_id' => fn() => null,
255+
),
256+
'Object with no filter' => array(
257+
'input' => fn() => (object) array(
258+
'ID' => 123,
259+
'post_title' => 'Test',
260+
'extra' => 'prop',
261+
),
262+
'output' => OBJECT,
263+
'filter' => 'raw',
264+
'expected_id' => fn() => 123,
265+
),
266+
'Invalid output type' => array(
267+
'input' => fn() => self::$post_id,
268+
'output' => 'invalid',
269+
'filter' => 'raw',
270+
'expected_id' => fn() => self::$post_id,
271+
),
272+
'Invalid output value "WP_Post"' => array(
273+
'input' => fn() => self::$post_id,
274+
'output' => 'WP_Post',
275+
'filter' => 'raw',
276+
'expected_id' => fn() => self::$post_id,
277+
),
278+
);
279+
}
280+
281+
/**
282+
* Gets a test post instance.
283+
*
284+
* @return WP_Post Post object.
285+
*/
286+
private function get_test_post_instance(): WP_Post {
287+
$post = WP_Post::get_instance( self::$post_id );
288+
$this->assertInstanceOf( WP_Post::class, $post );
289+
return $post;
290+
}
291+
}

0 commit comments

Comments
 (0)