Skip to content

Commit 313ec71

Browse files
authored
Add conditional return type for sanitize_post_field (#383)
1 parent 8fb9fdc commit 313ec71

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

functionMap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
'sanitize_bookmark_field' => ['array<int, int>|int|string', 'field' => "'link_id'|'link_url'|'link_name'|'link_image'|'link_target'|'link_description'|'link_visible'|'link_owner'|'link_rating'|'link_updated'|'link_rel'|'link_notes'|'link_rss'|'link_category'"],
135135
'sanitize_category' => ['T', '@phpstan-template' => 'T of array|object', 'category' => 'T'],
136136
'sanitize_post' => ['(T is \WP_Post ? \WP_Post : (T is object ? object : (T is array ? array : T)))', '@phpstan-template T' => 'of mixed', 'post' => 'T'],
137+
'sanitize_post_field' => ["(\$field is 'ID'|'post_parent'|'menu_order' ? (T is int ? T : int) : (\$field is 'ancestors' ? (T is array<int<0, max>>|list<int<0, max>> ? T : (T is list ? list<int<0, max>> : array<int<0, max>>)) : (\$context is 'raw' ? T : (\$context is 'attribute'|'js' ? string : (\$context is 'edit' ? (\$field is not 'post_content' ? string : mixed) : mixed)))))", '@phpstan-template T' => 'of mixed', 'value' => 'T'],
137138
'sanitize_sql_orderby' => ['(T is non-falsy-string ? T|false : false)', '@phpstan-template T' => 'of string', 'orderby' => 'T'],
138139
'sanitize_term' => ['T', '@phpstan-template' => 'T of array|object', 'term' => 'T'],
139140
'sanitize_term_field' => ["(\$field is 'parent'|'term_id'|'count'|'term_group'|'term_taxonomy_id'|'object_id' ? int<0, max> : (\$context is 'raw' ? T : (\$context is 'attribute'|'edit'|'js' ? string : mixed)))", '@phpstan-template T' => 'of string', 'value' => 'T'],
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStubs\WordPress\Core\Tests;
6+
7+
use function sanitize_post_field;
8+
use function PHPStan\Testing\assertType;
9+
10+
$postId = Faker::int();
11+
12+
// Int fields => int - regardless of (non-constant) $value
13+
assertType('int', sanitize_post_field('ID', Faker::mixed(), $postId, 'raw'));
14+
assertType('int', sanitize_post_field('post_parent', Faker::mixed(), $postId, 'raw'));
15+
assertType('int', sanitize_post_field('menu_order', Faker::mixed(), $postId, 'raw'));
16+
// Also in any other context
17+
assertType('int', sanitize_post_field('ID', Faker::mixed(), $postId, Faker::string()));
18+
// But as-is if $value is int or int range
19+
assertType('123', sanitize_post_field('ID', 123, $postId, 'raw'));
20+
assertType('int<1, max>', sanitize_post_field('ID', Faker::positiveInt(), $postId, 'raw'));
21+
assertType('123', sanitize_post_field('ID', 123, $postId, Faker::string()));
22+
23+
// Int array fields => array<int<0, max>> - regardless of (non-constant) $value and $context
24+
assertType('array<int<0, max>>', sanitize_post_field('ancestors', Faker::mixed(), $postId, Faker::string()));
25+
// But as-is if $value is array<int<0, max>> or list<int<0, max>> - regardless of $context
26+
assertType('array{41, 42, 43}', sanitize_post_field('ancestors', [41, 42, 43], $postId, Faker::string()));
27+
assertType('array{foo: 42}', sanitize_post_field('ancestors', ['foo' => 42], $postId, Faker::string()));
28+
// And retain list type - regardless of $context
29+
assertType('array<int<0, max>, int<0, max>>', sanitize_post_field('ancestors', Faker::list(), $postId, Faker::string()));
30+
31+
// All other constant fields:
32+
33+
// In raw context => as-is
34+
assertType("'field value'", sanitize_post_field('field', 'field value', $postId, 'raw'));
35+
assertType('123', sanitize_post_field('field', 123, $postId, 'raw'));
36+
assertType("array{foo: 'bar'}", sanitize_post_field('field', ['foo' => 'bar'], $postId, 'raw'));
37+
assertType('true', sanitize_post_field('field', true, $postId, 'raw'));
38+
assertType('bool', sanitize_post_field('field', Faker::bool(), $postId, 'raw'));
39+
assertType('int', sanitize_post_field('field', Faker::int(), $postId, 'raw'));
40+
assertType('string', sanitize_post_field('field', Faker::string(), $postId, 'raw'));
41+
42+
// Field values in edit may be filtered to mixed, but are escaped using esc_html or esc_attr => string
43+
assertType('string', sanitize_post_field('field', 'field value', $postId, 'edit'));
44+
assertType('string', sanitize_post_field('field', Faker::string(), $postId, 'edit'));
45+
// Except for field 'post_content' which is only escaped if ! user_can_richedit()
46+
assertType('mixed', sanitize_post_field('post_content', 'field value', $postId, 'edit'));
47+
assertType('mixed', sanitize_post_field('post_content', Faker::string(), $postId, 'edit'));
48+
49+
// Field values in attribute/js context are not filtered, but are escaped using esc_attr/esc_js
50+
// => string, but not as given in the argument
51+
assertType('string', sanitize_post_field('field', 'field value', $postId, 'attribute'));
52+
assertType('string', sanitize_post_field('field', Faker::string(), $postId, 'attribute'));
53+
assertType('string', sanitize_post_field('field', 'field value', $postId, 'js'));
54+
assertType('string', sanitize_post_field('field', Faker::string(), $postId, 'js'));
55+
56+
// Field values in any other context may be filtered to mixed => mixed
57+
assertType('mixed', sanitize_post_field('field', Faker::array(), $postId, 'db'));
58+
assertType('mixed', sanitize_post_field('field', Faker::array(), $postId, 'display'));
59+
assertType('mixed', sanitize_post_field('field', Faker::array(), $postId, 'rss'));
60+
assertType('mixed', sanitize_post_field('field', Faker::bool(), $postId, 'db'));
61+
assertType('mixed', sanitize_post_field('field', Faker::bool(), $postId, 'display'));
62+
assertType('mixed', sanitize_post_field('field', Faker::bool(), $postId, 'rss'));
63+
assertType('mixed', sanitize_post_field('field', Faker::int(), $postId, 'db'));
64+
assertType('mixed', sanitize_post_field('field', Faker::int(), $postId, 'display'));
65+
assertType('mixed', sanitize_post_field('field', Faker::int(), $postId, 'rss'));
66+
assertType('mixed', sanitize_post_field('field', Faker::object(), $postId, 'db'));
67+
assertType('mixed', sanitize_post_field('field', Faker::object(), $postId, 'display'));
68+
assertType('mixed', sanitize_post_field('field', Faker::object(), $postId, 'rss'));
69+
assertType('mixed', sanitize_post_field('field', Faker::string(), $postId, 'db'));
70+
assertType('mixed', sanitize_post_field('field', Faker::string(), $postId, 'display'));
71+
assertType('mixed', sanitize_post_field('field', Faker::string(), $postId, 'rss'));
72+
73+
// Non constant field:
74+
75+
// Non constant field in raw context => int (int field) or array<int<0, max>> (int array field) or T (other field)
76+
assertType("'field value'|array<int<0, max>>|int", sanitize_post_field(Faker::string(), 'field value', $postId, 'raw'));
77+
assertType('array<int<0, max>>|int|string', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'raw'));
78+
79+
// Non constant field in attribute|js context
80+
// => int (int field) or array<int<0, max>> (int array field) or string (other field)
81+
assertType('array<int<0, max>>|int|string', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'attribute'));
82+
assertType('array<int<0, max>>|int|string', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'js'));
83+
84+
// Non constant field in attribute|js context than raw
85+
// => int (int field) or array<int<0, max>> (int array field) or string (other field)
86+
assertType('array<int<0, max>>|int|string', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'attribute'));
87+
assertType('array<int<0, max>>|int|string', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'js'));
88+
89+
// Non constant field in any other context than attribute|js|raw => mixed
90+
assertType('mixed', sanitize_post_field(Faker::string(), 123, $postId, 'db'));
91+
assertType('mixed', sanitize_post_field(Faker::string(), 'field value', $postId, 'display'));
92+
assertType('mixed', sanitize_post_field(Faker::string(), Faker::array(), $postId, 'rss'));
93+
// And because of 'post_content':
94+
assertType('mixed', sanitize_post_field(Faker::string(), Faker::string(), $postId, 'edit'));

wordpress-stubs.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132359,6 +132359,9 @@ function sanitize_post($post, $context = 'display')
132359132359
* 'db', 'display', 'attribute' and 'js'. Default 'display'.
132360132360
* @return mixed Sanitized value.
132361132361
* @phpstan-param 'raw'|'edit'|'db'|'display'|'attribute'|'js' $context
132362+
* @phpstan-template T of mixed
132363+
* @phpstan-param T $value
132364+
* @phpstan-return ($field is 'ID'|'post_parent'|'menu_order' ? (T is int ? T : int) : ($field is 'ancestors' ? (T is array<int<0, max>>|list<int<0, max>> ? T : (T is list ? list<int<0, max>> : array<int<0, max>>)) : ($context is 'raw' ? T : ($context is 'attribute'|'js' ? string : ($context is 'edit' ? ($field is not 'post_content' ? string : mixed) : mixed)))))
132362132365
*/
132363132366
function sanitize_post_field($field, $value, $post_id, $context = 'display')
132364132367
{

0 commit comments

Comments
 (0)